All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.phase4.peppol.Phase4PeppolSender Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
/*
 * Copyright (C) 2015-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.phase4.peppol;

import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.OffsetDateTime;
import java.util.UUID;
import java.util.function.Consumer;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.unece.cefact.namespaces.sbdh.StandardBusinessDocument;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.io.IHasInputStream;
import com.helger.commons.mime.CMimeType;
import com.helger.commons.mime.IMimeType;
import com.helger.commons.state.ESuccess;
import com.helger.commons.state.ETriState;
import com.helger.commons.string.StringHelper;
import com.helger.diver.api.coord.DVRCoordinate;
import com.helger.peppol.reporting.api.PeppolReportingHelper;
import com.helger.peppol.reporting.api.PeppolReportingItem;
import com.helger.peppol.reporting.api.backend.PeppolReportingBackend;
import com.helger.peppol.reporting.api.backend.PeppolReportingBackendException;
import com.helger.peppol.sbdh.CPeppolSBDH;
import com.helger.peppol.sbdh.PeppolSBDHData;
import com.helger.peppol.sbdh.payload.PeppolSBDHPayloadBinaryMarshaller;
import com.helger.peppol.sbdh.payload.PeppolSBDHPayloadTextMarshaller;
import com.helger.peppol.sbdh.spec12.BinaryContentType;
import com.helger.peppol.sbdh.spec12.TextContentType;
import com.helger.peppol.sbdh.write.PeppolSBDHDocumentWriter;
import com.helger.peppol.utils.CertificateRevocationChecker;
import com.helger.peppol.utils.EPeppolCertificateCheckResult;
import com.helger.peppol.utils.ERevocationCheckMode;
import com.helger.peppol.utils.PeppolCertificateChecker;
import com.helger.peppol.utils.PeppolCertificateHelper;
import com.helger.peppolid.IDocumentTypeIdentifier;
import com.helger.peppolid.IParticipantIdentifier;
import com.helger.peppolid.IProcessIdentifier;
import com.helger.peppolid.factory.PeppolIdentifierFactory;
import com.helger.peppolid.peppol.doctype.IPeppolDocumentTypeIdentifierParts;
import com.helger.peppolid.peppol.doctype.PeppolDocumentTypeIdentifierParts;
import com.helger.phase4.CAS4;
import com.helger.phase4.attachment.AS4OutgoingAttachment;
import com.helger.phase4.attachment.EAS4CompressionMode;
import com.helger.phase4.config.AS4Configuration;
import com.helger.phase4.dynamicdiscovery.AS4EndpointDetailProviderConstant;
import com.helger.phase4.dynamicdiscovery.AS4EndpointDetailProviderPeppol;
import com.helger.phase4.dynamicdiscovery.IAS4EndpointDetailProvider;
import com.helger.phase4.mgr.MetaAS4Manager;
import com.helger.phase4.model.MessageProperty;
import com.helger.phase4.profile.peppol.AS4PeppolProfileRegistarSPI;
import com.helger.phase4.profile.peppol.PeppolPMode;
import com.helger.phase4.profile.peppol.Phase4PeppolHttpClientSettings;
import com.helger.phase4.sender.AbstractAS4UserMessageBuilderMIMEPayload;
import com.helger.phase4.sender.IAS4SendingDateTimeConsumer;
import com.helger.phase4.util.Phase4Exception;
import com.helger.phive.api.executorset.IValidationExecutorSetRegistry;
import com.helger.phive.xml.source.IValidationSourceXML;
import com.helger.sbdh.CSBDH;
import com.helger.sbdh.SBDMarshaller;
import com.helger.smpclient.peppol.PeppolWildcardSelector;
import com.helger.smpclient.peppol.SMPClientReadOnly;
import com.helger.smpclient.url.IPeppolURLProvider;
import com.helger.smpclient.url.PeppolURLProvider;
import com.helger.xml.serialize.read.DOMReader;
import com.helger.xsds.peppol.smp1.EndpointType;

/**
 * This class contains all the specifics to send AS4 messages to PEPPOL. See
 * sendAS4Message as the main method to trigger the sending, with
 * all potential customization.
 *
 * @author Philip Helger
 */
@Immutable
public final class Phase4PeppolSender
{
  public static final PeppolIdentifierFactory IF = PeppolIdentifierFactory.INSTANCE;
  public static final IPeppolURLProvider URL_PROVIDER = PeppolURLProvider.INSTANCE;

  private static final Logger LOGGER = LoggerFactory.getLogger (Phase4PeppolSender.class);

  private Phase4PeppolSender ()
  {}

  @Nullable
  private static StandardBusinessDocument _createSBD (@Nonnull final IParticipantIdentifier aSenderID,
                                                      @Nonnull final IParticipantIdentifier aReceiverID,
                                                      @Nonnull final IDocumentTypeIdentifier aDocTypeID,
                                                      @Nonnull final IProcessIdentifier aProcID,
                                                      @Nullable final String sCountryC1,
                                                      @Nullable final String sInstanceIdentifier,
                                                      @Nullable final String sTypeVersion,
                                                      @Nonnull final Element aPayloadElement,
                                                      final boolean bClonePayloadElement)
  {
    final PeppolSBDHData aData = new PeppolSBDHData (IF);
    aData.setSender (aSenderID.getScheme (), aSenderID.getValue ());
    aData.setReceiver (aReceiverID.getScheme (), aReceiverID.getValue ());
    aData.setDocumentType (aDocTypeID.getScheme (), aDocTypeID.getValue ());
    aData.setProcess (aProcID.getScheme (), aProcID.getValue ());
    aData.setCountryC1 (sCountryC1);

    String sRealTypeVersion = sTypeVersion;
    if (StringHelper.hasNoText (sRealTypeVersion))
    {
      // Determine from document type
      try
      {
        final IPeppolDocumentTypeIdentifierParts aParts = PeppolDocumentTypeIdentifierParts.extractFromIdentifier (aDocTypeID);
        sRealTypeVersion = aParts.getVersion ();
      }
      catch (final IllegalArgumentException ex)
      {
        // failure
      }
    }
    if (StringHelper.hasNoText (sRealTypeVersion))
    {
      LOGGER.warn ("No TypeVersion was provided and none could be deduced from the document type identifier '" +
                   aDocTypeID.getURIEncoded () +
                   "'");
      return null;
    }
    String sRealInstanceIdentifier = sInstanceIdentifier;
    if (StringHelper.hasNoText (sRealInstanceIdentifier))
    {
      sRealInstanceIdentifier = UUID.randomUUID ().toString ();
      if (LOGGER.isDebugEnabled ())
        LOGGER.debug ("As no SBDH InstanceIdentifier was provided, a random one was created: '" +
                      sRealInstanceIdentifier +
                      "'");
    }
    aData.setDocumentIdentification (aPayloadElement.getNamespaceURI (),
                                     sRealTypeVersion,
                                     aPayloadElement.getLocalName (),
                                     sRealInstanceIdentifier,
                                     MetaAS4Manager.getTimestampMgr ().getCurrentXMLDateTime ());

    // Not cloning the payload element is for saving memory only (if it can be
    // ensured, the source payload element is not altered externally of course)
    if (bClonePayloadElement)
      aData.setBusinessMessage (aPayloadElement);
    else
      aData.setBusinessMessageNoClone (aPayloadElement);

    // Check with logging
    if (!aData.areAllFieldsSet (true))
      throw new IllegalArgumentException ("The Peppol SBDH data is incomplete. See logs for details.");

    return new PeppolSBDHDocumentWriter ().createStandardBusinessDocument (aData);
  }

  /**
   * @param aSenderID
   *        Sender participant ID. May not be null.
   * @param aReceiverID
   *        Receiver participant ID. May not be null.
   * @param aDocTypeID
   *        Document type ID. May not be null.
   * @param aProcID
   *        Process ID. May not be null.
   * @param sInstanceIdentifier
   *        SBDH instance identifier. May be null to create a
   *        random ID.
   * @param sTypeVersion
   *        SBDH syntax version ID (e.g. "2.1" for OASIS UBL 2.1). May be
   *        null to use the default.
   * @param aPayloadElement
   *        Payload element to be wrapped. May not be null.
   * @return The domain object representation of the created SBDH or
   *         null if not all parameters are present.
   * @deprecated Use the version with the additional CountryC1 parameter
   *             instead.
   */
  @Nullable
  @Deprecated (forRemoval = true, since = "2.1.1")
  public static StandardBusinessDocument createSBDH (@Nonnull final IParticipantIdentifier aSenderID,
                                                     @Nonnull final IParticipantIdentifier aReceiverID,
                                                     @Nonnull final IDocumentTypeIdentifier aDocTypeID,
                                                     @Nonnull final IProcessIdentifier aProcID,
                                                     @Nullable final String sInstanceIdentifier,
                                                     @Nullable final String sTypeVersion,
                                                     @Nonnull final Element aPayloadElement)
  {
    return _createSBD (aSenderID,
                       aReceiverID,
                       aDocTypeID,
                       aProcID,
                       null,
                       sInstanceIdentifier,
                       sTypeVersion,
                       aPayloadElement,
                       true);
  }

  /**
   * @param aSenderID
   *        Sender participant ID. May not be null.
   * @param aReceiverID
   *        Receiver participant ID. May not be null.
   * @param aDocTypeID
   *        Document type ID. May not be null.
   * @param aProcID
   *        Process ID. May not be null.
   * @param sCountryC1
   *        Country code of C1. May be null.
   * @param sInstanceIdentifier
   *        SBDH instance identifier. May be null to create a
   *        random ID.
   * @param sTypeVersion
   *        SBDH syntax version ID (e.g. "2.1" for OASIS UBL 2.1). May be
   *        null to use the default.
   * @param aPayloadElement
   *        Payload element to be wrapped. May not be null.
   * @return The domain object representation of the created SBDH or
   *         null if not all parameters are present.
   * @since 2.1.1
   */
  @Nullable
  public static StandardBusinessDocument createSBDH (@Nonnull final IParticipantIdentifier aSenderID,
                                                     @Nonnull final IParticipantIdentifier aReceiverID,
                                                     @Nonnull final IDocumentTypeIdentifier aDocTypeID,
                                                     @Nonnull final IProcessIdentifier aProcID,
                                                     @Nullable final String sCountryC1,
                                                     @Nullable final String sInstanceIdentifier,
                                                     @Nullable final String sTypeVersion,
                                                     @Nonnull final Element aPayloadElement)
  {
    return _createSBD (aSenderID,
                       aReceiverID,
                       aDocTypeID,
                       aProcID,
                       sCountryC1,
                       sInstanceIdentifier,
                       sTypeVersion,
                       aPayloadElement,
                       true);
  }

  /**
   * @param aPayloadElement
   *        The payload element to be validated. May not be null.
   * @param aRegistry
   *        The validation registry to be used. May be null to
   *        indicate to use the default one.
   * @param aVESID
   *        The VESID to validate against. May be null.
   * @param aValidationResultHandler
   *        The validation result handler to be used. May be null.
   * @throws Phase4PeppolException
   *         If the validation result handler decides to do so....
   */
  private static void _validatePayload (@Nonnull final Element aPayloadElement,
                                        @Nullable final IValidationExecutorSetRegistry  aRegistry,
                                        @Nullable final DVRCoordinate aVESID,
                                        @Nullable final IPhase4PeppolValidationResultHandler aValidationResultHandler) throws Phase4PeppolException
  {
    // Client side validation
    if (aVESID != null)
    {
      if (aValidationResultHandler != null)
      {
        if (aRegistry == null)
        {
          // Default registry
          Phase4PeppolValidation.validateOutgoingBusinessDocument (aPayloadElement, aVESID, aValidationResultHandler);
        }
        else
        {
          // Custom registry
          Phase4PeppolValidation.validateOutgoingBusinessDocument (aPayloadElement,
                                                                   aRegistry,
                                                                   aVESID,
                                                                   aValidationResultHandler);
        }
      }
      else
        LOGGER.warn ("A VES ID is present but no ValidationResultHandler - therefore no validation is performed");
    }
    else
    {
      if (aValidationResultHandler != null)
        LOGGER.warn ("A ValidationResultHandler is present but no VESID - therefore no validation is performed");
    }
  }

  /**
   * Get the receiver certificate from the specified SMP endpoint.
   *
   * @param aReceiverCert
   *        The determined receiver AP certificate to check. Never
   *        null.
   * @param aCertificateConsumer
   *        An optional consumer that is invoked with the received AP
   *        certificate to be used for the transmission. The certification check
   *        result must be considered when used. May be null.
   * @param eCacheOSCResult
   *        Possibility to override the usage of OSCP caching flag on a per
   *        query basis. Use {@link ETriState#UNDEFINED} to solely use the
   *        global flag.
   * @param eCheckMode
   *        Possibility to override the OSCP checking flag on a per query basis.
   *        May be null to use the global flag from
   *        {@link CertificateRevocationChecker#getRevocationCheckMode()}.
   * @throws Phase4PeppolException
   *         in case of error
   */
  private static void _checkReceiverAPCert (@Nullable final X509Certificate aReceiverCert,
                                            @Nullable final IPhase4PeppolCertificateCheckResultHandler aCertificateConsumer,
                                            @Nonnull final ETriState eCacheOSCResult,
                                            @Nullable final ERevocationCheckMode eCheckMode) throws Phase4PeppolException
  {
    if (LOGGER.isDebugEnabled ())
      LOGGER.debug ("Using the following receiver AP certificate from the SMP: " + aReceiverCert);

    final OffsetDateTime aNow = MetaAS4Manager.getTimestampMgr ().getCurrentDateTime ();
    final EPeppolCertificateCheckResult eCertCheckResult = PeppolCertificateChecker.checkPeppolAPCertificate (aReceiverCert,
                                                                                                              aNow,
                                                                                                              eCacheOSCResult,
                                                                                                              eCheckMode);

    // Interested in the certificate?
    if (aCertificateConsumer != null)
      aCertificateConsumer.onCertificateCheckResult (aReceiverCert, aNow, eCertCheckResult);

    if (eCertCheckResult.isInvalid ())
    {
      throw new Phase4PeppolException ("The configured receiver AP certificate is not valid (at " +
                                       aNow +
                                       ") and cannot be used for sending. Aborting. Reason: " +
                                       eCertCheckResult.getReason ());
    }
  }

  /**
   * @return Create a new Builder for AS4 messages if the payload is present and
   *         the SBDH is always created internally. Never null.
   * @see #sbdhBuilder() if you already have a ready Standard Business Document
   * @since 0.9.4
   */
  @Nonnull
  public static Builder builder ()
  {
    return new Builder ();
  }

  /**
   * @return Create a new Builder for AS4 messages if the SBDH payload is
   *         already present. This builder is slightly more limited, because it
   *         doesn't offer validation, as it is expected to be done before.
   *         Never null.
   * @see #builder() if you want phase4 to create the Standard Business Document
   * @since 0.9.6
   */
  @Nonnull
  public static SBDHBuilder sbdhBuilder ()
  {
    return new SBDHBuilder ();
  }

  /**
   * Abstract builder base class with the minimum requirements configuration
   *
   * @author Philip Helger
   * @param 
   *        The implementation type
   * @since 0.9.6
   */
  @NotThreadSafe
  public abstract static class AbstractPeppolUserMessageBuilder >
                                                                extends
                                                                AbstractAS4UserMessageBuilderMIMEPayload 
  {
    public static final boolean DEFAULT_COMPRESS_PAYLOAD = true;
    public static final boolean DEFAULT_CHECK_RECEIVER_AP_CERTIFICATE = true;
    /**
     * @deprecated Use {@link #DEFAULT_CHECK_RECEIVER_AP_CERTIFICATE} instead
     */
    @Deprecated (forRemoval = true, since = "2.2.0")
    public static final boolean DEFAULT_BOOLEAN_CHECK_AP_CERTIFICATE = DEFAULT_CHECK_RECEIVER_AP_CERTIFICATE;

    // C1
    protected IParticipantIdentifier m_aSenderID;
    // C4
    protected IParticipantIdentifier m_aReceiverID;
    protected IDocumentTypeIdentifier m_aDocTypeID;
    protected IProcessIdentifier m_aProcessID;
    protected String m_sCountryC1;

    protected IMimeType m_aPayloadMimeType;
    protected boolean m_bCompressPayload;
    protected String m_sPayloadContentID;

    protected IAS4EndpointDetailProvider m_aEndpointDetailProvider;
    private IPhase4PeppolCertificateCheckResultHandler m_aCertificateConsumer;
    private Consumer  m_aAPEndpointURLConsumer;
    private boolean m_bCheckReceiverAPCertificate;

    // Status var
    private OffsetDateTime m_aEffectiveSendingDT;

    /**
     * Create a new builder, with the defaults from
     * {@link AbstractAS4UserMessageBuilderMIMEPayload#AbstractAS4UserMessageBuilderMIMEPayload()}
     */
    public AbstractPeppolUserMessageBuilder ()
    {
      // Override default values
      try
      {
        as4ProfileID (AS4PeppolProfileRegistarSPI.AS4_PROFILE_ID);

        // Use the Peppol specific timeout settings
        httpClientFactory (new Phase4PeppolHttpClientSettings ());
        agreementRef (PeppolPMode.DEFAULT_AGREEMENT_ID);
        fromPartyIDType (PeppolPMode.DEFAULT_PARTY_TYPE_ID);
        fromRole (CAS4.DEFAULT_INITIATOR_URL);
        toPartyIDType (PeppolPMode.DEFAULT_PARTY_TYPE_ID);
        toRole (CAS4.DEFAULT_RESPONDER_URL);
        payloadMimeType (CMimeType.APPLICATION_XML);
        compressPayload (DEFAULT_COMPRESS_PAYLOAD);
        checkReceiverAPCertificate (DEFAULT_CHECK_RECEIVER_AP_CERTIFICATE);
      }
      catch (final Exception ex)
      {
        throw new IllegalStateException ("Failed to init AS4 Client builder", ex);
      }
    }

    /**
     * Set the sender participant ID of the message. The participant ID must be
     * provided prior to sending.
     *
     * @param aSenderID
     *        The sender participant ID. May not be null.
     * @return this for chaining
     */
    @Nonnull
    public final IMPLTYPE senderParticipantID (@Nonnull final IParticipantIdentifier aSenderID)
    {
      ValueEnforcer.notNull (aSenderID, "SenderID");
      if (m_aSenderID != null)
        LOGGER.warn ("An existing SenderParticipantID is overridden");
      m_aSenderID = aSenderID;
      return thisAsT ();
    }

    /**
     * Set the receiver participant ID of the message. The participant ID must
     * be provided prior to sending. This ends up in the "finalRecipient"
     * UserMessage property.
     *
     * @param aReceiverID
     *        The receiver participant ID. May not be null.
     * @return this for chaining
     */
    @Nonnull
    public final IMPLTYPE receiverParticipantID (@Nonnull final IParticipantIdentifier aReceiverID)
    {
      ValueEnforcer.notNull (aReceiverID, "ReceiverID");
      if (m_aReceiverID != null)
        LOGGER.warn ("An existing ReceiverParticipantID is overridden");
      m_aReceiverID = aReceiverID;
      return thisAsT ();
    }

    /**
     * @return The currently set Document Type ID. May be null.
     * @since 2.2.2
     */
    @Nullable
    public final IDocumentTypeIdentifier documentTypeID ()
    {
      return m_aDocTypeID;
    }

    /**
     * Set the document type ID to be send. The document type must be provided
     * prior to sending. This is a shortcut to the {@link #action(String)}
     * method.
     *
     * @param aDocTypeID
     *        The document type ID to be used. May not be null.
     * @return this for chaining
     */
    @Nonnull
    public final IMPLTYPE documentTypeID (@Nonnull final IDocumentTypeIdentifier aDocTypeID)
    {
      ValueEnforcer.notNull (aDocTypeID, "DocTypeID");
      if (m_aDocTypeID != null)
        LOGGER.warn ("An existing DocumentTypeID is overridden");
      m_aDocTypeID = aDocTypeID;
      return action (aDocTypeID.getURIEncoded ());
    }

    /**
     * @return The currently set Process ID. May be null.
     * @since 2.2.2
     */
    @Nullable
    public final IProcessIdentifier processID ()
    {
      return m_aProcessID;
    }

    /**
     * Set the process ID to be send. The process ID must be provided prior to
     * sending. This is a shortcut to the {@link #service(String, String)}
     * method.
     *
     * @param aProcessID
     *        The process ID to be used. May not be null.
     * @return this for chaining
     */
    @Nonnull
    public final IMPLTYPE processID (@Nonnull final IProcessIdentifier aProcessID)
    {
      ValueEnforcer.notNull (aProcessID, "ProcessID");
      if (m_aProcessID != null)
        LOGGER.warn ("An existing ProcessID is overridden");
      m_aProcessID = aProcessID;
      return service (aProcessID.getScheme (), aProcessID.getValue ());
    }

    /**
     * Set the country code of C1 to be used in the SBDH. This field was
     * introduced in the Peppol Business Message Envelope specification 2.0.
     * 
* Note: There is no date yet, when it becomes mandatory. * * @param sCountryC1 * The country code of C1 to be used. May be null. * @return this for chaining * @since 2.1.3 */ @Nonnull public final IMPLTYPE countryC1 (@Nullable final String sCountryC1) { m_sCountryC1 = sCountryC1; return thisAsT (); } /** * Set the "sender party ID" which is the CN part of the PEPPOL AP * certificate. An example value is e.g. "POP000123" but it MUST match the * certificate you are using. This must be provided prior to sending. This * is a shortcut to the {@link #fromPartyID(String)} method. * * @param sSenderPartyID * The sender party ID. May neither be null nor empty. * @return this for chaining */ @Nonnull public final IMPLTYPE senderPartyID (@Nonnull @Nonempty final String sSenderPartyID) { ValueEnforcer.notEmpty (sSenderPartyID, "SenderPartyID"); return fromPartyID (sSenderPartyID); } /** * Set the MIME type of the payload. By default it is * application/xml and MUST usually not be changed. This value * is required for sending. * * @param aPayloadMimeType * The payload MIME type. May not be null. * @return this for chaining */ @Nonnull public final IMPLTYPE payloadMimeType (@Nonnull final IMimeType aPayloadMimeType) { ValueEnforcer.notNull (aPayloadMimeType, "PayloadMimeType"); m_aPayloadMimeType = aPayloadMimeType; return thisAsT (); } /** * Enable or disable the AS4 compression of the payload. By default * compression is disabled. * * @param bCompressPayload * true to compress the payload, false to * not compress it. * @return this for chaining. */ @Nonnull public final IMPLTYPE compressPayload (final boolean bCompressPayload) { m_bCompressPayload = bCompressPayload; return thisAsT (); } /** * Set an optional payload "Content-ID". This method is usually not needed, * because in Peppol there are currently no rules on the Content-ID. By * default a random Content-ID is created. * * @param sPayloadContentID * The new payload content ID. May be null. * @return this for chaining * @since 1.3.1 */ @Nonnull public final IMPLTYPE payloadContentID (@Nullable final String sPayloadContentID) { m_sPayloadContentID = sPayloadContentID; return thisAsT (); } /** * Set the abstract endpoint detail provider to be used. This can be an SMP * lookup routine or in certain test cases a predefined certificate and * endpoint URL. * * @param aEndpointDetailProvider * The endpoint detail provider to be used. May not be * null. * @return this for chaining * @see #smpClient(SMPClientReadOnly) */ @Nonnull public final IMPLTYPE endpointDetailProvider (@Nonnull final IAS4EndpointDetailProvider aEndpointDetailProvider) { ValueEnforcer.notNull (aEndpointDetailProvider, "EndpointDetailProvider"); if (m_aEndpointDetailProvider != null) LOGGER.warn ("An existing EndpointDetailProvider is overridden"); m_aEndpointDetailProvider = aEndpointDetailProvider; return thisAsT (); } /** * Set the SMP client to be used. This is the point where e.g. the * differentiation between SMK and SML can be done. This must be set prior * to sending. If the endpoint information are already known you can also * use {@link #receiverEndpointDetails(X509Certificate, String)} instead. * * @param aSMPClient * The SMP client to be used. May not be null. * @return this for chaining * @see #receiverEndpointDetails(X509Certificate, String) * @see #endpointDetailProvider(IAS4EndpointDetailProvider) */ @Nonnull public final IMPLTYPE smpClient (@Nonnull final SMPClientReadOnly aSMPClient) { // Use the default mode return smpClient (aSMPClient, AS4EndpointDetailProviderPeppol.DEFAULT_WILDCARD_SELECTION_MODE); } /** * Set the SMP client to be used. This is the point where e.g. the * differentiation between SMK and SML can be done. This must be set prior * to sending. If the endpoint information are already known you can also * use {@link #receiverEndpointDetails(X509Certificate, String)} instead. * * @param aSMPClient * The SMP client to be used. May not be null. * @param eWildcardSelectionMode * The wildcard selection mode to use. May not be null. * @return this for chaining * @see #receiverEndpointDetails(X509Certificate, String) * @see #endpointDetailProvider(IAS4EndpointDetailProvider) * @since 2.8.1 */ @Nonnull public final IMPLTYPE smpClient (@Nonnull final SMPClientReadOnly aSMPClient, @Nonnull final PeppolWildcardSelector.EMode eWildcardSelectionMode) { return endpointDetailProvider (AS4EndpointDetailProviderPeppol.create (aSMPClient) .setWildcardSelectionMode (eWildcardSelectionMode)); } /** * Use this method to explicit set the AP certificate and AP endpoint URL * that was retrieved from a previous SMP query. * * @param aEndpoint * The Peppol SMP Endpoint instance. May not be null. * @return this for chaining * @throws CertificateException * In case the conversion from byte array to X509 certificate failed * @since 2.7.6 */ @Nonnull public final IMPLTYPE receiverEndpointDetails (@Nonnull final EndpointType aEndpoint) throws CertificateException { return receiverEndpointDetails (SMPClientReadOnly.getEndpointCertificate (aEndpoint), SMPClientReadOnly.getEndpointAddress (aEndpoint)); } /** * Use this method to explicit set the AP certificate and AP endpoint URL * that was retrieved externally (e.g. via an SMP call or for a static test * case). * * @param aCert * The Peppol AP certificate that should be used to encrypt the * message for the receiver. May not be null. * @param sDestURL * The destination URL of the receiving AP to send the AS4 message * to. Must be a valid URL and may neither be null nor * empty. * @return this for chaining */ @Nonnull public final IMPLTYPE receiverEndpointDetails (@Nonnull final X509Certificate aCert, @Nonnull @Nonempty final String sDestURL) { return endpointDetailProvider (new AS4EndpointDetailProviderConstant (aCert, sDestURL)); } /** * Set an optional Consumer for the retrieved certificate from the endpoint * details provider, independent of its usability. * * @param aCertificateConsumer * The consumer to be used. The first parameter is the certificate * itself and the second parameter is the internal check result. May * be null. * @return this for chaining */ @Nonnull public final IMPLTYPE certificateConsumer (@Nullable final IPhase4PeppolCertificateCheckResultHandler aCertificateConsumer) { m_aCertificateConsumer = aCertificateConsumer; return thisAsT (); } /** * Set an optional Consumer for the destination AP address retrieved from * the endpoint details provider, independent of its usability. * * @param aAPEndpointURLConsumer * The consumer to be used. May be null. * @return this for chaining * @since 1.3.3 */ @Nonnull public final IMPLTYPE endpointURLConsumer (@Nullable final Consumer aAPEndpointURLConsumer) { m_aAPEndpointURLConsumer = aAPEndpointURLConsumer; return thisAsT (); } /** * Enable or disable the check of the receiver AP certificate. This checks * the validity of the certificate as well as the revocation status. It is * strongly recommended to enable this check. * * @param bCheckReceiverAPCertificate * true to enable it, false to disable it. * @return this for chaining * @since 1.3.10 */ @Nonnull public final IMPLTYPE checkReceiverAPCertificate (final boolean bCheckReceiverAPCertificate) { m_bCheckReceiverAPCertificate = bCheckReceiverAPCertificate; return thisAsT (); } /** * The effective sending date time of the message. That is set only if * message sending takes place. * * @return The effective sending date time or null if the * messages was not sent yet. * @since 2.2.2 */ @Nullable public final OffsetDateTime effectiveSendingDateTime () { return m_aEffectiveSendingDT; } protected final boolean isEndpointDetailProviderUsable () { // Sender ID doesn't matter here if (m_aReceiverID == null) { LOGGER.warn ("The field 'receiverID' is not set"); return false; } if (m_aDocTypeID == null) { LOGGER.warn ("The field 'docTypeID' is not set"); return false; } if (m_aProcessID == null) { LOGGER.warn ("The field 'processID' is not set"); return false; } if (m_aEndpointDetailProvider == null) { LOGGER.warn ("The field 'endpointDetailProvider' is not set"); return false; } return true; } @Override @OverridingMethodsMustInvokeSuper protected ESuccess finishFields () throws Phase4Exception { if (!isEndpointDetailProviderUsable ()) { LOGGER.error ("At least one mandatory field for endpoint discovery is not set and therefore the AS4 message cannot be send."); return ESuccess.FAILURE; } // e.g. SMP lookup. Throws an Phase4Exception in case of error m_aEndpointDetailProvider.init (m_aDocTypeID, m_aProcessID, m_aReceiverID); // Certificate from e.g. SMP lookup (may throw an exception) final X509Certificate aReceiverCert = m_aEndpointDetailProvider.getReceiverAPCertificate (); if (m_bCheckReceiverAPCertificate) _checkReceiverAPCert (aReceiverCert, m_aCertificateConsumer, ETriState.UNDEFINED, null); else { LOGGER.warn ("The check of the receiver's Peppol AP certificate was explicitly disabled."); // Interested in the certificate? if (m_aCertificateConsumer != null) { final OffsetDateTime aNow = MetaAS4Manager.getTimestampMgr ().getCurrentDateTime (); m_aCertificateConsumer.onCertificateCheckResult (aReceiverCert, aNow, EPeppolCertificateCheckResult.NOT_CHECKED); } } receiverCertificate (aReceiverCert); // URL from e.g. SMP lookup (may throw an exception) final String sDestURL = m_aEndpointDetailProvider.getReceiverAPEndpointURL (); if (m_aAPEndpointURLConsumer != null) m_aAPEndpointURLConsumer.accept (sDestURL); endpointURL (sDestURL); // From receiver certificate toPartyID (PeppolCertificateHelper.getSubjectCN (aReceiverCert)); // Super at the end return super.finishFields (); } @Override @OverridingMethodsMustInvokeSuper public boolean isEveryRequiredFieldSet () { if (!super.isEveryRequiredFieldSet ()) return false; if (m_aPayload == null) { LOGGER.warn ("The field 'payload' is not set"); return false; } if (m_aSenderID == null) { LOGGER.warn ("The field 'senderID' is not set"); return false; } if (m_aReceiverID == null) { LOGGER.warn ("The field 'receiverID' is not set"); return false; } if (m_aDocTypeID == null) { LOGGER.warn ("The field 'docTypeID' is not set"); return false; } if (m_aProcessID == null) { LOGGER.warn ("The field 'processID' is not set"); return false; } // m_sCountryC1 is mandatory since 1.1.2024 if (StringHelper.hasNoText (m_sCountryC1)) { LOGGER.warn ("The field 'countryC1' is not set"); return false; } // m_aPayloadMimeType may be null // m_bCompressPayload may be null // m_sPayloadContentID may be null if (m_aEndpointDetailProvider == null) { LOGGER.warn ("The field 'endpointDetailProvider' is not set"); return false; } // m_aCertificateConsumer may be null // m_aAPEndpointURLConsumer may be null // All valid return true; } @Override protected void customizeBeforeSending () throws Phase4Exception { // Add mandatory properties addMessageProperty (MessageProperty.builder () .name (CAS4.ORIGINAL_SENDER) .type (m_aSenderID.getScheme ()) .value (m_aSenderID.getValue ())); addMessageProperty (MessageProperty.builder () .name (CAS4.FINAL_RECIPIENT) .type (m_aReceiverID.getScheme ()) .value (m_aReceiverID.getValue ())); // Explicitly remember the old handler // Try to do this as close to sending as possible, to avoid that another // sendingDateTimConsumer is used final IAS4SendingDateTimeConsumer aExisting = m_aSendingDTConsumer; sendingDateTimeConsumer (aSendingDT -> { // Store in this instance m_aEffectiveSendingDT = aSendingDT; // Call the original handler if (aExisting != null) aExisting.onEffectiveSendingDateTime (aSendingDT); }); } /** * Create a Peppol Reporting Item in case sending was successful. This The * end user ID needs to be provided from the outside, because it cannot be * used from the sending data. The end user ID is only needed for grouping * the reporting data later on, and is NOT part of the transmission (neither * in TSR nor in EUSR).
* The item is simply created but not stored. * * @param sEndUserID * The local end user ID, required to group all reporting items. May * neither be null nor empty. * @return The created reporting item. Never null. * @throws Phase4PeppolException * in case something goes wrong * @see #createAndStorePeppolReportingItemAfterSending(String) * @since 2.2.2 */ @Nonnull public final PeppolReportingItem createPeppolReportingItemAfterSending (@Nonnull @Nonempty final String sEndUserID) throws Phase4PeppolException { ValueEnforcer.notEmpty (sEndUserID, "EndUserID"); if (m_aEffectiveSendingDT == null) throw new Phase4PeppolException ("A Peppol Reporting item can only be created AFTER sending"); // No Country C4 necessary for sending return PeppolReportingItem.builder () .exchangeDateTime (m_aEffectiveSendingDT) .directionSending () .c2ID (m_sFromPartyID) .c3ID (m_sToPartyID) .docTypeID (m_aDocTypeID) .processID (m_aProcessID) .transportProtocolPeppolAS4v2 () .c1CountryCode (m_sCountryC1) .c4CountryCode (null) .endUserID (sEndUserID) .build (); } /** * This is a shortcut for creating and storing a Peppol reporting item in a * shot. See the creation method for the extended documentation. * * @param sEndUserID * The local end user ID, required to group all reporting items. May * neither be null nor empty. * @throws Phase4PeppolException * in case something goes wrong * @see #createPeppolReportingItemAfterSending(String) * @since 2.2.2 */ public final void createAndStorePeppolReportingItemAfterSending (@Nonnull @Nonempty final String sEndUserID) throws Phase4PeppolException { // Consistency check if (PeppolReportingBackend.getBackendService () == null) throw new Phase4PeppolException ("No Peppol Reporting Backend is available. Cannot store Reporting Items"); // Filter out document types on Reporting if (PeppolReportingHelper.isDocumentTypeEligableForReporting (m_aDocTypeID)) { try { LOGGER.info ("Creating Peppol Reporting Item and storing it"); // Create reporting item final PeppolReportingItem aReportingItem = createPeppolReportingItemAfterSending (sEndUserID); // Store it in configured backend PeppolReportingBackend.withBackendDo (AS4Configuration.getConfig (), aBackend -> aBackend.storeReportingItem (aReportingItem)); } catch (final PeppolReportingBackendException ex) { throw new Phase4PeppolException ("Failed to store Peppol Reporting Item", ex); } } else { LOGGER.info ("The Document Type ID is not eligible for Peppol Reporting: '" + m_aDocTypeID.getURIEncoded () + "'"); } } } /** * The builder class for sending AS4 messages using Peppol specifics. Use * {@link #sendMessage()} or {@link #sendMessageAndCheckForReceipt()} to * trigger the main transmission.
* This builder class assumes, that only the payload (e.g. the Invoice) is * present, and that both validation and SBDH creation happens inside. * * @author Philip Helger * @since 0.9.4 */ @NotThreadSafe public static class Builder extends AbstractPeppolUserMessageBuilder { private String m_sSBDHInstanceIdentifier; private String m_sSBDHTypeVersion; private Element m_aPayloadElement; private byte [] m_aPayloadBytes; private IHasInputStream m_aPayloadHasIS; private Consumer m_aSBDDocumentConsumer; private Consumer m_aSBDBytesConsumer; private IValidationExecutorSetRegistry m_aVESRegistry; private DVRCoordinate m_aVESID; private IPhase4PeppolValidationResultHandler m_aValidationResultHandler; /** * Create a new builder, with the defaults from * {@link AbstractPeppolUserMessageBuilder#AbstractPeppolUserMessageBuilder()} */ public Builder () {} /** * Set the SBDH instance identifier. If none is provided, a random ID is * used. Usually this must NOT be set. In case of a retry, the same Instance * Identifier should be used. Also an MLR should always refer to the * Instance Identifier of the original transmission. * * @param sSBDHInstanceIdentifier * The SBDH instance identifier to be used. May be null. * @return this for chaining */ @Nonnull public Builder sbdhInstanceIdentifier (@Nullable final String sSBDHInstanceIdentifier) { m_sSBDHInstanceIdentifier = sSBDHInstanceIdentifier; return this; } /** * Set the SBDH document identification type version. If none is provided, * the value is extracted from the document type identifier. * * @param sSBDHTypeVersion * The SBDH document identification type version to be used. May be * null. * @return this for chaining * @since 0.13.0 */ @Nonnull public Builder sbdhTypeVersion (@Nullable final String sSBDHTypeVersion) { m_sSBDHTypeVersion = sSBDHTypeVersion; return this; } /** * Set the payload element to be used, if it is available as a parsed DOM * element. Internally the DOM element will be cloned before sending it out. * If this method is called, it overwrites any other explicitly set payload. * * @param aPayloadElement * The payload element to be used. They payload element MUST have a * namespace URI. May not be null. * @return this for chaining */ @Nonnull public Builder payload (@Nonnull final Element aPayloadElement) { ValueEnforcer.notNull (aPayloadElement, "Payload"); ValueEnforcer.notNull (aPayloadElement.getNamespaceURI (), "Payload.NamespaceURI"); m_aPayloadElement = aPayloadElement; m_aPayloadBytes = null; m_aPayloadHasIS = null; return this; } /** * Set the payload to be used as a byte array. It will be parsed internally * to a DOM element. Compared to {@link #payload(Element)} the read DOM * element will not be cloned internally, so this option is less memory * intensive. If this method is called, it overwrites any other explicitly * set payload. * * @param aPayloadBytes * The payload bytes to be used. May not be null. * @return this for chaining */ @Nonnull public Builder payload (@Nonnull final byte [] aPayloadBytes) { ValueEnforcer.notNull (aPayloadBytes, "PayloadBytes"); m_aPayloadElement = null; m_aPayloadBytes = aPayloadBytes; m_aPayloadHasIS = null; return this; } /** * Set the payload to be used as an InputStream provider. It will be parsed * internally to a DOM element. Compared to {@link #payload(Element)} the * read DOM element will not be cloned internally, so this option is less * memory intensive. If this method is called, it overwrites any other * explicitly set payload. * * @param aPayloadHasIS * The payload input stream provider to be used. May not be * null. * @return this for chaining */ @Nonnull public Builder payload (@Nonnull final IHasInputStream aPayloadHasIS) { ValueEnforcer.notNull (aPayloadHasIS, "PayloadHasIS"); m_aPayloadElement = null; m_aPayloadBytes = null; m_aPayloadHasIS = aPayloadHasIS; return this; } /** * Use the provided byte array as the binary content of the Peppol SBDH * message. Internally the data will be wrapped in a predefined * "BinaryContent" element. * * @param aBinaryPayload * The bytes to be wrapped. May not be null. * @param aMimeType * The MIME type to use. May not be null. * @param aCharset * The character set to be used, if the MIME type is text based. May * be null. * @return this for chaining * @since 0.12.1 */ @Nonnull public Builder payloadBinaryContent (@Nonnull final byte [] aBinaryPayload, @Nonnull final IMimeType aMimeType, @Nullable final Charset aCharset) { ValueEnforcer.notNull (aBinaryPayload, "BinaryPayload"); ValueEnforcer.notNull (aMimeType, "MimeType"); final BinaryContentType aBC = new BinaryContentType (); aBC.setValue (aBinaryPayload); aBC.setMimeType (aMimeType.getAsString ()); aBC.setEncoding (aCharset == null ? null : aCharset.name ()); final Element aElement = new PeppolSBDHPayloadBinaryMarshaller ().getAsElement (aBC); if (aElement == null) throw new IllegalStateException ("Failed to create 'BinaryContent' element."); return payload (aElement); } /** * Use the provided byte array as the binary content of the Peppol SBDH * message. Internally the data will be wrapped in a predefined * "BinaryContent" element. * * @param sTextPayload * The text to be wrapped. May not be null. * @param aMimeType * The MIME type to use. May not be null. * @return this for chaining * @since 0.12.1 */ @Nonnull public Builder payloadTextContent (@Nonnull final String sTextPayload, @Nonnull final IMimeType aMimeType) { ValueEnforcer.notNull (sTextPayload, "TextPayload"); ValueEnforcer.notNull (aMimeType, "MimeType"); final TextContentType aTC = new TextContentType (); aTC.setValue (sTextPayload); aTC.setMimeType (aMimeType.getAsString ()); final Element aElement = new PeppolSBDHPayloadTextMarshaller ().getAsElement (aTC); if (aElement == null) throw new IllegalStateException ("Failed to create 'TextContent' element."); return payload (aElement); } /** * Set an optional Consumer for the created StandardBusinessDocument (SBD). * * @param aSBDDocumentConsumer * The consumer to be used. May be null. * @return this for chaining * @since 0.10.0 */ @Nonnull public Builder sbdDocumentConsumer (@Nullable final Consumer aSBDDocumentConsumer) { m_aSBDDocumentConsumer = aSBDDocumentConsumer; return this; } /** * Set an optional Consumer for the created StandardBusinessDocument (SBD) * bytes. * * @param aSBDBytesConsumer * The consumer to be used. May be null. * @return this for chaining */ @Nonnull public Builder sbdBytesConsumer (@Nullable final Consumer aSBDBytesConsumer) { m_aSBDBytesConsumer = aSBDBytesConsumer; return this; } /** * Set a custom validation registry to use in VESID lookup. This may be * needed if other Peppol formats like XRechnung or SimplerInvoicing should * be send through this client. The same registry instance should be used * for all sending operations to ensure that validation artefact caching * works best. * * @param aVESRegistry * The registry to use. May be null to indicate that the * default registry (official Peppol artefacts only) should be used. * @return this for chaining * @since 0.10.1 */ @Nonnull public Builder validationRegistry (@Nullable final IValidationExecutorSetRegistry aVESRegistry) { m_aVESRegistry = aVESRegistry; return this; } /** * Set the client side validation to be used. If this method is not invoked, * than it's the responsibility of the caller to validate the document prior * to sending it. This method uses a default "do nothing validation result * handler". * * @param aVESID * The Validation Execution Set ID as in * PeppolValidation2024_05.VID_OPENPEPPOL_INVOICE_UBL_V3. * May be null. * @return this for chaining * @see #validationConfiguration(DVRCoordinate, * IPhase4PeppolValidationResultHandler) */ @Nonnull public Builder validationConfiguration (@Nullable final DVRCoordinate aVESID) { final IPhase4PeppolValidationResultHandler aHdl = aVESID == null ? null : new Phase4PeppolValidatonResultHandler (); return validationConfiguration (aVESID, aHdl); } /** * Set the client side validation to be used. If this method is not invoked, * than it's the responsibility of the caller to validate the document prior * to sending it. If the validation should happen internally, both the VESID * AND the result handler must be set. * * @param aVESID * The Validation Execution Set ID as in * PeppolValidation2024_05.VID_OPENPEPPOL_INVOICE_UBL_V3. * May be null. * @param aValidationResultHandler * The validation result handler for positive and negative response * handling. May be null. * @return this for chaining */ @Nonnull public Builder validationConfiguration (@Nullable final DVRCoordinate aVESID, @Nullable final IPhase4PeppolValidationResultHandler aValidationResultHandler) { m_aVESID = aVESID; m_aValidationResultHandler = aValidationResultHandler; return this; } /** * Disable the validation for the outbound call. * * @return this for chaining * @since 2.1.1 */ @Nonnull public Builder disableValidation () { return validationConfiguration (null, null); } @Override protected ESuccess finishFields () throws Phase4Exception { // Ensure a DOM element is present final Element aPayloadElement; final boolean bClonePayloadElement; if (m_aPayloadElement != null) { // Already provided as a DOM element aPayloadElement = m_aPayloadElement; bClonePayloadElement = true; } else if (m_aPayloadBytes != null) { // Parse it final Document aDoc = DOMReader.readXMLDOM (m_aPayloadBytes); if (aDoc == null) throw new Phase4PeppolException ("Failed to parse payload bytes to a DOM node"); aPayloadElement = aDoc.getDocumentElement (); if (aPayloadElement == null) throw new Phase4PeppolException ("The parsed XML document must have a root element"); if (aPayloadElement.getNamespaceURI () == null) throw new Phase4PeppolException ("The root element of the parsed XML document does not have a namespace URI"); bClonePayloadElement = false; } else if (m_aPayloadHasIS != null) { // Parse it final InputStream aIS = m_aPayloadHasIS.getBufferedInputStream (); if (aIS == null) throw new Phase4PeppolException ("Failed to create payload InputStream from provider"); final Document aDoc = DOMReader.readXMLDOM (aIS); if (aDoc == null) throw new Phase4PeppolException ("Failed to parse payload InputStream to a DOM node"); aPayloadElement = aDoc.getDocumentElement (); if (aPayloadElement == null) throw new Phase4PeppolException ("The parsed XML document must have a root element"); if (aPayloadElement.getNamespaceURI () == null) throw new Phase4PeppolException ("The root element of the parsed XML document does not have a namespace URI"); bClonePayloadElement = false; } else throw new IllegalStateException ("Unexpected - neither element nor bytes nor InputStream provider are present"); // Consistency check if (CSBDH.SBDH_NS.equals (aPayloadElement.getNamespaceURI ())) throw new Phase4PeppolException ("You cannot set a Standard Business Document as the payload for the regular builder. The SBD is created automatically inside of this builder. Use Phase4PeppolSender.sbdhBuilder() if you have a pre-build SBD."); // Optional payload validation _validatePayload (aPayloadElement, m_aVESRegistry, m_aVESID, m_aValidationResultHandler); // Perform SMP lookup if (super.finishFields ().isFailure ()) return ESuccess.FAILURE; // Created SBDH if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Start creating SBDH for AS4 message"); final StandardBusinessDocument aSBD = _createSBD (m_aSenderID, m_aReceiverID, m_aDocTypeID, m_aProcessID, m_sCountryC1, m_sSBDHInstanceIdentifier, m_sSBDHTypeVersion, aPayloadElement, bClonePayloadElement); if (aSBD == null) { // A log message was already provided return ESuccess.FAILURE; } if (false) { // This is the developer code, to be able to send out messages without // Country C1 // After all the checks, the Scope element is removed LOGGER.error ("Explicitly removing COUNTRY_C1 from the SBDH. Development only!"); aSBD.getStandardBusinessDocumentHeader () .getBusinessScope () .getScope () .removeIf (x -> CPeppolSBDH.SCOPE_COUNTRY_C1.equals (x.getType ())); } if (m_aSBDDocumentConsumer != null) m_aSBDDocumentConsumer.accept (aSBD); final byte [] aSBDBytes = new SBDMarshaller ().getAsBytes (aSBD); if (m_aSBDBytesConsumer != null) m_aSBDBytesConsumer.accept (aSBDBytes); // Now we have the main payload payload (AS4OutgoingAttachment.builder () .data (aSBDBytes) .mimeType (m_aPayloadMimeType) .compression (m_bCompressPayload ? EAS4CompressionMode.GZIP : null) .contentID (m_sPayloadContentID)); return ESuccess.SUCCESS; } } /** * A builder class for sending AS4 messages using Peppol specifics. Use * {@link #sendMessage()} or {@link #sendMessageAndCheckForReceipt()} to * trigger the main transmission.
* This builder class assumes, that the SBDH was created outside, therefore no * validation can occur. * * @author Philip Helger * @since 0.9.6 */ @NotThreadSafe public static class SBDHBuilder extends AbstractPeppolUserMessageBuilder { private byte [] m_aPayloadBytes; /** * Create a new builder with the defaults from * {@link AbstractPeppolUserMessageBuilder#AbstractPeppolUserMessageBuilder()} */ public SBDHBuilder () {} /** * Set the SBDH payload to be used as a byte array. This means, that you * need to pass in all other mandatory fields manually (sender participant * ID, receiver participant ID, document Type ID, process ID and country * C1). * * @param aSBDHBytes * The SBDH bytes to be used. May not be null. * @return this for chaining * @see #senderParticipantID(IParticipantIdentifier) * @see #receiverParticipantID(IParticipantIdentifier) * @see #documentTypeID(IDocumentTypeIdentifier) * @see #processID(IProcessIdentifier) * @see #countryC1(String) */ @Nonnull public SBDHBuilder payload (@Nonnull final byte [] aSBDHBytes) { ValueEnforcer.notNull (aSBDHBytes, "SBDHBytes"); m_aPayloadBytes = aSBDHBytes; return this; } /** * Set the payload, the sender participant ID, the receiver participant ID, * the document type ID and the process ID. * * @param aSBDH * The SBDH to use. May not be null. * @return this for chaining * @since 0.10.2 * @see #payload(byte[]) * @see #senderParticipantID(IParticipantIdentifier) * @see #receiverParticipantID(IParticipantIdentifier) * @see #documentTypeID(IDocumentTypeIdentifier) * @see #processID(IProcessIdentifier) * @see #countryC1(String) */ @Nonnull public SBDHBuilder payloadAndMetadata (@Nonnull final PeppolSBDHData aSBDH) { ValueEnforcer.notNull (aSBDH, "SBDH"); // Check with logging if (!aSBDH.areAllFieldsSet (true)) throw new IllegalArgumentException ("The provided Peppol SBDH data is incomplete. See logs for details."); final StandardBusinessDocument aJaxbSbdh = new PeppolSBDHDocumentWriter ().createStandardBusinessDocument (aSBDH); return senderParticipantID (aSBDH.getSenderAsIdentifier ()).receiverParticipantID (aSBDH.getReceiverAsIdentifier ()) .documentTypeID (aSBDH.getDocumentTypeAsIdentifier ()) .processID (aSBDH.getProcessAsIdentifier ()) .countryC1 (aSBDH.getCountryC1 ()) .payload (new SBDMarshaller ().getAsBytes (aJaxbSbdh)); } @Override @OverridingMethodsMustInvokeSuper public boolean isEveryRequiredFieldSet () { if (!super.isEveryRequiredFieldSet ()) return false; if (m_aPayloadBytes == null) { LOGGER.warn ("The field 'payloadBytes' is not set"); return false; } // All valid return true; } @Override @OverridingMethodsMustInvokeSuper protected ESuccess finishFields () throws Phase4Exception { // Perform SMP lookup if (super.finishFields ().isFailure ()) return ESuccess.FAILURE; // Now we have the main payload payload (AS4OutgoingAttachment.builder () .data (m_aPayloadBytes) .mimeType (m_aPayloadMimeType) .compression (m_bCompressPayload ? EAS4CompressionMode.GZIP : null)); return ESuccess.SUCCESS; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy