From c5c9043ef76341fdbe84c8254992499ad621ae6b Mon Sep 17 00:00:00 2001 From: mrconlisk Date: Mon, 19 Oct 2026 14:58:30 -0400 Subject: [PATCH 1/2] Added support for multiple partnerships with matching as2 ids --- .../main/java/org/openas2/BaseSession.java | 8 ++ .../receiver/MessageBuilderModule.java | 8 ++ .../receiver/PartnershipNameRoutingTest.java | 78 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java diff --git a/Server/src/main/java/org/openas2/BaseSession.java b/Server/src/main/java/org/openas2/BaseSession.java index b234da60..c8b15644 100644 --- a/Server/src/main/java/org/openas2/BaseSession.java +++ b/Server/src/main/java/org/openas2/BaseSession.java @@ -10,10 +10,12 @@ import org.openas2.processor.Processor; import org.openas2.processor.ProcessorModule; import org.openas2.processor.receiver.DirectoryPollingModule; +import org.openas2.processor.receiver.MessageBuilderModule; import org.openas2.processor.sender.InterruptedSendReport; import org.openas2.util.FileUtil; import org.openas2.util.Properties; import org.openas2.util.XMLUtil; +import org.w3c.dom.Element; import org.w3c.dom.Node; import jakarta.activation.CommandMap; @@ -309,6 +311,12 @@ public DirectoryPollingModule getPartnershipPoller(String senderAs2Id, String re } public void loadPartnershipPoller(Node moduleNode, String partnershipName, String configSource) throws OpenAS2Exception { + // Tie the poller to its partnership by name so that partnerships sharing the same + // sender and receiver AS2 IDs each send using their own configuration. Pollers declared in + // config.xml are excluded since their "name" is only the defaults string or "generic" + if (PARTNERSHIP_POLLER.equals(configSource) && partnershipName != null && moduleNode instanceof Element) { + ((Element) moduleNode).setAttribute(MessageBuilderModule.PARAM_PARTNERSHIP_NAME, partnershipName); + } DirectoryPollingModule procmod = (DirectoryPollingModule) XMLUtil.getComponent(moduleNode, this); if (procmod == null) { // Must be disable so do nothing diff --git a/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java b/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java index 49eef826..e0254502 100644 --- a/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java +++ b/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java @@ -53,6 +53,8 @@ public abstract class MessageBuilderModule extends BaseReceiverModule { public static final String PARAM_DELIMITERS = "delimiters"; public static final String PARAM_MERGE_EXTRA = "mergeextratokens"; public static final String PARAM_DEFAULTS = "defaults"; + // Set automatically on pollers declared in a partnership so the message uses that exact partnership + public static final String PARAM_PARTNERSHIP_NAME = "partnership_name"; public static final String PARAM_MIMETYPE = "mimetype"; public static final String PARAM_RESEND_MAX_RETRIES = "resend_max_retries"; // Note: When this option is enabled, you can also configure its quoting using "quote_send_file_name" at partnership-level @@ -287,6 +289,12 @@ public Message buildBaseMessage(String filename) throws OpenAS2Exception { boolean mergeExtraTokens = "true".equalsIgnoreCase(mergeExtra); params.setParameters(format, delimiters, filename, mergeExtraTokens); } + // A partnership poller knows its partnership by name, which takes precedence over the + // sender/receiver ID lookup so partnerships between the same partners are not confused + String partnershipName = getParameter(PARAM_PARTNERSHIP_NAME, false); + if (partnershipName != null && partnershipName.length() > 0) { + msg.getPartnership().setName(partnershipName); + } // Should have sender/receiver now so update the message's partnership with any // stored information based on the identified partner IDs getSession().getPartnershipFactory().updatePartnership(msg, true); diff --git a/Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java b/Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java new file mode 100644 index 00000000..2ef64190 --- /dev/null +++ b/Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java @@ -0,0 +1,78 @@ +package org.openas2.processor.receiver; + +import org.junit.jupiter.api.Test; +import org.openas2.Session; +import org.openas2.message.Message; +import org.openas2.partner.Partnership; +import org.openas2.partner.XMLPartnershipFactory; + +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Verifies that a partnership poller resolves the partnership it was declared in by name, so that + * several partnerships between the same sender and receiver AS2 IDs can each send with their own + * configuration instead of all using the first partnership that matches the ID pair. + */ +public class PartnershipNameRoutingTest { + + private static final String DEFAULTS = "sender.as2_id=MyCompany_OID, receiver.as2_id=PartnerA_OID"; + + private static Partnership partnership(String name, String subject) { + Partnership p = new Partnership(); + p.setName(name); + p.setSenderID(Partnership.PID_AS2, "MyCompany_OID"); + p.setReceiverID(Partnership.PID_AS2, "PartnerA_OID"); + p.setAttribute(Partnership.PA_SUBJECT, subject); + return p; + } + + private static AS2DirectoryPollingModule poller(String partnershipName) { + XMLPartnershipFactory factory = new XMLPartnershipFactory(); + List partnerships = new ArrayList(); + partnerships.add(partnership("MyCompany-to-PartnerA", "first config")); + partnerships.add(partnership("MyCompany-to-PartnerA-alt", "second config")); + factory.setPartnerships(partnerships); + + Session session = (Session) Proxy.newProxyInstance(Session.class.getClassLoader(), new Class[]{Session.class}, + (proxy, method, args) -> "getPartnershipFactory".equals(method.getName()) ? factory : null); + + AS2DirectoryPollingModule module = new AS2DirectoryPollingModule() { + @Override + public Session getSession() { + return session; + } + }; + module.setParameter(MessageBuilderModule.PARAM_DEFAULTS, DEFAULTS); + if (partnershipName != null) { + module.setParameter(MessageBuilderModule.PARAM_PARTNERSHIP_NAME, partnershipName); + } + return module; + } + + @Test + public void pollerUsesItsOwnPartnershipWhenAnotherSharesTheSameIds() throws Exception { + Message msg = poller("MyCompany-to-PartnerA-alt").buildBaseMessage("test.edi"); + + assertEquals("MyCompany-to-PartnerA-alt", msg.getPartnership().getName()); + assertEquals("second config", msg.getSubject()); + } + + @Test + public void pollerWithoutPartnershipNameStillResolvesByIds() throws Exception { + Message msg = poller(null).buildBaseMessage("test.edi"); + + assertEquals("MyCompany-to-PartnerA", msg.getPartnership().getName()); + assertEquals("first config", msg.getSubject()); + } + + @Test + public void unknownPartnershipNameFallsBackToIdLookup() throws Exception { + Message msg = poller("no-such-partnership").buildBaseMessage("test.edi"); + + assertEquals("MyCompany-to-PartnerA", msg.getPartnership().getName()); + } +} From 0fcdfe9e07ade8b688da60161c07ae59da5db807 Mon Sep 17 00:00:00 2001 From: mrconlisk Date: Tue, 20 Oct 2026 14:26:11 -0400 Subject: [PATCH 2/2] updated approach through poller defaults --- Server/src/config/config.xml | 2 +- .../main/java/org/openas2/BaseSession.java | 8 ------- .../org/openas2/params/MessageParameters.java | 10 ++++++++ .../receiver/MessageBuilderModule.java | 8 ------- .../receiver/PartnershipNameRoutingTest.java | 23 +++++++++++++++---- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/Server/src/config/config.xml b/Server/src/config/config.xml index a4ee28ed..42bea2a3 100644 --- a/Server/src/config/config.xml +++ b/Server/src/config/config.xml @@ -80,7 +80,7 @@ pollerConfigBase.outboxdir="$properties.storageBaseDir$/outbox/$partnership.receiver.as2_id$" pollerConfigBase.errordir="$properties.storageBaseDir$/outbox/error/$date.yyyy$-$date.MM$-$date.dd$/$partnership.receiver.as2_id$" pollerConfigBase.interval="5" - pollerConfigBase.defaults="sender.as2_id=$partnership.sender.as2_id$, receiver.as2_id=$partnership.receiver.as2_id$" + pollerConfigBase.defaults="sender.as2_id=$partnership.sender.as2_id$, receiver.as2_id=$partnership.receiver.as2_id$, name=$partnership.name$" pollerConfigBase.sendfilename="true" pollerConfigBase.mimetype="application/EDI-X12" pollerConfigBase.process_files_in_parallel="false" diff --git a/Server/src/main/java/org/openas2/BaseSession.java b/Server/src/main/java/org/openas2/BaseSession.java index c8b15644..b234da60 100644 --- a/Server/src/main/java/org/openas2/BaseSession.java +++ b/Server/src/main/java/org/openas2/BaseSession.java @@ -10,12 +10,10 @@ import org.openas2.processor.Processor; import org.openas2.processor.ProcessorModule; import org.openas2.processor.receiver.DirectoryPollingModule; -import org.openas2.processor.receiver.MessageBuilderModule; import org.openas2.processor.sender.InterruptedSendReport; import org.openas2.util.FileUtil; import org.openas2.util.Properties; import org.openas2.util.XMLUtil; -import org.w3c.dom.Element; import org.w3c.dom.Node; import jakarta.activation.CommandMap; @@ -311,12 +309,6 @@ public DirectoryPollingModule getPartnershipPoller(String senderAs2Id, String re } public void loadPartnershipPoller(Node moduleNode, String partnershipName, String configSource) throws OpenAS2Exception { - // Tie the poller to its partnership by name so that partnerships sharing the same - // sender and receiver AS2 IDs each send using their own configuration. Pollers declared in - // config.xml are excluded since their "name" is only the defaults string or "generic" - if (PARTNERSHIP_POLLER.equals(configSource) && partnershipName != null && moduleNode instanceof Element) { - ((Element) moduleNode).setAttribute(MessageBuilderModule.PARAM_PARTNERSHIP_NAME, partnershipName); - } DirectoryPollingModule procmod = (DirectoryPollingModule) XMLUtil.getComponent(moduleNode, this); if (procmod == null) { // Must be disable so do nothing diff --git a/Server/src/main/java/org/openas2/params/MessageParameters.java b/Server/src/main/java/org/openas2/params/MessageParameters.java index cd26c1b1..65fd3284 100644 --- a/Server/src/main/java/org/openas2/params/MessageParameters.java +++ b/Server/src/main/java/org/openas2/params/MessageParameters.java @@ -13,6 +13,7 @@ public class MessageParameters extends ParameterParser { public static final String KEY_SENDER = "sender"; public static final String KEY_RECEIVER = "receiver"; + public static final String KEY_NAME = "name"; public static final String KEY_ATTRIBUTES = "attributes"; public static final String KEY_HEADERS = "headers"; public static final String KEY_CONTENT_FILENAME = "content-disposition"; @@ -29,6 +30,15 @@ public MessageParameters(Message target) { public void setParameter(String key, String value) throws InvalidParameterException { StringTokenizer keyParts = new StringTokenizer(key, ".", false); + if (keyParts.countTokens() == 1) { + if (key.equals(KEY_NAME)) { + getTarget().getPartnership().setName(value); + } else { + throw new InvalidParameterException("Unsupported key identifier", this, key, null); + } + return; + } + if (keyParts.countTokens() != 2) { throw new InvalidParameterException("Invalid key format", this, key, null); } diff --git a/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java b/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java index e0254502..49eef826 100644 --- a/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java +++ b/Server/src/main/java/org/openas2/processor/receiver/MessageBuilderModule.java @@ -53,8 +53,6 @@ public abstract class MessageBuilderModule extends BaseReceiverModule { public static final String PARAM_DELIMITERS = "delimiters"; public static final String PARAM_MERGE_EXTRA = "mergeextratokens"; public static final String PARAM_DEFAULTS = "defaults"; - // Set automatically on pollers declared in a partnership so the message uses that exact partnership - public static final String PARAM_PARTNERSHIP_NAME = "partnership_name"; public static final String PARAM_MIMETYPE = "mimetype"; public static final String PARAM_RESEND_MAX_RETRIES = "resend_max_retries"; // Note: When this option is enabled, you can also configure its quoting using "quote_send_file_name" at partnership-level @@ -289,12 +287,6 @@ public Message buildBaseMessage(String filename) throws OpenAS2Exception { boolean mergeExtraTokens = "true".equalsIgnoreCase(mergeExtra); params.setParameters(format, delimiters, filename, mergeExtraTokens); } - // A partnership poller knows its partnership by name, which takes precedence over the - // sender/receiver ID lookup so partnerships between the same partners are not confused - String partnershipName = getParameter(PARAM_PARTNERSHIP_NAME, false); - if (partnershipName != null && partnershipName.length() > 0) { - msg.getPartnership().setName(partnershipName); - } // Should have sender/receiver now so update the message's partnership with any // stored information based on the identified partner IDs getSession().getPartnershipFactory().updatePartnership(msg, true); diff --git a/Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java b/Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java index 2ef64190..939f148e 100644 --- a/Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java +++ b/Server/src/test/java/org/openas2/processor/receiver/PartnershipNameRoutingTest.java @@ -2,7 +2,10 @@ import org.junit.jupiter.api.Test; import org.openas2.Session; +import org.openas2.message.AS2Message; import org.openas2.message.Message; +import org.openas2.params.InvalidParameterException; +import org.openas2.params.MessageParameters; import org.openas2.partner.Partnership; import org.openas2.partner.XMLPartnershipFactory; @@ -11,11 +14,13 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** - * Verifies that a partnership poller resolves the partnership it was declared in by name, so that - * several partnerships between the same sender and receiver AS2 IDs can each send with their own - * configuration instead of all using the first partnership that matches the ID pair. + * Verifies that a poller whose "defaults" include the partnership name (e.g. + * "..., name=$partnership.name$") resolves that partnership by name, so that several partnerships + * between the same sender and receiver AS2 IDs can each send with their own configuration instead + * of all using the first partnership that matches the ID pair. */ public class PartnershipNameRoutingTest { @@ -46,10 +51,11 @@ public Session getSession() { return session; } }; - module.setParameter(MessageBuilderModule.PARAM_DEFAULTS, DEFAULTS); + String defaults = DEFAULTS; if (partnershipName != null) { - module.setParameter(MessageBuilderModule.PARAM_PARTNERSHIP_NAME, partnershipName); + defaults += ", " + MessageParameters.KEY_NAME + "=" + partnershipName; } + module.setParameter(MessageBuilderModule.PARAM_DEFAULTS, defaults); return module; } @@ -75,4 +81,11 @@ public void unknownPartnershipNameFallsBackToIdLookup() throws Exception { assertEquals("MyCompany-to-PartnerA", msg.getPartnership().getName()); } + + @Test + public void unsupportedSinglePartKeyIsRejected() { + MessageParameters params = new MessageParameters(new AS2Message()); + + assertThrows(InvalidParameterException.class, () -> params.setParameters("foo=bar")); + } }