From f0e1c3ae7d71eb81b767a2e29b956f5ec2210d0f Mon Sep 17 00:00:00 2001 From: Dino Patti Date: Wed, 30 Sep 2026 09:59:07 +0200 Subject: [PATCH] Honour PMode ReceptionAwareness.DuplicateDetection on incoming messages The flag was stored with the PMode but never consulted: AS4RequestHandler always ran the duplicate check. The check is now skipped when the resolved PMode explicitly disables duplicate detection; the default is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../phase4/incoming/AS4RequestHandler.java | 62 ++++++++++++------- .../servlet/UserMessageDuplicateTest.java | 40 ++++++++++++ 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4RequestHandler.java b/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4RequestHandler.java index 74dec05a2..528661527 100644 --- a/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4RequestHandler.java +++ b/phase4-lib/src/main/java/com/helger/phase4/incoming/AS4RequestHandler.java @@ -107,6 +107,7 @@ import com.helger.phase4.model.message.EAS4MessageType; import com.helger.phase4.model.message.MessageHelperMethods; import com.helger.phase4.model.pmode.IPMode; +import com.helger.phase4.model.pmode.PModeReceptionAwareness; import com.helger.phase4.model.pmode.leg.EPModeSendReceiptReplyPattern; import com.helger.phase4.model.pmode.leg.PModeLeg; import com.helger.phase4.model.pmode.leg.PModeLegSecurity; @@ -1709,34 +1710,49 @@ private IAS4ResponseFactory _handleIncomingSoapMessageAndInvokeSPIs (@NonNull fi { final String sProfileID = aIncomingState.getProfileID (); - if (LOGGER.isDebugEnabled ()) - LOGGER.debug ("Now checking for duplicate message with message ID '" + - sMessageID + - "' and profile ID '" + - sProfileID + - "'"); - - // Run duplicate message check - final boolean bIsDuplicate = MetaAS4Manager.getIncomingDuplicateMgr () - .registerAndCheck (sMessageID, - sProfileID, - aPMode == null ? null : aPMode.getID ()) - .isBreak (); - if (bIsDuplicate) + // Duplicate detection can be disabled per PMode (PMode[].ReceptionAwareness.DetectDuplicates). + // Without a PMode, or without an explicit setting, duplicates are always detected. + final PModeReceptionAwareness aReceptionAwareness = aPMode == null ? null : aPMode.getReceptionAwareness (); + if (aReceptionAwareness != null && !aReceptionAwareness.isDuplicateDetection ()) { - final String sDetails = "Not invoking SPIs, because message with Message ID '" + - sMessageID + - "' was already handled (this is a duplicate)"; - LOGGER.error (sDetails); - aEbmsErrorMessages.add (EEbmsError.EBMS_PHASE4_DUPLICATE.errorBuilder (m_aLocale) - .refToMessageInError (sMessageID) - .errorDetail (sDetails) - .build ()); + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Not checking for duplicate message with message ID '" + + sMessageID + + "', because PMode '" + + aPMode.getID () + + "' disables duplicate detection"); } else { if (LOGGER.isDebugEnabled ()) - LOGGER.debug ("Message with ID '" + sMessageID + "' is not a duplicate"); + LOGGER.debug ("Now checking for duplicate message with message ID '" + + sMessageID + + "' and profile ID '" + + sProfileID + + "'"); + + // Run duplicate message check + final boolean bIsDuplicate = MetaAS4Manager.getIncomingDuplicateMgr () + .registerAndCheck (sMessageID, + sProfileID, + aPMode == null ? null : aPMode.getID ()) + .isBreak (); + if (bIsDuplicate) + { + final String sDetails = "Not invoking SPIs, because message with Message ID '" + + sMessageID + + "' was already handled (this is a duplicate)"; + LOGGER.error (sDetails); + aEbmsErrorMessages.add (EEbmsError.EBMS_PHASE4_DUPLICATE.errorBuilder (m_aLocale) + .refToMessageInError (sMessageID) + .errorDetail (sDetails) + .build ()); + } + else + { + if (LOGGER.isDebugEnabled ()) + LOGGER.debug ("Message with ID '" + sMessageID + "' is not a duplicate"); + } } } diff --git a/phase4-test/src/test/java/com/helger/phase4/server/servlet/UserMessageDuplicateTest.java b/phase4-test/src/test/java/com/helger/phase4/server/servlet/UserMessageDuplicateTest.java index 43f35f3e2..eb129f393 100644 --- a/phase4-test/src/test/java/com/helger/phase4/server/servlet/UserMessageDuplicateTest.java +++ b/phase4-test/src/test/java/com/helger/phase4/server/servlet/UserMessageDuplicateTest.java @@ -25,13 +25,19 @@ import org.w3c.dom.Node; import com.helger.base.concurrent.ThreadHelper; +import com.helger.collection.commons.CommonsHashMap; +import com.helger.collection.commons.ICommonsMap; import com.helger.io.resource.ClassPathResource; import com.helger.phase4.AS4TestConstants; import com.helger.phase4.config.AS4Configuration; import com.helger.phase4.logging.Phase4LoggerFactory; import com.helger.phase4.messaging.http.HttpXMLEntity; +import com.helger.phase4.mgr.MetaAS4Manager; import com.helger.phase4.model.ESoapVersion; import com.helger.phase4.model.error.EEbmsError; +import com.helger.phase4.model.pmode.IPMode; +import com.helger.phase4.model.pmode.PMode; +import com.helger.phase4.model.pmode.PModeReceptionAwareness; import com.helger.phase4.server.message.MockMessages; import com.helger.xml.serialize.read.DOMReader; @@ -102,4 +108,38 @@ public void testDuplicateSignedMessage () throws Exception // Send second sendPlainMessageExpectError (aEntity, EEbmsError.EBMS_PHASE4_DUPLICATE.getErrorCode ()); } + + @Test + public void testDuplicateDetectionDisabledInPMode () throws Exception + { + final Node aPayload = DOMReader.readXMLDOM (new ClassPathResource (AS4TestConstants.TEST_SOAP_BODY_PAYLOAD_XML)); + + // Disable duplicate detection in all PModes of the receiving side + final ICommonsMap aOldValues = new CommonsHashMap <> (); + for (final IPMode aPMode : MetaAS4Manager.getPModeMgr ().getAll ()) + if (aPMode instanceof final PMode aRealPMode) + { + aOldValues.put (aRealPMode, aRealPMode.getReceptionAwareness ()); + final PModeReceptionAwareness aRA = PModeReceptionAwareness.createDefault (); + aRA.setDuplicateDetection (false); + aRealPMode.setReceptionAwareness (aRA); + } + assertTrue (aOldValues.isNotEmpty ()); + + try + { + final Document aDoc = MockMessages.createUserMessageNotSigned (m_eSoapVersion, aPayload, null) + .getAsSoapDocument (aPayload); + final HttpEntity aEntity = new HttpXMLEntity (aDoc, m_eSoapVersion.getMimeType ()); + + // The same message is accepted twice, because the PMode disables duplicate detection + sendPlainMessageExpectSuccess (aEntity); + sendPlainMessageExpectSuccess (aEntity); + } + finally + { + for (final var aEntry : aOldValues.entrySet ()) + aEntry.getKey ().setReceptionAwareness (aEntry.getValue ()); + } + } }