diff --git a/ci/tests/130-destination-set/channels/01-destination-set/channel.xml b/ci/tests/130-destination-set/channels/01-destination-set/channel.xml
new file mode 100644
index 0000000000..eb5e1e45dc
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/channel.xml
@@ -0,0 +1,369 @@
+
+ 156458a3-9184-47ed-8ed8-abac6599c243
+ 4
+ Destination set
+
+ 1
+
+ 0
+ sourceConnector
+
+
+
+ None
+ true
+ false
+ false
+ 1
+
+
+ Default Resource
+ [Default Resource]
+
+
+ 1000
+
+
+
+
+
+ Remove Dest A
+ 0
+ true
+ REMOVE
+
+ 1
+
+ msg
+ EQUALS
+
+ 'step-remove'
+
+
+
+ Remove all except Dest C
+ 1
+ true
+ REMOVE_ALL_EXCEPT
+
+ 3
+
+ msg
+ EQUALS
+
+ 'step-remove-all-except'
+
+
+
+ Run the call and record the destinations left
+ 2
+ true
+
+
+
+ RAW
+ RAW
+
+
+ JavaScript
+
+
+
+
+
+ JavaScript
+
+
+
+
+
+
+
+ Channel Reader
+ SOURCE
+ true
+ true
+
+
+
+ 1
+ Dest A
+
+
+
+ false
+ false
+ 10000
+ false
+ 0
+ false
+ false
+ 1
+
+ false
+
+
+ Default Resource
+ [Default Resource]
+
+
+ 1000
+ true
+
+ none
+ ${message.encodedData}
+
+
+
+
+ RAW
+ RAW
+
+
+ JavaScript
+
+
+
+
+
+ JavaScript
+
+
+
+
+
+
+ RAW
+ RAW
+
+
+ JavaScript
+
+
+
+
+
+ JavaScript
+
+
+
+
+
+
+
+ Channel Writer
+ DESTINATION
+ true
+ true
+
+
+ 2
+ Dest B
+
+
+
+ false
+ false
+ 10000
+ false
+ 0
+ false
+ false
+ 1
+
+ false
+
+
+ Default Resource
+ [Default Resource]
+
+
+ 1000
+ true
+
+ none
+ ${message.encodedData}
+
+
+
+
+ RAW
+ RAW
+
+
+ JavaScript
+
+
+
+
+
+ JavaScript
+
+
+
+
+
+
+ RAW
+ RAW
+
+
+ JavaScript
+
+
+
+
+
+ JavaScript
+
+
+
+
+
+
+
+ Channel Writer
+ DESTINATION
+ true
+ true
+
+
+ 3
+ Dest C
+
+
+
+ false
+ false
+ 10000
+ false
+ 0
+ false
+ false
+ 1
+
+ false
+
+
+ Default Resource
+ [Default Resource]
+
+
+ 1000
+ true
+
+ none
+ ${message.encodedData}
+
+
+
+
+ RAW
+ RAW
+
+
+ JavaScript
+
+
+
+
+
+ JavaScript
+
+
+
+
+
+
+ RAW
+ RAW
+
+
+ JavaScript
+
+
+
+
+
+ JavaScript
+
+
+
+
+
+
+
+ Channel Writer
+ DESTINATION
+ true
+ true
+
+
+ // Modify the message variable below to pre process data
+return message;
+ // This script executes once after a message has been processed
+// Responses returned from here will be stored as "Postprocessor" in the response map
+return;
+ // This script executes once when the channel is deployed
+// You only have access to the globalMap and globalChannelMap here to persist data
+return;
+ // This script executes once when the channel is undeployed
+// You only have access to the globalMap and globalChannelMap here to persist data
+return;
+
+ true
+ DEVELOPMENT
+ false
+ false
+ false
+ false
+ false
+ false
+ STARTED
+ true
+
+
+ SOURCE
+ STRING
+ mirth_source
+
+
+ TYPE
+ STRING
+ mirth_type
+
+
+
+ None
+
+
+
+
+ Default Resource
+ [Default Resource]
+
+
+
+
+
+ true
+
+
+ America/Chicago
+
+
+ true
+ false
+
+ 1
+
+
+
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/dest02_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/dest02_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/dest02_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/dest03_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/dest03_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/dest03_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/source b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/source
new file mode 100644
index 0000000000..ea83baba87
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/source
@@ -0,0 +1 @@
+step-remove
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/source_transformed b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/source_transformed
new file mode 100644
index 0000000000..50dee0dc2a
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/01-filter-step-remove/source_transformed
@@ -0,0 +1 @@
+step [2, 3]
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/dest03_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/dest03_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/dest03_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/source b/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/source
new file mode 100644
index 0000000000..a5c1ffc06c
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/source
@@ -0,0 +1 @@
+step-remove-all-except
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/source_transformed b/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/source_transformed
new file mode 100644
index 0000000000..14d59ed881
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/02-filter-step-remove-all-except/source_transformed
@@ -0,0 +1 @@
+step [3]
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/dest02_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/dest02_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/dest02_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/source b/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/source
new file mode 100644
index 0000000000..a26abc86ff
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/source
@@ -0,0 +1 @@
+destinationSet.remove(['Dest A', 3])
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/source_transformed b/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/source_transformed
new file mode 100644
index 0000000000..4c38f8095e
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/03-remove-by-name-and-id/source_transformed
@@ -0,0 +1 @@
+true [2]
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/dest02_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/dest02_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/dest02_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/source b/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/source
new file mode 100644
index 0000000000..d74f98a41c
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/source
@@ -0,0 +1 @@
+destinationSet.removeAllExcept('Dest B')
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/source_transformed b/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/source_transformed
new file mode 100644
index 0000000000..4c38f8095e
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/04-remove-all-except/source_transformed
@@ -0,0 +1 @@
+true [2]
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest01_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest01_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest01_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest02_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest02_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest02_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest03_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest03_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/dest03_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/source b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/source
new file mode 100644
index 0000000000..eea2157ada
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/source
@@ -0,0 +1 @@
+destinationSet.size() + ' ' + destinationSet.contains('Dest C')
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/source_transformed b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/source_transformed
new file mode 100644
index 0000000000..b9110abc41
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/05-size-and-contains/source_transformed
@@ -0,0 +1 @@
+3 true [1, 2, 3]
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/dest01_status b/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/dest01_status
new file mode 100644
index 0000000000..59586dad7f
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/dest01_status
@@ -0,0 +1 @@
+SENT
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/source b/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/source
new file mode 100644
index 0000000000..df56f65b1c
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/source
@@ -0,0 +1 @@
+destinationSet.removeIf(function (id) { return id > 1; })
\ No newline at end of file
diff --git a/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/source_transformed b/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/source_transformed
new file mode 100644
index 0000000000..2a6201f9eb
--- /dev/null
+++ b/ci/tests/130-destination-set/channels/01-destination-set/messages/06-remove-if/source_transformed
@@ -0,0 +1 @@
+true [1]
\ No newline at end of file
diff --git a/client/src/main/java/com/mirth/connect/client/ui/reference/ReferenceListFactory.java b/client/src/main/java/com/mirth/connect/client/ui/reference/ReferenceListFactory.java
index 7974d33497..54211b2ab4 100644
--- a/client/src/main/java/com/mirth/connect/client/ui/reference/ReferenceListFactory.java
+++ b/client/src/main/java/com/mirth/connect/client/ui/reference/ReferenceListFactory.java
@@ -329,8 +329,8 @@ private void addCodeTemplateReferences() {
addReference(new FunctionReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), null, "Create Segment (in message, index)", "Create a new segment in specified message (msg or tmp) at segment index i", "function createSegment(segmentName, msg, i) {}", new CodeTemplateFunctionDefinition("createSegment", new Parameters("segmentName", "String", "The name of the segment to create.").add("msg", "XML", "The parent XML object in which to create the segment.").add("i", "Number", "The index at which to insert the segment into the parent XML object."), "XML", "An E4X XML object representing the created segment.")));
addReference(new FunctionReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), null, "Create Segment After Segment", "Create a new segment and insert it after the target segment", "function createSegmentAfter(insertSegmentName, afterThisSegment) {}", new CodeTemplateFunctionDefinition("createSegmentAfter", new Parameters("insertSegmentName", "String", "The name of the segment to create.").add("afterThisSegment", "XML", "The existing segment that the new segment will be inserted after."), "XML", "An E4X XML object representing the created segment.")));
addReference(new ParameterizedCodeReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), "Delete Segment", "Delete a segment from the message", "delete ${msg}['${segment}']"));
- addReference(new ParameterizedCodeReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), "Remove one or more from Destination Set", "Stop one or more destinations from being processed for this message. Only available in the preprocessor or source filter/transformer.", "destinationSet.remove([${metaDataIdOrConnectorNames}]);"));
- addReference(new ParameterizedCodeReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), "Remove all except specified from Destination Set", "Stop all except the specified destinations from being processed for this message. Only available in the preprocessor or source filter/transformer.", "destinationSet.removeAllExcept([${metaDataIdOrConnectorNames}]);"));
+ addReference(new ParameterizedCodeReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), "Remove one or more from Destination Set", "Stop one or more destinations from being processed for this message. Only available in the preprocessor or source filter/transformer.", "destinationSet.removeAll([${metaDataIdOrConnectorNames}]);"));
+ addReference(new ParameterizedCodeReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), "Remove all except specified from Destination Set", "Stop all except the specified destinations from being processed for this message. Only available in the preprocessor or source filter/transformer.", "destinationSet.retainAll([${metaDataIdOrConnectorNames}]);"));
addReference(new ParameterizedCodeReference(CONTEXT_CONNECTOR, Category.MESSAGE.toString(), "Remove all from Destination Set", "Stop all destinations from being processed for this message. Only available in the preprocessor or source filter/transformer.", "destinationSet.removeAll();"));
addReference(new ParameterizedCodeReference(CONTEXT_CHANNEL, Category.MESSAGE.toString(), "Message Reprocessed", "Get a variable indicating if this message was reprocessed.", "var reprocessed = sourceMap.get('reprocessed') == true;"));
addReference(new ParameterizedCodeReference(CONTEXT_CHANNEL, Category.MESSAGE.toString(), "Message Replaced", "Get a variable indicating if this message was reprocessed and replaced.", "var replaced = sourceMap.get('replaced') == true;"));
diff --git a/server/src/main/java/com/mirth/connect/plugins/destinationsetfilter/DestinationSetFilterStep.java b/server/src/main/java/com/mirth/connect/plugins/destinationsetfilter/DestinationSetFilterStep.java
index f40e1e04fe..973a84653e 100644
--- a/server/src/main/java/com/mirth/connect/plugins/destinationsetfilter/DestinationSetFilterStep.java
+++ b/server/src/main/java/com/mirth/connect/plugins/destinationsetfilter/DestinationSetFilterStep.java
@@ -147,9 +147,9 @@ public String getScript(boolean loadFiles) throws ScriptBuilderException {
script.append("{\n\tdestinationSet.");
if (behavior == Behavior.REMOVE) {
- script.append("remove");
+ script.append("removeAll");
} else if (behavior == Behavior.REMOVE_ALL_EXCEPT) {
- script.append("removeAllExcept");
+ script.append("retainAll");
} else {
script.append("removeAll");
}
diff --git a/server/src/main/java/com/mirth/connect/server/userutil/DestinationSet.java b/server/src/main/java/com/mirth/connect/server/userutil/DestinationSet.java
index 738d4c350f..76e1ff6e09 100644
--- a/server/src/main/java/com/mirth/connect/server/userutil/DestinationSet.java
+++ b/server/src/main/java/com/mirth/connect/server/userutil/DestinationSet.java
@@ -1,17 +1,15 @@
-/*
- * Copyright (c) Mirth Corporation. All rights reserved.
- *
- * http://www.mirthcorp.com
- *
- * The software in this package is published under the terms of the MPL license a copy of which has
- * been included with this distribution in the LICENSE.txt file.
- */
+// SPDX-License-Identifier: MPL-2.0
+// SPDX-FileCopyrightText: Mirth Corporation
+// SPDX-FileCopyrightText: 2025 Richard Ogin
+// SPDX-FileCopyrightText: 2026 Tony Germano
package com.mirth.connect.server.userutil;
+import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@@ -23,45 +21,55 @@
/**
* Utility class used in the preprocessor or source filter/transformer to prevent the message from
* being sent to specific destinations.
+ *
+ * This is a set of the metaDataIds of the destinations the message will be sent to. Methods that
+ * look up or remove destinations accept either a metaDataId or a destination connector name.
+ * Destinations cannot be added to the set.
*/
-public class DestinationSet {
+public class DestinationSet extends AbstractSet {
- private Map destinationIdMap;
- private Set metaDataIds;
+ private final Map destinationIdMap;
+ private final Set metaDataIds;
/**
* DestinationSet instances should NOT be constructed manually. The instance "destinationSet"
* provided in the scope should be used.
- *
+ *
* @param connectorMessage
* The delegate ImmutableConnectorMessage object.
*/
+ @SuppressWarnings("unchecked")
public DestinationSet(ImmutableConnectorMessage connectorMessage) {
+ Map destinationIdMap = null;
+ Set metaDataIds = null;
+
try {
- if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) {
- this.destinationIdMap = connectorMessage.getDestinationIdMap();
- this.metaDataIds = (Set) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY);
+ if (connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY) instanceof Set> set) {
+ destinationIdMap = connectorMessage.getDestinationIdMap();
+ metaDataIds = (Set) set;
}
} catch (Exception e) {
}
+
+ this.destinationIdMap = destinationIdMap != null ? destinationIdMap : Collections.emptyMap();
+ this.metaDataIds = metaDataIds != null ? metaDataIds : Collections.emptySet();
}
/**
* Stop a destination from being processed for this message.
- *
+ *
* @param metaDataIdOrConnectorName
* An integer representing the metaDataId of a destination connector, or the actual
* destination connector name.
* @return A boolean indicating whether at least one destination connector was actually removed
* from processing for this message.
*/
+ @Override
public boolean remove(Object metaDataIdOrConnectorName) {
- if (metaDataIds != null) {
- Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName);
+ Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName);
- if (metaDataId != null) {
- return metaDataIds.remove(metaDataId);
- }
+ if (metaDataId != null) {
+ return metaDataIds.remove(metaDataId);
}
return false;
@@ -69,7 +77,8 @@ public boolean remove(Object metaDataIdOrConnectorName) {
/**
* Stop a destination from being processed for this message.
- *
+ * This is the same as {@link #removeAll(Collection)}.
+ *
* @param metaDataIdOrConnectorNames
* A collection of integers representing the metaDataId of a destination connectors,
* or the actual destination connector names. JavaScript arrays can be used.
@@ -77,20 +86,12 @@ public boolean remove(Object metaDataIdOrConnectorName) {
* from processing for this message.
*/
public boolean remove(Collection