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 lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
class ReplyChannelEMail {
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Email {
@JsonProperty("address")
private String address;
@JsonProperty("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;
import com.fasterxml.jackson.annotation.JsonInclude;
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.jwk.ApiJwkSet;
import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel;
import lombok.Data;
import java.util.ArrayList;
......
......@@ -3,10 +3,10 @@ package dev.fitko.fitconnect.api.services;
import com.nimbusds.jose.jwk.RSAKey;
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.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.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.submission.CreateSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
......@@ -22,7 +22,7 @@ import java.util.UUID;
* A technical system that creates a submission via the FIT-Connect Submission API.
* <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>
*/
......@@ -102,7 +102,7 @@ public interface Sender {
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 attachmentId unique identifier of the attachment
......@@ -125,7 +125,7 @@ public interface Sender {
*
* @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
*/
......@@ -174,7 +174,7 @@ public interface Sender {
* @param submissionId unique identifier of the submission the log should be retrieved for
* @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;
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.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.submission.Submission;
import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
......@@ -39,7 +39,16 @@ public interface Subscriber {
* @param offset position in the dataset
* @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}.
......@@ -50,7 +59,7 @@ public interface Subscriber {
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 attachmentId unique identifier of the attachments
......@@ -61,9 +70,8 @@ public interface Subscriber {
/**
* 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
*
* @return List of {@link EventLogEntry}s for the given case
*/
List<EventLogEntry> getEventLog(UUID caseId, UUID destinationId);
......@@ -77,10 +85,10 @@ public interface Subscriber {
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 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
*/
ValidationResult validateHashIntegrity(String originalHash, byte[] data);
......@@ -88,9 +96,9 @@ public interface Subscriber {
/**
* Checks if a received callback can be trusted by validating the provided request data
*
* @param hmac authentication code provided by the callback
* @param timestamp timestamp provided by the callback
* @param httpBody HTTP body provided by the callback
* @param hmac authentication code provided by the callback
* @param timestamp timestamp 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
* @return {@code true} if hmac and timestamp provided by the callback meet the required conditions
*/
......@@ -100,7 +108,6 @@ public interface Subscriber {
* Sends a confirmation event if the received submission matches all validations.
*
* @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>
*/
void acceptSubmission(EventPayload eventPayload);
......@@ -109,7 +116,6 @@ public interface Subscriber {
* Sends a rejection event if the received submission violates any validation rule.
*
* @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>
*/
void rejectSubmission(EventPayload eventPayload);
......
package dev.fitko.fitconnect.api.services.crypto;
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.submission.SubmitSubmission;
import dev.fitko.fitconnect.api.exceptions.DecryptionException;
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.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7516">JSON-Web-Encryption</a>
*/
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.
*
......@@ -35,18 +24,7 @@ public interface CryptoService {
*
* @throws DecryptionException if the payload cannot be decrypted or there was an issue with the key
*/
byte[] decryptBytes(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;
byte[] decryptToBytes(RSAKey privateKey, String encryptedData) throws DecryptionException;
/**
* Encrypts an object with the given public key.
......
......@@ -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.event.EventLog;
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.submission.Submission;
import dev.fitko.fitconnect.api.exceptions.EventLogException;
......@@ -38,9 +38,9 @@ public interface EventLogService {
* @param submissionId unique identifier of the submission the log should be retrieved for
* @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.
......
......@@ -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.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.submission.CreateSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.ServiceType;
......@@ -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
* 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>
*/
......@@ -25,7 +25,7 @@ public interface SubmissionService {
/**
* 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
*
* @see <a href="https://docs.fitko.de/fit-connect/docs/sending/start-submission">Announcing a submission</a>
......@@ -48,6 +48,15 @@ public interface SubmissionService {
*/
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}.
*
......@@ -56,10 +65,10 @@ public interface SubmissionService {
* @param limit number of submissions in result (max. is 500)
* @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 attachmentId unique destination identifier
......@@ -68,7 +77,7 @@ public interface SubmissionService {
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 attachmentId unique attachment identifier
......
......@@ -63,11 +63,6 @@
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
......@@ -129,6 +124,10 @@
<commitIdGenerationMode>full</commitIdGenerationMode>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
......
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.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.submission.SentSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
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.client.sender.model.EncryptedSubmissionPayload;
import dev.fitko.fitconnect.client.sender.model.SubmissionPayload;
import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission;
import dev.fitko.fitconnect.client.sender.model.SendableSubmission;
import dev.fitko.fitconnect.client.sender.strategies.SendEncryptedSubmissionStrategy;
import dev.fitko.fitconnect.client.sender.strategies.SendNewSubmissionStrategy;
import dev.fitko.fitconnect.client.util.ValidDataGuard;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
......@@ -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}
* @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) {
final Optional<RSAKey> key = Optional.ofNullable(sender.getEncryptionKeyForDestination(destinationId));
return key.isEmpty() ? Optional.empty() : Optional.of(key.get().toJSONString());
public String getPublicKeyForDestination(final UUID destinationId) throws KeyNotRetrievedException, InvalidKeyException {
return sender.getEncryptionKeyForDestination(destinationId).toJSONString();
}
/**
......@@ -60,9 +61,9 @@ public class SenderClient {
* Retrieve the current status of a {@link Submission}.
*
* @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 caseId = sentSubmission.getCaseId();
final UUID submissionId = sentSubmission.getSubmissionId();
......@@ -88,9 +89,9 @@ public class SenderClient {
*
* @return {@link SentSubmission} object of the handed in submission
*/
public SentSubmission submit(final SubmissionPayload submissionPayload) {
dataGuard.ensureValidDataPayload(submissionPayload);
return new SendNewSubmissionStrategy(sender).send(submissionPayload);
public SentSubmission send(final SendableSubmission sendableSubmission) {
dataGuard.ensureValidDataPayload(sendableSubmission);
return new SendNewSubmissionStrategy(sender).send(sendableSubmission);
}
/**
......@@ -98,8 +99,8 @@ public class SenderClient {
*
* @return {@link SentSubmission} object of the handed in submission
*/
public SentSubmission submit(final EncryptedSubmissionPayload encryptedSubmissionPayload) {
dataGuard.ensureValidDataPayload(encryptedSubmissionPayload);
return new SendEncryptedSubmissionStrategy(sender).send(encryptedSubmissionPayload);
public SentSubmission send(final SendableEncryptedSubmission sendableEncryptedSubmission) {
dataGuard.ensureValidDataPayload(sendableEncryptedSubmission);
return new SendEncryptedSubmissionStrategy(sender).send(sendableEncryptedSubmission);
}
}
......@@ -3,11 +3,15 @@ package dev.fitko.fitconnect.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.jwk.RSAKey;
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.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.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.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.SubmissionForPickup;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
......@@ -16,9 +20,9 @@ import dev.fitko.fitconnect.api.exceptions.DecryptionException;
import dev.fitko.fitconnect.api.exceptions.EventCreationException;
import dev.fitko.fitconnect.api.exceptions.RestApiException;
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.model.DecryptedAttachmentPayload;
import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment;
import dev.fitko.fitconnect.client.subscriber.model.ReceivedData;
import dev.fitko.fitconnect.core.util.StopWatch;
import org.slf4j.Logger;
......@@ -29,9 +33,9 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
......@@ -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
*/
public Set<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId) {
return getAvailableSubmissions(destinationId, 0, DEFAULT_SUBMISSION_LIMIT);
public Set<SubmissionForPickup> getAvailableSubmissionsForDestination(final UUID destinationId) {
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 limit number of submissions in result (max. is 500)
* @return set of available submissions in the given range
*/
public Set<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId, final int offset, final int limit) {
final Set<SubmissionForPickup> submissions = subscriber.pollAvailableSubmissions(destinationId, offset, limit);
public Set<SubmissionForPickup> getAvailableSubmissionsForDestination(final UUID destinationId, final int offset, final int limit) {
final Set<SubmissionForPickup> submissions = subscriber.pollAvailableSubmissionsForDestination(destinationId, offset, limit);
LOGGER.info("Received {} submission(s) for destination {}", submissions.size(), destinationId);
return submissions;
}
......@@ -90,8 +94,19 @@ public class SubscriberClient {
/**
* Loads a single {@link SubmissionForPickup} by id.
*
* @param submissionId unique identifier of a {@link SubmissionForPickup}
* @return {@link ReceivedSubmission} to get the submission {@link Metadata}, {@link Data} and {@link Attachment}s as well as
* @param submission {@link SentSubmission} that is requested
* @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
*/
public ReceivedSubmission requestSubmission(final UUID submissionId) {
......@@ -120,7 +135,7 @@ public class SubscriberClient {
}
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 ValidationResult attachmentValidation = validateAttachments(decryptedAttachmentPayloads);
if (attachmentValidation.hasError()) {
......@@ -143,6 +158,17 @@ public class SubscriberClient {
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) {
return subscriber.validateCallback(hmac, timestamp, httpBody, callbackSecret);
}
......@@ -150,27 +176,23 @@ public class SubscriberClient {
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 ReceivedData receivedData = new ReceivedData(new String(decryptedData, StandardCharsets.UTF_8), mimeType);
final List<ReceivedAttachment> receivedAttachments = attachments.stream().map(mapToReceivedAttachment()).collect(Collectors.toList());
return new ReceivedSubmission(subscriber, submission, metadata, receivedData, receivedAttachments);
final List<Attachment> receivedAttachments = attachments.stream().map(this::mapToAttachment).collect(Collectors.toList());
// 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() {
return payload -> ReceivedAttachment.builder()
.attachmentId(payload.getAttachmentMetadata().getAttachmentId())
.filename(payload.getAttachmentMetadata().getFilename())
.mimeType(payload.getAttachmentMetadata().getMimeType())
.description(payload.getAttachmentMetadata().getDescription())
.data(payload.getDecryptedContent())
.build();
private Attachment mapToAttachment(final DecryptedAttachmentPayload payload) {
final ApiAttachment metadata = payload.getAttachmentMetadata();
return Attachment.fromByteArray(payload.getDecryptedContent(), metadata.getMimeType(), metadata.getFilename(), metadata.getDescription());
}
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()) {
LOGGER.info("Submission contains no attachments");
return Collections.emptyList();
}
final List<DecryptedAttachmentPayload> receivedAttachments = new ArrayList<>();
for (final Attachment metadata : attachmentMetadata) {
for (final ApiAttachment metadata : attachmentMetadata) {
final String encryptedAttachment = downloadAttachment(submissionId, metadata);
final byte[] decryptedAttachment = decryptAttachment(metadata, encryptedAttachment);
final DecryptedAttachmentPayload decryptedAttachmentPayload = DecryptedAttachmentPayload.builder()
......@@ -182,14 +204,14 @@ public class SubscriberClient {
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 byte[] decryptedAttachment = subscriber.decryptStringContent(privateKey, encryptedAttachment);
LOGGER.info("Decrypting attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDecryption));
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 String encryptedAttachment = subscriber.fetchAttachment(submissionId, metadata.getAttachmentId());
LOGGER.info("Downloading attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDownload));
......@@ -207,7 +229,7 @@ public class SubscriberClient {
private ValidationResult validateAttachments(final List<DecryptedAttachmentPayload> decryptedAttachmentPayloads) {
for (final DecryptedAttachmentPayload decryptedAttachment : decryptedAttachmentPayloads) {
final Attachment attachmentMetadata = decryptedAttachment.getAttachmentMetadata();
final ApiAttachment attachmentMetadata = decryptedAttachment.getAttachmentMetadata();
final UUID attachmentId = attachmentMetadata.getAttachmentId();
final ValidationResult result = subscriber.validateHashIntegrity(attachmentMetadata.getHash().getContent(), decryptedAttachment.getDecryptedContent());
if (result.hasError()) {
......
......@@ -7,23 +7,27 @@ import dev.fitko.fitconnect.client.SenderClient;
import dev.fitko.fitconnect.client.SubscriberClient;
import dev.fitko.fitconnect.client.cli.batch.BatchImporter;
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.sender.SubmissionBuilder;
import dev.fitko.fitconnect.client.sender.model.SubmissionPayload;
import dev.fitko.fitconnect.client.sender.model.Attachment;
import dev.fitko.fitconnect.client.sender.model.SendableSubmission;
import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission;
import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment;
import dev.fitko.fitconnect.core.util.StopWatch;
import org.apache.tika.Tika;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
......@@ -59,7 +63,7 @@ class CommandExecutor {
void getAllSubmissions(final GetAllSubmissionsCommand getAllSubmissionsCommand) throws IOException {
final var destinationId = getAllSubmissionsCommand.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) {
final GetOneSubmissionCommand getOneSubmissionCommand = new GetOneSubmissionCommand();
getOneSubmissionCommand.submissionId = submission.getSubmissionId();
......@@ -71,15 +75,16 @@ class CommandExecutor {
void listSubmissions(final ListAllSubmissionsCommand listAllSubmissionsCommand) {
final var destinationId = listAllSubmissionsCommand.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());
}
}
SentSubmission sendSubmission(final SendSubmissionCommand sendSubmissionCommand) throws IOException {
LOGGER.info("Sending new submission to destination {}", sendSubmissionCommand.destinationId);
final Tika mimeTypeDetector = new Tika();
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;
if (sendSubmissionCommand.dataType == AttachmentDataType.json) {
submission = sendWithJsonData(sendSubmissionCommand, attachments);
......@@ -89,7 +94,6 @@ class CommandExecutor {
LOGGER.info("Import took {}", StopWatch.stopWithFormattedTime(startTime));
return submission;
}
void sendBatch(final SendBatchCommand sendBatchCommand) throws BatchImportException, IOException {
final List<ImportRecord> importRecords = batchImporter.readRecords(sendBatchCommand.dataPath);
LOGGER.info("Sending batch of {} submissions", importRecords.size());
......@@ -106,28 +110,28 @@ class CommandExecutor {
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()
.withAttachments(attachments)
.withJsonData(getDataAsString(sendSubmissionCommand.data))
.withDestination(sendSubmissionCommand.destinationId)
.withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
final SendableSubmission sendableSubmission = SendableSubmission.Builder()
.setDestination(sendSubmissionCommand.destinationId)
.setServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
.setJsonData(getDataAsString(sendSubmissionCommand.data))
.addAttachments(attachments)
.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()
.withAttachments(attachments)
.withXmlData(getDataAsString(sendSubmissionCommand.data))
.withDestination(sendSubmissionCommand.destinationId)
.withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
final SendableSubmission sendableSubmission = SendableSubmission.Builder()
.setDestination(sendSubmissionCommand.destinationId)
.setServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey)
.setXmlData(getDataAsString(sendSubmissionCommand.data))
.addAttachments(attachments)
.build();
return senderClient.submit(submissionPayload);
return senderClient.send(sendableSubmission);
}
private String getTargetFolderPath(final GetOneSubmissionCommand getOneSubmissionCommand) {
......@@ -143,6 +147,9 @@ class CommandExecutor {
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 {
LOGGER.info("Creating data directory for submission in {}", dataDirPath);
......@@ -151,12 +158,12 @@ class CommandExecutor {
final var fileEnding = AttachmentDataType.getFileTypeFromMimeType(receivedSubmission.getDataMimeType());
final var filePath = Path.of(dataDirPath + "/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()) {
final String filename = attachment.getFilename();
for (final Attachment attachment : receivedSubmission.getAttachments()) {
final String filename = attachment.getFileName();
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;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.stream.Collectors;
public final class CommandLineRunner {
......@@ -29,16 +30,16 @@ public final class CommandLineRunner {
private static ApplicationConfig loadConfig() {
try {
return ApplicationConfigLoader.loadConfig(DEFAULT_CONFIG_NAME);
return ApplicationConfigLoader.loadConfigFromEnvironment();
} catch (final Exception e) {
LOGGER.warn("Could not load default config {} {}", DEFAULT_CONFIG_NAME, e);
return null;
LOGGER.warn("Could not load config from environment, loading default config {} {}", DEFAULT_CONFIG_NAME, e);
return ApplicationConfigLoader.loadConfigFromPath(Path.of(DEFAULT_CONFIG_NAME));
}
}
private static CommandLineClient getCommandLineClient(final ApplicationConfig config) {
final SenderClient senderClient = config == null ? ClientFactory.senderClient() : ClientFactory.senderClient(config);
final var subscriberClient = config == null ? ClientFactory.subscriberClient() : ClientFactory.subscriberClient(config);
final SenderClient senderClient = ClientFactory.getSenderClient(config);
final var subscriberClient = ClientFactory.getSubscriberClient(config);
return new CommandLineClient(senderClient, subscriberClient);
}
......
......@@ -12,8 +12,8 @@ public enum AttachmentDataType {
this.fileType = fileType;
}
public static String getFileTypeFromMimeType(final MimeType mimeType) {
switch (mimeType) {
public static String getFileTypeFromMimeType(final String mimeType) {
switch (MimeType.fromValue(mimeType)) {
case APPLICATION_JSON:
return json.fileType;
case APPLICATION_XML:
......
package dev.fitko.fitconnect.client.factory;
import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.config.BuildInfo;
import dev.fitko.fitconnect.api.exceptions.InitializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
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() {
}
......@@ -24,23 +26,25 @@ public final class ApplicationConfigLoader {
* @return ApplicationConfig
* @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 {
return loadConfigFromYaml(Files.readString(configPath));
return loadConfigFromYamlString(Files.readString(configPath));
} catch (final IOException | NullPointerException e) {
LOGGER.error("config file could not be loaded from path {}", configPath);
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
* @throws InitializationException if the config file could not be loaded
*/
public static ApplicationConfig loadConfig(final String configPath) {
return loadConfig(Path.of(configPath));
public static ApplicationConfig loadConfigFromEnvironment() {
final Path configPath = getPathFromEnvironment();
return loadConfigFromPath(configPath);
}
/**
......@@ -49,17 +53,17 @@ public final class ApplicationConfigLoader {
* @param configYaml string content of the yaml config file
* @return ApplicationConfig
*/
public static ApplicationConfig loadConfigFromYaml(final String configYaml) {
public static ApplicationConfig loadConfigFromYamlString(final String configYaml) {
return new Yaml().loadAs(configYaml, ApplicationConfig.class);
}
/**
* Load BuildInfo properties.
*
* @return {@link BuildInfo}
*/
public static BuildInfo loadBuildInfo() {
final InputStream resource = ApplicationConfigLoader.class.getClassLoader().getResourceAsStream(BUILD_INFO_PATH);
return new Yaml().loadAs(resource, BuildInfo.class);
private static Path getPathFromEnvironment() {
try {
return Path.of(System.getenv(CONFIG_ENV_KEY_NAME));
} catch (final NullPointerException | InvalidPathException e) {
LOGGER.error("Environment variable {} could not be loaded", CONFIG_ENV_KEY_NAME);
throw new InitializationException(e.getMessage(), e);
}
}
}
......@@ -43,8 +43,10 @@ import dev.fitko.fitconnect.core.validation.DefaultValidationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
......@@ -57,22 +59,13 @@ public final class ClientFactory {
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 DESTINATION_SCHEMA_DIR = "/destination-schema";
private static final String METADATA_SCHEMA_DIR = "/metadata-schema";
private static final String BUILD_INFO_PATH = "buildinfo.yaml";
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());
private ClientFactory() {
}
/**
......@@ -80,19 +73,10 @@ public final class ClientFactory {
*
* @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 ...");
return new SenderClient(getSender(config, ApplicationConfigLoader.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());
return new SenderClient(getSender(config, loadBuildInfo()));
}
/**
......@@ -100,35 +84,31 @@ public final class ClientFactory {
*
* @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 ...");
final Subscriber subscriber = getSubscriber(config, ApplicationConfigLoader.loadBuildInfo());
final Subscriber subscriber = getSubscriber(config, loadBuildInfo());
final SubscriberConfig subscriberConfig = config.getSubscriberConfig();
LOGGER.info("Reading private decryption key from {} ", subscriberConfig.getPrivateDecryptionKeyPath());
final String privateKeyPath = readPath(subscriberConfig.getPrivateDecryptionKeyPath(), "Decryption Key");
LOGGER.info("Reading private decryption key");
final String privateKeyPath = readPath(getPrivateDecryptionKeyPathFromSubscriber(subscriberConfig), "Decryption Key");
final RSAKey privateKey = readRSAKeyFromString(privateKeyPath);
return new SubscriberClient(subscriber, privateKey);
}
/**
* 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() {
return routingClient(loadConfig());
}
public static RoutingClient getRoutingClient(final ApplicationConfig config) {
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 ...");
final RestTemplate restTemplate = getRestTemplate(config, ApplicationConfigLoader.loadBuildInfo());
final RestTemplate restTemplate = getRestTemplate(config, loadBuildInfo());
final SchemaProvider schemaProvider = getSchemaProvider();
final OAuthService authService = getSenderConfiguredAuthService(config, restTemplate);
......@@ -178,7 +158,6 @@ public final class ClientFactory {
return new SubmissionSubscriber(submissionService, eventLogService, cryptoService, validator, setService);
}
private static OAuthService getSenderConfiguredAuthService(final ApplicationConfig config, final RestTemplate restTemplate) {
final String clientId = config.getSenderConfig().getClientId();
final String clientSecret = config.getSenderConfig().getClientSecret();
......@@ -265,24 +244,23 @@ public final class ClientFactory {
}
}
private static Path readConfigFromEnvironment() {
try {
return Path.of(System.getenv(CONFIG_ENV_KEY_NAME));
} catch (final NullPointerException e) {
LOGGER.error("Environment variable {} could not be loaded", CONFIG_ENV_KEY_NAME);
throw new InitializationException(e.getMessage(), e);
private static BuildInfo loadBuildInfo() {
final InputStream resource = ClientFactory.class.getClassLoader().getResourceAsStream(BUILD_INFO_PATH);
return new Yaml().loadAs(resource, BuildInfo.class);
}
private static void checkNotNull(final ApplicationConfig config) {
if(config == null){
throw new InitializationException("application config must not be null");
}
}
private static ApplicationConfig loadConfig() {
try {
final Path configPath = readConfigFromEnvironment();
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);
private static String getPrivateDecryptionKeyPathFromSubscriber(final SubscriberConfig subscriberConfig) {
if(subscriberConfig.getPrivateDecryptionKeyPaths().size() != 1){
throw new InitializationException("Currently only one configured private key per subscriber is allowed !");
}
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;
import lombok.AllArgsConstructor;
import lombok.Value;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* 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 {
int offset;
int limit;
public static Builder Builder() {
public static MandatoryProperties 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 ARS_PATTERN = Pattern.compile("^(\\d{2}|\\d{3}|\\d{5}|\\d{9}|\\d{12})$");
......@@ -39,14 +147,9 @@ public class DestinationSearch {
private int offset = 0;
private int limit = 100;
/**
* Leika Key of the requested service.
*
* @param leikaKey service identifier
* @return Builder
* @throws IllegalArgumentException if the leika key pattern is not matching
*/
public Builder withLeikaKey(final String leikaKey) throws IllegalArgumentException {
@Override
public OptionalProperties withLeikaKey(final String leikaKey) throws IllegalArgumentException {
if (!LEIKA_KEY_PATTERN.matcher(leikaKey).matches()) {
throw new IllegalArgumentException("Leika key does not match allowed pattern " + LEIKA_KEY_PATTERN);
}
......@@ -54,14 +157,8 @@ public class DestinationSearch {
return this;
}
/**
* Official municipal code of the place.
*
* @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{
@Override
public Pagination withArs(final String ars) throws IllegalArgumentException {
if (!ARS_PATTERN.matcher(ars).matches()) {
throw new IllegalArgumentException("ARS key does not match allowed pattern " + ARS_PATTERN);
}
......@@ -70,14 +167,8 @@ public class DestinationSearch {
return this;
}
/**
* Official regional key of the area.
*
* @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 {
@Override
public Pagination withAgs(final String ags) throws IllegalArgumentException {
if (!AGS_PATTERN.matcher(ags).matches()) {
throw new IllegalArgumentException("AGS key does not match allowed pattern " + AGS_PATTERN);
}
......@@ -90,10 +181,10 @@ public class DestinationSearch {
*
* @param areaId id of the area
* @return Builder
* @see dev.fitko.fitconnect.client.RoutingClient#findAreas(String, int, int)
* @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()) {
throw new IllegalArgumentException("AreaId key does not match allowed pattern " + AREA_ID_PATTERN);
}
......@@ -101,14 +192,8 @@ public class DestinationSearch {
return this;
}
/**
* 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
*/
public Builder withOffset(final int offset) throws IllegalArgumentException{
@Override
public Builder withOffset(final int offset) throws IllegalArgumentException {
if (limit < 0) {
throw new IllegalArgumentException("offset must be positive");
}
......@@ -116,13 +201,7 @@ public class DestinationSearch {
return this;
}
/**
* 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
*/
@Override
public Builder withLimit(final int limit) throws IllegalArgumentException {
if (limit > 500) {
throw new IllegalArgumentException("limit must no be > 500");
......@@ -131,15 +210,14 @@ public class DestinationSearch {
return this;
}
/**
* Construct the search request.
*
* @return DestinationSearch
*/
@Override
public DestinationSearch build() throws IllegalArgumentException {
if(leikaKey == null){
if (leikaKey == null) {
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);
}
}
......
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);
}
}