/*
* Copyright 2010-2017 Norwegian Agency for Public Management and eGovernment (Difi)
*
* Licensed under the EUPL, Version 1.1 or – as soon they
* will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
*
* You may not use this work except in compliance with the Licence.
*
* You may obtain a copy of the Licence at:
*
* https://joinup.ec.europa.eu/community/eupl/og_page/eupl
*
* Unless required by applicable law or agreed to in
* writing, software distributed under the Licence is
* distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied.
* See the Licence for the specific language governing
* permissions and limitations under the Licence.
*/
package no.difi.oxalis.outbound.transmission;
import brave.Span;
import brave.Tracer;
import com.google.common.io.ByteStreams;
import com.google.inject.Inject;
import no.difi.oxalis.api.lang.OxalisTransmissionException;
import no.difi.oxalis.api.lookup.LookupService;
import no.difi.oxalis.api.outbound.TransmissionRequest;
import no.difi.oxalis.commons.sbdh.SbdhParser;
import no.difi.oxalis.sniffer.PeppolStandardBusinessHeader;
import no.difi.oxalis.sniffer.document.NoSbdhParser;
import no.difi.oxalis.sniffer.identifier.InstanceId;
import no.difi.oxalis.sniffer.sbdh.SbdhWrapper;
import no.difi.vefa.peppol.common.model.DocumentTypeIdentifier;
import no.difi.vefa.peppol.common.model.Endpoint;
import no.difi.vefa.peppol.common.model.ParticipantIdentifier;
import no.difi.vefa.peppol.common.model.ProcessIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* @author steinar
* @author thore
* Date: 04.11.13
* Time: 10:04
* @author erlend
*/
public class TransmissionRequestBuilder {
private static final Logger log = LoggerFactory.getLogger(TransmissionRequestBuilder.class);
private final NoSbdhParser noSbdhParser;
private final LookupService lookupService;
private final Tracer tracer;
private boolean allowOverride;
/**
* Will contain the payload PEPPOL document
*/
private byte[] payload;
/**
* The address of the endpoint either supplied by the caller or looked up in the SMP
*/
private Endpoint endpoint;
/**
* The header fields supplied by the caller as opposed to the header fields parsed from the payload
*/
private PeppolStandardBusinessHeader suppliedHeaderFields = new PeppolStandardBusinessHeader();
/**
* The header fields in effect, i.e. merge the parsed header fields with the supplied ones,
* giving precedence to the supplied ones.
*/
private PeppolStandardBusinessHeader effectiveStandardBusinessHeader;
@Inject
public TransmissionRequestBuilder(NoSbdhParser noSbdhParser, LookupService lookupService, Tracer tracer) {
this.noSbdhParser = noSbdhParser;
this.lookupService = lookupService;
this.tracer = tracer;
}
public void reset() {
suppliedHeaderFields = new PeppolStandardBusinessHeader();
effectiveStandardBusinessHeader = null;
}
/**
* Supplies the builder with the contents of the message to be sent.
*/
public TransmissionRequestBuilder payLoad(InputStream inputStream) {
savePayLoad(inputStream);
return this;
}
/**
* Overrides the endpoint URL and the AS2 System identifier for the AS2 protocol.
* You had better know what you are doing :-)
*/
public TransmissionRequestBuilder overrideAs2Endpoint(Endpoint endpoint) {
this.endpoint = endpoint;
return this;
}
public TransmissionRequestBuilder receiver(ParticipantIdentifier receiverId) {
suppliedHeaderFields.setRecipientId(receiverId);
return this;
}
public TransmissionRequestBuilder sender(ParticipantIdentifier senderId) {
suppliedHeaderFields.setSenderId(senderId);
return this;
}
public TransmissionRequestBuilder documentType(DocumentTypeIdentifier documentTypeIdentifier) {
suppliedHeaderFields.setDocumentTypeIdentifier(documentTypeIdentifier);
return this;
}
public TransmissionRequestBuilder processType(ProcessIdentifier processTypeId) {
suppliedHeaderFields.setProfileTypeIdentifier(processTypeId);
return this;
}
public TransmissionRequestBuilder instanceId(InstanceId instanceId) {
suppliedHeaderFields.setInstanceId(instanceId);
return this;
}
public TransmissionRequest build(Span root) throws OxalisTransmissionException {
Span span = tracer.newChild(root.context()).name("build").start();
try {
return build();
} finally {
span.finish();
}
}
/**
* Builds the actual {@link TransmissionRequest}.
* <p>
* The {@link PeppolStandardBusinessHeader} is built as following:
* <p>
* <ol>
* <li>If the payload contains an SBHD, allow override if global "overrideAllowed" flag is set,
* otherwise use the one parsed</li>
* <li>If the payload does not contain an SBDH, parse payload to determine some of the SBDH attributes
* and allow override if global "overrideAllowed" flag is set.</li>
* </ol>
*
* @return Prepared transmission request.
*/
public TransmissionRequest build() throws OxalisTransmissionException {
if (payload.length < 2)
throw new OxalisTransmissionException("You have forgotten to provide payload");
PeppolStandardBusinessHeader optionalParsedSbdh = null;
try {
optionalParsedSbdh = new PeppolStandardBusinessHeader(SbdhParser.parse(new ByteArrayInputStream(payload)));
} catch (IllegalStateException e) {
// No action.
}
// Calculates the effectiveStandardBusinessHeader to be used
effectiveStandardBusinessHeader = makeEffectiveSbdh(
Optional.ofNullable(optionalParsedSbdh), suppliedHeaderFields);
// If the endpoint has not been overridden by the caller, look up the endpoint address in
// the SMP using the data supplied in the payload
if (isEndpointSuppliedByCaller() && isOverrideAllowed()) {
log.warn("Endpoint was set by caller not retrieved from SMP, make sure this is intended behaviour.");
} else {
Endpoint endpoint = lookupService.lookup(effectiveStandardBusinessHeader.toVefa(), null);
if (isEndpointSuppliedByCaller() && !this.endpoint.equals(endpoint)) {
throw new IllegalStateException("You are not allowed to override the EndpointAddress from SMP.");
}
this.endpoint = endpoint;
}
// make sure payload is encapsulated in SBDH
if (optionalParsedSbdh == null) {
// Wraps the payload with an SBDH, as this is required for AS2
payload = wrapPayLoadWithSBDH(new ByteArrayInputStream(payload), effectiveStandardBusinessHeader);
}
// Transfers all the properties of this object into the newly created TransmissionRequest
return new DefaultTransmissionRequest(
getEffectiveStandardBusinessHeader().toVefa(), getPayload(), getEndpoint());
}
/**
* Merges the SBDH parsed from the payload with the SBDH data supplied by the caller, i.e. the caller wishes to
* override the contents of the SBDH parsed. That is, if the payload contains an SBDH
*
* @param optionalParsedSbdh the SBDH as parsed (extracted) from the payload.
* @param peppolSbdhSuppliedByCaller the SBDH data supplied by the caller in order to override data from the payload
* @return the merged, effective SBDH created by combining the two data sets
*/
PeppolStandardBusinessHeader makeEffectiveSbdh(Optional<PeppolStandardBusinessHeader> optionalParsedSbdh,
PeppolStandardBusinessHeader peppolSbdhSuppliedByCaller) {
PeppolStandardBusinessHeader effectiveSbdh;
if (isOverrideAllowed()) {
if (peppolSbdhSuppliedByCaller.isComplete()) {
// we have sufficient meta data (set explicitly by the caller using API functions)
effectiveSbdh = peppolSbdhSuppliedByCaller;
} else {
// missing meta data, parse payload, which does not contain SBHD, in order to deduce missing fields
PeppolStandardBusinessHeader parsedPeppolStandardBusinessHeader = parsePayLoadAndDeduceSbdh(optionalParsedSbdh);
effectiveSbdh = createEffectiveHeader(parsedPeppolStandardBusinessHeader, peppolSbdhSuppliedByCaller);
}
} else {
// override is not allowed, make sure we do not override any restricted headers
PeppolStandardBusinessHeader parsedPeppolStandardBusinessHeader = parsePayLoadAndDeduceSbdh(optionalParsedSbdh);
List<String> overriddenHeaders = findRestricedHeadersThatWillBeOverridden(parsedPeppolStandardBusinessHeader, peppolSbdhSuppliedByCaller);
if (overriddenHeaders.isEmpty()) {
effectiveSbdh = createEffectiveHeader(parsedPeppolStandardBusinessHeader, peppolSbdhSuppliedByCaller);
} else {
throw new IllegalStateException("Your are not allowed to override " + Arrays.toString(overriddenHeaders.toArray()) + " in production mode, makes sure headers match the ones in the document.");
}
}
if (!effectiveSbdh.isComplete()) {
throw new IllegalStateException("TransmissionRequest can not be built, missing " + Arrays.toString(effectiveSbdh.listMissingProperties().toArray()) + " metadata.");
}
return effectiveSbdh;
}
private PeppolStandardBusinessHeader parsePayLoadAndDeduceSbdh(
Optional<PeppolStandardBusinessHeader> optionallyParsedSbdh) {
return optionallyParsedSbdh
.orElseGet(() -> noSbdhParser.parse(new ByteArrayInputStream(payload)));
}
/**
* Merges the supplied header fields with the SBDH parsed or derived from the payload thus allowing the caller
* to explicitly override whatever has been supplied in the payload.
*
* @param parsed the PeppolStandardBusinessHeader parsed from the payload
* @param supplied the header fields supplied by the caller
* @return the merged and effective headers
*/
protected PeppolStandardBusinessHeader createEffectiveHeader(final PeppolStandardBusinessHeader parsed,
final PeppolStandardBusinessHeader supplied) {
// Creates a copy of the original business headers
PeppolStandardBusinessHeader mergedHeaders = new PeppolStandardBusinessHeader(parsed);
if (supplied.getSenderId() != null) {
mergedHeaders.setSenderId(supplied.getSenderId());
}
if (supplied.getRecipientId() != null) {
mergedHeaders.setRecipientId(supplied.getRecipientId());
}
if (supplied.getDocumentTypeIdentifier() != null) {
mergedHeaders.setDocumentTypeIdentifier(supplied.getDocumentTypeIdentifier());
}
if (supplied.getProfileTypeIdentifier() != null) {
mergedHeaders.setProfileTypeIdentifier(supplied.getProfileTypeIdentifier());
}
// If instanceId was supplied by caller, use it otherwise, create new
if (supplied.getInstanceId() != null) {
mergedHeaders.setInstanceId(supplied.getInstanceId());
} else {
mergedHeaders.setInstanceId(new InstanceId());
}
if (supplied.getCreationDateAndTime() != null) {
mergedHeaders.setCreationDateAndTime(supplied.getCreationDateAndTime());
}
return mergedHeaders;
}
/**
* Returns a list of "restricted" header names that will be overridden when calling #createEffectiveHeader
* The restricted header names are SenderId, RecipientId, DocumentTypeIdentifier and ProfileTypeIdentifier
* Compares values that exist both as parsed and supplied headers.
* Ignores values that only exists in one of them (that allows for sending new and unknown document types)
*/
protected List<String> findRestricedHeadersThatWillBeOverridden(final PeppolStandardBusinessHeader parsed,
final PeppolStandardBusinessHeader supplied) {
List<String> headers = new ArrayList<>();
if ((parsed.getSenderId() != null) && (supplied.getSenderId() != null)
&& (!supplied.getSenderId().equals(parsed.getSenderId()))) headers.add("SenderId");
if ((parsed.getRecipientId() != null) && (supplied.getRecipientId() != null)
&& (!supplied.getRecipientId().equals(parsed.getRecipientId()))) headers.add("RecipientId");
if ((parsed.getDocumentTypeIdentifier() != null) && (supplied.getDocumentTypeIdentifier() != null)
&& (!supplied.getDocumentTypeIdentifier().equals(parsed.getDocumentTypeIdentifier())))
headers.add("DocumentTypeIdentifier");
if ((parsed.getProfileTypeIdentifier() != null) && (supplied.getProfileTypeIdentifier() != null)
&& (!supplied.getProfileTypeIdentifier().equals(parsed.getProfileTypeIdentifier())))
headers.add("ProfileTypeIdentifier");
return headers;
}
protected PeppolStandardBusinessHeader getEffectiveStandardBusinessHeader() {
return effectiveStandardBusinessHeader;
}
protected void savePayLoad(InputStream inputStream) {
try {
payload = ByteStreams.toByteArray(inputStream);
} catch (IOException e) {
throw new IllegalStateException("Unable to save the payload: " + e.getMessage(), e);
}
}
protected InputStream getPayload() {
return new ByteArrayInputStream(payload);
}
public Endpoint getEndpoint() {
return endpoint;
}
public boolean isOverrideAllowed() {
return allowOverride;
}
private boolean isEndpointSuppliedByCaller() {
return endpoint != null;
}
private byte[] wrapPayLoadWithSBDH(ByteArrayInputStream byteArrayInputStream,
PeppolStandardBusinessHeader effectiveStandardBusinessHeader) {
SbdhWrapper sbdhWrapper = new SbdhWrapper();
return sbdhWrapper.wrap(byteArrayInputStream, effectiveStandardBusinessHeader.toVefa());
}
/**
* For testing purposes only
*/
public void setTransmissionBuilderOverride(boolean transmissionBuilderOverride) {
this.allowOverride = transmissionBuilderOverride;
}
}