no.digipost.api.client.MessageSender.java Source code

Java tutorial

Introduction

Here is the source code for no.digipost.api.client.MessageSender.java

Source

/**
 * 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.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.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.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 DocumentsPreparer documentsPreparer;

    private DateTime printKeyCachedTime = null;
    private DigipostPublicKey cachedPrintKey;

    public MessageSender(ApiService apiService, EventLogger eventLogger, PdfValidator pdfValidator) {
        super(apiService, eventLogger);
        this.documentsPreparer = new DocumentsPreparer(pdfValidator);
    }

    public void setPdfValidationSettings(PdfValidationSettings settings) {
        this.documentsPreparer.setPdfValidationSettings(settings);
    }

    /**
     * Sender melding med alle dokumenter og innhold med n API-foresprsel (HTTP multipart request).
     * Dersom dokumentene skal direkte til print og skal prekrypteres fr sending kan det gjres en ekstra request for  hente
     * krypteringsnkkel.
     */
    public MessageDelivery sendMultipartMessage(Message message, Map<Document, InputStream> documentsAndContent) {
        Encrypter encrypter = fetchEncryptionKeyForRecipientIfNecessary(message).map(keyToEncrypter)
                .orElse(FAIL_IF_TRYING_TO_ENCRYPT);

        try (MultiPart multiPart = new MultiPart()) {
            BodyPart messageBodyPart = new BodyPart(message, MediaType.valueOf(MediaTypes.DIGIPOST_MEDIA_TYPE_V6));
            ContentDisposition messagePart = ContentDisposition.type("attachment").fileName("message").build();
            messageBodyPart.setContentDisposition(messagePart);
            multiPart.bodyPart(messageBodyPart);

            for (Entry<Document, InputStream> documentAndContent : documentsPreparer
                    .prepare(documentsAndContent, message, encrypter).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 " + message.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 gjre en
     * GET-foresprsel mot serveren for  hente en representasjon av
     * forsendelsesressursen slik den er p serveren. Dette vil ikke fre til
     * noen endringer av ressursen.
     *
     * Dersom forsendelsen ikke eksisterer fra fr, 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 fr. 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 frst 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, STARTER INTERAKSJON MED API: HENT PUBLIC KEY ***");
            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.validate(message.getDeliveryMethod(), document, byteContent);
            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 nkkel 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 krypteringsnkkel. Indikerer en feil hos Digipost.");
            }
            log("Mottaker er Digipost-bruker. Hentet krypteringsnkkel.");
        } else {
            log("Mottaker er ikke Digipost-bruker.");
        }
        return result;
    }

    public DigipostPublicKey getEncryptionKeyForPrint() {
        DateTime now = DateTime.now();

        if (printKeyCachedTime == null
                || new Duration(printKeyCachedTime, now).isLongerThan(Duration.standardMinutes(5))) {
            log("*** STARTER INTERAKSJON MED API: HENT KRYPTERINGSNKKEL 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 krypteringsnkkel 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
     * frst 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 Optional<DigipostPublicKey> fetchEncryptionKeyForRecipientIfNecessary(Message message) {
        if (message.hasAnyDocumentRequiringPreEncryption()) {
            if (message.isDirectPrint()) {
                eventLogger.log("Direkte print. Bruker krypteringsnkkel for print.");
                return optional(getEncryptionKeyForPrint());

            } else {
                IdentificationResultWithEncryptionKey result = identifyAndGetEncryptionKey(
                        message.recipient.toIdentification());
                if (result.getResult().getResult() == IdentificationResultCode.DIGIPOST) {
                    eventLogger.log("Mottaker er Digipost-bruker. Bruker brukers krypteringsnkkel.");
                    return optional(new DigipostPublicKey(result.getEncryptionKey()));
                } else if (message.recipient.hasPrintDetails()) {
                    eventLogger.log("Mottaker er ikke Digipost-bruker. Bruker krypteringsnkkel for print.");
                    return optional(getEncryptionKeyForPrint());
                } else {
                    throw new DigipostClientException(ErrorCode.UNKNOWN_RECIPIENT,
                            "Mottaker er ikke Digipost-bruker og forsendelse mangler print-fallback.");
                }
            }
        }
        return none();
    }

}