
no.digipost.api.client.MessageSender Maven / Gradle / Ivy
/**
* Copyright (C) Posten Norge AS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package no.digipost.api.client;
import no.digipost.api.client.delivery.DocumentContent;
import no.digipost.api.client.errorhandling.DigipostClientException;
import no.digipost.api.client.errorhandling.ErrorCode;
import no.digipost.api.client.representations.*;
import no.digipost.api.client.util.DigipostPublicKey;
import no.digipost.api.client.util.Encrypter;
import no.digipost.print.validate.PdfValidationSettings;
import no.digipost.print.validate.PdfValidator;
import no.motif.f.Apply;
import no.motif.f.Fn;
import no.motif.single.Optional;
import org.apache.commons.io.IOUtils;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static no.digipost.api.client.errorhandling.ErrorCode.GENERAL_ERROR;
import static no.digipost.api.client.util.Encrypter.FAIL_IF_TRYING_TO_ENCRYPT;
import static no.digipost.api.client.util.Encrypter.keyToEncrypter;
import static no.motif.Singular.*;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
public class MessageSender extends Communicator {
private final Fn resolvePdfValidationSettings = new Fn() {
@Override public PdfValidationSettings $(MayHaveSender message) {
return apiService.getSenderInformation(message).getPdfValidationSettings();
}};
private final DocumentsPreparer documentsPreparer;
private final DigipostClientConfig digipostClientConfig;
private DateTime printKeyCachedTime = null;
private DigipostPublicKey cachedPrintKey;
public MessageSender(DigipostClientConfig digipostClientConfig, ApiService apiService, EventLogger eventLogger, PdfValidator pdfValidator) {
super(apiService, eventLogger);
this.documentsPreparer = new DocumentsPreparer(pdfValidator);
this.digipostClientConfig = digipostClientConfig;
}
/**
* Sender melding med alle dokumenter og innhold med én API-forespørsel (HTTP multipart request).
* Dersom dokumentene skal direkte til print og skal prekrypteres før sending kan det gjøres en ekstra request for å hente
* krypteringsnøkkel.
*/
public MessageDelivery sendMultipartMessage(Message message, Map documentsAndContent) {
EncryptionKeyAndDocsWithInputstream encryptionAndInputStream = fetchEncryptionKeyForRecipientIfNecessaryAndMapContentToInputstream(message, documentsAndContent);
Encrypter encrypter = encryptionAndInputStream.digipostPublicKeys.map(keyToEncrypter).orElse(FAIL_IF_TRYING_TO_ENCRYPT);
Map documentInputStream = encryptionAndInputStream.documentsAndInputstream;
Message singleChannelMessage = encryptionAndInputStream.getSingleChannelMessage();
try (MultiPart multiPart = new MultiPart()) {
BodyPart messageBodyPart = new BodyPart(singleChannelMessage, MediaType.valueOf(MediaTypes.DIGIPOST_MEDIA_TYPE_V6));
ContentDisposition messagePart = ContentDisposition.type("attachment").fileName("message").build();
messageBodyPart.setContentDisposition(messagePart);
multiPart.bodyPart(messageBodyPart);
Map preparedDocuments = documentsPreparer.prepare(
documentInputStream, singleChannelMessage, encrypter, Apply.partially(resolvePdfValidationSettings).of(singleChannelMessage));
for (Entry documentAndContent : preparedDocuments.entrySet()) {
Document document = documentAndContent.getKey();
InputStream content = documentAndContent.getValue();
BodyPart bodyPart = new BodyPart(content, new MediaType("application", defaultIfBlank(document.getDigipostFileType(), "octet-stream")));
ContentDisposition documentPart = ContentDisposition.type("attachment").fileName(document.uuid).build();
bodyPart.setContentDisposition(documentPart);
multiPart.bodyPart(bodyPart);
}
log("*** STARTER INTERAKSJON MED API: SENDER MELDING MED ID " + singleChannelMessage.messageId + " ***");
Response response = apiService.multipartMessage(multiPart);
checkResponse(response);
log("Brevet ble sendt. Status: [" + response + "]");
return response.readEntity(MessageDelivery.class);
} catch (Exception e) {
throw DigipostClientException.from(e);
}
}
/**
* Oppretter en forsendelsesressurs på serveren eller henter en allerede
* opprettet forsendelsesressurs.
*
* Dersom forsendelsen allerede er opprettet, vil denne metoden gjøre en
* GET-forespørsel mot serveren for å hente en representasjon av
* forsendelsesressursen slik den er på serveren. Dette vil ikke føre til
* noen endringer av ressursen.
*
* Dersom forsendelsen ikke eksisterer fra før, vil denne metoden opprette
* en ny forsendelsesressurs på serveren og returnere en representasjon av
* ressursen.
*
*/
public MessageDelivery createOrFetchMessage(final Message message) {
Response response = apiService.createMessage(message);
if (resourceAlreadyExists(response)) {
Response existingMessageResponse = apiService.fetchExistingMessage(response.getLocation());
checkResponse(existingMessageResponse);
MessageDelivery delivery = existingMessageResponse.readEntity(MessageDelivery.class);
checkThatExistingMessageIsIdenticalToNewMessage(delivery, message);
checkThatMessageHasNotAlreadyBeenDelivered(delivery);
log("Identisk forsendelse fantes fra før. Bruker denne istedenfor å opprette ny. Status: [" + response.toString() + "]");
return delivery;
} else {
checkResponse(response);
log("Forsendelse opprettet. Status: [" + response.toString() + "]");
return response.readEntity(MessageDelivery.class);
}
}
/**
* Legger til innhold til et dokument. For at denne metoden skal
* kunne kalles, må man først ha opprettet forsendelsesressursen på serveren
* ved metoden {@code createOrFetchMesssage}.
*/
public MessageDelivery addContent(final MessageDelivery message, final Document document, final InputStream documentContent, final InputStream printDocumentContent) {
verifyCorrectStatus(message, MessageStatus.NOT_COMPLETE);
final InputStream unencryptetContent;
if (message.willBeDeliveredInDigipost()) {
unencryptetContent = documentContent;
} else {
unencryptetContent = printDocumentContent;
document.setDigipostFileType(FileType.PDF);
}
MessageDelivery delivery;
if (document.isPreEncrypt()) {
log("*** DOKUMENTET SKAL PREKRYPTERES. VALIDERES, OG HENTER PUBLIC KEY VIA API ***");
byte[] byteContent;
try {
byteContent = IOUtils.toByteArray(unencryptetContent);
} catch (IOException e) {
throw new DigipostClientException(GENERAL_ERROR, "Unable to read content of document with uuid " + document.uuid, e);
}
documentsPreparer.validateAndSetNrOfPages(message.getChannel(), document, byteContent, Apply.partially(resolvePdfValidationSettings).of(message));
InputStream encryptetContent = fetchKeyAndEncrypt(document, new ByteArrayInputStream(byteContent));
delivery = uploadContent(message, document, encryptetContent);
} else {
delivery = uploadContent(message, document, unencryptetContent);
}
return delivery;
}
public MessageDelivery sendMessage(final MessageDelivery message) {
MessageDelivery deliveredMessage = null;
if (message.isAlreadyDeliveredToDigipost()) {
log("\n\n---BREVET ER ALLEREDE SENDT");
} else if (message.getSendLink() == null) {
log("\n\n---BREVET ER IKKE KOMPLETT, KAN IKKE SENDE");
} else {
deliveredMessage = send(message);
}
return deliveredMessage;
}
/**
* Henter brukers public nøkkel fra serveren og krypterer brevet som skal
* sendes med denne.
*/
public InputStream fetchKeyAndEncrypt(Document document, InputStream content) {
checkThatMessageCanBePreEncrypted(document);
Response encryptionKeyResponse = apiService.getEncryptionKey(document.getEncryptionKeyLink().getUri());
checkResponse(encryptionKeyResponse);
EncryptionKey key = encryptionKeyResponse.readEntity(EncryptionKey.class);
return the(new DigipostPublicKey(key)).map(keyToEncrypter).orElse(FAIL_IF_TRYING_TO_ENCRYPT).encrypt(content);
}
public IdentificationResultWithEncryptionKey identifyAndGetEncryptionKey(Identification identification) {
Response response = apiService.identifyAndGetEncryptionKey(identification);
checkResponse(response);
IdentificationResultWithEncryptionKey result = response.readEntity(IdentificationResultWithEncryptionKey.class);
if (result.getResult().getResult() == IdentificationResultCode.DIGIPOST) {
if (result.getEncryptionKey() == null) {
throw new DigipostClientException(ErrorCode.SERVER_ERROR, "Server identifisert mottaker som Digipost-bruker, men sendte ikke med krypteringsnøkkel. Indikerer en feil hos Digipost.");
}
log("Mottaker er Digipost-bruker. Hentet krypteringsnøkkel.");
} else {
log("Mottaker er ikke Digipost-bruker.");
}
return result;
}
public DigipostPublicKey getEncryptionKeyForPrint() {
DateTime now = DateTime.now();
if (!digipostClientConfig.cachePrintKey || (printKeyCachedTime == null || new Duration(printKeyCachedTime, now).isLongerThan(Duration.standardMinutes(5)))) {
log("*** STARTER INTERAKSJON MED API: HENT KRYPTERINGSNØKKEL FOR PRINT ***");
Response response = apiService.getEncryptionKeyForPrint();
checkResponse(response);
EncryptionKey encryptionKey = response.readEntity(EncryptionKey.class);
cachedPrintKey = new DigipostPublicKey(encryptionKey);
printKeyCachedTime = now;
return cachedPrintKey;
} else {
log("Bruker cachet krypteringsnøkkel for print");
return cachedPrintKey;
}
}
private MessageDelivery uploadContent(MessageDelivery createdMessage, Document document, InputStream documentContent) {
log("*** STARTER INTERAKSJON MED API: LEGGE TIL FIL ***");
Response response = apiService.addContent(document, documentContent);
checkResponse(response);
log("Innhold ble lagt til. Status: [" + response + "]");
return response.readEntity(MessageDelivery.class);
}
/**
* Sender en forsendelse. For at denne metoden skal kunne kalles, må man
* først ha lagt innhold til forsendelsen med {@code addContent}.
*/
private MessageDelivery send(final MessageDelivery delivery) {
log("*** STARTER INTERAKSJON MED API: SENDER MELDING MED ID " + delivery.getMessageId() + " ***");
Response response = apiService.send(delivery);
checkResponse(response);
log("Brevet ble sendt. Status: [" + response.toString() + "]");
return response.readEntity(MessageDelivery.class);
}
private void checkThatMessageHasNotAlreadyBeenDelivered(final MessageDelivery existingMessage) {
switch (existingMessage.getStatus()) {
case DELIVERED: {
String errorMessage = String.format("En forsendelse med samme id=[%s] er allerede levert til mottaker den [%s]. "
+ "Dette skyldes sannsynligvis doble kall til Digipost.", existingMessage.getMessageId(),
existingMessage.getDeliveryTime());
log(errorMessage);
throw new DigipostClientException(ErrorCode.DIGIPOST_MESSAGE_ALREADY_DELIVERED, errorMessage);
}
case DELIVERED_TO_PRINT: {
String errorMessage = String.format("En forsendelse med samme id=[%s] er allerede levert til print den [%s]. "
+ "Dette skyldes sannsynligvis doble kall til Digipost.", existingMessage.getMessageId(),
existingMessage.getDeliveryTime());
log(errorMessage);
throw new DigipostClientException(ErrorCode.PRINT_MESSAGE_ALREADY_DELIVERED, errorMessage);
}
default:
break;
}
}
private void checkThatMessageCanBePreEncrypted(final Document document) {
Link encryptionKeyLink = document.getEncryptionKeyLink();
if (encryptionKeyLink == null) {
String errorMessage = "Document med id [" + document.uuid + "] kan ikke prekrypteres.";
log(errorMessage);
throw new DigipostClientException(ErrorCode.CANNOT_PREENCRYPT, errorMessage);
}
}
private void verifyCorrectStatus(final MessageDelivery createdMessage, final MessageStatus expectedStatus) {
if (createdMessage.getStatus() != expectedStatus) {
throw new DigipostClientException(ErrorCode.INVALID_TRANSACTION,
"Kan ikke legge til innhold til en forsendelse som ikke er i tilstanden " + expectedStatus + ".");
}
}
private EncryptionKeyAndDocsWithInputstream fetchEncryptionKeyForRecipientIfNecessaryAndMapContentToInputstream(Message message,
Map documentsAndContent) {
final Map documentsAndInputstream = new LinkedHashMap<>();
Optional publicKeys = none();
Message singleChannelMessage;
if (message.isDirectPrint()) {
singleChannelMessage = setMapAndMessageToPrint(message, documentsAndContent, documentsAndInputstream);
if (singleChannelMessage.hasAnyDocumentRequiringPreEncryption()) {
eventLogger.log("Direkte print. Bruker krypteringsnøkkel for print.");
publicKeys = optional(getEncryptionKeyForPrint());
}
} else if (!message.recipient.hasPrintDetails() && !message.hasAnyDocumentRequiringPreEncryption()) {
singleChannelMessage = setMapAndMessageToDigipost(message, documentsAndContent, documentsAndInputstream);
} else {
IdentificationResultWithEncryptionKey result = identifyAndGetEncryptionKey(message.recipient.toIdentification());
if (result.getResultCode() == IdentificationResultCode.DIGIPOST) {
singleChannelMessage = setMapAndMessageToDigipost(message, documentsAndContent, documentsAndInputstream);
if (singleChannelMessage.hasAnyDocumentRequiringPreEncryption()) {
eventLogger.log("Mottaker er Digipost-bruker. Bruker brukers krypteringsnøkkel.");
publicKeys = optional(new DigipostPublicKey(result.getEncryptionKey()));
}
} else if (message.recipient.hasPrintDetails()) {
singleChannelMessage = setMapAndMessageToPrint(message, documentsAndContent, documentsAndInputstream);
if (singleChannelMessage.hasAnyDocumentRequiringPreEncryption()) {
eventLogger.log("Mottaker er ikke Digipost-bruker. Bruker krypteringsnøkkel for print.");
publicKeys = optional(getEncryptionKeyForPrint());
}
} else {
throw new DigipostClientException(ErrorCode.UNKNOWN_RECIPIENT, "Mottaker er ikke Digipost-bruker og forsendelse mangler print-fallback.");
}
}
return new EncryptionKeyAndDocsWithInputstream(publicKeys, documentsAndInputstream, singleChannelMessage);
}
static Message setMapAndMessageToDigipost(Message messageToCopy, Map documentsAndContent,
Map documentsAndInputStream){
Message singleChannelMessage = Message.copyMessageWithOnlyDigipostDetails(messageToCopy);
setDigipostContentToUUID(documentsAndContent, documentsAndInputStream, singleChannelMessage.getAllDocuments());
return singleChannelMessage;
}
static Message setMapAndMessageToPrint(Message messageToCopy, Map documentsAndContent,
Map documentsAndInputStream){
Message singleChannelMessage = Message.copyMessageWithOnlyPrintDetails(messageToCopy);
setPrintContentToUUID(documentsAndContent, documentsAndInputStream, singleChannelMessage.getAllDocuments());
return singleChannelMessage;
}
static void setDigipostContentToUUID(Map documentsAndContent, Map documentsAndInputstream, List allDocuments) {
for(Document doc : allDocuments) {
documentsAndInputstream.put(doc, documentsAndContent.get(doc.uuid).getDigipostContent());
}
}
static void setPrintContentToUUID(Map documentsAndContent, Map documentsAndInputstream, List allDocuments) {
for(Document doc : allDocuments) {
documentsAndInputstream.put(doc, documentsAndContent.get(doc.uuid).getPrintContent());
}
}
private static class EncryptionKeyAndDocsWithInputstream{
public final Optional digipostPublicKeys;
public final Map documentsAndInputstream;
private final Message singleChannelMessage;
public EncryptionKeyAndDocsWithInputstream(Optional digipostPublicKeys,
Map documentsAndInputstream, Message singleChannelMessage){
this.digipostPublicKeys = digipostPublicKeys;
this.documentsAndInputstream = documentsAndInputstream;
this.singleChannelMessage = singleChannelMessage;
}
public Message getSingleChannelMessage(){
return singleChannelMessage;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy