diff --git a/modules/cli-module/pom.xml b/modules/cli-module/pom.xml
index 6ff24a6d..0ed5ab4a 100644
--- a/modules/cli-module/pom.xml
+++ b/modules/cli-module/pom.xml
@@ -87,6 +87,20 @@
true
+
+
+ com.esotericsoftware
+ kryo
+ 5.0.0-RC1
+ compile
+
+
+ de.javakaffee
+ kryo-serializers
+ 0.45
+ compile
+
+
org.apache.logging.log4j
diff --git a/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/CliDataLocator.java b/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/CliDataLocator.java
new file mode 100644
index 00000000..4ca53288
--- /dev/null
+++ b/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/CliDataLocator.java
@@ -0,0 +1,30 @@
+package org.simplejavamail.internal.clisupport;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * This helper makes sure we can load cli.data no matter how the code is run (from mvn, intellij, command line).
+ */
+public class CliDataLocator {
+ public static String determinelocateCLIDataFile() {
+ // the following is needed bacause this is a project with submodules
+ // and it changes depending on how the code is executed
+ if (new File("src/test/resources/log4j2.xml").exists()) {
+ return "src/main/resources/cli.data";
+ } else if (new File("modules/cli-module/src/test/resources/log4j2.xml").exists()) {
+ return "modules/cli-module/src/main/resources/cli.data";
+ } else {
+ URL resource = CliDataLocator.class.getClassLoader().getResource("log4j2.xml");
+ if (resource != null) {
+ try {
+ return new File(resource.toURI()).getParentFile().getPath() + "/cli.data";
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ throw new AssertionError("Was unable to locate resources folder. Did you delete log4j2.xml?");
+ }
+ }
+}
diff --git a/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/CliSupport.java b/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/CliSupport.java
index 9d6b9d2d..a8f50a02 100644
--- a/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/CliSupport.java
+++ b/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/CliSupport.java
@@ -1,43 +1,68 @@
package org.simplejavamail.internal.clisupport;
+import org.jetbrains.annotations.TestOnly;
import org.simplejavamail.api.email.EmailStartingBuilder;
import org.simplejavamail.api.internal.clisupport.model.CliDeclaredOptionSpec;
import org.simplejavamail.api.internal.clisupport.model.CliReceivedCommand;
import org.simplejavamail.api.mailer.MailerFromSessionBuilder;
import org.simplejavamail.api.mailer.MailerRegularBuilder;
+import org.simplejavamail.internal.clisupport.serialization.SerializationUtil;
+import org.simplejavamail.internal.util.MiscUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.ParseResult;
+import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.simplejavamail.internal.clisupport.BuilderApiToPicocliCommandsMapper.generateOptionsFromBuilderApi;
import static org.simplejavamail.internal.clisupport.CliCommandLineProducer.configurePicoCli;
+import static org.simplejavamail.internal.clisupport.CliExecutionException.ERROR_INVOKING_BUILDER_API;
public class CliSupport {
-
+ private static final Logger LOGGER = LoggerFactory.getLogger(CliSupport.class);
+
private static final int CONSOLE_TEXT_WIDTH = 150;
-
- private static final Class>[] RELEVANT_BUILDER_ROOT_API = {EmailStartingBuilder.class, MailerRegularBuilder.class, MailerFromSessionBuilder.class};
- private static final List DECLARED_OPTIONS = generateOptionsFromBuilderApi(RELEVANT_BUILDER_ROOT_API);
- private static final CommandLine PICOCLI_COMMAND_LINE = configurePicoCli(DECLARED_OPTIONS, CONSOLE_TEXT_WIDTH);
+
+ private static final File CLI_DATAFILE = new File(CliDataLocator.determinelocateCLIDataFile());
+
+ private static final Class>[] RELEVANT_BUILDER_ROOT_API = new Class[] { EmailStartingBuilder.class, MailerRegularBuilder.class, MailerFromSessionBuilder.class };
+ private static final List DECLARED_OPTIONS = produceCliDeclaredOptionSpec();
+ private static final CommandLine PICOCLI_COMMAND_LINE = configurePicoCli(DECLARED_OPTIONS, CONSOLE_TEXT_WIDTH);
+
+ private static List produceCliDeclaredOptionSpec() {
+ try {
+ if (!CLI_DATAFILE.exists()) {
+ LOGGER.info("Initial cli.data not found, writing to (one time action): {}", CLI_DATAFILE);
+ List serializable = generateOptionsFromBuilderApi(RELEVANT_BUILDER_ROOT_API);
+ MiscUtil.writeFileBytes(CLI_DATAFILE, SerializationUtil.serialize(serializable));
+ }
+ return SerializationUtil.deserialize(MiscUtil.readFileBytes(CLI_DATAFILE));
+ } catch (IOException e) {
+ throw new CliExecutionException(ERROR_INVOKING_BUILDER_API, e);
+ }
+ }
public static void runCLI(String[] args) {
ParseResult pr = PICOCLI_COMMAND_LINE.parseArgs(cutOffAtHelp(args));
-
+
if (!CliCommandLineConsumerUsageHelper.processAndApplyHelp(pr, CONSOLE_TEXT_WIDTH)) {
CliReceivedCommand cliReceivedOptionData = CliCommandLineConsumer.consumeCommandLineInput(pr, DECLARED_OPTIONS);
CliCommandLineConsumerResultHandler.processCliResult(cliReceivedOptionData);
}
}
-
+
+ @TestOnly
public static void listUsagesForAllOptions() {
for (CliDeclaredOptionSpec declaredOption : DECLARED_OPTIONS) {
- runCLI(new String[]{"send", declaredOption.getName() + "--help"});
+ runCLI(new String[] { "send", declaredOption.getName() + "--help" });
System.out.print("\n\n\n");
}
}
-
+
/**
* This is needed to avoid picocli to error out on other --params that are misconfigured.
*/
@@ -49,11 +74,11 @@ private static String[] cutOffAtHelp(String[] args) {
break;
}
}
-
+
if (argsToKeep.isEmpty()) {
argsToKeep.add("--help");
}
-
+
return argsToKeep.toArray(new String[]{});
}
}
\ No newline at end of file
diff --git a/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/serialization/SerializationUtil.java b/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/serialization/SerializationUtil.java
new file mode 100644
index 00000000..6e18edf5
--- /dev/null
+++ b/modules/cli-module/src/main/java/org/simplejavamail/internal/clisupport/serialization/SerializationUtil.java
@@ -0,0 +1,60 @@
+package org.simplejavamail.internal.clisupport.serialization;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.serializers.FieldSerializer;
+import com.esotericsoftware.kryo.serializers.JavaSerializer;
+import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;
+import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer;
+import org.jetbrains.annotations.NotNull;
+import org.objenesis.strategy.StdInstantiatorStrategy;
+
+import java.io.IOException;
+
+/**
+ * Used to serialize attachments of nested Outlook messages. This is needed because outlook-message-parser returns a Java structure from a .msg source, but this conversion is 1-way. An Email object
+ * represents attachments as DataSources however, so for this we need to serialize back from the java structure to a binary format. This Util does this.
+ *
+ * Then for users to obtain the Javastructure again, they must use this util to deserialize the relevant attachment.
+ *
+ * @see GitHub issue #314
+ */
+public class SerializationUtil {
+
+ private static final Kryo KRYO = initKryo();
+
+ @NotNull
+ private static Kryo initKryo() {
+ Kryo kryo = new Kryo();
+ kryo.setRegistrationRequired(false);
+ kryo.setInstantiatorStrategy(new DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
+// final FieldSerializer.FieldSerializerConfig config = new FieldSerializer.FieldSerializerConfig();
+// config.setSerializeTransient(true);
+ UnmodifiableCollectionsSerializer.registerSerializers(kryo);
+ return kryo;
+ }
+
+ private static void registerClassSerializer(final Kryo kryo, final FieldSerializer.FieldSerializerConfig config, Class type) {
+ kryo.register(type, new FieldSerializer(kryo, type, config));
+ }
+
+ private static void registerClassJavaSerializer(final Kryo kryo, Class type) {
+ kryo.register(type, new JavaSerializer());
+ }
+
+ @NotNull
+ public static byte[] serialize(@NotNull final Object serializable)
+ throws IOException {
+ final Output output = new Output(1024, -1);
+ KRYO.writeClassAndObject(output, serializable);
+ return output.toBytes();
+ }
+
+ @SuppressWarnings("unchecked")
+ @NotNull
+ public static T deserialize(@NotNull final byte[] serialized)
+ throws IOException {
+ return (T) KRYO.readClassAndObject(new Input(serialized));
+ }
+}