From 78a4fcb4afa0d6aeac7e44488dcace8f59fa93a7 Mon Sep 17 00:00:00 2001 From: Soham Bhambare Date: Tue, 7 Jul 2026 11:22:08 -0400 Subject: [PATCH 1/3] Enhance ListMessagesCommand with date range filtering Updated ListMessagesCommand to support date filtering. --- .../app/message/ListMessagesCommand.java | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/Server/src/main/java/org/openas2/app/message/ListMessagesCommand.java b/Server/src/main/java/org/openas2/app/message/ListMessagesCommand.java index 4a7631c1d..df6bd0ee8 100644 --- a/Server/src/main/java/org/openas2/app/message/ListMessagesCommand.java +++ b/Server/src/main/java/org/openas2/app/message/ListMessagesCommand.java @@ -8,7 +8,10 @@ import org.openas2.processor.ProcessorModule; import org.openas2.processor.msgtracking.DbTrackingModule; import org.openas2.processor.msgtracking.TrackingModule; +import org.openas2.util.DateUtil; +import java.sql.Timestamp; +import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -20,8 +23,10 @@ */ public class ListMessagesCommand extends AliasedMessagesCommand { + private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public String getDefaultDescription() { - return "List all messages"; + return "List messages, optionally bounded by from=/to= (yyyy-MM-dd)"; } @@ -30,22 +35,35 @@ public String getDefaultName() { } public String getDefaultUsage() { - return "list"; + return "list [from=yyyy-MM-dd] [to=yyyy-MM-dd]"; } public CommandResult execute(MessageFactory messageFactory, Object[] params) throws OpenAS2Exception { synchronized (messageFactory) { - + List mpl = getSession().getProcessor().getModulesSupportingAction(TrackingModule.DO_TRACK_MSG); if (mpl == null || mpl.isEmpty()) { CommandResult cmdRes = new CommandResult(CommandResult.TYPE_ERROR); cmdRes.getResults().add("No DB tracking module available."); + return cmdRes; } // Assume we only load one DB tracking module - not sure it makes sense if more than 1 was loaded DbTrackingModule db = (DbTrackingModule) mpl.get(0); - ArrayList> messages = db.listMessages(); + HashMap filters = parseParams(params); + + Timestamp from; + Timestamp to; + try { + from = parseBound(filters.get("from"), " 00:00:00"); + to = parseBound(filters.get("to"), " 23:59:59"); + } catch (ParseException e) { + return new CommandResult(CommandResult.TYPE_ERROR, + "Invalid date - 'from'/'to' must be in yyyy-MM-dd format: " + e.getMessage()); + } + + ArrayList> messages = db.listMessages(from, to); CommandResult cmdRes = new CommandResult(CommandResult.TYPE_OK); @@ -58,4 +76,28 @@ public CommandResult execute(MessageFactory messageFactory, Object[] params) thr return cmdRes; } } + + private HashMap parseParams(Object[] params) { + HashMap filters = new HashMap(); + for (Object param : params) { + String s = param.toString(); + int equalsPos = s.indexOf('='); + if (equalsPos > 0) { + filters.put(s.substring(0, equalsPos), s.substring(equalsPos + 1)); + } + } + return filters; + } + + /** + * @param value - the raw "yyyy-MM-dd" parameter value, or null if the caller didn't supply one + * @param timeOfDay - " 00:00:00" or " 23:59:59" to make the bound inclusive of the whole day + * @return the parsed bound, or null if value was null (i.e. unbounded on that side) + */ + private Timestamp parseBound(String value, String timeOfDay) throws ParseException { + if (value == null) { + return null; + } + return new Timestamp(DateUtil.parseDate(DATE_FORMAT, value + timeOfDay).getTime()); + } } From 5204875b17684dfa2fa4986912c3ffe9e643bc94 Mon Sep 17 00:00:00 2001 From: Soham Bhambare Date: Tue, 7 Jul 2026 11:22:50 -0400 Subject: [PATCH 2/3] Enhance listMessages with date filtering Refactored listMessages method to accept optional date bounds for filtering results. --- .../msgtracking/DbTrackingModule.java | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/Server/src/main/java/org/openas2/processor/msgtracking/DbTrackingModule.java b/Server/src/main/java/org/openas2/processor/msgtracking/DbTrackingModule.java index d134557db..34735c821 100644 --- a/Server/src/main/java/org/openas2/processor/msgtracking/DbTrackingModule.java +++ b/Server/src/main/java/org/openas2/processor/msgtracking/DbTrackingModule.java @@ -18,6 +18,7 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; +import java.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; import java.util.HashMap; @@ -181,27 +182,51 @@ protected synchronized void persist(Message msg, Map map) { } } - public ArrayList> listMessages() { + /** + * List messages, optionally bounded by CREATE_DT. Either bound may be null, in which case + * that side is left open (a null "from" and null "to" together mean no date filter at all). + * + * @param from - lower bound (inclusive) on CREATE_DT, or null for no lower bound + * @param to - upper bound (inclusive) on CREATE_DT, or null for no upper bound + */ + public ArrayList> listMessages(Timestamp from, Timestamp to) { ArrayList> rows = new ArrayList>(); - try (Connection conn = dbHandler.getConnection()) { - Statement s = conn.createStatement(); - ResultSet rs = s.executeQuery("SELECT " + FIELDS.MSG_ID - + ",CREATE_DT,SENDER_ID,RECEIVER_ID,MSG_ID,FILE_NAME,ENCRYPTION_ALGORITHM,SIGNATURE_ALGORITHM,MDN_MODE,STATE FROM " - + tableName); - ResultSetMetaData meta = rs.getMetaData(); - while (rs.next()) { - HashMap row = new HashMap(); - for (int i = 1; i <= meta.getColumnCount(); i++) { - String key = meta.getColumnName(i); - String value = rs.getString(key); - row.put(key, value); + StringBuilder sql = new StringBuilder("SELECT " + FIELDS.MSG_ID + + ",CREATE_DT,SENDER_ID,RECEIVER_ID,MSG_ID,FILE_NAME,ENCRYPTION_ALGORITHM,SIGNATURE_ALGORITHM,MDN_MODE,STATE FROM " + + tableName); + if (from != null) { + sql.append(sql.indexOf(" WHERE ") < 0 ? " WHERE " : " AND ").append("CREATE_DT >= ?"); + } + if (to != null) { + sql.append(sql.indexOf(" WHERE ") < 0 ? " WHERE " : " AND ").append("CREATE_DT <= ?"); + } + sql.append(" ORDER BY CREATE_DT DESC"); + + try (Connection conn = dbHandler.getConnection(); + PreparedStatement s = conn.prepareStatement(sql.toString())) { + int paramIndex = 1; + if (from != null) { + s.setTimestamp(paramIndex++, from); + } + if (to != null) { + s.setTimestamp(paramIndex++, to); + } + try (ResultSet rs = s.executeQuery()) { + ResultSetMetaData meta = rs.getMetaData(); + while (rs.next()) { + HashMap row = new HashMap(); + for (int i = 1; i <= meta.getColumnCount(); i++) { + String key = meta.getColumnName(i); + String value = rs.getString(key); + row.put(key, value); + } + rows.add(row); } - rows.add(row); } } catch (Exception e) { - e.printStackTrace(); + logger.error("Failed to list messages between " + from + " and " + to, e); } return rows; } From 5bcf5144d19eb5b5bce056da79e84dceae1eeb38 Mon Sep 17 00:00:00 2001 From: Soham Bhambare Date: Tue, 7 Jul 2026 11:29:13 -0400 Subject: [PATCH 3/3] Add tests for DbTrackingModule.listMessages method This test class verifies the behavior of the DbTrackingModule's listMessages method to ensure it correctly filters messages based on the specified date range. It includes tests for various scenarios, such as returning rows within the range, no rows when the range matches nothing, and handling open-ended date ranges. --- .../DbTrackingModuleListMessagesTest.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Server/src/test/java/org/openas2/processor/msgtracking/DbTrackingModuleListMessagesTest.java diff --git a/Server/src/test/java/org/openas2/processor/msgtracking/DbTrackingModuleListMessagesTest.java b/Server/src/test/java/org/openas2/processor/msgtracking/DbTrackingModuleListMessagesTest.java new file mode 100644 index 000000000..ab5bff18f --- /dev/null +++ b/Server/src/test/java/org/openas2/processor/msgtracking/DbTrackingModuleListMessagesTest.java @@ -0,0 +1,119 @@ +package org.openas2.processor.msgtracking; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.openas2.app.BaseServerSetup; +import org.openas2.processor.ProcessorModule; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Verifies that DbTrackingModule.listMessages(from, to) only returns rows whose CREATE_DT + * falls within the requested bound, rather than the whole table (the OOM fix). + */ +@TestInstance(Lifecycle.PER_CLASS) +public class DbTrackingModuleListMessagesTest extends BaseServerSetup { + + private DbTrackingModule db; + + @BeforeAll + public void setUp() throws Exception { + super.createFileSystemResources(this.getClass().getName()); + super.setStartActiveModules(true); + super.setup(); + + List mpl = session.getProcessor().getModulesSupportingAction(TrackingModule.DO_TRACK_MSG); + db = (DbTrackingModule) mpl.get(0); + + try (Connection conn = db.dbHandler.getConnection()) { + Statement s = conn.createStatement(); + // Apply the same schema used in production (Server/src/config/db_ddl.sql) so this + // test exercises listMessages() against the real msg_metadata table shape. + String ddl = new String(Files.readAllBytes(Paths.get("src", "config", "db_ddl.sql"))); + for (String statement : ddl.split(";")) { + if (!statement.trim().isEmpty()) { + s.execute(statement); + } + } + s.executeUpdate(insertSql("msg-before-range", "2020-01-01 10:00:00")); + s.executeUpdate(insertSql("msg-in-range-1", "2026-06-15 09:00:00")); + s.executeUpdate(insertSql("msg-in-range-2", "2026-06-20 23:59:00")); + s.executeUpdate(insertSql("msg-after-range", "2030-01-01 00:00:00")); + } + } + + private String insertSql(String msgId, String createDt) { + return "INSERT INTO msg_metadata (msg_id, sender_id, receiver_id, state, create_dt) VALUES ('" + + msgId + "', 'SENDER', 'RECEIVER', 'MSG_STATUS_MSG_SENT', '" + createDt + "')"; + } + + @AfterAll + public void tearDown() throws Exception { + super.tearDown(); + } + + @Test + public void onlyReturnsRowsWithinTheRequestedDateRange() { + ArrayList> rows = db.listMessages( + Timestamp.valueOf("2026-06-01 00:00:00"), + Timestamp.valueOf("2026-06-30 23:59:59")); + + List ids = new ArrayList(); + for (HashMap row : rows) { + ids.add(row.get("MSG_ID")); + } + + assertEquals(2, rows.size(), "Only the two in-range rows should be returned, not the whole table"); + assertTrue(ids.contains("msg-in-range-1")); + assertTrue(ids.contains("msg-in-range-2")); + assertFalse(ids.contains("msg-before-range")); + assertFalse(ids.contains("msg-after-range")); + } + + @Test + public void returnsNoRowsWhenRangeMatchesNothing() { + ArrayList> rows = db.listMessages( + Timestamp.valueOf("2024-01-01 00:00:00"), + Timestamp.valueOf("2024-01-31 23:59:59")); + + assertTrue(rows.isEmpty()); + } + + @Test + public void returnsEveryRowWhenNoBoundsAreGiven() { + ArrayList> rows = db.listMessages(null, null); + + assertEquals(4, rows.size(), "With no from/to, every row in the table should come back"); + } + + @Test + public void aFromWithNoToIsOpenEnded() { + ArrayList> rows = db.listMessages( + Timestamp.valueOf("2026-01-01 00:00:00"), null); + + List ids = new ArrayList(); + for (HashMap row : rows) { + ids.add(row.get("MSG_ID")); + } + + assertEquals(3, rows.size()); + assertTrue(ids.contains("msg-in-range-1")); + assertTrue(ids.contains("msg-in-range-2")); + assertTrue(ids.contains("msg-after-range")); + assertFalse(ids.contains("msg-before-range")); + } +}