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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@

import java.awt.Component;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Map;
import java.util.prefs.Preferences;

Expand All @@ -29,6 +31,8 @@
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

import java.text.SimpleDateFormat;

import org.apache.commons.lang3.StringUtils;
import org.jdesktop.swingx.decorator.HighlighterFactory;

Expand Down Expand Up @@ -510,6 +514,81 @@ public Boolean hasAdvancedCriteria() {
return hasAdvancedCriteria;
}

public void applyFilter(MessageFilter messageFilter) {
stopEditing();
resetSelections();

if (messageFilter == null) {
return;
}

ItemSelectionTableModel<Integer, String> connectorModel = ((ItemSelectionTableModel<Integer, String>) connectorTable.getModel());
DefaultTableModel contentSearchModel = ((DefaultTableModel) contentSearchTable.getModel());
DefaultTableModel metaDataSearchModel = ((DefaultTableModel) metaDataSearchTable.getModel());

List<Integer> includedMetaDataIds = messageFilter.getIncludedMetaDataIds();
List<Integer> excludedMetaDataIds = messageFilter.getExcludedMetaDataIds();

if (includedMetaDataIds != null) {
connectorModel.unselectAllKeys();
for (Integer metaDataId : includedMetaDataIds) {
connectorModel.selectKey(metaDataId);
}
} else if (excludedMetaDataIds != null) {
connectorModel.selectAllKeys();
for (int row = 0; row < connectorModel.getRowCount(); row++) {
Integer metaDataId = (Integer) connectorModel.getValueAt(row, ItemSelectionTableModel.KEY_COLUMN);
if (excludedMetaDataIds.contains(metaDataId)) {
connectorModel.setValueAt(Boolean.FALSE, row, ItemSelectionTableModel.CHECKBOX_COLUMN);
}
}
}

messageIdLowerField.setText(Objects.toString(messageFilter.getMinMessageId(), ""));
messageIdUpperField.setText(Objects.toString(messageFilter.getMaxMessageId(), ""));
originalIdLowerField.setText(Objects.toString(messageFilter.getOriginalIdLower(), ""));
originalIdUpperField.setText(Objects.toString(messageFilter.getOriginalIdUpper(), ""));
importIdLowerField.setText(Objects.toString(messageFilter.getImportIdLower(), ""));
importIdUpperField.setText(Objects.toString(messageFilter.getImportIdUpper(), ""));
serverIdField.setText(StringUtils.defaultString(messageFilter.getServerId()));
sendAttemptsLower.setValue((messageFilter.getSendAttemptsLower() == null) ? 0 : messageFilter.getSendAttemptsLower());
sendAttemptsUpper.setValue(Objects.toString(messageFilter.getSendAttemptsUpper(), ""));
attachmentCheckBox.setSelected(Boolean.TRUE.equals(messageFilter.getAttachment()));
errorCheckBox.setSelected(Boolean.TRUE.equals(messageFilter.getError()));

if (messageFilter.getContentSearch() != null) {
for (ContentSearchElement contentSearchElement : messageFilter.getContentSearch()) {
for (String search : contentSearchElement.getSearches()) {
contentSearchModel.addRow(new Object[] { ContentType.fromCode(contentSearchElement.getContentCode()), search });
}
}
}

if (messageFilter.getMetaDataSearch() != null) {
for (MetaDataSearchElement metaDataSearchElement : messageFilter.getMetaDataSearch()) {
if (cachedMetaDataColumns.containsKey(metaDataSearchElement.getColumnName())) {
metaDataSearchModel.addRow(new Object[] {
metaDataSearchElement.getColumnName(),
MetaDataSearchOperator.fromString(metaDataSearchElement.getOperator()),
formatMetaDataValue(metaDataSearchElement.getValue()),
Boolean.TRUE.equals(metaDataSearchElement.getIgnoreCase()) });
}
}
}
}

private String formatMetaDataValue(Object value) {
if (value == null) {
return "";
}

if (value instanceof Calendar calendar) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
}

return String.valueOf(value);
}

private void stopEditing() {
// if the user had typed in a value in the content search table, close the cell editor so that any value that was entered will be included in the search
TableCellEditor cellEditor = contentSearchTable.getCellEditor();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2025 Mitch Gaffigan

package com.mirth.connect.client.ui.browsers.message;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.mirth.connect.model.converters.ObjectXMLSerializer;
import com.mirth.connect.model.filters.MessageFilter;

class MessageBrowserRecentFilterStore {
private static final int MAX_RECENT_FILTERS = 10;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Max should be configurable in mirth.properties

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can we take a feature issue on this? It seems useful even with the MRU size hard coded.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sure. How about a broad feature issue that finds hardcoded constants or limits in the Client and makes them configurable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That sounds like a big to me, but maybe with sub-issues for each constant, sure. The issues I had in mind were:

  1. Allow administrators to default and lock user preferences via mirth.properties
  2. Make MAX_RECENT_FILTERS a user preference

private static final String RECENT_FILTERS_PREFERENCE_PREFIX = "messageBrowserRecentFilters.";
// TODO: Replace with a cross-session time-limited encrypted store
// (In-memory assuming text searches represent PHI)
private static final Map<String, String> RECENT_FILTERS_BY_CHANNEL = new ConcurrentHashMap<>();
private Logger logger = LogManager.getLogger(this.getClass());

private final String prefKey;

public MessageBrowserRecentFilterStore(String channelId) {
this.prefKey = RECENT_FILTERS_PREFERENCE_PREFIX + channelId;
}

public List<MessageFilter> getRecentFilters() {
try {
String serialized = RECENT_FILTERS_BY_CHANNEL.getOrDefault(prefKey, "");
if (serialized.isBlank()) return List.of();

var result = ObjectXMLSerializer.getInstance().deserializeList(serialized, MessageFilter.class);
if (result == null) return List.of();

return result;
} catch (Exception e) {
// Fail quietly if the stored filters cannot be deserialized for any reason.
logger.warn("Failed to deserialize recent message filters for key {}.", prefKey, e);
return List.of();
Comment on lines +31 to +43
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

getRecentFilters() deserializes into a raw List.class and then unchecked-casts to List<MessageFilter>. If the stored preference value is corrupted or from a different version, elements may not actually be MessageFilter instances, leading to runtime failures later (e.g., when iterating in restoreRecentFilter()). Consider validating the deserialized value/type (ensure it’s a List and that each element is a MessageFilter) before returning it, otherwise return an empty list / clear the preference key.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the first version. That's future me's problem.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

new ArrayList<>()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@kpalang, not sure what you are referring to by that.

}
}

public void addRecentFilter(MessageFilter filter) {
if (filter == null) {
throw new IllegalArgumentException("Filter cannot be null");
}

var filters = new ArrayList<>(getRecentFilters());

// Remove then re-add to avoid duplicates
filters.remove(filter);
filters.add(0, filter);

// Trim to the maximum number of recent filters
if (filters.size() > MAX_RECENT_FILTERS) {
filters.subList(MAX_RECENT_FILTERS, filters.size()).clear();
}
Comment thread
jonbartels marked this conversation as resolved.

try {
RECENT_FILTERS_BY_CHANNEL.put(prefKey, ObjectXMLSerializer.getInstance().serialize(filters));
} catch (Exception e) {
logger.warn("Failed to serialize recent message filters for key {}.", prefKey, e);
}
}
}
Loading
Loading