From 50f55a217fb832bbe56f6ed77249ee1a0f62a0a3 Mon Sep 17 00:00:00 2001
From: Martin Vogel <martin.vogel@sinc.de>
Date: Wed, 12 Mar 2025 12:46:18 +0100
Subject: [PATCH] refactor: add runnable picocli commands (planning#2217)

---
 .../java/dev/fitko/fitconnect/cli/CLI.java    | 11 +++-
 .../cli/{ => commands}/ConfigCommand.java     |  2 +-
 .../cli/commands/CreateTestKeysCommand.java   | 20 ------
 .../commands/GetAllSubmissionsCommand.java    | 51 +++++++++-----
 .../cli/commands/GetOneSubmissionCommand.java | 28 --------
 .../cli/commands/GetSubmissionCommand.java    | 60 +++++++++++++++++
 .../cli/{ => commands}/KeyGenCommand.java     |  9 +--
 .../commands/ListAllSubmissionsCommand.java   | 24 -------
 .../cli/{ => commands}/ListCommand.java       |  4 +-
 .../fitconnect/cli/util/SubmissionWriter.java | 66 +++++++++++++++++++
 10 files changed, 176 insertions(+), 99 deletions(-)
 rename java/cli/src/main/java/dev/fitko/fitconnect/cli/{ => commands}/ConfigCommand.java (93%)
 delete mode 100644 java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/CreateTestKeysCommand.java
 delete mode 100644 java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java
 create mode 100644 java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetSubmissionCommand.java
 rename java/cli/src/main/java/dev/fitko/fitconnect/cli/{ => commands}/KeyGenCommand.java (90%)
 delete mode 100644 java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ListAllSubmissionsCommand.java
 rename java/cli/src/main/java/dev/fitko/fitconnect/cli/{ => commands}/ListCommand.java (94%)
 create mode 100644 java/cli/src/main/java/dev/fitko/fitconnect/cli/util/SubmissionWriter.java

diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/CLI.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/CLI.java
index b898858..51527db 100644
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/CLI.java
+++ b/java/cli/src/main/java/dev/fitko/fitconnect/cli/CLI.java
@@ -1,5 +1,9 @@
 package dev.fitko.fitconnect.cli;
 
+import dev.fitko.fitconnect.cli.commands.GetAllSubmissionsCommand;
+import dev.fitko.fitconnect.cli.commands.GetSubmissionCommand;
+import dev.fitko.fitconnect.cli.commands.KeyGenCommand;
+import dev.fitko.fitconnect.cli.commands.ListCommand;
 import org.fusesource.jansi.AnsiConsole;
 import picocli.CommandLine;
 
@@ -12,7 +16,11 @@ import static java.lang.System.exit;
 import static java.lang.System.out;
 
 @CommandLine.Command(version = "2.0.0",
-        subcommands = {KeyGenCommand.class, ListCommand.class},
+        subcommands = {
+                KeyGenCommand.class,
+                ListCommand.class,
+                GetSubmissionCommand.class,
+                GetAllSubmissionsCommand.class},
         mixinStandardHelpOptions = true)
 public class CLI implements Runnable {
     private static final String LOGO = "/splash_screen_banner.txt";
@@ -24,6 +32,7 @@ public class CLI implements Runnable {
         int exitCode = commandLine.execute(args);
         exit(exitCode);
     }
+
     @Override
     public void run() {
         final CommandLine.Help.Ansi ansi = new CommandLine.Help.ColorScheme.Builder().ansi();
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/ConfigCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ConfigCommand.java
similarity index 93%
rename from java/cli/src/main/java/dev/fitko/fitconnect/cli/ConfigCommand.java
rename to java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ConfigCommand.java
index 0b0bbd1..920a798 100644
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/ConfigCommand.java
+++ b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ConfigCommand.java
@@ -1,4 +1,4 @@
-package dev.fitko.fitconnect.cli;
+package dev.fitko.fitconnect.cli.commands;
 
 import dev.fitko.fitconnect.api.config.ApplicationConfig;
 import dev.fitko.fitconnect.client.bootstrap.ApplicationConfigLoader;
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/CreateTestKeysCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/CreateTestKeysCommand.java
deleted file mode 100644
index 4901e7e..0000000
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/CreateTestKeysCommand.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package dev.fitko.fitconnect.cli.commands;
-
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
-
-@Parameters(
-        commandNames = {CreateTestKeysCommand.CREATE_TEST_KEYS_COMMAND_NAME},
-        commandDescription = "Generates JWK test keys for encryption, decryption, signing and signature validation",
-        separators = "="
-)
-public class CreateTestKeysCommand {
-
-    public static final String CREATE_TEST_KEYS_COMMAND_NAME = "keygen";
-
-    @Parameter(names = {"--outDir"}, description = "Output directory folder where the generated test keys are written to", arity = 1)
-    public String outputDir;
-
-    @Parameter(names = {"--withConfig"}, description = "Generates config.yaml with paths of the generated keys", arity = 1)
-    public boolean generateConfig;
-}
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java
index 9ad74e0..551105c 100644
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java
+++ b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetAllSubmissionsCommand.java
@@ -1,27 +1,44 @@
 package dev.fitko.fitconnect.cli.commands;
 
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
-import dev.fitko.fitconnect.cli.util.UUIDConverter;
-import dev.fitko.fitconnect.cli.util.UUIDValidator;
+import dev.fitko.fitconnect.api.config.ApplicationConfig;
+import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
+import dev.fitko.fitconnect.client.SubscriberClient;
+import dev.fitko.fitconnect.client.bootstrap.ApplicationConfigLoader;
+import dev.fitko.fitconnect.client.bootstrap.ClientFactory;
+import lombok.SneakyThrows;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
 
+import java.nio.file.Path;
+import java.util.Set;
 import java.util.UUID;
 
-@Parameters(
-        commandNames = {GetAllSubmissionsCommand.GET_ALL_CMD_NAME},
-        commandDescription = "Fetches all available submission for a destination",
-        separators = "="
-)
-public class GetAllSubmissionsCommand {
+@CommandLine.Command(name = "get", description = "Fetch a submission by id", mixinStandardHelpOptions = true)
+public class GetAllSubmissionsCommand implements Runnable {
 
-    public static final String GET_ALL_CMD_NAME = "all";
+    private static final Logger LOGGER = LoggerFactory.getLogger(GetAllSubmissionsCommand.class);
 
-    @Parameter(names = { "--config" }, description = "Path to the config yaml", arity = 1)
-    public String config;
+    @CommandLine.Option(names = {"--config"}, description = "Path to the config yaml", required = true)
+    public Path configPath;
 
-    @Parameter(names = { "--destinationId" }, description = "Unique destination identifier in UUID format", converter = UUIDConverter.class, validateWith = UUIDValidator.class, arity = 1, required = true)
-    public UUID destinationId;
+    @CommandLine.Option(names = {"-d", "--dest"}, required = true, description = "UUID of the destination")
+    private UUID destinationId;
 
-    @Parameter(names = { "--target" }, description = "Target folder where attachments and data is written to", arity = 1)
-    public String targetFolder;
+    @CommandLine.Option(names = {"--target"}, description = "Target folder where attachments and data is written to", required = false)
+    public Path targetFolder;
+
+    @Override
+    @SneakyThrows
+    public void run() {
+
+        LOGGER.info("Getting all available submissions for destination {}", destinationId);
+
+        final ApplicationConfig config = ApplicationConfigLoader.loadConfigFromPath(configPath);
+        final SubscriberClient subscriberClient = ClientFactory.createSubscriberClient(config);
+
+        final GetSubmissionCommand getSubmissionCommand = new GetSubmissionCommand();
+        final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissionsForDestination(destinationId);
+        submissions.forEach(submission -> getSubmissionCommand.getSubmission(subscriberClient, submission.getSubmissionId(), targetFolder));
+    }
 }
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java
deleted file mode 100644
index fd74bd7..0000000
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetOneSubmissionCommand.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package dev.fitko.fitconnect.cli.commands;
-
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
-import dev.fitko.fitconnect.cli.util.UUIDConverter;
-import dev.fitko.fitconnect.cli.util.UUIDValidator;
-
-import java.util.UUID;
-
-
-@Parameters(
-        commandNames = {GetOneSubmissionCommand.GET_CMD_NAME},
-        commandDescription = "Fetches one submission by id",
-        separators = "="
-)
-public class GetOneSubmissionCommand {
-
-    public static final String GET_CMD_NAME = "get";
-
-    @Parameter(names = { "--config" }, description = "Path to the config yaml", arity = 1)
-    public String config;
-
-    @Parameter(names = { "--submissionId" }, description = "Unique submission identifier in UUID format", converter = UUIDConverter.class, validateWith = UUIDValidator.class, arity = 1, required = true)
-    public UUID submissionId;
-
-    @Parameter(names = { "--target" }, description = "Target folder where attachments and data is written to", arity = 1)
-    public String targetFolder;
-}
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetSubmissionCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetSubmissionCommand.java
new file mode 100644
index 0000000..6ada861
--- /dev/null
+++ b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/GetSubmissionCommand.java
@@ -0,0 +1,60 @@
+package dev.fitko.fitconnect.cli.commands;
+
+import dev.fitko.fitconnect.api.config.ApplicationConfig;
+import dev.fitko.fitconnect.cli.util.SubmissionWriter;
+import dev.fitko.fitconnect.client.SubscriberClient;
+import dev.fitko.fitconnect.client.bootstrap.ApplicationConfigLoader;
+import dev.fitko.fitconnect.client.bootstrap.ClientFactory;
+import dev.fitko.fitconnect.core.utils.StopWatch;
+import lombok.SneakyThrows;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+
+import java.nio.file.Path;
+import java.util.UUID;
+
+@CommandLine.Command(name = "get", description = "Fetch a submission by id", mixinStandardHelpOptions = true)
+public class GetSubmissionCommand implements Runnable {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GetSubmissionCommand.class);
+
+    @CommandLine.Option(names = {"-c", "--config"}, required = true, description = "Path to the config yaml with subscriber clientId and clientSecret")
+    public Path configPath;
+
+    @CommandLine.Option(names = {"-id", "--submissionId"}, description = "Unique submission identifier in UUID format", required = true)
+    public UUID submissionId;
+
+    @CommandLine.Option(names = {"--target"}, description = "Target folder where attachments and data is written to", required = false)
+    public Path targetFolder;
+
+    @Override
+    public void run() {
+
+        LOGGER.info("Getting submission for id {}", submissionId);
+
+        final ApplicationConfig config = ApplicationConfigLoader.loadConfigFromPath(configPath);
+        final SubscriberClient subscriberClient = ClientFactory.createSubscriberClient(config);
+
+        getSubmission(subscriberClient, submissionId, targetFolder);
+    }
+
+    @SneakyThrows
+    public void getSubmission(SubscriberClient subscriberClient, UUID submissionId, Path targetFolder) {
+        LOGGER.info("Getting submission for id {}", submissionId);
+
+        final var startTime = StopWatch.start();
+        final var submission = subscriberClient.requestSubmission(submissionId);
+        LOGGER.info("Submission download took {}", StopWatch.stop(startTime));
+        if (submission == null) {
+            LOGGER.info("No submission found for submission id {}", submissionId);
+        } else {
+            submission.acceptSubmission();
+            SubmissionWriter.writeSubmissionData(submission, getTargetFolderPath(targetFolder, submissionId));
+        }
+    }
+
+    private String getTargetFolderPath(Path targetFolder, UUID submissionId) {
+        return targetFolder != null ? targetFolder + "/" + submissionId : submissionId.toString();
+    }
+}
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/KeyGenCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/KeyGenCommand.java
similarity index 90%
rename from java/cli/src/main/java/dev/fitko/fitconnect/cli/KeyGenCommand.java
rename to java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/KeyGenCommand.java
index ecd9fce..77bb043 100644
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/KeyGenCommand.java
+++ b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/KeyGenCommand.java
@@ -1,4 +1,4 @@
-package dev.fitko.fitconnect.cli;
+package dev.fitko.fitconnect.cli.commands;
 
 import dev.fitko.fitconnect.api.domain.crypto.JWKPair;
 import dev.fitko.fitconnect.tools.keygen.KeyWriter;
@@ -10,12 +10,11 @@ import picocli.CommandLine;
 import picocli.CommandLine.Option;
 
 import java.nio.file.Path;
-import java.util.concurrent.Callable;
 
 import static dev.fitko.fitconnect.tools.keygen.TestKeyBuilder.DEFAULT_KEY_SIZE;
 
 @CommandLine.Command(name = "keygen", description = "Generate JWKs for TEST usage", mixinStandardHelpOptions = true)
-public class KeyGenCommand implements Callable<Integer> {
+public class KeyGenCommand implements Runnable {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(KeyGenCommand.class);
 
@@ -26,8 +25,7 @@ public class KeyGenCommand implements Callable<Integer> {
     private boolean generateConfig;
 
     @Override
-    public Integer call() {
-
+    public void run() {
         LOGGER.info("Generating Test JWKs ...");
 
         final JWKPair encryptionKeyPair = TestKeyBuilder.generateEncryptionKeyPair(DEFAULT_KEY_SIZE);
@@ -43,6 +41,5 @@ public class KeyGenCommand implements Callable<Integer> {
         }
 
         KeyWriter.writeKeys(settingsBuilder.build());
-        return null;
     }
 }
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ListAllSubmissionsCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ListAllSubmissionsCommand.java
deleted file mode 100644
index 0099467..0000000
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ListAllSubmissionsCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package dev.fitko.fitconnect.cli.commands;
-
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.Parameters;
-import dev.fitko.fitconnect.cli.util.UUIDConverter;
-import dev.fitko.fitconnect.cli.util.UUIDValidator;
-
-import java.util.UUID;
-
-@Parameters(
-        commandNames = {ListAllSubmissionsCommand.LIST_CMD_NAME},
-        commandDescription = "Lists available submissions",
-        separators = "="
-)
-public class ListAllSubmissionsCommand {
-
-    public static final String LIST_CMD_NAME = "list";
-
-    @Parameter(names = { "--config" }, description = "Path to the config yaml", arity = 1)
-    public String config;
-
-    @Parameter(names = { "--destinationId" }, description = "Unique destination identifier in UUID format", converter = UUIDConverter.class,  validateWith = UUIDValidator.class, arity = 1, required = true)
-    public UUID destinationId;
-}
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/ListCommand.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ListCommand.java
similarity index 94%
rename from java/cli/src/main/java/dev/fitko/fitconnect/cli/ListCommand.java
rename to java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ListCommand.java
index 51e5942..0013522 100644
--- a/java/cli/src/main/java/dev/fitko/fitconnect/cli/ListCommand.java
+++ b/java/cli/src/main/java/dev/fitko/fitconnect/cli/commands/ListCommand.java
@@ -1,4 +1,4 @@
-package dev.fitko.fitconnect.cli;
+package dev.fitko.fitconnect.cli.commands;
 
 import dev.fitko.fitconnect.api.config.ApplicationConfig;
 import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
@@ -21,7 +21,7 @@ public class ListCommand implements Runnable {
     @CommandLine.Option(names = {"-c", "--config"}, required = true, description = "Path to the config yaml with subscriber clientId and clientSecret")
     private Path configPath;
 
-    @CommandLine.Option(names = {"-d", "--dest"}, required = true, description = "UUID of the destinationId to poll")
+    @CommandLine.Option(names = {"-d", "--dest"}, required = true, description = "UUID of the destination to poll")
     private UUID destinationId;
 
     @Override
diff --git a/java/cli/src/main/java/dev/fitko/fitconnect/cli/util/SubmissionWriter.java b/java/cli/src/main/java/dev/fitko/fitconnect/cli/util/SubmissionWriter.java
new file mode 100644
index 0000000..2e3a446
--- /dev/null
+++ b/java/cli/src/main/java/dev/fitko/fitconnect/cli/util/SubmissionWriter.java
@@ -0,0 +1,66 @@
+package dev.fitko.fitconnect.cli.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import dev.fitko.fitconnect.api.domain.model.attachment.Attachment;
+import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
+import dev.fitko.fitconnect.api.domain.subscriber.ReceivedSubmission;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SubmissionWriter {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SubmissionWriter.class);
+
+    private static final ObjectWriter JSON_WRITER = new ObjectMapper().writerWithDefaultPrettyPrinter();
+
+    public static void writeSubmissionData(final ReceivedSubmission receivedSubmission, final String dataDirPath) throws IOException {
+
+        LOGGER.info("Creating data directory for submission in {}", dataDirPath);
+        Files.createDirectories(Path.of(dataDirPath));
+
+        final var fileEnding = AttachmentDataType.getFileTypeFromMimeType(receivedSubmission.getDataMimeType());
+        final var filePath = Path.of(dataDirPath + "/data." + fileEnding);
+        LOGGER.info("Writing data.{}", fileEnding);
+        Files.write(filePath, receivedSubmission.getDataAsString().getBytes(StandardCharsets.UTF_8));
+
+        LOGGER.info("Writing metadata.json");
+        final Metadata metadata = receivedSubmission.getMetadata();
+        Files.write(Path.of(dataDirPath, "metadata.json"), JSON_WRITER.writeValueAsString(metadata).getBytes());
+
+        final List<Attachment> attachments = receivedSubmission.getAttachments();
+        for (int i = 0; i < attachments.size(); i++) {
+            final Attachment attachment = attachments.get(i);
+            final String filename = getAttachmentFilename(attachment, i);
+            LOGGER.info("Writing attachment {}", filename);
+            final Path tragetPath = Path.of(dataDirPath, filename);
+            if (attachment.isInMemoryAttachment()) {
+                Files.write(tragetPath, attachment.getDataAsBytes());
+            } else {
+                try (OutputStream os = Files.newOutputStream(tragetPath)) {
+                    attachment.getDataAsInputStream().transferTo(os);
+                }
+            }
+        }
+    }
+
+    private static String getAttachmentFilename(Attachment attachment, int i) {
+        if (attachment.getFileName() == null) {
+            if (attachment.getMimeType() != null) {
+                return "attachment_" + i + "." + attachment.getMimeType().split("/")[1];
+            }
+            return attachment + "_" + i;
+        }
+        return attachment.getFileName();
+    }
+}
-- 
GitLab