diff --git a/Server/pom.xml b/Server/pom.xml
index 86febd97..dcce6ac8 100644
--- a/Server/pom.xml
+++ b/Server/pom.xml
@@ -43,13 +43,13 @@
org.apache.maven.plugins
maven-dependency-plugin
-
-
-
- properties
-
-
-
+
+
+
+ properties
+
+
+
org.apache.maven.plugins
@@ -79,10 +79,10 @@
-
-
-
-
+
+
+
+
@@ -99,16 +99,16 @@
package
-
-
-
-
-
-
+
+
+
+
+
@@ -120,8 +120,8 @@
+ file="${project.build.directory}/${project.build.finalName}.jar"
+ todir="${package.assembly.dir}/lib" verbose="true"/>
@@ -138,14 +138,14 @@
-
+
-
-
+ destfile="${project.basedir}/dist/${project.dist.package.name}"
+ update="true">
+
+
@@ -160,18 +160,18 @@
org.apache.maven.plugins
maven-jar-plugin
-
-
-
-
-
- true
-
-
- org.openas2.app.OpenAS2Server
-
-
-
+
+
+
+
+
+ true
+
+
+ org.openas2.app.OpenAS2Server
+
+
+
org.apache.maven.plugins
@@ -188,6 +188,15 @@
+
+ software.amazon.awssdk
+ dynamodb
+
+
+ software.amazon.awssdk
+ dynamodb-enhanced
+
+
org.dom4j
dom4j
@@ -212,26 +221,26 @@
commons-cli
commons-cli
-
- org.slf4j
- slf4j-api
-
-
- ch.qos.logback
- logback-classic
-
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
com.sun.mail
jakarta.mail
-
+
com.h2database
h2
-
- com.zaxxer
- HikariCP
-
+
+ com.zaxxer
+ HikariCP
+
org.bouncycastle
bcpg-jdk18on
@@ -258,31 +267,31 @@
jackson-databind
jar
-
- com.fasterxml.jackson.module
- jackson-module-jaxb-annotations
-
+
+ com.fasterxml.jackson.module
+ jackson-module-jaxb-annotations
+
org.glassfish.jersey.media
jersey-media-json-jackson
jar
-
- org.glassfish.jersey.inject
- jersey-hk2
+
+ org.glassfish.jersey.inject
+ jersey-hk2
jakarta.xml.bind
jakarta.xml.bind-api
-
- jakarta.ws.rs
- jakarta.ws.rs-api
-
-
- jakarta.annotation
- jakarta.annotation-api
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ jakarta.annotation
+ jakarta.annotation-api
org.junit.jupiter
@@ -294,15 +303,15 @@
hamcrest
test
-
- org.mockito
- mockito-junit-jupiter
- test
-
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
org.mockito
mockito-core
test
-
+
diff --git a/Server/src/config/as2_certs.p12 b/Server/src/config/as2_certs.p12
index 47d0232a..42e850f6 100644
Binary files a/Server/src/config/as2_certs.p12 and b/Server/src/config/as2_certs.p12 differ
diff --git a/Server/src/config/config.xml b/Server/src/config/config.xml
index a242e8d6..42395c3d 100644
--- a/Server/src/config/config.xml
+++ b/Server/src/config/config.xml
@@ -27,6 +27,7 @@
module.AS2SenderModule.readtimeout="60000"
module.MDNSenderModule.enabled="true"
module.DbTrackingModule.enabled="true"
+ module.DynamoDbTrackingModule.enabled="false"
module.MDNFileModule.enabled="true"
module.MDNFileModule.filename="$properties.storageBaseDir$/$mdn.msg.sender.as2_id$-$mdn.msg.receiver.as2_id$/mdn/$date.yyyy-MM-dd$/$mdn.msg.headers.message-id$"
module.MDNFileModule.tempdir="$properties.storageBaseDir$/temp"
@@ -74,6 +75,9 @@
msg_tracking.tcp_server_start="true"
msg_tracking.tcp_server_port="9092"
msg_tracking.tcp_server_password="openas2"
+ msg_tracking.aws_region="us-east-1"
+ msg_tracking.dynamodb_table_name="as2db-dev"
+ msg_tracking.consistent_read="true"
reject_unsigned_messages="false"
pollerConfigBase.outboxdir="$properties.storageBaseDir$/outbox/$partnership.receiver.as2_id$"
pollerConfigBase.errordir="$properties.storageBaseDir$/outbox/error/$date.YYYY$-$date.MM$-$date.dd$/$partnership.receiver.as2_id$"
@@ -109,7 +113,7 @@
ssl_protocol="TLS"
ssl_keystore="$properties.ssl_keystore$"
ssl_keystore_password="$properties.ssl_keystore_password$"
- userid="$properties.restapi.command.processor.userid$"
+ userid="$properties.restapi.command.processor.userid$"
password="$properties.restapi.command.processor.password$" />
+
getParameters() {
if (parameters == null) {
- parameters = new HashMap();
+ parameters = new HashMap<>();
}
return parameters;
diff --git a/Server/src/main/java/org/openas2/processor/msgtracking/DynamoDBHandler.java b/Server/src/main/java/org/openas2/processor/msgtracking/DynamoDBHandler.java
new file mode 100644
index 00000000..86459ac4
--- /dev/null
+++ b/Server/src/main/java/org/openas2/processor/msgtracking/DynamoDBHandler.java
@@ -0,0 +1,198 @@
+/* Copyright Uhuru Technology 2016 https://www.uhurutechnology.com
+ * Distributed under the GPLv3 license or a commercial license must be acquired.
+ */
+package org.openas2.processor.msgtracking;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.openas2.OpenAS2Exception;
+
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
+
+import jakarta.annotation.Nullable;
+
+import java.net.URI;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+
+/**
+ * DynamoDB handler for message tracking.
+ * Implements the IDBHandler interface to provide DynamoDB as a backend
+ * for storing AS2 message metadata.
+ */
+class DynamoDBHandler implements IDBHandler {
+
+ private static final Logger logger = LoggerFactory.getLogger(DynamoDBHandler.class);
+
+ @Nullable
+ private DynamoDbClient dynamoDbClient = null;
+
+ private String awsRegion = null;
+
+ /**
+ * Creates DynamoDB client with provided credentials and configuration.
+ *
+ * @param connectString Not used for DynamoDB (table name passed in params)
+ * @param userName AWS Access Key ID (can be null to use default credentials)
+ * @param pwd AWS Secret Access Key (can be null to use default credentials)
+ * @throws OpenAS2Exception if client creation fails
+ */
+ @Override
+ public void createConnectionPool(String connectString, String userName, String pwd) throws OpenAS2Exception {
+ if (dynamoDbClient != null) {
+ throw new OpenAS2Exception("DynamoDB client already initialized. Cannot create a new client. Stop current one first.");
+ }
+
+ try {
+ DynamoDbClientBuilder builder = DynamoDbClient.builder();
+
+ // Set region
+ if (awsRegion != null && !awsRegion.isEmpty()) {
+ builder.region(Region.of(awsRegion));
+ } else {
+ // Use default region from environment/config
+ logger.warn("No AWS region specified, using default region provider");
+ }
+
+ // Set credentials
+ if (userName != null && !userName.isEmpty() && pwd != null && !pwd.isEmpty()) {
+ AwsBasicCredentials credentials = AwsBasicCredentials.create(userName, pwd);
+ builder.credentialsProvider(StaticCredentialsProvider.create(credentials));
+ logger.info("Using provided AWS credentials");
+ } else {
+ // Use default credentials chain (environment variables, instance profile, etc.)
+ builder.credentialsProvider(DefaultCredentialsProvider.create());
+ logger.info("Using default AWS credentials provider chain");
+ }
+
+ dynamoDbClient = builder.build();
+ logger.info("DynamoDB client initialized successfully for region: {}", awsRegion);
+
+ } catch (Exception e) {
+ throw new OpenAS2Exception("Failed to initialize DynamoDB client: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Initializes the DynamoDB handler with configuration parameters.
+ *
+ * @param jdbcConnectString Not used for DynamoDB
+ * @param dbUser AWS Access Key ID (can be null)
+ * @param dbPwd AWS Secret Access Key (can be null)
+ * @param params Configuration parameters including table_name, aws_region, dynamodb_endpoint
+ * @throws OpenAS2Exception if initialization fails
+ */
+ @Override
+ public void start(String jdbcConnectString, String dbUser, String dbPwd, Map params)
+ throws OpenAS2Exception {
+
+ // Extract DynamoDB-specific parameters
+ String tableName = params.get(DbTrackingModule.PARAM_TABLE_NAME);
+ if (tableName == null || tableName.isEmpty()) {
+ tableName = "msg_metadata"; // Default table name
+ }
+
+ awsRegion = params.get("aws_region");
+ if (awsRegion == null || awsRegion.isEmpty()) {
+ awsRegion = "us-east-1"; // Default region
+ logger.warn("No aws_region parameter specified, defaulting to us-east-1");
+ }
+
+ // Create the client
+ createConnectionPool(jdbcConnectString, dbUser, dbPwd);
+
+ // If custom endpoint is specified (for local DynamoDB testing)
+ String endpoint = params.get("dynamodb_endpoint");
+ if (endpoint != null && !endpoint.isEmpty()) {
+ logger.info("Using custom DynamoDB endpoint: {}", endpoint);
+ dynamoDbClient.close();
+ dynamoDbClient = DynamoDbClient.builder()
+ .region(Region.of(awsRegion))
+ .endpointOverride(URI.create(endpoint))
+ .credentialsProvider(dbUser != null && dbPwd != null
+ ? StaticCredentialsProvider.create(AwsBasicCredentials.create(dbUser, dbPwd))
+ : DefaultCredentialsProvider.create())
+ .build();
+ }
+
+ logger.info("DynamoDB handler started with table: {} in region: {}", tableName, awsRegion);
+ }
+
+ /**
+ * Stops the DynamoDB handler and releases resources.
+ */
+ @Override
+ public void stop() {
+ destroyConnectionPool();
+ }
+
+ /**
+ * Closes the DynamoDB client and releases resources.
+ */
+ @Override
+ public void destroyConnectionPool() {
+ if (dynamoDbClient != null) {
+ try {
+ dynamoDbClient.close();
+ logger.info("DynamoDB client closed successfully");
+ } catch (Exception e) {
+ logger.error("Error closing DynamoDB client", e);
+ } finally {
+ dynamoDbClient = null;
+ }
+ }
+ }
+
+ /**
+ * Returns the DynamoDB client.
+ * Note: This method signature is maintained for interface compatibility,
+ * but returns null as DynamoDB doesn't use JDBC connections.
+ * Use getDynamoDbClient() instead.
+ *
+ * @return null (DynamoDB doesn't use JDBC Connection)
+ * @throws SQLException Not thrown
+ * @throws OpenAS2Exception if client is not initialized
+ */
+ @Override
+ public Connection getConnection() throws SQLException, OpenAS2Exception {
+ if (dynamoDbClient == null) {
+ throw new OpenAS2Exception("DynamoDB client not initialized");
+ }
+ // Return null as DynamoDB doesn't use JDBC connections
+ // The actual DynamoDB client can be accessed via getDynamoDbClient()
+ return null;
+ }
+
+ /**
+ * Gets the DynamoDB client for performing operations.
+ *
+ * @return the DynamoDB client
+ * @throws OpenAS2Exception if client is not initialized
+ */
+ public DynamoDbClient getDynamoDbClient() throws OpenAS2Exception {
+ if (dynamoDbClient == null) {
+ throw new OpenAS2Exception("DynamoDB client not initialized");
+ }
+ return dynamoDbClient;
+ }
+
+ /**
+ * Shuts down the DynamoDB client.
+ *
+ * @param connectString Not used for DynamoDB
+ * @return true if shutdown was successful
+ * @throws SQLException Not thrown
+ * @throws OpenAS2Exception if shutdown fails
+ */
+ @Override
+ public boolean shutdown(String connectString) throws SQLException, OpenAS2Exception {
+ destroyConnectionPool();
+ return true;
+ }
+}
diff --git a/Server/src/main/java/org/openas2/processor/msgtracking/DynamoDBTrackingModule.java b/Server/src/main/java/org/openas2/processor/msgtracking/DynamoDBTrackingModule.java
new file mode 100644
index 00000000..6bc3b34c
--- /dev/null
+++ b/Server/src/main/java/org/openas2/processor/msgtracking/DynamoDBTrackingModule.java
@@ -0,0 +1,380 @@
+/* Copyright Uhuru Technology 2016 https://www.uhurutechnology.com
+ * Distributed under the GPLv3 license or a commercial license must be acquired.
+ */
+package org.openas2.processor.msgtracking;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.openas2.OpenAS2Exception;
+import org.openas2.Session;
+import org.openas2.message.Message;
+import org.openas2.util.DateUtil;
+
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
+import software.amazon.awssdk.enhanced.dynamodb.Key;
+import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
+
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * DynamoDB-based message tracking module.
+ * Stores AS2 message metadata in Amazon DynamoDB.
+ */
+public class DynamoDBTrackingModule extends BaseMsgTrackingModule {
+
+ public static final String PARAM_AWS_ACCESS_KEY = "aws_access_key_id";
+ public static final String PARAM_AWS_SECRET_KEY = "aws_secret_access_key";
+ public static final String PARAM_CONSISTENT_READ = "consistent_read";
+
+ private static final Logger logger = LoggerFactory.getLogger(DynamoDBTrackingModule.class);
+ private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
+
+ private DynamoDBHandler dbHandler = null;
+ private DynamoDbTable table = null;
+ private String tableName;
+ private boolean consistentRead = false; // Optional: defaults to eventually consistent if not specified
+ private boolean isRunning = false;
+
+ @Override
+ public void init(Session session, Map options) throws OpenAS2Exception {
+ super.init(session, options);
+ tableName = getParameter(DbTrackingModule.PARAM_TABLE_NAME, "msg_metadata");
+
+ // Configure read consistency (optional - defaults to eventually consistent if not specified)
+ String consistentReadParam = getParameter(PARAM_CONSISTENT_READ, null);
+ if (consistentReadParam != null) {
+ consistentRead = Boolean.parseBoolean(consistentReadParam);
+ }
+ // else: remains false (eventually consistent, DynamoDB default)
+ logger.info("DynamoDB tracking module initialized with consistent_read_param={}, consistent_read={}", consistentReadParam, consistentRead);
+ }
+
+ @Override
+ protected void persist(Message msg, Map map) {
+ try {
+ // Check if record exists
+ String msgId = map.get(FIELDS.MSG_ID);
+ if (msgId == null || msgId.isEmpty()) {
+ logger.error("Cannot persist record without MSG_ID: {}", map);
+ return;
+ }
+ MessageMetadata existingMetadata = getMessageMetadata(msgId);
+ MessageMetadata metadata = mapToMetadata(map, existingMetadata);
+
+ // Put item to DynamoDB (will insert or update)
+ table.putItem(metadata);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("{} tracking record in DynamoDB: {}", existingMetadata != null ? "Updated" : "Created", metadata);
+ }
+ } catch (Exception e) {
+ msg.setLogMsg("Failed to persist tracking event to DynamoDB: " +
+ org.openas2.util.Logging.getExceptionMsg(e) + " ::: Data map: " + map);
+ logger.error(msg.getLogMsg(), e);
+ }
+ }
+
+ /**
+ * Lists all messages from DynamoDB.
+ * Note: Uses scan operation which can be expensive for large tables.
+ * Consider implementing pagination for production use.
+ *
+ * @return list of message metadata records
+ */
+ public ArrayList
-
- org.codehaus.mojo
- versions-maven-plugin
- 2.16.2
-
+
+ org.codehaus.mojo
+ versions-maven-plugin
+ 2.16.2
+
false
file://${session.executionRootDirectory}/version-rules.xml
-
-
+
+
org.apache.maven.plugins
maven-antrun-plugin
@@ -406,26 +424,28 @@
-
-
- com.github.spotbugs
- spotbugs-maven-plugin
- 4.8.5.0
-
- Max
- medium
- true
- ${session.executionRootDirectory}/spotbugs-security-include.xml
- ${session.executionRootDirectory}/spotbugs-security-exclude.xml
-
-
- com.h3xstream.findsecbugs
- findsecbugs-plugin
- 1.13.0
-
-
-
-
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.8.5.0
+
+ Max
+ medium
+ true
+ ${session.executionRootDirectory}/spotbugs-security-include.xml
+
+ ${session.executionRootDirectory}/spotbugs-security-exclude.xml
+
+
+
+ com.h3xstream.findsecbugs
+ findsecbugs-plugin
+ 1.13.0
+
+
+
+
@@ -450,11 +470,11 @@
org.apache.maven.plugins
maven-javadoc-plugin
3.6.3
-
- true
- true
- false
-
+
+ true
+ true
+ false
+
attach-javadocs
@@ -469,10 +489,10 @@
maven-gpg-plugin
3.2.4
-
- --pinentry-mode
- loopback
-
+
+ --pinentry-mode
+ loopback
+