Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • fit-connect/sdk-java
1 result
Show changes
Showing
with 435 additions and 459 deletions
package dev.fitko.fitconnect.api.domain.model.destination; package dev.fitko.fitconnect.api.domain.model.replychannel;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
class ReplyChannelEMail { @AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Email {
@JsonProperty("address")
private String address;
@JsonProperty("usePgp") @JsonProperty("usePgp")
private Boolean usePgp; private Boolean usePgp;
@JsonProperty("pgpPublicKey")
private String pgpPublicKey;
} }
package dev.fitko.fitconnect.api.domain.model.replychannel;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Fink {
@JsonProperty("finkPostfachRef")
private String finkPostboxRef;
@JsonProperty("host")
private String host;
}
package dev.fitko.fitconnect.api.domain.model.replychannel;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ReplyChannel {
@JsonProperty("deMail")
private DeMail deMail;
@JsonProperty("elster")
private Elster elster;
@JsonProperty("eMail")
private Email eMail;
@JsonProperty("fink")
private Fink fink;
public static ReplyChannel fromFink(final String finkPostboxRef, final String host) {
return new ReplyChannel(null, null, null, new Fink(finkPostboxRef, host));
}
public static ReplyChannel fromEmailWithPgp(final String address, final String pgpPublicKey) {
return new ReplyChannel(null, null, new Email(address, true, pgpPublicKey), null);
}
public static ReplyChannel fromEmail(final String address) {
return new ReplyChannel(null, null, new Email(address, false, null), null);
}
public static ReplyChannel fromDeMail(final String address) {
return new ReplyChannel(new DeMail(address), null, null, null);
}
public static ReplyChannel fromElster(final String accountId, final String deliveryTicket, final String reference) {
return new ReplyChannel(null, new Elster(accountId, deliveryTicket, reference), null, null);
}
}
...@@ -2,10 +2,10 @@ package dev.fitko.fitconnect.api.domain.model.route; ...@@ -2,10 +2,10 @@ package dev.fitko.fitconnect.api.domain.model.route;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import dev.fitko.fitconnect.api.domain.model.destination.ReplyChannel;
import dev.fitko.fitconnect.api.domain.model.destination.StatusEnum; import dev.fitko.fitconnect.api.domain.model.destination.StatusEnum;
import dev.fitko.fitconnect.api.domain.model.jwk.ApiJwkSet; import dev.fitko.fitconnect.api.domain.model.jwk.ApiJwkSet;
import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel;
import lombok.Data; import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
......
...@@ -3,10 +3,10 @@ package dev.fitko.fitconnect.api.services; ...@@ -3,10 +3,10 @@ package dev.fitko.fitconnect.api.services;
import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.RSAKey;
import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.EventStatus; import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission; import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.Submission;
...@@ -22,7 +22,7 @@ import java.util.UUID; ...@@ -22,7 +22,7 @@ import java.util.UUID;
* A technical system that creates a submission via the FIT-Connect Submission API. * A technical system that creates a submission via the FIT-Connect Submission API.
* <p> * <p>
* The Sender acts as a common interface wrapping all client functionality for authenticating <p> * The Sender acts as a common interface wrapping all client functionality for authenticating <p>
* and creating a valid {@link SubmitSubmission} including encrypted {@link Data} and {@link Attachment}s * and creating a valid {@link SubmitSubmission} including encrypted {@link Data} and {@link ApiAttachment}s
* *
* @see <a href="https://docs.fitko.de/fit-connect/docs/sending/overview">Sending Submissions</a> * @see <a href="https://docs.fitko.de/fit-connect/docs/sending/overview">Sending Submissions</a>
*/ */
...@@ -102,7 +102,7 @@ public interface Sender { ...@@ -102,7 +102,7 @@ public interface Sender {
SubmissionForPickup createSubmission(CreateSubmission submission) throws SubmissionNotCreatedException; SubmissionForPickup createSubmission(CreateSubmission submission) throws SubmissionNotCreatedException;
/** /**
* Uploads encrypted {@link Attachment}s to for given submission id. * Uploads encrypted {@link ApiAttachment}s to for given submission id.
* *
* @param submissionId unique identifier of the submission * @param submissionId unique identifier of the submission
* @param attachmentId unique identifier of the attachment * @param attachmentId unique identifier of the attachment
...@@ -125,7 +125,7 @@ public interface Sender { ...@@ -125,7 +125,7 @@ public interface Sender {
* *
* @param destinationId unique identifier of the destination * @param destinationId unique identifier of the destination
* *
* @return the public key for encrypting {@link Metadata}, {@link Data} and {@link Attachment}s * @return the public key for encrypting {@link Metadata}, {@link Data} and {@link ApiAttachment}s
* *
* @throws KeyNotRetrievedException if a technical problem occurred fetching the key * @throws KeyNotRetrievedException if a technical problem occurred fetching the key
*/ */
...@@ -174,7 +174,7 @@ public interface Sender { ...@@ -174,7 +174,7 @@ public interface Sender {
* @param submissionId unique identifier of the submission the log should be retrieved for * @param submissionId unique identifier of the submission the log should be retrieved for
* @param authenticationTags {@link AuthenticationTags} used for SET-Event integrity validation * @param authenticationTags {@link AuthenticationTags} used for SET-Event integrity validation
* *
* @return {@link EventStatus} the current status * @return {@link SubmissionStatus} the current status
*/ */
EventStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws RestApiException; SubmissionStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws RestApiException;
} }
...@@ -5,7 +5,7 @@ import dev.fitko.fitconnect.api.domain.model.destination.Destination; ...@@ -5,7 +5,7 @@ import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.EventPayload; import dev.fitko.fitconnect.api.domain.model.event.EventPayload;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
...@@ -39,7 +39,16 @@ public interface Subscriber { ...@@ -39,7 +39,16 @@ public interface Subscriber {
* @param offset position in the dataset * @param offset position in the dataset
* @return list of found {@link SubmissionForPickup}s * @return list of found {@link SubmissionForPickup}s
*/ */
Set<SubmissionForPickup> pollAvailableSubmissions(UUID destinationId, int offset, int limit); Set<SubmissionForPickup> pollAvailableSubmissionsForDestination(UUID destinationId, int offset, int limit);
/**
* Polls available {@link SubmissionForPickup}s for all destinations the {@link Subscriber} is assigned to.
*
* @param limit number of submissions in result (max. is 500)
* @param offset position in the dataset
* @return list of found {@link SubmissionForPickup}s
*/
Set<SubmissionForPickup> pollAvailableSubmissions(int offset, int limit);
/** /**
* Gets a {@link Submission}. * Gets a {@link Submission}.
...@@ -50,7 +59,7 @@ public interface Subscriber { ...@@ -50,7 +59,7 @@ public interface Subscriber {
Submission getSubmission(UUID submissionId); Submission getSubmission(UUID submissionId);
/** /**
* Loads encrypted {@link Attachment} for a {@link Submission}. * Loads encrypted {@link ApiAttachment} for a {@link Submission}.
* *
* @param submissionId the unique identifier of a {@link Submission} * @param submissionId the unique identifier of a {@link Submission}
* @param attachmentId unique identifier of the attachments * @param attachmentId unique identifier of the attachments
...@@ -61,9 +70,8 @@ public interface Subscriber { ...@@ -61,9 +70,8 @@ public interface Subscriber {
/** /**
* Retrieve the entire event log for a submissions caseId of a specific destination. * Retrieve the entire event log for a submissions caseId of a specific destination.
* *
* @param caseId unique identifier of the case the log should be retrieved for * @param caseId unique identifier of the case the log should be retrieved for
* @param destinationId unique identifier of the {@link Destination} the log should be retrieved for * @param destinationId unique identifier of the {@link Destination} the log should be retrieved for
*
* @return List of {@link EventLogEntry}s for the given case * @return List of {@link EventLogEntry}s for the given case
*/ */
List<EventLogEntry> getEventLog(UUID caseId, UUID destinationId); List<EventLogEntry> getEventLog(UUID caseId, UUID destinationId);
...@@ -77,10 +85,10 @@ public interface Subscriber { ...@@ -77,10 +85,10 @@ public interface Subscriber {
ValidationResult validateMetadata(Metadata metadata); ValidationResult validateMetadata(Metadata metadata);
/** /**
* Validates data integrity of {@link Attachment} or {@link Data}. * Validates data integrity of {@link ApiAttachment} or {@link Data}.
* *
* @param originalHash original hash value of raw data as hex string * @param originalHash original hash value of raw data as hex string
* @param data unencrypted data as byte[] * @param data unencrypted data as byte[]
* @return a {@link ValidationResult}, contains an error if the hash values are not equal * @return a {@link ValidationResult}, contains an error if the hash values are not equal
*/ */
ValidationResult validateHashIntegrity(String originalHash, byte[] data); ValidationResult validateHashIntegrity(String originalHash, byte[] data);
...@@ -88,9 +96,9 @@ public interface Subscriber { ...@@ -88,9 +96,9 @@ public interface Subscriber {
/** /**
* Checks if a received callback can be trusted by validating the provided request data * Checks if a received callback can be trusted by validating the provided request data
* *
* @param hmac authentication code provided by the callback * @param hmac authentication code provided by the callback
* @param timestamp timestamp provided by the callback * @param timestamp timestamp provided by the callback
* @param httpBody HTTP body provided by the callback * @param httpBody HTTP body provided by the callback
* @param callbackSecret secret owned by the client, which is used to calculate the hmac * @param callbackSecret secret owned by the client, which is used to calculate the hmac
* @return {@code true} if hmac and timestamp provided by the callback meet the required conditions * @return {@code true} if hmac and timestamp provided by the callback meet the required conditions
*/ */
...@@ -100,7 +108,6 @@ public interface Subscriber { ...@@ -100,7 +108,6 @@ public interface Subscriber {
* Sends a confirmation event if the received submission matches all validations. * Sends a confirmation event if the received submission matches all validations.
* *
* @param eventPayload contains submissionId, caseId, destination and a list of problems * @param eventPayload contains submissionId, caseId, destination and a list of problems
* @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a> * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a>
*/ */
void acceptSubmission(EventPayload eventPayload); void acceptSubmission(EventPayload eventPayload);
...@@ -109,7 +116,6 @@ public interface Subscriber { ...@@ -109,7 +116,6 @@ public interface Subscriber {
* Sends a rejection event if the received submission violates any validation rule. * Sends a rejection event if the received submission violates any validation rule.
* *
* @param eventPayload contains submissionId, caseId, destination and a list of problems * @param eventPayload contains submissionId, caseId, destination and a list of problems
* @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a> * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a>
*/ */
void rejectSubmission(EventPayload eventPayload); void rejectSubmission(EventPayload eventPayload);
......
package dev.fitko.fitconnect.api.services.crypto; package dev.fitko.fitconnect.api.services.crypto;
import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.RSAKey;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission; import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
import dev.fitko.fitconnect.api.exceptions.DecryptionException; import dev.fitko.fitconnect.api.exceptions.DecryptionException;
import dev.fitko.fitconnect.api.exceptions.EncryptionException; import dev.fitko.fitconnect.api.exceptions.EncryptionException;
/** /**
* A service that allows to encrypt and decrypt {@link Data} and {@link Attachment}s of a {@link SubmitSubmission} * A service that allows to encrypt and decrypt {@link Data} and {@link ApiAttachment}s of a {@link SubmitSubmission}
* via JSON-Web-Encryption. * via JSON-Web-Encryption.
* *
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7516">JSON-Web-Encryption</a> * @see <a href="https://datatracker.ietf.org/doc/html/rfc7516">JSON-Web-Encryption</a>
*/ */
public interface CryptoService { public interface CryptoService {
/**
* Decrypts a JWE encrypted string with the given private key.
*
* @param privateKey RSA private key for decryption of JWE
* @param encryptedData serialized encrypted JWE Hex string
* @return decrypted JWE string
*
* @throws DecryptionException if the payload cannot be decrypted or there was an issue with the key
*/
String decryptString(RSAKey privateKey, String encryptedData) throws DecryptionException;
/** /**
* Decrypts a JWE encrypted string with the given private key. * Decrypts a JWE encrypted string with the given private key.
* *
...@@ -35,18 +24,7 @@ public interface CryptoService { ...@@ -35,18 +24,7 @@ public interface CryptoService {
* *
* @throws DecryptionException if the payload cannot be decrypted or there was an issue with the key * @throws DecryptionException if the payload cannot be decrypted or there was an issue with the key
*/ */
byte[] decryptBytes(RSAKey privateKey, String encryptedData) throws DecryptionException; byte[] decryptToBytes(RSAKey privateKey, String encryptedData) throws DecryptionException;
/**
* Encrypts a string with the given public key.
*
* @param encryptionKey RSA public key for encryption of string payload
* @param data string data that should be encrypted
* @return Hex string of the encrypted JWE object
*
* @throws EncryptionException if the payload cannot be encrypted or there was an issue with the key
*/
String encryptString(RSAKey encryptionKey, String data) throws EncryptionException;
/** /**
* Encrypts an object with the given public key. * Encrypts an object with the given public key.
......
...@@ -3,7 +3,7 @@ package dev.fitko.fitconnect.api.services.events; ...@@ -3,7 +3,7 @@ package dev.fitko.fitconnect.api.services.events;
import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.event.EventLog; import dev.fitko.fitconnect.api.domain.model.event.EventLog;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.EventStatus; import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.exceptions.EventLogException; import dev.fitko.fitconnect.api.exceptions.EventLogException;
...@@ -38,9 +38,9 @@ public interface EventLogService { ...@@ -38,9 +38,9 @@ public interface EventLogService {
* @param submissionId unique identifier of the submission the log should be retrieved for * @param submissionId unique identifier of the submission the log should be retrieved for
* @param authenticationTags {@link AuthenticationTags} used for SET-Event integrity validation * @param authenticationTags {@link AuthenticationTags} used for SET-Event integrity validation
* *
* @return {@link EventStatus} the current status * @return {@link SubmissionStatus} the current status
*/ */
EventStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws RestApiException, EventLogException; SubmissionStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws RestApiException, EventLogException;
/** /**
* Send an event for a given caseId. * Send an event for a given caseId.
......
...@@ -2,7 +2,7 @@ package dev.fitko.fitconnect.api.services.submission; ...@@ -2,7 +2,7 @@ package dev.fitko.fitconnect.api.services.submission;
import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission; import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; import dev.fitko.fitconnect.api.domain.model.submission.ServiceType;
...@@ -16,7 +16,7 @@ import java.util.UUID; ...@@ -16,7 +16,7 @@ import java.util.UUID;
/** /**
* A service that provides access to the FIT-Connect REST-API and allows creating and loading {@link Submission}s * A service that provides access to the FIT-Connect REST-API and allows creating and loading {@link Submission}s
* and {@link Attachment}s. * and {@link ApiAttachment}s.
* *
* @see <a href="https://docs.fitko.de/fit-connect/docs/apis/submission-api">FIT-Connect Submission API</a> * @see <a href="https://docs.fitko.de/fit-connect/docs/apis/submission-api">FIT-Connect Submission API</a>
*/ */
...@@ -25,7 +25,7 @@ public interface SubmissionService { ...@@ -25,7 +25,7 @@ public interface SubmissionService {
/** /**
* Announce a new submission. This step is necessary before actually {@link #sendSubmission(SubmitSubmission) sending} the submission. * Announce a new submission. This step is necessary before actually {@link #sendSubmission(SubmitSubmission) sending} the submission.
* *
* @param submission submission that contains all {@link Attachment}s that should be announced, as well as a specified {@link ServiceType} * @param submission submission that contains all {@link ApiAttachment}s that should be announced, as well as a specified {@link ServiceType}
* @return announced submission with submissionId, caseId and destinationID * @return announced submission with submissionId, caseId and destinationID
* *
* @see <a href="https://docs.fitko.de/fit-connect/docs/sending/start-submission">Announcing a submission</a> * @see <a href="https://docs.fitko.de/fit-connect/docs/sending/start-submission">Announcing a submission</a>
...@@ -48,6 +48,15 @@ public interface SubmissionService { ...@@ -48,6 +48,15 @@ public interface SubmissionService {
*/ */
Submission getSubmission(UUID submissionId); Submission getSubmission(UUID submissionId);
/**
* Get all available submissions.
*
* @param offset position in the dataset
* @param limit number of submissions in result (max. is 500)
* @return SubmissionsForPickup containing a list of submissions
*/
SubmissionsForPickup pollAvailableSubmissions(int offset, int limit);
/** /**
* Get all available submissions for a given {@link Destination}. * Get all available submissions for a given {@link Destination}.
* *
...@@ -56,10 +65,10 @@ public interface SubmissionService { ...@@ -56,10 +65,10 @@ public interface SubmissionService {
* @param limit number of submissions in result (max. is 500) * @param limit number of submissions in result (max. is 500)
* @return SubmissionsForPickup containing a list of submissions * @return SubmissionsForPickup containing a list of submissions
*/ */
SubmissionsForPickup pollAvailableSubmissions(UUID destinationId, int offset, int limit); SubmissionsForPickup pollAvailableSubmissionsForDestination(UUID destinationId, int offset, int limit);
/** /**
* Upload an encrypted {@link Attachment}. * Upload an encrypted {@link ApiAttachment}.
* *
* @param submissionId unique identifier of an announced submission * @param submissionId unique identifier of an announced submission
* @param attachmentId unique destination identifier * @param attachmentId unique destination identifier
...@@ -68,7 +77,7 @@ public interface SubmissionService { ...@@ -68,7 +77,7 @@ public interface SubmissionService {
void uploadAttachment(UUID submissionId, UUID attachmentId, String encryptedAttachment); void uploadAttachment(UUID submissionId, UUID attachmentId, String encryptedAttachment);
/** /**
* Get an {@link Attachment} by id for a given {@link Submission}. * Get an {@link ApiAttachment} by id for a given {@link Submission}.
* *
* @param submissionId unique submission identifier * @param submissionId unique submission identifier
* @param attachmentId unique attachment identifier * @param attachmentId unique attachment identifier
......
...@@ -63,11 +63,6 @@ ...@@ -63,11 +63,6 @@
<artifactId>hamcrest-all</artifactId> <artifactId>hamcrest-all</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
...@@ -129,6 +124,10 @@ ...@@ -129,6 +124,10 @@
<commitIdGenerationMode>full</commitIdGenerationMode> <commitIdGenerationMode>full</commitIdGenerationMode>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins> </plugins>
</build> </build>
......
package dev.fitko.fitconnect.client; package dev.fitko.fitconnect.client;
import com.nimbusds.jose.jwk.RSAKey;
import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.EventStatus; import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission; import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission; import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult; import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.InvalidKeyException;
import dev.fitko.fitconnect.api.exceptions.KeyNotRetrievedException;
import dev.fitko.fitconnect.api.services.Sender; import dev.fitko.fitconnect.api.services.Sender;
import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission;
import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; import dev.fitko.fitconnect.client.sender.model.SendableSubmission;
import dev.fitko.fitconnect.client.sender.strategies.SendEncryptedSubmissionStrategy; import dev.fitko.fitconnect.client.sender.strategies.SendEncryptedSubmissionStrategy;
import dev.fitko.fitconnect.client.sender.strategies.SendNewSubmissionStrategy; import dev.fitko.fitconnect.client.sender.strategies.SendNewSubmissionStrategy;
import dev.fitko.fitconnect.client.util.ValidDataGuard; import dev.fitko.fitconnect.client.util.ValidDataGuard;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
/** /**
...@@ -35,14 +35,15 @@ public class SenderClient { ...@@ -35,14 +35,15 @@ public class SenderClient {
} }
/** /**
* Retrieve the public encryption key * Retrieve the public encryption key for a given destination.
* *
* @param destinationId unique identifier of a {@link Destination} * @param destinationId unique identifier of a {@link Destination}
* @return optional string containing the public JWK * @return optional string containing the public JWK
* @throws KeyNotRetrievedException is there was a technical problem retrieving the key
* @throws InvalidKeyException if the key validation failed
*/ */
public Optional<String> getPublicKey(final UUID destinationId) { public String getPublicKeyForDestination(final UUID destinationId) throws KeyNotRetrievedException, InvalidKeyException {
final Optional<RSAKey> key = Optional.ofNullable(sender.getEncryptionKeyForDestination(destinationId)); return sender.getEncryptionKeyForDestination(destinationId).toJSONString();
return key.isEmpty() ? Optional.empty() : Optional.of(key.get().toJSONString());
} }
/** /**
...@@ -60,9 +61,9 @@ public class SenderClient { ...@@ -60,9 +61,9 @@ public class SenderClient {
* Retrieve the current status of a {@link Submission}. * Retrieve the current status of a {@link Submission}.
* *
* @param sentSubmission the original {@link SentSubmission} the status should be retrieved for * @param sentSubmission the original {@link SentSubmission} the status should be retrieved for
* @return {@link EventStatus} the current status * @return {@link SubmissionStatus} the current status
*/ */
public EventStatus getStatusForSubmission(final SentSubmission sentSubmission) { public SubmissionStatus getStatusForSubmission(final SentSubmission sentSubmission) {
final UUID destinationId = sentSubmission.getDestinationId(); final UUID destinationId = sentSubmission.getDestinationId();
final UUID caseId = sentSubmission.getCaseId(); final UUID caseId = sentSubmission.getCaseId();
final UUID submissionId = sentSubmission.getSubmissionId(); final UUID submissionId = sentSubmission.getSubmissionId();
...@@ -88,9 +89,9 @@ public class SenderClient { ...@@ -88,9 +89,9 @@ public class SenderClient {
* *
* @return {@link SentSubmission} object of the handed in submission * @return {@link SentSubmission} object of the handed in submission
*/ */
public SentSubmission submit(final SubmissionPayload submissionPayload) { public SentSubmission send(final SendableSubmission sendableSubmission) {
dataGuard.ensureValidDataPayload(submissionPayload); dataGuard.ensureValidDataPayload(sendableSubmission);
return new SendNewSubmissionStrategy(sender).send(submissionPayload); return new SendNewSubmissionStrategy(sender).send(sendableSubmission);
} }
/** /**
...@@ -98,8 +99,8 @@ public class SenderClient { ...@@ -98,8 +99,8 @@ public class SenderClient {
* *
* @return {@link SentSubmission} object of the handed in submission * @return {@link SentSubmission} object of the handed in submission
*/ */
public SentSubmission submit(final EncryptedSubmissionPayload encryptedSubmissionPayload) { public SentSubmission send(final SendableEncryptedSubmission sendableEncryptedSubmission) {
dataGuard.ensureValidDataPayload(encryptedSubmissionPayload); dataGuard.ensureValidDataPayload(sendableEncryptedSubmission);
return new SendEncryptedSubmissionStrategy(sender).send(encryptedSubmissionPayload); return new SendEncryptedSubmissionStrategy(sender).send(sendableEncryptedSubmission);
} }
} }
...@@ -3,11 +3,15 @@ package dev.fitko.fitconnect.client; ...@@ -3,11 +3,15 @@ package dev.fitko.fitconnect.client;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.RSAKey;
import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.event.Event;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.EventPayload;
import dev.fitko.fitconnect.api.domain.model.event.problems.Problem;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult; import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
...@@ -16,9 +20,9 @@ import dev.fitko.fitconnect.api.exceptions.DecryptionException; ...@@ -16,9 +20,9 @@ import dev.fitko.fitconnect.api.exceptions.DecryptionException;
import dev.fitko.fitconnect.api.exceptions.EventCreationException; import dev.fitko.fitconnect.api.exceptions.EventCreationException;
import dev.fitko.fitconnect.api.exceptions.RestApiException; import dev.fitko.fitconnect.api.exceptions.RestApiException;
import dev.fitko.fitconnect.api.services.Subscriber; import dev.fitko.fitconnect.api.services.Subscriber;
import dev.fitko.fitconnect.client.sender.model.Attachment;
import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission;
import dev.fitko.fitconnect.client.subscriber.model.DecryptedAttachmentPayload; import dev.fitko.fitconnect.client.subscriber.model.DecryptedAttachmentPayload;
import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment;
import dev.fitko.fitconnect.client.subscriber.model.ReceivedData; import dev.fitko.fitconnect.client.subscriber.model.ReceivedData;
import dev.fitko.fitconnect.core.util.StopWatch; import dev.fitko.fitconnect.core.util.StopWatch;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -29,9 +33,9 @@ import java.nio.charset.StandardCharsets; ...@@ -29,9 +33,9 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
...@@ -53,25 +57,25 @@ public class SubscriberClient { ...@@ -53,25 +57,25 @@ public class SubscriberClient {
} }
/** /**
* Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}. * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}s destination.
* *
* @param destinationId unique identifier for a destination * @param destinationId filter criterion for the submissions
* @return set of the first 0..500 available submissions * @return set of the first 0..500 available submissions
*/ */
public Set<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId) { public Set<SubmissionForPickup> getAvailableSubmissionsForDestination(final UUID destinationId) {
return getAvailableSubmissions(destinationId, 0, DEFAULT_SUBMISSION_LIMIT); return getAvailableSubmissionsForDestination(destinationId, 0, DEFAULT_SUBMISSION_LIMIT);
} }
/** /**
* Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}. * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}s destination.
* *
* @param destinationId unique identifier for a destination * @param destinationId filter criterion for the submissions
* @param offset position in the dataset * @param offset position in the dataset
* @param limit number of submissions in result (max. is 500) * @param limit number of submissions in result (max. is 500)
* @return set of available submissions in the given range * @return set of available submissions in the given range
*/ */
public Set<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId, final int offset, final int limit) { public Set<SubmissionForPickup> getAvailableSubmissionsForDestination(final UUID destinationId, final int offset, final int limit) {
final Set<SubmissionForPickup> submissions = subscriber.pollAvailableSubmissions(destinationId, offset, limit); final Set<SubmissionForPickup> submissions = subscriber.pollAvailableSubmissionsForDestination(destinationId, offset, limit);
LOGGER.info("Received {} submission(s) for destination {}", submissions.size(), destinationId); LOGGER.info("Received {} submission(s) for destination {}", submissions.size(), destinationId);
return submissions; return submissions;
} }
...@@ -90,8 +94,19 @@ public class SubscriberClient { ...@@ -90,8 +94,19 @@ public class SubscriberClient {
/** /**
* Loads a single {@link SubmissionForPickup} by id. * Loads a single {@link SubmissionForPickup} by id.
* *
* @param submissionId unique identifier of a {@link SubmissionForPickup} * @param submission {@link SentSubmission} that is requested
* @return {@link ReceivedSubmission} to get the submission {@link Metadata}, {@link Data} and {@link Attachment}s as well as * @return {@link ReceivedSubmission} to get the submission {@link Metadata}, {@link Data} and {@link ApiAttachment}s as well as
* accept or reject the loaded submission
*/
public ReceivedSubmission requestSubmission(final SubmissionForPickup submission) {
return requestSubmission(submission.getSubmissionId());
}
/**
* Loads a single {@link SubmissionForPickup} by id.
*
* @param submissionId unique identifier of a submission
* @return {@link ReceivedSubmission} to get the submission {@link Metadata}, {@link Data} and {@link ApiAttachment}s as well as
* accept or reject the loaded submission * accept or reject the loaded submission
*/ */
public ReceivedSubmission requestSubmission(final UUID submissionId) { public ReceivedSubmission requestSubmission(final UUID submissionId) {
...@@ -120,7 +135,7 @@ public class SubscriberClient { ...@@ -120,7 +135,7 @@ public class SubscriberClient {
} }
LOGGER.info("Loading and decrypting attachments ..."); LOGGER.info("Loading and decrypting attachments ...");
final List<Attachment> attachmentMetadata = metadata.getContentStructure().getAttachments(); final List<ApiAttachment> attachmentMetadata = metadata.getContentStructure().getAttachments();
final List<DecryptedAttachmentPayload> decryptedAttachmentPayloads = loadAttachments(submissionId, attachmentMetadata); final List<DecryptedAttachmentPayload> decryptedAttachmentPayloads = loadAttachments(submissionId, attachmentMetadata);
final ValidationResult attachmentValidation = validateAttachments(decryptedAttachmentPayloads); final ValidationResult attachmentValidation = validateAttachments(decryptedAttachmentPayloads);
if (attachmentValidation.hasError()) { if (attachmentValidation.hasError()) {
...@@ -143,6 +158,17 @@ public class SubscriberClient { ...@@ -143,6 +158,17 @@ public class SubscriberClient {
return null; return null;
} }
/**
* Send a reject-submission {@link Event} to confirm that the handed in submission was rejected.
*
* @param submissionForPickup the submission that should be rejected
* @param rejectionProblems list of {@link Problem}s that give more detailed information on why the submission was rejected
* @see <a href="https://docs.fitko.de/fit-connect/docs/getting-started/event-log/events">FIT-Connect Events</a>
*/
public void rejectSubmission(final SubmissionForPickup submissionForPickup, final List<Problem> rejectionProblems) {
subscriber.rejectSubmission(EventPayload.forRejectEvent(submissionForPickup, rejectionProblems));
}
public ValidationResult validateCallback(final String hmac, final Long timestamp, final String httpBody, final String callbackSecret) { public ValidationResult validateCallback(final String hmac, final Long timestamp, final String httpBody, final String callbackSecret) {
return subscriber.validateCallback(hmac, timestamp, httpBody, callbackSecret); return subscriber.validateCallback(hmac, timestamp, httpBody, callbackSecret);
} }
...@@ -150,27 +176,23 @@ public class SubscriberClient { ...@@ -150,27 +176,23 @@ public class SubscriberClient {
private ReceivedSubmission buildReceivedSubmission(final Submission submission, final Metadata metadata, final byte[] decryptedData, final List<DecryptedAttachmentPayload> attachments) { private ReceivedSubmission buildReceivedSubmission(final Submission submission, final Metadata metadata, final byte[] decryptedData, final List<DecryptedAttachmentPayload> attachments) {
final MimeType mimeType = metadata.getContentStructure().getData().getSubmissionSchema().getMimeType(); final MimeType mimeType = metadata.getContentStructure().getData().getSubmissionSchema().getMimeType();
final ReceivedData receivedData = new ReceivedData(new String(decryptedData, StandardCharsets.UTF_8), mimeType); final ReceivedData receivedData = new ReceivedData(new String(decryptedData, StandardCharsets.UTF_8), mimeType);
final List<ReceivedAttachment> receivedAttachments = attachments.stream().map(mapToReceivedAttachment()).collect(Collectors.toList()); final List<Attachment> receivedAttachments = attachments.stream().map(this::mapToAttachment).collect(Collectors.toList());
return new ReceivedSubmission(subscriber, submission, metadata, receivedData, receivedAttachments); // TODO setting auth tags was refactored in #598-auto-reject
return new ReceivedSubmission(subscriber, submission, metadata, receivedData, receivedAttachments, Map.of());
} }
private static Function<DecryptedAttachmentPayload, ReceivedAttachment> mapToReceivedAttachment() { private Attachment mapToAttachment(final DecryptedAttachmentPayload payload) {
return payload -> ReceivedAttachment.builder() final ApiAttachment metadata = payload.getAttachmentMetadata();
.attachmentId(payload.getAttachmentMetadata().getAttachmentId()) return Attachment.fromByteArray(payload.getDecryptedContent(), metadata.getMimeType(), metadata.getFilename(), metadata.getDescription());
.filename(payload.getAttachmentMetadata().getFilename())
.mimeType(payload.getAttachmentMetadata().getMimeType())
.description(payload.getAttachmentMetadata().getDescription())
.data(payload.getDecryptedContent())
.build();
} }
private List<DecryptedAttachmentPayload> loadAttachments(final UUID submissionId, final List<Attachment> attachmentMetadata) { private List<DecryptedAttachmentPayload> loadAttachments(final UUID submissionId, final List<ApiAttachment> attachmentMetadata) {
if (attachmentMetadata == null || attachmentMetadata.isEmpty()) { if (attachmentMetadata == null || attachmentMetadata.isEmpty()) {
LOGGER.info("Submission contains no attachments"); LOGGER.info("Submission contains no attachments");
return Collections.emptyList(); return Collections.emptyList();
} }
final List<DecryptedAttachmentPayload> receivedAttachments = new ArrayList<>(); final List<DecryptedAttachmentPayload> receivedAttachments = new ArrayList<>();
for (final Attachment metadata : attachmentMetadata) { for (final ApiAttachment metadata : attachmentMetadata) {
final String encryptedAttachment = downloadAttachment(submissionId, metadata); final String encryptedAttachment = downloadAttachment(submissionId, metadata);
final byte[] decryptedAttachment = decryptAttachment(metadata, encryptedAttachment); final byte[] decryptedAttachment = decryptAttachment(metadata, encryptedAttachment);
final DecryptedAttachmentPayload decryptedAttachmentPayload = DecryptedAttachmentPayload.builder() final DecryptedAttachmentPayload decryptedAttachmentPayload = DecryptedAttachmentPayload.builder()
...@@ -182,14 +204,14 @@ public class SubscriberClient { ...@@ -182,14 +204,14 @@ public class SubscriberClient {
return receivedAttachments; return receivedAttachments;
} }
private byte[] decryptAttachment(final Attachment metadata, final String encryptedAttachment) { private byte[] decryptAttachment(final ApiAttachment metadata, final String encryptedAttachment) {
final var startDecryption = StopWatch.start(); final var startDecryption = StopWatch.start();
final byte[] decryptedAttachment = subscriber.decryptStringContent(privateKey, encryptedAttachment); final byte[] decryptedAttachment = subscriber.decryptStringContent(privateKey, encryptedAttachment);
LOGGER.info("Decrypting attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDecryption)); LOGGER.info("Decrypting attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDecryption));
return decryptedAttachment; return decryptedAttachment;
} }
private String downloadAttachment(final UUID submissionId, final Attachment metadata) { private String downloadAttachment(final UUID submissionId, final ApiAttachment metadata) {
final var startDownload = StopWatch.start(); final var startDownload = StopWatch.start();
final String encryptedAttachment = subscriber.fetchAttachment(submissionId, metadata.getAttachmentId()); final String encryptedAttachment = subscriber.fetchAttachment(submissionId, metadata.getAttachmentId());
LOGGER.info("Downloading attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDownload)); LOGGER.info("Downloading attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDownload));
...@@ -207,7 +229,7 @@ public class SubscriberClient { ...@@ -207,7 +229,7 @@ public class SubscriberClient {
private ValidationResult validateAttachments(final List<DecryptedAttachmentPayload> decryptedAttachmentPayloads) { private ValidationResult validateAttachments(final List<DecryptedAttachmentPayload> decryptedAttachmentPayloads) {
for (final DecryptedAttachmentPayload decryptedAttachment : decryptedAttachmentPayloads) { for (final DecryptedAttachmentPayload decryptedAttachment : decryptedAttachmentPayloads) {
final Attachment attachmentMetadata = decryptedAttachment.getAttachmentMetadata(); final ApiAttachment attachmentMetadata = decryptedAttachment.getAttachmentMetadata();
final UUID attachmentId = attachmentMetadata.getAttachmentId(); final UUID attachmentId = attachmentMetadata.getAttachmentId();
final ValidationResult result = subscriber.validateHashIntegrity(attachmentMetadata.getHash().getContent(), decryptedAttachment.getDecryptedContent()); final ValidationResult result = subscriber.validateHashIntegrity(attachmentMetadata.getHash().getContent(), decryptedAttachment.getDecryptedContent());
if (result.hasError()) { if (result.hasError()) {
......
...@@ -7,23 +7,27 @@ import dev.fitko.fitconnect.client.SenderClient; ...@@ -7,23 +7,27 @@ import dev.fitko.fitconnect.client.SenderClient;
import dev.fitko.fitconnect.client.SubscriberClient; import dev.fitko.fitconnect.client.SubscriberClient;
import dev.fitko.fitconnect.client.cli.batch.BatchImporter; import dev.fitko.fitconnect.client.cli.batch.BatchImporter;
import dev.fitko.fitconnect.client.cli.batch.ImportRecord; import dev.fitko.fitconnect.client.cli.batch.ImportRecord;
import dev.fitko.fitconnect.client.cli.commands.*; import dev.fitko.fitconnect.client.cli.commands.GetAllSubmissionsCommand;
import dev.fitko.fitconnect.client.cli.commands.GetOneSubmissionCommand;
import dev.fitko.fitconnect.client.cli.commands.ListAllSubmissionsCommand;
import dev.fitko.fitconnect.client.cli.commands.SendBatchCommand;
import dev.fitko.fitconnect.client.cli.commands.SendSubmissionCommand;
import dev.fitko.fitconnect.client.cli.util.AttachmentDataType; import dev.fitko.fitconnect.client.cli.util.AttachmentDataType;
import dev.fitko.fitconnect.client.sender.SubmissionBuilder; import dev.fitko.fitconnect.client.sender.model.Attachment;
import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; import dev.fitko.fitconnect.client.sender.model.SendableSubmission;
import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission;
import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment;
import dev.fitko.fitconnect.core.util.StopWatch; import dev.fitko.fitconnect.core.util.StopWatch;
import org.apache.tika.Tika;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
...@@ -59,7 +63,7 @@ class CommandExecutor { ...@@ -59,7 +63,7 @@ class CommandExecutor {
void getAllSubmissions(final GetAllSubmissionsCommand getAllSubmissionsCommand) throws IOException { void getAllSubmissions(final GetAllSubmissionsCommand getAllSubmissionsCommand) throws IOException {
final var destinationId = getAllSubmissionsCommand.destinationId; final var destinationId = getAllSubmissionsCommand.destinationId;
LOGGER.info("Getting all available submissions for destination {}", destinationId); LOGGER.info("Getting all available submissions for destination {}", destinationId);
final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissions(destinationId); final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissionsForDestination(destinationId);
for (final SubmissionForPickup submission : submissions) { for (final SubmissionForPickup submission : submissions) {
final GetOneSubmissionCommand getOneSubmissionCommand = new GetOneSubmissionCommand(); final GetOneSubmissionCommand getOneSubmissionCommand = new GetOneSubmissionCommand();
getOneSubmissionCommand.submissionId = submission.getSubmissionId(); getOneSubmissionCommand.submissionId = submission.getSubmissionId();
...@@ -71,15 +75,16 @@ class CommandExecutor { ...@@ -71,15 +75,16 @@ class CommandExecutor {
void listSubmissions(final ListAllSubmissionsCommand listAllSubmissionsCommand) { void listSubmissions(final ListAllSubmissionsCommand listAllSubmissionsCommand) {
final var destinationId = listAllSubmissionsCommand.destinationId; final var destinationId = listAllSubmissionsCommand.destinationId;
LOGGER.info("Listing available submissions for destination {}", destinationId); LOGGER.info("Listing available submissions for destination {}", destinationId);
for (final SubmissionForPickup submission : subscriberClient.getAvailableSubmissions(destinationId)) { for (final SubmissionForPickup submission : subscriberClient.getAvailableSubmissionsForDestination(destinationId)) {
LOGGER.info("caseId: {} - submissionId: {}", submission.getCaseId(), submission.getSubmissionId()); LOGGER.info("caseId: {} - submissionId: {}", submission.getCaseId(), submission.getSubmissionId());
} }
} }
SentSubmission sendSubmission(final SendSubmissionCommand sendSubmissionCommand) throws IOException { SentSubmission sendSubmission(final SendSubmissionCommand sendSubmissionCommand) throws IOException {
LOGGER.info("Sending new submission to destination {}", sendSubmissionCommand.destinationId); LOGGER.info("Sending new submission to destination {}", sendSubmissionCommand.destinationId);
final Tika mimeTypeDetector = new Tika();
final var startTime = StopWatch.start(); final var startTime = StopWatch.start();
final List<File> attachments = sendSubmissionCommand.attachments.stream().map(File::new).collect(Collectors.toList()); final List<Attachment> attachments = sendSubmissionCommand.attachments.stream().map(toAttachment(mimeTypeDetector)).collect(Collectors.toList());
final SentSubmission submission; final SentSubmission submission;
if (sendSubmissionCommand.dataType == AttachmentDataType.json) { if (sendSubmissionCommand.dataType == AttachmentDataType.json) {
submission = sendWithJsonData(sendSubmissionCommand, attachments); submission = sendWithJsonData(sendSubmissionCommand, attachments);
...@@ -89,7 +94,6 @@ class CommandExecutor { ...@@ -89,7 +94,6 @@ class CommandExecutor {
LOGGER.info("Import took {}", StopWatch.stopWithFormattedTime(startTime)); LOGGER.info("Import took {}", StopWatch.stopWithFormattedTime(startTime));
return submission; return submission;
} }
void sendBatch(final SendBatchCommand sendBatchCommand) throws BatchImportException, IOException { void sendBatch(final SendBatchCommand sendBatchCommand) throws BatchImportException, IOException {
final List<ImportRecord> importRecords = batchImporter.readRecords(sendBatchCommand.dataPath); final List<ImportRecord> importRecords = batchImporter.readRecords(sendBatchCommand.dataPath);
LOGGER.info("Sending batch of {} submissions", importRecords.size()); LOGGER.info("Sending batch of {} submissions", importRecords.size());
...@@ -106,28 +110,28 @@ class CommandExecutor { ...@@ -106,28 +110,28 @@ class CommandExecutor {
LOGGER.info("DONE ! Finished batch import of {} submissions in {}", importCount, StopWatch.stopWithFormattedTime(startTime)); LOGGER.info("DONE ! Finished batch import of {} submissions in {}", importCount, StopWatch.stopWithFormattedTime(startTime));
} }
private SentSubmission sendWithJsonData(final SendSubmissionCommand sendSubmissionCommand, final List<File> attachments) throws IOException { private SentSubmission sendWithJsonData(final SendSubmissionCommand sendSubmissionCommand, final List<Attachment> attachments) throws IOException {
final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() final SendableSubmission sendableSubmission = SendableSubmission.Builder()
.withAttachments(attachments) .setDestination(sendSubmissionCommand.destinationId)
.withJsonData(getDataAsString(sendSubmissionCommand.data)) .setServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
.withDestination(sendSubmissionCommand.destinationId) .setJsonData(getDataAsString(sendSubmissionCommand.data))
.withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey) .addAttachments(attachments)
.build(); .build();
return senderClient.submit(submissionPayload); return senderClient.send(sendableSubmission);
} }
private SentSubmission sendWithXmlData(final SendSubmissionCommand sendSubmissionCommand, final List<File> attachments) throws IOException { private SentSubmission sendWithXmlData(final SendSubmissionCommand sendSubmissionCommand, final List<Attachment> attachments) throws IOException {
final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() final SendableSubmission sendableSubmission = SendableSubmission.Builder()
.withAttachments(attachments) .setDestination(sendSubmissionCommand.destinationId)
.withXmlData(getDataAsString(sendSubmissionCommand.data)) .setServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
.withDestination(sendSubmissionCommand.destinationId) .setXmlData(getDataAsString(sendSubmissionCommand.data))
.withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey) .addAttachments(attachments)
.build(); .build();
return senderClient.submit(submissionPayload); return senderClient.send(sendableSubmission);
} }
private String getTargetFolderPath(final GetOneSubmissionCommand getOneSubmissionCommand) { private String getTargetFolderPath(final GetOneSubmissionCommand getOneSubmissionCommand) {
...@@ -143,6 +147,9 @@ class CommandExecutor { ...@@ -143,6 +147,9 @@ class CommandExecutor {
return Files.readString(Path.of(dataPath)); return Files.readString(Path.of(dataPath));
} }
private Function<String, Attachment> toAttachment(final Tika mimeTypeDetector) {
return path -> Attachment.fromPath(Path.of(path), mimeTypeDetector.detect(path), Path.of(path).getFileName().toString(), "attachment");
}
private void writeData(final ReceivedSubmission receivedSubmission, final String dataDirPath) throws IOException { private void writeData(final ReceivedSubmission receivedSubmission, final String dataDirPath) throws IOException {
LOGGER.info("Creating data directory for submission in {}", dataDirPath); LOGGER.info("Creating data directory for submission in {}", dataDirPath);
...@@ -151,12 +158,12 @@ class CommandExecutor { ...@@ -151,12 +158,12 @@ class CommandExecutor {
final var fileEnding = AttachmentDataType.getFileTypeFromMimeType(receivedSubmission.getDataMimeType()); final var fileEnding = AttachmentDataType.getFileTypeFromMimeType(receivedSubmission.getDataMimeType());
final var filePath = Path.of(dataDirPath + "/data." + fileEnding); final var filePath = Path.of(dataDirPath + "/data." + fileEnding);
LOGGER.info("Writing data.{}", fileEnding); LOGGER.info("Writing data.{}", fileEnding);
Files.write(filePath, receivedSubmission.getData().getBytes(StandardCharsets.UTF_8)); Files.write(filePath, receivedSubmission.getDataAsString().getBytes(StandardCharsets.UTF_8));
for (final ReceivedAttachment attachment : receivedSubmission.getAttachments()) { for (final Attachment attachment : receivedSubmission.getAttachments()) {
final String filename = attachment.getFilename(); final String filename = attachment.getFileName();
LOGGER.info("Writing attachment {}", filename); LOGGER.info("Writing attachment {}", filename);
Files.write(Path.of(dataDirPath, filename), attachment.getData()); Files.write(Path.of(dataDirPath, filename), attachment.getDataAsBytes());
} }
} }
......
...@@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; ...@@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public final class CommandLineRunner { public final class CommandLineRunner {
...@@ -29,16 +30,16 @@ public final class CommandLineRunner { ...@@ -29,16 +30,16 @@ public final class CommandLineRunner {
private static ApplicationConfig loadConfig() { private static ApplicationConfig loadConfig() {
try { try {
return ApplicationConfigLoader.loadConfig(DEFAULT_CONFIG_NAME); return ApplicationConfigLoader.loadConfigFromEnvironment();
} catch (final Exception e) { } catch (final Exception e) {
LOGGER.warn("Could not load default config {} {}", DEFAULT_CONFIG_NAME, e); LOGGER.warn("Could not load config from environment, loading default config {} {}", DEFAULT_CONFIG_NAME, e);
return null; return ApplicationConfigLoader.loadConfigFromPath(Path.of(DEFAULT_CONFIG_NAME));
} }
} }
private static CommandLineClient getCommandLineClient(final ApplicationConfig config) { private static CommandLineClient getCommandLineClient(final ApplicationConfig config) {
final SenderClient senderClient = config == null ? ClientFactory.senderClient() : ClientFactory.senderClient(config); final SenderClient senderClient = ClientFactory.getSenderClient(config);
final var subscriberClient = config == null ? ClientFactory.subscriberClient() : ClientFactory.subscriberClient(config); final var subscriberClient = ClientFactory.getSubscriberClient(config);
return new CommandLineClient(senderClient, subscriberClient); return new CommandLineClient(senderClient, subscriberClient);
} }
......
...@@ -12,8 +12,8 @@ public enum AttachmentDataType { ...@@ -12,8 +12,8 @@ public enum AttachmentDataType {
this.fileType = fileType; this.fileType = fileType;
} }
public static String getFileTypeFromMimeType(final MimeType mimeType) { public static String getFileTypeFromMimeType(final String mimeType) {
switch (mimeType) { switch (MimeType.fromValue(mimeType)) {
case APPLICATION_JSON: case APPLICATION_JSON:
return json.fileType; return json.fileType;
case APPLICATION_XML: case APPLICATION_XML:
......
package dev.fitko.fitconnect.client.factory; package dev.fitko.fitconnect.client.factory;
import dev.fitko.fitconnect.api.config.ApplicationConfig; import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.config.BuildInfo;
import dev.fitko.fitconnect.api.exceptions.InitializationException; import dev.fitko.fitconnect.api.exceptions.InitializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
public final class ApplicationConfigLoader { public final class ApplicationConfigLoader {
private static final String BUILD_INFO_PATH = "buildinfo.yaml"; private static final Logger LOGGER = LoggerFactory.getLogger(ClientFactory.class);
private static final String CONFIG_ENV_KEY_NAME = "FIT_CONNECT_CONFIG";
private ApplicationConfigLoader() { private ApplicationConfigLoader() {
} }
...@@ -24,23 +26,25 @@ public final class ApplicationConfigLoader { ...@@ -24,23 +26,25 @@ public final class ApplicationConfigLoader {
* @return ApplicationConfig * @return ApplicationConfig
* @throws InitializationException if the config file could not be loaded * @throws InitializationException if the config file could not be loaded
*/ */
public static ApplicationConfig loadConfig(final Path configPath) { public static ApplicationConfig loadConfigFromPath(final Path configPath) {
try { try {
return loadConfigFromYaml(Files.readString(configPath)); return loadConfigFromYamlString(Files.readString(configPath));
} catch (final IOException | NullPointerException e) { } catch (final IOException | NullPointerException e) {
LOGGER.error("config file could not be loaded from path {}", configPath);
throw new InitializationException(e.getMessage(), e); throw new InitializationException(e.getMessage(), e);
} }
} }
/** /**
* Load ApplicationConfig from path string. * Load ApplicationConfig from FIT_CONNECT_CONFIG environment variable.
* Make sure this variable is set and points to a config.yaml.
* *
* @param configPath string of path to config file
* @return ApplicationConfig * @return ApplicationConfig
* @throws InitializationException if the config file could not be loaded * @throws InitializationException if the config file could not be loaded
*/ */
public static ApplicationConfig loadConfig(final String configPath) { public static ApplicationConfig loadConfigFromEnvironment() {
return loadConfig(Path.of(configPath)); final Path configPath = getPathFromEnvironment();
return loadConfigFromPath(configPath);
} }
/** /**
...@@ -49,17 +53,17 @@ public final class ApplicationConfigLoader { ...@@ -49,17 +53,17 @@ public final class ApplicationConfigLoader {
* @param configYaml string content of the yaml config file * @param configYaml string content of the yaml config file
* @return ApplicationConfig * @return ApplicationConfig
*/ */
public static ApplicationConfig loadConfigFromYaml(final String configYaml) { public static ApplicationConfig loadConfigFromYamlString(final String configYaml) {
return new Yaml().loadAs(configYaml, ApplicationConfig.class); return new Yaml().loadAs(configYaml, ApplicationConfig.class);
} }
/** private static Path getPathFromEnvironment() {
* Load BuildInfo properties. try {
* return Path.of(System.getenv(CONFIG_ENV_KEY_NAME));
* @return {@link BuildInfo} } catch (final NullPointerException | InvalidPathException e) {
*/ LOGGER.error("Environment variable {} could not be loaded", CONFIG_ENV_KEY_NAME);
public static BuildInfo loadBuildInfo() { throw new InitializationException(e.getMessage(), e);
final InputStream resource = ApplicationConfigLoader.class.getClassLoader().getResourceAsStream(BUILD_INFO_PATH); }
return new Yaml().loadAs(resource, BuildInfo.class);
} }
} }
...@@ -43,8 +43,10 @@ import dev.fitko.fitconnect.core.validation.DefaultValidationService; ...@@ -43,8 +43,10 @@ import dev.fitko.fitconnect.core.validation.DefaultValidationService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.text.ParseException; import java.text.ParseException;
...@@ -57,22 +59,13 @@ public final class ClientFactory { ...@@ -57,22 +59,13 @@ public final class ClientFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientFactory.class); private static final Logger LOGGER = LoggerFactory.getLogger(ClientFactory.class);
private static final String CONFIG_ENV_KEY_NAME = "FIT_CONNECT_CONFIG";
private static final String SET_SCHEMA_DIR = "/set-schema"; private static final String SET_SCHEMA_DIR = "/set-schema";
private static final String DESTINATION_SCHEMA_DIR = "/destination-schema"; private static final String DESTINATION_SCHEMA_DIR = "/destination-schema";
private static final String METADATA_SCHEMA_DIR = "/metadata-schema"; private static final String METADATA_SCHEMA_DIR = "/metadata-schema";
private static final String BUILD_INFO_PATH = "buildinfo.yaml";
private ClientFactory() {
}
/** private ClientFactory() {
* Create a new {@link SenderClient} to send submissions that is automatically configured via the config.yml file.
*
* @return the sender client
*/
public static SenderClient senderClient() {
return senderClient(loadConfig());
} }
/** /**
...@@ -80,19 +73,10 @@ public final class ClientFactory { ...@@ -80,19 +73,10 @@ public final class ClientFactory {
* *
* @return the sender client * @return the sender client
*/ */
public static SenderClient senderClient(final ApplicationConfig config) { public static SenderClient getSenderClient(final ApplicationConfig config) {
checkNotNull(config);
LOGGER.info("Initializing sender client ..."); LOGGER.info("Initializing sender client ...");
return new SenderClient(getSender(config, ApplicationConfigLoader.loadBuildInfo())); return new SenderClient(getSender(config, loadBuildInfo()));
}
/**
* Create a new {@link SubscriberClient} to receive submissions that is automatically configured via config.yml file.
*
* @return the subscriber client
*/
public static SubscriberClient subscriberClient() {
return subscriberClient(loadConfig());
} }
/** /**
...@@ -100,35 +84,31 @@ public final class ClientFactory { ...@@ -100,35 +84,31 @@ public final class ClientFactory {
* *
* @return the subscriber client * @return the subscriber client
*/ */
public static SubscriberClient subscriberClient(final ApplicationConfig config) { public static SubscriberClient getSubscriberClient(final ApplicationConfig config) {
checkNotNull(config);
LOGGER.info("Initializing subscriber client ..."); LOGGER.info("Initializing subscriber client ...");
final Subscriber subscriber = getSubscriber(config, ApplicationConfigLoader.loadBuildInfo()); final Subscriber subscriber = getSubscriber(config, loadBuildInfo());
final SubscriberConfig subscriberConfig = config.getSubscriberConfig(); final SubscriberConfig subscriberConfig = config.getSubscriberConfig();
LOGGER.info("Reading private decryption key from {} ", subscriberConfig.getPrivateDecryptionKeyPath()); LOGGER.info("Reading private decryption key");
final String privateKeyPath = readPath(subscriberConfig.getPrivateDecryptionKeyPath(), "Decryption Key"); final String privateKeyPath = readPath(getPrivateDecryptionKeyPathFromSubscriber(subscriberConfig), "Decryption Key");
final RSAKey privateKey = readRSAKeyFromString(privateKeyPath); final RSAKey privateKey = readRSAKeyFromString(privateKeyPath);
return new SubscriberClient(subscriber, privateKey); return new SubscriberClient(subscriber, privateKey);
} }
/** /**
* Create a new {@link RoutingClient} to find destinations and services that is automatically configured via a provided {@link ApplicationConfig}. * Create a new {@link RoutingClient} to find destinations and services that is automatically configured via a provided {@link ApplicationConfig}.
* *
* @return the routing client * @return the routing client
*/ */
public static RoutingClient routingClient() { public static RoutingClient getRoutingClient(final ApplicationConfig config) {
return routingClient(loadConfig());
} checkNotNull(config);
/**
* Create a new {@link RoutingClient} to find destinations and services that is automatically configured via a provided {@link ApplicationConfig}.
*
* @return the routing client
*/
public static RoutingClient routingClient(final ApplicationConfig config) {
LOGGER.info("Initializing routing client ..."); LOGGER.info("Initializing routing client ...");
final RestTemplate restTemplate = getRestTemplate(config, ApplicationConfigLoader.loadBuildInfo()); final RestTemplate restTemplate = getRestTemplate(config, loadBuildInfo());
final SchemaProvider schemaProvider = getSchemaProvider(); final SchemaProvider schemaProvider = getSchemaProvider();
final OAuthService authService = getSenderConfiguredAuthService(config, restTemplate); final OAuthService authService = getSenderConfiguredAuthService(config, restTemplate);
...@@ -178,7 +158,6 @@ public final class ClientFactory { ...@@ -178,7 +158,6 @@ public final class ClientFactory {
return new SubmissionSubscriber(submissionService, eventLogService, cryptoService, validator, setService); return new SubmissionSubscriber(submissionService, eventLogService, cryptoService, validator, setService);
} }
private static OAuthService getSenderConfiguredAuthService(final ApplicationConfig config, final RestTemplate restTemplate) { private static OAuthService getSenderConfiguredAuthService(final ApplicationConfig config, final RestTemplate restTemplate) {
final String clientId = config.getSenderConfig().getClientId(); final String clientId = config.getSenderConfig().getClientId();
final String clientSecret = config.getSenderConfig().getClientSecret(); final String clientSecret = config.getSenderConfig().getClientSecret();
...@@ -265,24 +244,23 @@ public final class ClientFactory { ...@@ -265,24 +244,23 @@ public final class ClientFactory {
} }
} }
private static Path readConfigFromEnvironment() { private static BuildInfo loadBuildInfo() {
try { final InputStream resource = ClientFactory.class.getClassLoader().getResourceAsStream(BUILD_INFO_PATH);
return Path.of(System.getenv(CONFIG_ENV_KEY_NAME)); return new Yaml().loadAs(resource, BuildInfo.class);
} catch (final NullPointerException e) { }
LOGGER.error("Environment variable {} could not be loaded", CONFIG_ENV_KEY_NAME);
throw new InitializationException(e.getMessage(), e); private static void checkNotNull(final ApplicationConfig config) {
if(config == null){
throw new InitializationException("application config must not be null");
} }
} }
private static ApplicationConfig loadConfig() { private static String getPrivateDecryptionKeyPathFromSubscriber(final SubscriberConfig subscriberConfig) {
try { if(subscriberConfig.getPrivateDecryptionKeyPaths().size() != 1){
final Path configPath = readConfigFromEnvironment(); throw new InitializationException("Currently only one configured private key per subscriber is allowed !");
final ApplicationConfig applicationConfig = ApplicationConfigLoader.loadConfig(configPath);
LOGGER.info("Using sdk environment config `{}` ", applicationConfig.getActiveEnvironment().getName());
return applicationConfig;
} catch (final InitializationException e) {
LOGGER.error("Config could not be loaded, please provide a 'config.yml' by setting the environment variable 'FIT_CONNECT_CONFIG'");
throw new InitializationException(e.getMessage(), e);
} }
final String keyPath = subscriberConfig.getPrivateDecryptionKeyPaths().get(0);
LOGGER.info("Reading private decryption key from {}", keyPath);
return keyPath;
} }
} }
...@@ -3,9 +3,10 @@ package dev.fitko.fitconnect.client.router; ...@@ -3,9 +3,10 @@ package dev.fitko.fitconnect.client.router;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Value; import lombok.Value;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Builds a new search request for a service identifier and AT MAX. ONE OTHER search criterion (ars | ags | areaId). * Builds a new search request for a service identifier and AT MAX. ONE OTHER search criterion (ars | ags | areaId).
*/ */
...@@ -21,11 +22,118 @@ public class DestinationSearch { ...@@ -21,11 +22,118 @@ public class DestinationSearch {
int offset; int offset;
int limit; int limit;
public static Builder Builder() { public static MandatoryProperties Builder() {
return new Builder(); return new Builder();
} }
public static class Builder { public interface MandatoryProperties {
/**
* Leika Key of the requested service.
*
* @param leikaKey service identifier
* @return Builder
* @throws IllegalArgumentException if the leika key pattern is not matching
*/
OptionalProperties withLeikaKey(final String leikaKey) throws IllegalArgumentException;
}
public interface OptionalProperties {
/**
* Official municipal code of the place.
*
* @param ars amtlicher regionalschlüssel
* @return Builder
* @throws IllegalArgumentException if the ars key pattern is not matching
*/
Pagination withArs(final String ars) throws IllegalArgumentException;
/**
* Official regional key of the area.
*
* @param ags amtlicher gemeindeschlüssel
* @return Builder
* @throws IllegalArgumentException if the ags key pattern is not matching
*/
Pagination withAgs(final String ags) throws IllegalArgumentException;
/**
* ID of the area. This ID can be determined via the routing clients <code>findAreas</code> search.
*
* @param areaId id of the area
* @return Builder
* @throws IllegalArgumentException if the area id pattern is not matching
* @see dev.fitko.fitconnect.client.RoutingClient#findAreas(String, int, int)
*/
Pagination withAreaId(final String areaId) throws IllegalArgumentException;
/**
* Start position of the subset of the result set. Default is 0.
*
* @param offset start of the subset
* @return Builder
* @throws IllegalArgumentException if the offset is a negative number
*/
Builder withOffset(final int offset) throws IllegalArgumentException;
/**
* Max. size of the subset of the result set. Maximum is 500. Default is 100.
*
* @param limit max. entries in the subset
* @return Builder
* @throws IllegalArgumentException if the limit is > 500
*/
Builder withLimit(final int limit) throws IllegalArgumentException;
/**
* Construct the search request.
*
* @return DestinationSearch
*/
DestinationSearch build() throws IllegalArgumentException;
}
public interface Pagination {
/**
* Start position of the subset of the result set. Default is 0.
*
* @param offset start of the subset
* @return Builder
* @throws IllegalArgumentException if the offset is a negative number
*/
Pagination withOffset(final int offset) throws IllegalArgumentException;
/**
* Max. size of the subset of the result set. Maximum is 500. Default is 100.
*
* @param limit max. entries in the subset
* @return Builder
* @throws IllegalArgumentException if the limit is > 500
*/
Pagination withLimit(final int limit) throws IllegalArgumentException;
/**
* Construct the search request.
*
* @return DestinationSearch
*/
DestinationSearch build() throws IllegalArgumentException;
}
public interface BuildSearch {
/**
* Construct the search request.
*
* @return DestinationSearch
*/
DestinationSearch build() throws IllegalArgumentException;
}
private static class Builder implements MandatoryProperties, OptionalProperties, Pagination, BuildSearch {
private static final Pattern AGS_PATTERN = Pattern.compile("^(\\d{2}|\\d{3}|\\d{5}|\\d{8})$"); private static final Pattern AGS_PATTERN = Pattern.compile("^(\\d{2}|\\d{3}|\\d{5}|\\d{8})$");
private static final Pattern ARS_PATTERN = Pattern.compile("^(\\d{2}|\\d{3}|\\d{5}|\\d{9}|\\d{12})$"); private static final Pattern ARS_PATTERN = Pattern.compile("^(\\d{2}|\\d{3}|\\d{5}|\\d{9}|\\d{12})$");
...@@ -39,14 +147,9 @@ public class DestinationSearch { ...@@ -39,14 +147,9 @@ public class DestinationSearch {
private int offset = 0; private int offset = 0;
private int limit = 100; private int limit = 100;
/**
* Leika Key of the requested service. @Override
* public OptionalProperties withLeikaKey(final String leikaKey) throws IllegalArgumentException {
* @param leikaKey service identifier
* @return Builder
* @throws IllegalArgumentException if the leika key pattern is not matching
*/
public Builder withLeikaKey(final String leikaKey) throws IllegalArgumentException {
if (!LEIKA_KEY_PATTERN.matcher(leikaKey).matches()) { if (!LEIKA_KEY_PATTERN.matcher(leikaKey).matches()) {
throw new IllegalArgumentException("Leika key does not match allowed pattern " + LEIKA_KEY_PATTERN); throw new IllegalArgumentException("Leika key does not match allowed pattern " + LEIKA_KEY_PATTERN);
} }
...@@ -54,14 +157,8 @@ public class DestinationSearch { ...@@ -54,14 +157,8 @@ public class DestinationSearch {
return this; return this;
} }
/** @Override
* Official municipal code of the place. public Pagination withArs(final String ars) throws IllegalArgumentException {
*
* @param ars amtlicher regionalschlüssel
* @return Builder
* @throws IllegalArgumentException if the ars key pattern is not matching
*/
public Builder withArs(final String ars) throws IllegalArgumentException{
if (!ARS_PATTERN.matcher(ars).matches()) { if (!ARS_PATTERN.matcher(ars).matches()) {
throw new IllegalArgumentException("ARS key does not match allowed pattern " + ARS_PATTERN); throw new IllegalArgumentException("ARS key does not match allowed pattern " + ARS_PATTERN);
} }
...@@ -70,14 +167,8 @@ public class DestinationSearch { ...@@ -70,14 +167,8 @@ public class DestinationSearch {
return this; return this;
} }
/** @Override
* Official regional key of the area. public Pagination withAgs(final String ags) throws IllegalArgumentException {
*
* @param ags amtlicher gemeindeschlüssel
* @return Builder
* @throws IllegalArgumentException if the ags key pattern is not matching
*/
public Builder withAgs(final String ags) throws IllegalArgumentException {
if (!AGS_PATTERN.matcher(ags).matches()) { if (!AGS_PATTERN.matcher(ags).matches()) {
throw new IllegalArgumentException("AGS key does not match allowed pattern " + AGS_PATTERN); throw new IllegalArgumentException("AGS key does not match allowed pattern " + AGS_PATTERN);
} }
...@@ -90,10 +181,10 @@ public class DestinationSearch { ...@@ -90,10 +181,10 @@ public class DestinationSearch {
* *
* @param areaId id of the area * @param areaId id of the area
* @return Builder * @return Builder
* @see dev.fitko.fitconnect.client.RoutingClient#findAreas(String, int, int)
* @throws IllegalArgumentException if the area id pattern is not matching * @throws IllegalArgumentException if the area id pattern is not matching
* @see dev.fitko.fitconnect.client.RoutingClient#findAreas(String, int, int)
*/ */
public Builder withAreaId(final String areaId) throws IllegalArgumentException { public Pagination withAreaId(final String areaId) throws IllegalArgumentException {
if (!AREA_ID_PATTERN.matcher(areaId).matches()) { if (!AREA_ID_PATTERN.matcher(areaId).matches()) {
throw new IllegalArgumentException("AreaId key does not match allowed pattern " + AREA_ID_PATTERN); throw new IllegalArgumentException("AreaId key does not match allowed pattern " + AREA_ID_PATTERN);
} }
...@@ -101,14 +192,8 @@ public class DestinationSearch { ...@@ -101,14 +192,8 @@ public class DestinationSearch {
return this; return this;
} }
/** @Override
* Start position of the subset of the result set. Default is 0. public Builder withOffset(final int offset) throws IllegalArgumentException {
*
* @param offset start of the subset
* @return Builder
* @throws IllegalArgumentException if the offset is a negative number
*/
public Builder withOffset(final int offset) throws IllegalArgumentException{
if (limit < 0) { if (limit < 0) {
throw new IllegalArgumentException("offset must be positive"); throw new IllegalArgumentException("offset must be positive");
} }
...@@ -116,13 +201,7 @@ public class DestinationSearch { ...@@ -116,13 +201,7 @@ public class DestinationSearch {
return this; return this;
} }
/** @Override
* Max. size of the subset of the result set. Maximum is 500. Default is 100.
*
* @param limit max. entries in the subset
* @return Builder
* @throws IllegalArgumentException if the limit is > 500
*/
public Builder withLimit(final int limit) throws IllegalArgumentException { public Builder withLimit(final int limit) throws IllegalArgumentException {
if (limit > 500) { if (limit > 500) {
throw new IllegalArgumentException("limit must no be > 500"); throw new IllegalArgumentException("limit must no be > 500");
...@@ -131,15 +210,14 @@ public class DestinationSearch { ...@@ -131,15 +210,14 @@ public class DestinationSearch {
return this; return this;
} }
/** @Override
* Construct the search request.
*
* @return DestinationSearch
*/
public DestinationSearch build() throws IllegalArgumentException { public DestinationSearch build() throws IllegalArgumentException {
if(leikaKey == null){ if (leikaKey == null) {
throw new IllegalArgumentException("leikaKey is mandatory"); throw new IllegalArgumentException("leikaKey is mandatory");
} }
if(Arrays.asList(areaId, ags, ars).stream().allMatch(Objects::isNull)){
throw new IllegalArgumentException("at least one regional search criterion (areaId, ars or ags) is mandatory");
}
return new DestinationSearch(leikaKey, ars, ags, areaId, offset, limit); return new DestinationSearch(leikaKey, ars, ags, areaId, offset, limit);
} }
} }
......
package dev.fitko.fitconnect.client.sender;
import dev.fitko.fitconnect.api.domain.model.submission.ServiceType;
import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload;
import dev.fitko.fitconnect.client.sender.steps.EncryptedBuildStep;
import dev.fitko.fitconnect.client.sender.steps.EncryptedBuilderStartStep;
import dev.fitko.fitconnect.client.sender.steps.EncryptedDataStep;
import dev.fitko.fitconnect.client.sender.steps.EncryptedDestinationStep;
import dev.fitko.fitconnect.client.sender.steps.EncryptedMetadataStep;
import dev.fitko.fitconnect.client.sender.steps.EncryptedServiceTypeStep;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
@Getter
public final class EncryptedSubmissionBuilder implements EncryptedBuilderStartStep,
EncryptedDataStep,
EncryptedMetadataStep,
EncryptedDestinationStep,
EncryptedServiceTypeStep,
EncryptedBuildStep {
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptedSubmissionBuilder.class);
private UUID destinationId;
private Map<UUID, String> encryptedAttachments = Collections.emptyMap();
private ServiceType serviceType;
private String encryptedData;
private String encryptedMetadata;
private EncryptedSubmissionBuilder() {
}
public static EncryptedBuilderStartStep Builder() {
return new EncryptedSubmissionBuilder();
}
@Override
public EncryptedServiceTypeStep withDestination(final UUID destinationId) {
this.destinationId = destinationId;
return this;
}
@Override
public EncryptedDataStep withEncryptedAttachments(final Map<UUID, String> encryptedAttachments) {
this.encryptedAttachments = encryptedAttachments == null ? Collections.emptyMap() : encryptedAttachments;
return this;
}
@Override
public EncryptedDataStep withEncryptedAttachment(final UUID attachmentId, final String encryptedAttachment) {
if (attachmentId == null || encryptedAttachment == null) {
return withEncryptedAttachments(Collections.emptyMap());
}
return withEncryptedAttachments(Map.of(attachmentId, encryptedAttachment));
}
@Override
public EncryptedMetadataStep withEncryptedData(final String encryptedData) {
this.encryptedData = encryptedData;
return this;
}
@Override
public EncryptedDestinationStep withEncryptedMetadata(final String encryptedMetadata) {
this.encryptedMetadata = encryptedMetadata;
return this;
}
@Override
public EncryptedBuildStep withServiceType(final String serviceTypeName, final String leikaKey) {
serviceType = ServiceType.builder()
.identifier(leikaKey)
.name(serviceTypeName)
.build();
return this;
}
@Override
public EncryptedBuildStep withServiceType(final String serviceTypeName, final String description, final String leikaKey) {
serviceType = ServiceType.builder()
.identifier(leikaKey)
.description(description)
.name(serviceTypeName)
.build();
return this;
}
@Override
public EncryptedSubmissionPayload build() {
return new EncryptedSubmissionPayload(this);
}
}
package dev.fitko.fitconnect.client.sender;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.domain.model.submission.ServiceType;
import dev.fitko.fitconnect.client.sender.model.SubmissionPayload;
import dev.fitko.fitconnect.client.sender.steps.BuildStep;
import dev.fitko.fitconnect.client.sender.steps.BuilderStartStep;
import dev.fitko.fitconnect.client.sender.steps.DataStep;
import dev.fitko.fitconnect.client.sender.steps.DestinationStep;
import dev.fitko.fitconnect.client.sender.steps.ServiceTypeStep;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType.APPLICATION_JSON;
import static dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType.APPLICATION_XML;
@Getter
public final class SubmissionBuilder implements BuilderStartStep,
DataStep,
DestinationStep,
ServiceTypeStep,
BuildStep {
private static final Logger LOGGER = LoggerFactory.getLogger(SubmissionBuilder.class);
private UUID destinationId;
private List<File> attachments = Collections.emptyList();
private String data;
private MimeType dataMimeType;
private ServiceType serviceType;
private SubmissionBuilder() {
}
public static BuilderStartStep Builder() {
return new SubmissionBuilder();
}
@Override
public ServiceTypeStep withDestination(final UUID destinationId) {
this.destinationId = destinationId;
return this;
}
@Override
public DataStep withAttachments(final List<File> attachments) {
this.attachments = attachments == null ? Collections.emptyList() : attachments;
return this;
}
@Override
public DataStep withAttachment(final File attachment) {
withAttachments(attachment == null ? Collections.emptyList() : List.of(attachment));
return this;
}
@Override
public DestinationStep withJsonData(final String jsonData) {
data = jsonData;
dataMimeType = APPLICATION_JSON;
return this;
}
@Override
public DestinationStep withXmlData(final String xmlData) {
data = xmlData;
dataMimeType = APPLICATION_XML;
return this;
}
@Override
public BuildStep withServiceType(final String serviceTypeName, final String leikaKey) {
serviceType = ServiceType.builder()
.identifier(leikaKey)
.name(serviceTypeName)
.build();
return this;
}
@Override
public BuildStep withServiceType(final String serviceTypeName, final String description, final String leikaKey) {
serviceType = ServiceType.builder()
.identifier(leikaKey)
.description(description)
.name(serviceTypeName)
.build();
return this;
}
@Override
public SubmissionPayload build() {
return new SubmissionPayload(this);
}
}