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> listMessages() { + ArrayList> rows = new ArrayList<>(); + + try { + // Scan the table (consider using pagination for large datasets) + ScanEnhancedRequest scanRequest = ScanEnhancedRequest.builder() + .limit(1000) // Limit to 1000 items + .build(); + + table.scan(scanRequest).items().forEach(metadata -> { + Map row = metadataToMap(metadata); + rows.add(row); + }); + + if (logger.isDebugEnabled()) { + logger.debug("Listed " + rows.size() + " messages from DynamoDB"); + } + + } catch (DynamoDbException e) { + logger.error("Failed to list messages from DynamoDB", e); + } + + return rows; + } + + /** + * Retrieves a specific message by ID from DynamoDB. + * Uses strongly consistent reads if configured. + * + * @param msgId the message ID to retrieve + * @return map containing message metadata, or empty map if not found + */ + public Map showMessage(String msgId) { + Map row = new HashMap<>(); + + try { + Key key = Key.builder() + .partitionValue(msgId) + .build(); + + GetItemEnhancedRequest request = GetItemEnhancedRequest.builder() + .key(key) + .consistentRead(consistentRead) + .build(); + + MessageMetadata metadata = table.getItem(request); + + if (metadata != null) { + row = metadataToMap(metadata); + if (logger.isTraceEnabled()) { + logger.trace("Retrieved message from DynamoDB: " + msgId + " (consistentRead=" + consistentRead + ")"); + } + } else { + logger.warn("Message not found in DynamoDB: " + msgId); + } + + } catch (DynamoDbException e) { + logger.error("Failed to retrieve message from DynamoDB: " + msgId, e); + } + + return row; + } + + /** + * Retrieves messages within a date range for chart data. + * Uses the StateIndex GSI for efficient querying. + * + * @param params map containing startDate and endDate + * @return list of message metadata within the date range + */ + public ArrayList> getDataCharts(HashMap params) { + ArrayList> rows = new ArrayList<>(); + + try { + String startDate = params.get("startDate"); + String endDate = params.get("endDate"); + + if (startDate == null || endDate == null) { + logger.error("startDate and endDate are required for getDataCharts"); + return rows; + } + + // Convert dates to timestamps for comparison + String startTimestamp = startDate + " 00:00:00.000"; + String endTimestamp = endDate + " 23:59:59.999"; + + // Use scan with filter expression for date range + // Note: For better performance, consider using GSI with state as partition key + Map expressionValues = new HashMap<>(); + expressionValues.put(":startDate", AttributeValue.builder().s(startTimestamp).build()); + expressionValues.put(":endDate", AttributeValue.builder().s(endTimestamp).build()); + + ScanEnhancedRequest scanRequest = ScanEnhancedRequest.builder() + .filterExpression(software.amazon.awssdk.enhanced.dynamodb.Expression.builder() + .expression("createDt BETWEEN :startDate AND :endDate") + .expressionValues(expressionValues) + .build()) + .limit(1000) + .build(); + + table.scan(scanRequest).items().forEach(metadata -> { + HashMap row = new HashMap<>(); + row.put(FIELDS.MSG_ID, metadata.getMsgId()); + row.put(FIELDS.STATE, metadata.getState()); + row.put(FIELDS.STATUS, metadata.getStatus()); + row.put(FIELDS.CREATE_DT, metadata.getCreateDt()); + rows.add(row); + }); + + if (logger.isDebugEnabled()) { + logger.debug("Retrieved {} chart data records from DynamoDB", rows.size()); + } + + } catch (Exception e) { + logger.error("Failed to retrieve chart data from DynamoDB", e); + } + + return rows; + } + + @Override + public boolean isRunning() { + return isRunning; + } + + @Override + public void start() throws OpenAS2Exception { + dbHandler = new DynamoDBHandler(); + dbHandler.start(null, + getParameter(PARAM_AWS_ACCESS_KEY, null), + getParameter(PARAM_AWS_SECRET_KEY, null), + getParameters()); + + // Initialize Enhanced Client + DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() + .dynamoDbClient(dbHandler.getDynamoDbClient()) + .build(); + + // Get table reference + table = enhancedClient.table(tableName, TableSchema.fromBean(MessageMetadata.class)); + + isRunning = true; + logger.info("DynamoDB tracking module started with table: {}", tableName); + } + + @Override + public void stop() { + if (dbHandler != null) { + dbHandler.stop(); + } + isRunning = false; + logger.info("DynamoDB tracking module stopped"); + } + + @Override + public boolean healthcheck(List failures) { + try { + // Simple healthcheck: try to describe the table + if (table == null || dbHandler == null) { + failures.add(this.getClass().getSimpleName() + " - DynamoDB client not initialized"); + return false; + } + // Try a simple get operation to verify connectivity + table.describeTable(); + return true; + } catch (Exception e) { + failures.add(this.getClass().getSimpleName() + " - Failed to check DynamoDB tracking module: " + e.getMessage()); + return false; + } + } + + /** + * Retrieves a MessageMetadata object by message ID. + * Uses strongly consistent reads if configured (important for Global Tables). + * + * @param msgId the message ID + * @return MessageMetadata object or null if not found + */ + private MessageMetadata getMessageMetadata(String msgId) { + try { + Key key = Key.builder() + .partitionValue(msgId) + .build(); + + GetItemEnhancedRequest request = GetItemEnhancedRequest.builder() + .key(key) + .consistentRead(consistentRead) + .build(); + + return table.getItem(request); + } catch (Exception e) { + logger.trace("Message not found (this is normal for new records): {}", msgId); + return null; + } + } + + /** + * Converts a map to MessageMetadata object. + * + * @param map the source map + * @param existingMetadata existing metadata for update operations + * @return MessageMetadata object + */ + private MessageMetadata mapToMetadata(Map map, MessageMetadata existingMetadata) { + MessageMetadata metadata = existingMetadata != null ? existingMetadata : new MessageMetadata(); + + // Set partition key + metadata.setMsgId(map.get(FIELDS.MSG_ID)); + + // Set other fields only if they're present in the map + metadata.setPriorMsgId(map.get(FIELDS.PRIOR_MSG_ID)); + metadata.setMdnId(map.get(FIELDS.MDN_ID)); + metadata.setDirection(map.get(FIELDS.DIRECTION)); + metadata.setIsResend(map.get(FIELDS.IS_RESEND)); + String resendCountStr = map.get(FIELDS.RESEND_COUNT); + if (resendCountStr != null && !resendCountStr.isEmpty()) { + try { + metadata.setResendCount(Integer.parseInt(resendCountStr)); + } catch (NumberFormatException e) { + logger.warn("Invalid resend_count value: {}", resendCountStr); + } + } + metadata.setSenderId(map.get(FIELDS.SENDER_ID)); + metadata.setReceiverId(map.get(FIELDS.RECEIVER_ID)); + metadata.setStatus(map.get(FIELDS.STATUS)); + metadata.setState(map.get(FIELDS.STATE)); + metadata.setStateMsg(map.get(FIELDS.STATE_MSG)); + metadata.setSignatureAlgorithm(map.get(FIELDS.SIGNATURE_ALGORITHM)); + metadata.setEncryptionAlgorithm(map.get(FIELDS.ENCRYPTION_ALGORITHM)); + metadata.setCompression(map.get(FIELDS.COMPRESSION)); + metadata.setFileName(map.get(FIELDS.FILE_NAME)); + metadata.setSentFileName(map.get(FIELDS.SENT_FILE_NAME)); + metadata.setContentType(map.get(FIELDS.CONTENT_TYPE)); + metadata.setContentTransferEncoding(map.get(FIELDS.CONTENT_TRANSFER_ENCODING)); + metadata.setMdnMode(map.get(FIELDS.MDN_MODE)); + metadata.setMdnResponse(map.get(FIELDS.MDN_RESPONSE)); + + // Handle timestamps + String currentTimestamp = DateUtil.getSqlTimestamp(); + + if (existingMetadata != null) { + // Update timestamp + metadata.setUpdateDt(currentTimestamp); + // Preserve create timestamp + if (existingMetadata.getCreateDt() != null) { + metadata.setCreateDt(existingMetadata.getCreateDt()); + } + } else { + // New record - set create timestamp + metadata.setCreateDt(currentTimestamp); + } + + return metadata; + } + + /** + * Converts MessageMetadata object to a map. + * + * @param metadata the MessageMetadata object + * @return map containing message metadata + */ + private Map metadataToMap(MessageMetadata metadata) { + Map map = new HashMap<>(); + map.put(FIELDS.MSG_ID, metadata.getMsgId()); + map.put(FIELDS.PRIOR_MSG_ID, metadata.getPriorMsgId()); + map.put(FIELDS.MDN_ID, metadata.getMdnId()); + map.put(FIELDS.DIRECTION, metadata.getDirection()); + map.put(FIELDS.IS_RESEND, metadata.getIsResend()); + map.put(FIELDS.RESEND_COUNT, metadata.getResendCountStr()); + map.put(FIELDS.SENDER_ID, metadata.getSenderId()); + map.put(FIELDS.RECEIVER_ID, metadata.getReceiverId()); + map.put(FIELDS.STATUS, metadata.getStatus()); + map.put(FIELDS.STATE, metadata.getState()); + map.put(FIELDS.STATE_MSG, metadata.getStateMsg()); + map.put(FIELDS.SIGNATURE_ALGORITHM, metadata.getSignatureAlgorithm()); + map.put(FIELDS.ENCRYPTION_ALGORITHM, metadata.getEncryptionAlgorithm()); + map.put(FIELDS.COMPRESSION, metadata.getCompression()); + map.put(FIELDS.FILE_NAME, metadata.getFileName()); + map.put(FIELDS.SENT_FILE_NAME, metadata.getSentFileName()); + map.put(FIELDS.CONTENT_TYPE, metadata.getContentType()); + map.put(FIELDS.CONTENT_TRANSFER_ENCODING, metadata.getContentTransferEncoding()); + map.put(FIELDS.MDN_MODE, metadata.getMdnMode()); + map.put(FIELDS.MDN_RESPONSE, metadata.getMdnResponse()); + map.put(FIELDS.CREATE_DT, metadata.getCreateDt()); + map.put(FIELDS.UPDATE_DT, metadata.getUpdateDt()); + return map; + } +} diff --git a/Server/src/main/java/org/openas2/processor/msgtracking/MessageMetadata.java b/Server/src/main/java/org/openas2/processor/msgtracking/MessageMetadata.java new file mode 100644 index 00000000..b753d79f --- /dev/null +++ b/Server/src/main/java/org/openas2/processor/msgtracking/MessageMetadata.java @@ -0,0 +1,244 @@ +/* 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.openas2.util.DateUtil; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey; + +import java.time.Instant; +import java.util.Map; + +/** + * DynamoDB data model for AS2 message metadata. + * Maps to the msg_metadata table structure. + */ +@DynamoDbBean +public class MessageMetadata { + + private String msgId; + private String priorMsgId; + private String mdnId; + private String direction; + private String isResend; + private Integer resendCount; + private String senderId; + private String receiverId; + private String status; + private String state; + private String stateMsg; + private String signatureAlgorithm; + private String encryptionAlgorithm; + private String compression; + private String fileName; + private String sentFileName; + private String contentType; + private String contentTransferEncoding; + private String mdnMode; + private String mdnResponse; + private String createDt; + private String updateDt; + + /** + * Default constructor required by DynamoDB Enhanced Client. + */ + public MessageMetadata() { + } + + @DynamoDbPartitionKey + public String getMsgId() { + return msgId; + } + + public void setMsgId(String msgId) { + this.msgId = msgId; + } + + public String getPriorMsgId() { + return priorMsgId; + } + + public void setPriorMsgId(String priorMsgId) { + this.priorMsgId = priorMsgId; + } + + public String getMdnId() { + return mdnId; + } + + public void setMdnId(String mdnId) { + this.mdnId = mdnId; + } + + public String getDirection() { + return direction; + } + + public void setDirection(String direction) { + this.direction = direction; + } + + public String getIsResend() { + return isResend; + } + + public void setIsResend(String isResend) { + this.isResend = isResend; + } + + public Integer getResendCount() { + return resendCount; + } + public String getResendCountStr() { + return resendCount == null ? null : resendCount.toString(); + } + + public void setResendCount(Integer resendCount) { + this.resendCount = resendCount; + } + + public String getSenderId() { + return senderId; + } + + public void setSenderId(String senderId) { + this.senderId = senderId; + } + + public String getReceiverId() { + return receiverId; + } + + public void setReceiverId(String receiverId) { + this.receiverId = receiverId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @DynamoDbSecondaryPartitionKey(indexNames = "StateIndex") + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getStateMsg() { + return stateMsg; + } + + public void setStateMsg(String stateMsg) { + this.stateMsg = stateMsg; + } + + public String getSignatureAlgorithm() { + return signatureAlgorithm; + } + + public void setSignatureAlgorithm(String signatureAlgorithm) { + this.signatureAlgorithm = signatureAlgorithm; + } + + public String getEncryptionAlgorithm() { + return encryptionAlgorithm; + } + + public void setEncryptionAlgorithm(String encryptionAlgorithm) { + this.encryptionAlgorithm = encryptionAlgorithm; + } + + public String getCompression() { + return compression; + } + + public void setCompression(String compression) { + this.compression = compression; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getSentFileName() { + return sentFileName; + } + + public void setSentFileName(String sentFileName) { + this.sentFileName = sentFileName; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getContentTransferEncoding() { + return contentTransferEncoding; + } + + public void setContentTransferEncoding(String contentTransferEncoding) { + this.contentTransferEncoding = contentTransferEncoding; + } + + public String getMdnMode() { + return mdnMode; + } + + public void setMdnMode(String mdnMode) { + this.mdnMode = mdnMode; + } + + public String getMdnResponse() { + return mdnResponse; + } + + public void setMdnResponse(String mdnResponse) { + this.mdnResponse = mdnResponse; + } + + @DynamoDbSecondarySortKey(indexNames = "StateIndex") + public String getCreateDt() { + return createDt; + } + + public void setCreateDt(String createDt) { + this.createDt = createDt; + } + + public String getUpdateDt() { + return updateDt; + } + + public void setUpdateDt(String updateDt) { + this.updateDt = updateDt; + } + + @Override + public String toString() { + return "MessageMetadata{" + + "msgId='" + msgId + '\'' + + ", senderId='" + senderId + '\'' + + ", receiverId='" + receiverId + '\'' + + ", state='" + state + '\'' + + ", status='" + status + '\'' + + ", createDt='" + createDt + '\'' + + '}'; + } +} diff --git a/pom.xml b/pom.xml index 85cc5298..3989b16a 100644 --- a/pom.xml +++ b/pom.xml @@ -34,10 +34,28 @@ 11 + 2.29.40 + + software.amazon.awssdk + bom + ${aws.java.sdk.version} + pom + import + + + software.amazon.awssdk + dynamodb + ${aws.java.sdk.version} + + + software.amazon.awssdk + dynamodb-enhanced + ${aws.java.sdk.version} + org.osgi org.osgi.core @@ -107,105 +125,105 @@ org.junit.jupiter - junit-jupiter - 5.13.4 + junit-jupiter + 5.13.4 test org.mockito mockito-core - - 5.18.0 + + 5.18.0 + test + + + org.mockito + mockito-junit-jupiter + + 5.18.0 + test + + + + org.hamcrest + hamcrest + 3.0 test - - org.mockito - mockito-junit-jupiter - - 5.18.0 - test - - - - org.hamcrest - hamcrest - 3.0 - test - commons-io commons-io 2.20.0 - - org.slf4j - slf4j-api - 2.0.17 - - - ch.qos.logback - logback-classic - 1.5.18 - - - jakarta.ws.rs - jakarta.ws.rs-api - 3.1.0 - - - jakarta.annotation - jakarta.annotation-api - 3.0.0 - - - org.glassfish.jersey.containers - jersey-container-grizzly2-http - 3.1.10 - jar - - - com.fasterxml.jackson.core - jackson-databind - 2.19.2 - jar - - - com.fasterxml.jackson.module - jackson-module-jaxb-annotations - 2.19.2 - - - org.glassfish.jersey.media - jersey-media-json-jackson - 3.1.10 - jar - - - org.glassfish.jersey.inject - jersey-hk2 - 3.1.10 - + + org.slf4j + slf4j-api + 2.0.17 + + + ch.qos.logback + logback-classic + 1.5.18 + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + + + jakarta.annotation + jakarta.annotation-api + 3.0.0 + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + 3.1.10 + jar + + + com.fasterxml.jackson.core + jackson-databind + 2.19.2 + jar + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + 2.19.2 + + + org.glassfish.jersey.media + jersey-media-json-jackson + 3.1.10 + jar + + + org.glassfish.jersey.inject + jersey-hk2 + 3.1.10 + jakarta.xml.bind jakarta.xml.bind-api 4.0.2 - com.sun.xml.bind - jaxb-core - 4.0.5 - - - com.sun.xml.bind - jaxb-impl - 4.0.5 - - - com.zaxxer - HikariCP - 7.0.0 - + com.sun.xml.bind + jaxb-core + 4.0.5 + + + com.sun.xml.bind + jaxb-impl + 4.0.5 + + + com.zaxxer + HikariCP + 7.0.0 + @@ -284,15 +302,15 @@ - - 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 +