Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)";
}


Expand All @@ -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<ProcessorModule> 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<HashMap<String,String>> messages = db.listMessages();
HashMap<String, String> 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<HashMap<String,String>> messages = db.listMessages(from, to);

CommandResult cmdRes = new CommandResult(CommandResult.TYPE_OK);

Expand All @@ -58,4 +76,28 @@ public CommandResult execute(MessageFactory messageFactory, Object[] params) thr
return cmdRes;
}
}

private HashMap<String, String> parseParams(Object[] params) {
HashMap<String, String> filters = new HashMap<String, String>();
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -181,27 +182,51 @@ protected synchronized void persist(Message msg, Map<String, String> map) {
}
}

public ArrayList<HashMap<String, String>> 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<HashMap<String, String>> listMessages(Timestamp from, Timestamp to) {

ArrayList<HashMap<String, String>> rows = new ArrayList<HashMap<String, String>>();

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<String, String> row = new HashMap<String, String>();
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<String, String> row = new HashMap<String, String>();
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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ProcessorModule> 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<HashMap<String, String>> rows = db.listMessages(
Timestamp.valueOf("2026-06-01 00:00:00"),
Timestamp.valueOf("2026-06-30 23:59:59"));

List<String> ids = new ArrayList<String>();
for (HashMap<String, String> 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<HashMap<String, String>> 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<HashMap<String, String>> 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<HashMap<String, String>> rows = db.listMessages(
Timestamp.valueOf("2026-01-01 00:00:00"), null);

List<String> ids = new ArrayList<String>();
for (HashMap<String, String> 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"));
}
}
Loading