diff --git a/phase4-dbnalliance-client/pom.xml b/phase4-dbnalliance-client/pom.xml index 6ea7e081a..c197030b8 100644 --- a/phase4-dbnalliance-client/pom.xml +++ b/phase4-dbnalliance-client/pom.xml @@ -37,8 +37,8 @@ http://www.apache.org/licenses/LICENSE-2.0 repo - - + + com.helger.phase4 @@ -48,6 +48,23 @@ com.helger.phase4 phase4-dynamic-discovery + + com.helger + ph-xhe + 4.0.1 + jar + + + com.helger.peppol + peppol-commons + ${peppol-commons.version} + + + com.helger.peppol + dbnalliance-xhe + 9.4.1-SNAPSHOT + jar + com.helger.phase4 phase4-profile-dbnalliance diff --git a/phase4-dbnalliance-client/src/main/java/com/helger/phase4/dbnalliance/Phase4DBNAllianceSender.java b/phase4-dbnalliance-client/src/main/java/com/helger/phase4/dbnalliance/Phase4DBNAllianceSender.java index a2928cbbb..61b8810e8 100644 --- a/phase4-dbnalliance-client/src/main/java/com/helger/phase4/dbnalliance/Phase4DBNAllianceSender.java +++ b/phase4-dbnalliance-client/src/main/java/com/helger/phase4/dbnalliance/Phase4DBNAllianceSender.java @@ -30,21 +30,35 @@ import com.helger.commons.ValueEnforcer; import com.helger.commons.annotation.Nonempty; +import com.helger.commons.datetime.XMLOffsetDateTime; import com.helger.commons.state.ESuccess; +import com.helger.peppol.smp.ESMPTransportProfile; import com.helger.peppol.utils.PeppolCertificateHelper; +import com.helger.peppol.xhe.DBNAlliancePayload; +import com.helger.peppol.xhe.DBNAllianceXHEData; +import com.helger.peppol.xhe.write.DBNAllianceXHEDocumentWriter; import com.helger.peppolid.IDocumentTypeIdentifier; import com.helger.peppolid.IParticipantIdentifier; import com.helger.peppolid.IProcessIdentifier; +import com.helger.peppolid.factory.BDXR2IdentifierFactory; import com.helger.phase4.CAS4; +import com.helger.phase4.attachment.AS4OutgoingAttachment; import com.helger.phase4.crypto.ICryptoSessionKeyProvider; import com.helger.phase4.dynamicdiscovery.AS4EndpointDetailProviderBDXR2; import com.helger.phase4.dynamicdiscovery.AS4EndpointDetailProviderConstant; import com.helger.phase4.dynamicdiscovery.IAS4EndpointDetailProvider; +import com.helger.phase4.mgr.MetaAS4Manager; import com.helger.phase4.profile.dbnalliance.DBNAlliancePMode; import com.helger.phase4.sender.AbstractAS4UserMessageBuilderMIMEPayload; import com.helger.phase4.sender.IAS4SendingDateTimeConsumer; import com.helger.phase4.util.Phase4Exception; import com.helger.smpclient.bdxr2.IBDXR2ServiceMetadataProvider; +import com.helger.xhe.v10.CXHE10; +import com.helger.xhe.v10.XHE10Marshaller; +import com.helger.xhe.v10.XHE10XHEType; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import org.w3c.dom.Element; /** * This class contains all the specifics to send AS4 messages with the @@ -56,10 +70,46 @@ @Immutable public final class Phase4DBNAllianceSender { + public static final BDXR2IdentifierFactory IF = BDXR2IdentifierFactory.INSTANCE; private static final Logger LOGGER = LoggerFactory.getLogger (Phase4DBNAllianceSender.class); private Phase4DBNAllianceSender () {} + + @Nullable + private static XHE10XHEType _createXHE (@Nonnull final IParticipantIdentifier aSenderID, + @Nonnull final IParticipantIdentifier aReceiverID, + @Nonnull final IDocumentTypeIdentifier aDocTypeID, + @Nonnull final IProcessIdentifier aProcID, + @Nonnull final Element aPayloadElement, + final boolean bClonePayloadElement + ) + { + final DBNAllianceXHEData aData = new DBNAllianceXHEData (IF); + aData.setFromParty (aSenderID.getScheme (), aSenderID.getValue ()); + aData.setToParty (aReceiverID.getScheme (), aReceiverID.getValue ()); + aData.setID (UUID.randomUUID ().toString ()); + aData.setCreationDateAndTime (MetaAS4Manager.getTimestampMgr ().getCurrentXMLDateTime ()); + + final DBNAlliancePayload aPayload = new DBNAlliancePayload (IF); + aPayload.setCustomizationID (null, aDocTypeID.getValue ()); + aPayload.setProfileID (aProcID.getScheme (), aProcID.getValue ()); + + // 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) + aPayload.setPayloadContent (aPayloadElement); + else + aPayload.setPayloadContentNoClone (aPayloadElement); + + aData.addPayload(aPayload); + + // check with logging + if (!aData.areAllFieldsSet (true)) + throw new IllegalArgumentException ("The DBNAlliance XHE data is incomplete. See logs for details."); + + return DBNAllianceXHEDocumentWriter.createExchangeHeaderEnvelope (aData); + } /** * @return Create a new Builder for AS4 messages if the XHE payload is @@ -83,6 +133,7 @@ public abstract static class AbstractDBNAllianceUserMessageBuilder { // C4 + protected IParticipantIdentifier m_aSenderID; protected IParticipantIdentifier m_aReceiverID; protected IDocumentTypeIdentifier m_aDocTypeID; protected IProcessIdentifier m_aProcessID; @@ -114,6 +165,24 @@ protected AbstractDBNAllianceUserMessageBuilder () 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 @@ -226,7 +295,9 @@ public final IMPLTYPE endpointDetailProvider (@Nonnull final IAS4EndpointDetailP @Nonnull public final IMPLTYPE smpClient (@Nonnull final IBDXR2ServiceMetadataProvider aSMPClient) { - return endpointDetailProvider (new AS4EndpointDetailProviderBDXR2 (aSMPClient)); + final AS4EndpointDetailProviderBDXR2 aEndpointDetailProvider = new AS4EndpointDetailProviderBDXR2 (aSMPClient); + aEndpointDetailProvider.setTransportProfile (ESMPTransportProfile.TRANSPORT_PROFILE_DBNA_AS4_v1); + return endpointDetailProvider (aEndpointDetailProvider); } /** @@ -420,7 +491,83 @@ protected void customizeBeforeSending () throws Phase4Exception public static class DBNAllianceUserMessageBuilder extends AbstractDBNAllianceUserMessageBuilder { + + private Element m_aPayloadElement; + public DBNAllianceUserMessageBuilder () {} + + /** + * 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 DBNAllianceUserMessageBuilder payload (@Nonnull final Element aPayloadElement) + { + ValueEnforcer.notNull (aPayloadElement, "Payload"); + ValueEnforcer.notNull (aPayloadElement.getNamespaceURI (), "Payload.NamespaceURI"); + m_aPayloadElement = aPayloadElement; + return this; + } + + @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 + throw new IllegalStateException ("Unexpected - element are not present"); + + // Consistency check + if (CXHE10.NAMESPACE_URI_XHE.equals(aPayloadElement.getNamespaceURI ())) + throw new Phase4DBNAllianceException ("You cannot set a Exchange Header Envelope as the payload for the regular builder. The XHE is created automatically inside of this builder."); + + // 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 XHE10XHEType aXHE = _createXHE (m_aSenderID, + m_aReceiverID, + m_aDocTypeID, + m_aProcessID, + aPayloadElement, + bClonePayloadElement); + if (aXHE == null) + { + // A log message was already provided + return ESuccess.FAILURE; + } + + final byte [] aXHEBytes = new XHE10Marshaller ().getAsBytes (aXHE); + + // Now we have the main payload + payload (AS4OutgoingAttachment.builder () + .data (aXHEBytes) + .compressionGZIP () + .mimeTypeXML () + .charset (StandardCharsets.UTF_8)); + + return ESuccess.SUCCESS; + } } } diff --git a/phase4-dbnalliance-client/src/test/java/com/helger/phase4/dbnalliance/MainPhase4DBNAllianceSenderExample.java b/phase4-dbnalliance-client/src/test/java/com/helger/phase4/dbnalliance/MainPhase4DBNAllianceSenderExample.java index 449322f5e..81abc4941 100644 --- a/phase4-dbnalliance-client/src/test/java/com/helger/phase4/dbnalliance/MainPhase4DBNAllianceSenderExample.java +++ b/phase4-dbnalliance-client/src/test/java/com/helger/phase4/dbnalliance/MainPhase4DBNAllianceSenderExample.java @@ -16,20 +16,24 @@ */ package com.helger.phase4.dbnalliance; +import com.helger.peppolid.bdxr.smp2.participant.BDXR2ParticipantIdentifier; +import com.helger.peppolid.factory.SimpleIdentifierFactory; import java.io.File; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.helger.phase4.attachment.AS4OutgoingAttachment; import com.helger.phase4.dump.AS4DumpManager; import com.helger.phase4.dump.AS4IncomingDumperFileBased; import com.helger.phase4.dump.AS4OutgoingDumperFileBased; import com.helger.phase4.sender.AbstractAS4UserMessageBuilder.ESimpleUserMessageSendResult; import com.helger.servlet.mock.MockServletContext; +import com.helger.smpclient.bdxr2.BDXR2ClientReadOnly; +import com.helger.smpclient.dbna.EDBNASML; +import com.helger.smpclient.url.DBNAURLProviderSMP; import com.helger.web.scope.mgr.WebScopeManager; +import com.helger.xml.serialize.read.DOMReader; +import org.w3c.dom.Element; public class MainPhase4DBNAllianceSenderExample { @@ -47,23 +51,26 @@ public static void main (final String [] args) try { // Read XML payload to send - final byte [] aPayloadBytes = Files.readAllBytes (new File ("src/test/resources/external/examples/base-example.xml").toPath ()); - if (aPayloadBytes == null) + final Element aPayloadElement = DOMReader.readXMLDOM (new File ("src/test/resources/external/examples/base-example.xml")) + .getDocumentElement (); + if (aPayloadElement == null) throw new IllegalStateException ("Failed to read file to be send"); - // Start configuring here + // Start configuring here + final BDXR2ParticipantIdentifier aReceiver = Phase4DBNAllianceSender.IF.createParticipantIdentifier ("us:ein", "365060483"); + BDXR2ClientReadOnly aSMPClient = new BDXR2ClientReadOnly (DBNAURLProviderSMP.INSTANCE.getSMPURIOfParticipant (aReceiver, + EDBNASML.TEST.getZoneName())); + aSMPClient.setVerifySignature (false); + final ESimpleUserMessageSendResult eResult; eResult = Phase4DBNAllianceSender.builder () - .fromPartyID ("AS4-Sender") - .toPartyID ("AS4-Receiver") - .endpointURL ("https://receiver.example.org/dbna/as4") - .service ("AS4-Service") - .action ("AS4-Action") - .payload (AS4OutgoingAttachment.builder () - .data (aPayloadBytes) - .compressionGZIP () - .mimeTypeXML () - .charset (StandardCharsets.UTF_8)) + .documentTypeID (SimpleIdentifierFactory.INSTANCE.createDocumentTypeIdentifier ("bdx-docid-qns", "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##DBNAlliance-1.0-data-Core")) + .processID (Phase4DBNAllianceSender.IF.createProcessIdentifier(null, "bdx:noprocess")) + .senderParticipantID (Phase4DBNAllianceSender.IF.createParticipantIdentifier ("us:ein", "365060483")) + .receiverParticipantID (aReceiver) + .fromPartyID("365060483") + .payload (aPayloadElement) + .smpClient (aSMPClient) .sendMessageAndCheckForReceipt (); LOGGER.info ("DBNAlliance send result: " + eResult); } diff --git a/phase4-dbnalliance-client/src/test/resources/application.properties b/phase4-dbnalliance-client/src/test/resources/application.properties new file mode 100644 index 000000000..d012a0990 --- /dev/null +++ b/phase4-dbnalliance-client/src/test/resources/application.properties @@ -0,0 +1,35 @@ +# +# 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. +# + +global.debug=true +global.production=false +global.nostartupinfo=true + +org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlin +org.apache.wss4j.crypto.merlin.keystore.type=pkcs12 +org.apache.wss4j.crypto.merlin.keystore.file=test-ap-2023.p12 +org.apache.wss4j.crypto.merlin.keystore.password=peppol +org.apache.wss4j.crypto.merlin.keystore.alias=cert +org.apache.wss4j.crypto.merlin.keystore.private.password=peppol + +org.apache.wss4j.crypto.merlin.load.cacerts=false +#org.apache.wss4j.crypto.merlin.truststore.provider= +org.apache.wss4j.crypto.merlin.truststore.type=jks +org.apache.wss4j.crypto.merlin.truststore.file=truststore/dbnalliance-truststore.jks +org.apache.wss4j.crypto.merlin.truststore.password=dbnalliance + +phase4.dump.path = generated/