Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
378 changes: 219 additions & 159 deletions server/src/com/mirth/connect/server/userutil/DestinationSet.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

/*
* Copyright (c) Mirth Corporation. All rights reserved.
*
Expand All @@ -7,162 +8,221 @@
* been included with this distribution in the LICENSE.txt file.
*/

package com.mirth.connect.server.userutil;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.mozilla.javascript.Context;

import com.mirth.connect.donkey.server.Constants;
import com.mirth.connect.userutil.ImmutableConnectorMessage;

/**
* Utility class used in the preprocessor or source filter/transformer to prevent the message from
* being sent to specific destinations.
*/
public class DestinationSet {

private Map<String, Integer> destinationIdMap;
private Set<Integer> metaDataIds;

/**
* DestinationSet instances should NOT be constructed manually. The instance "destinationSet"
* provided in the scope should be used.
*
* @param connectorMessage
* The delegate ImmutableConnectorMessage object.
*/
public DestinationSet(ImmutableConnectorMessage connectorMessage) {
try {
if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) {
this.destinationIdMap = connectorMessage.getDestinationIdMap();
this.metaDataIds = (Set<Integer>) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY);
}
} catch (Exception e) {
}
}

/**
* 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.
*/
public boolean remove(Object metaDataIdOrConnectorName) {
if (metaDataIds != null) {
Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName);

if (metaDataId != null) {
return metaDataIds.remove(metaDataId);
}
}

return false;
}

/**
* Stop a destination from being processed for this message.
*
* @param metaDataIdOrConnectorNames
* A collection of integers representing the metaDataId of a destination connectors,
* or the actual destination connector names. JavaScript arrays can be used.
* @return A boolean indicating whether at least one destination connector was actually removed
* from processing for this message.
*/
public boolean remove(Collection<Object> metaDataIdOrConnectorNames) {
boolean removed = false;

for (Object metaDataIdOrConnectorName : metaDataIdOrConnectorNames) {
if (remove(metaDataIdOrConnectorName)) {
removed = true;
}
}

return removed;
}

/**
* Stop all except one 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.
*/
public boolean removeAllExcept(Object metaDataIdOrConnectorName) {
if (metaDataIds != null) {
Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName);

if (metaDataId != null) {
return metaDataIds.retainAll(Collections.singleton(metaDataId));
}
}

return false;
}

/**
* Stop all except one destination from being processed for this message.
*
* @param metaDataIdOrConnectorNames
* A collection of integers representing the metaDataId of a destination connectors,
* or the actual destination connector names. JavaScript arrays can be used.
* @return A boolean indicating whether at least one destination connector was actually removed
* from processing for this message.
*/
public boolean removeAllExcept(Collection<Object> metaDataIdOrConnectorNames) {
if (metaDataIds != null) {
Set<Integer> set = new HashSet<Integer>();

for (Object metaDataIdOrConnectorName : metaDataIdOrConnectorNames) {
Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName);

if (metaDataId != null) {
set.add(metaDataId);
}
}

return metaDataIds.retainAll(set);
}

return false;
}

/**
* Stop all destinations from being processed for this message. This does NOT mark the source
* message as FILTERED.
*
* @return A boolean indicating whether at least one destination connector was actually removed
* from processing for this message.
*/
public boolean removeAll() {
if (metaDataIds != null && metaDataIds.size() > 0) {
metaDataIds.clear();
return true;
}

return false;
}

private Integer convertToMetaDataId(Object metaDataIdOrConnectorName) {
if (metaDataIdOrConnectorName != null) {
if (metaDataIdOrConnectorName instanceof Number) {
return ((Number) metaDataIdOrConnectorName).intValue();
} else if (metaDataIdOrConnectorName.getClass().getName().equals("org.mozilla.javascript.NativeNumber")) {
return (Integer) Context.jsToJava(metaDataIdOrConnectorName, int.class);
} else if (destinationIdMap != null) {
return destinationIdMap.get(metaDataIdOrConnectorName.toString());
}
}

return null;
}
}
package com.mirth.connect.server.userutil;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.mozilla.javascript.Context;

import com.mirth.connect.donkey.server.Constants;
import com.mirth.connect.userutil.ImmutableConnectorMessage;

/**
* Utility class used in the preprocessor or source filter/transformer to prevent the message from
* being sent to specific destinations.
*/
public class DestinationSet implements Set<Integer> {

private Map<String, Integer> destinationIdMap = Collections.emptyMap();
private Set<Integer> metaDataIds;

/**
* DestinationSet instances should NOT be constructed manually. The instance "destinationSet"
* provided in the scope should be used.
*
* @param connectorMessage
* The delegate ImmutableConnectorMessage object.
*/
public DestinationSet(ImmutableConnectorMessage connectorMessage) {
try {
if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) {
this.destinationIdMap = connectorMessage.getDestinationIdMap();
this.metaDataIds = (Set<Integer>) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY);
}
} catch (Exception e) {
metaDataIds = new HashSet<>();
Comment on lines +44 to +50
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor leaves metaDataIds null when the source map does not contain DESTINATION_SET_KEY. This will cause NullPointerException in all Set interface methods (size(), isEmpty(), iterator(), etc.) when the key is not present.

The catch block on line 50 only executes if an exception is thrown, not when the if-condition on line 45 is false. You need to initialize metaDataIds to an empty HashSet in all cases where it would otherwise remain null.

Suggested fix:

public DestinationSet(ImmutableConnectorMessage connectorMessage) {
    metaDataIds = new HashSet<>();  // Initialize first
    try {
        if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) {
            this.destinationIdMap = connectorMessage.getDestinationIdMap();
            this.metaDataIds = (Set<Integer>) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY);
        }
    } catch (Exception e) {
        // metaDataIds already initialized above
    }
}
Suggested change
try {
if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) {
this.destinationIdMap = connectorMessage.getDestinationIdMap();
this.metaDataIds = (Set<Integer>) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY);
}
} catch (Exception e) {
metaDataIds = new HashSet<>();
metaDataIds = new HashSet<>();
try {
if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) {
this.destinationIdMap = connectorMessage.getDestinationIdMap();
this.metaDataIds = (Set<Integer>) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY);
}
} catch (Exception e) {
// metaDataIds already initialized above

Copilot uses AI. Check for mistakes.
}
}

/**
* 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.
*/
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method overrides Set.remove; it is advisable to add an Override annotation.

Suggested change
*/
*/
@Override

Copilot uses AI. Check for mistakes.
public boolean remove(Object metaDataIdOrConnectorName) {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method DestinationSet.remove(..) could be confused with overloaded method remove, since dispatch depends on static types.

Copilot uses AI. Check for mistakes.
return remove(Collections.singleton(metaDataIdOrConnectorName));
}

/**
* Stop a destination from being processed for this message.
*
* @param metaDataIdOrConnectorNames
* A collection of integers representing the metaDataId of a destination connectors,
* or the actual destination connector names. JavaScript arrays can be used.
* @return A boolean indicating whether at least one destination connector was actually removed
* from processing for this message.
*/
public boolean remove(Collection<Object> metaDataIdOrConnectorNames) {
if(metaDataIdOrConnectorNames == null) { return false; }
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing after if is inconsistent with Java conventions. Should be if (metaDataIdOrConnectorNames == null) with a space after if and before the opening brace.

This pattern appears throughout the file (lines 77, 111, 190, 201). Consistent formatting improves code readability.

Suggested change
if(metaDataIdOrConnectorNames == null) { return false; }
if (metaDataIdOrConnectorNames == null) { return false; }

Copilot uses AI. Check for mistakes.

return metaDataIdOrConnectorNames.stream()
.map(this::convertToMetaDataId)
.filter(Optional::isPresent)
.map(Optional::get)
.map(metaDataIds::remove)
.filter(Boolean::booleanValue)
.count() > 0;
}

/**
* Stop all except one 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.
*/
public boolean removeAllExcept(Object metaDataIdOrConnectorName) {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method DestinationSet.removeAllExcept(..) could be confused with overloaded method removeAllExcept, since dispatch depends on static types.

Copilot uses AI. Check for mistakes.
return removeAllExcept(Collections.singleton(metaDataIdOrConnectorName));
}

/**
* Stop all except one destination from being processed for this message.
*
* @param metaDataIdOrConnectorNames
* A collection of integers representing the metaDataId of a destination connectors,
* or the actual destination connector names. JavaScript arrays can be used.
* @return A boolean indicating whether at least one destination connector was actually removed
* from processing for this message.
*/
public boolean removeAllExcept(Collection<Object> metaDataIdOrConnectorNames) {
if(metaDataIdOrConnectorNames == null) { return false; }
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing after if is inconsistent with Java conventions. Should be if (metaDataIdOrConnectorNames == null) with a space after if and before the opening brace.

Suggested change
if(metaDataIdOrConnectorNames == null) { return false; }
if (metaDataIdOrConnectorNames == null) { return false; }

Copilot uses AI. Check for mistakes.

Set<Integer> set = metaDataIdOrConnectorNames.stream()
.map(this::convertToMetaDataId)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());

return metaDataIds.retainAll(set);
}

/**
* Stop all destinations from being processed for this message. This does NOT mark the source
* message as FILTERED.
*
* @return A boolean indicating whether at least one destination connector was actually removed
* from processing for this message.
*/
public boolean removeAll() {
int origSize = size();
clear();
return origSize > 0;
}

private Optional<Integer> convertToMetaDataId(Object metaDataIdOrConnectorName) {
Integer result = null;

if (metaDataIdOrConnectorName != null) {
if (metaDataIdOrConnectorName instanceof Number) {
result = Integer.valueOf(((Number) metaDataIdOrConnectorName).intValue());
} else if (metaDataIdOrConnectorName.getClass().getName().equals("org.mozilla.javascript.NativeNumber")) {
result = (Integer) Context.jsToJava(metaDataIdOrConnectorName, int.class);
} else {
result = destinationIdMap.get(metaDataIdOrConnectorName.toString());
}
}

return Optional.ofNullable(result);
}

@Override
public int size() {
return metaDataIds.size();
}

@Override
public boolean isEmpty() {
return metaDataIds.isEmpty();
}

@Override
public boolean contains(Object metaDataIdOrConnectorName) {
Optional<Integer> m = convertToMetaDataId(metaDataIdOrConnectorName);

return m.isPresent() && metaDataIds.contains(m.get());
}

@Override
public Iterator<Integer> iterator() {
return Collections.unmodifiableSet(metaDataIds).iterator();
}

@Override
public Object[] toArray() {
return metaDataIds.toArray();
}

@Override
public <T> T[] toArray(T[] a) {
return metaDataIds.toArray(a);
}

@Override
public boolean add(Integer metaDataId) {
return metaDataId != null && metaDataIds.add(metaDataId);
}

@Override
public boolean containsAll(Collection<?> metaDataIdOrConnectorNames) {
if(metaDataIdOrConnectorNames == null) { return false; }
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing after if is inconsistent with Java conventions. Should be if (metaDataIdOrConnectorNames == null) with a space after if and before the opening brace.

Suggested change
if(metaDataIdOrConnectorNames == null) { return false; }
if (metaDataIdOrConnectorNames == null) { return false; }

Copilot uses AI. Check for mistakes.

return metaDataIdOrConnectorNames.stream()
.map(this::contains)
.allMatch(Boolean::booleanValue);
}

@Override
public boolean addAll(Collection<? extends Integer> metaDataIdOrConnectorNames) {
boolean changed = false;

if(metaDataIdOrConnectorNames != null) {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing after if is inconsistent with Java conventions. Should be if (metaDataIdOrConnectorNames != null) with a space after if and before the opening brace.

Copilot uses AI. Check for mistakes.
for(Object item : metaDataIdOrConnectorNames) {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing after for is inconsistent with Java conventions. Should be for (Object item : metaDataIdOrConnectorNames) with a space after for.

Suggested change
for(Object item : metaDataIdOrConnectorNames) {
for (Object item : metaDataIdOrConnectorNames) {

Copilot uses AI. Check for mistakes.
Optional<Integer> m = convertToMetaDataId(item);

if(m.isPresent() && metaDataIds.add(m.get())) {
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing after if is inconsistent with Java conventions. Should be if (m.isPresent() && metaDataIds.add(m.get())) with a space after if.

Suggested change
if(m.isPresent() && metaDataIds.add(m.get())) {
if (m.isPresent() && metaDataIds.add(m.get())) {

Copilot uses AI. Check for mistakes.
changed = true;
}
}
}

return changed;
}

@Override
public boolean retainAll(Collection<?> metaDataIdOrConnectorNames) {
return removeAllExcept((Collection<Object>)metaDataIdOrConnectorNames);
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchecked cast from Collection<?> to Collection<Object>. While this works, it will generate a compiler warning. Consider adding @SuppressWarnings("unchecked") to this method or refactoring to avoid the cast.

Copilot uses AI. Check for mistakes.
}

@Override
public boolean removeAll(Collection<?> metaDataIdOrConnectorNames) {
return remove((Collection<Object>)metaDataIdOrConnectorNames);
Copy link

Copilot AI Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchecked cast from Collection<?> to Collection<Object>. While this works, it will generate a compiler warning. Consider adding @SuppressWarnings("unchecked") to this method or refactoring to avoid the cast.

Copilot uses AI. Check for mistakes.
}

@Override
public void clear() {
metaDataIds.clear();
}
}
Loading