diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc
index a3de5dc58..af96fab57 100644
--- a/docs/src/main/asciidoc/_configprops.adoc
+++ b/docs/src/main/asciidoc/_configprops.adoc
@@ -94,6 +94,7 @@
|spring.cloud.aws.sns.endpoint | | Overrides the default endpoint.
|spring.cloud.aws.sns.region | | Overrides the default region.
|spring.cloud.aws.sqs.dualstack-enabled | | Configure whether the AWS client should use the AWS dualstack endpoint. Note that not each AWS service supports dual-stack. For complete list check AWS services that support IPv6
+|spring.cloud.aws.sqs.convert-message-id-to-uuid | `+++true+++` | Whether to convert SQS message IDs to UUIDs. Set to `false` for SQS-compatible providers that return non-UUID message IDs.
|spring.cloud.aws.sqs.enabled | `+++true+++` | Enables SQS integration.
|spring.cloud.aws.sqs.endpoint | | Overrides the default endpoint.
|spring.cloud.aws.sqs.listener.auto-startup | | Defines whether SQS listeners will start automatically or not.
diff --git a/docs/src/main/asciidoc/sqs.adoc b/docs/src/main/asciidoc/sqs.adoc
index bf120b162..233bd7555 100644
--- a/docs/src/main/asciidoc/sqs.adoc
+++ b/docs/src/main/asciidoc/sqs.adoc
@@ -319,6 +319,27 @@ If `SendBatchFailureStrategy#DO_NOT_THROW` is configured, no exception is thrown
For convenience, the `additionalInformation` parameters can be found as constants in the `SqsTemplateParameters` class.
+===== Non-UUID Message IDs
+
+By default, Spring Cloud AWS SQS expects the message ID returned by SQS to be a valid UUID.
+If a non-UUID message ID is received, an error is thrown with instructions to enable non-UUID support.
+
+To enable non-UUID message ID support (e.g., for Yandex Message Queue or other SQS-compatible providers):
+
+[source,properties]
+----
+spring.cloud.aws.sqs.convert-message-id-to-uuid=false
+----
+
+When disabled:
+
+* **Receive side**: The raw provider message ID is stored in the `Sqs_RawMessageId` header.
+ A deterministic UUID derived from the raw ID is used as the Spring `MessageHeaders.ID`.
+ Access the raw ID via `MessageHeaderUtils.getRawMessageId(message)`.
+* **Send side**: If the send response contains a non-UUID message ID,
+ `SendResult.messageId()` returns a deterministic UUID and the raw ID is available
+ in `SendResult.additionalInformation()` under the `rawMessageId` key.
+
[[template-message-conversion]]
==== Template Message Conversion
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java
index c6c89301e..7474074b5 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java
@@ -32,6 +32,7 @@
import io.awspring.cloud.sqs.operations.SqsTemplate;
import io.awspring.cloud.sqs.operations.SqsTemplateBuilder;
import io.awspring.cloud.sqs.support.converter.MessagingMessageConverter;
+import io.awspring.cloud.sqs.support.converter.SqsHeaderMapper;
import io.awspring.cloud.sqs.support.converter.SqsMessagingMessageConverter;
import io.awspring.cloud.sqs.support.converter.legacy.JacksonJsonMessageConverterMigration;
import io.awspring.cloud.sqs.support.converter.legacy.JacksonMessageConverterMigration;
@@ -64,6 +65,7 @@
* @author Maciej Walkowiak
* @author Wei Jiang
* @author Dongha Kim
+ * @author Jeongmin Kim
* @since 3.0
*/
@AutoConfiguration
@@ -159,10 +161,15 @@ private void configureProperties(SqsContainerOptionsBuilder options) {
static class SqsJacksonConfiguration {
@ConditionalOnMissingBean
@Bean
- public MessagingMessageConverter messageConverter(ObjectProvider jsonMapperProvider) {
- JsonMapper jsonMapper = jsonMapperProvider.getIfAvailable();
- return jsonMapper != null ? new SqsMessagingMessageConverter(jsonMapper)
+ public MessagingMessageConverter messageConverter(ObjectProvider jsonMapperProvider,
+ SqsProperties sqsProperties) {
+ SqsMessagingMessageConverter converter = jsonMapperProvider.getIfAvailable() != null
+ ? new SqsMessagingMessageConverter(jsonMapperProvider.getIfAvailable())
: new SqsMessagingMessageConverter();
+ SqsHeaderMapper headerMapper = new SqsHeaderMapper();
+ headerMapper.setConvertMessageIdToUuid(sqsProperties.getConvertMessageIdToUuid());
+ converter.setHeaderMapper(headerMapper);
+ return converter;
}
@Bean
@@ -179,8 +186,12 @@ public JacksonMessageConverterMigration jsonMapperWrapper(ObjectProvider messageConverter() {
- return new LegacyJackson2SqsMessagingMessageConverter();
+ public MessagingMessageConverter messageConverter(SqsProperties sqsProperties) {
+ LegacyJackson2SqsMessagingMessageConverter converter = new LegacyJackson2SqsMessagingMessageConverter();
+ SqsHeaderMapper headerMapper = new SqsHeaderMapper();
+ headerMapper.setConvertMessageIdToUuid(sqsProperties.getConvertMessageIdToUuid());
+ converter.setHeaderMapper(headerMapper);
+ return converter;
}
@Bean
diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsProperties.java
index 509768d2c..fb37f35bb 100644
--- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsProperties.java
+++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsProperties.java
@@ -26,6 +26,7 @@
*
* @author Tomaz Fernandes
* @author Wei Jiang
+ * @author Jeongmin Kim
* @since 3.0
*/
@ConfigurationProperties(prefix = SqsProperties.PREFIX)
@@ -51,6 +52,20 @@ public void setListener(Listener listener) {
private Boolean observationEnabled = false;
+ /**
+ * Whether to convert SQS message IDs to UUIDs. Set to {@code false} for SQS-compatible providers that return
+ * non-UUID message IDs.
+ */
+ private Boolean convertMessageIdToUuid = true;
+
+ public Boolean getConvertMessageIdToUuid() {
+ return convertMessageIdToUuid;
+ }
+
+ public void setConvertMessageIdToUuid(Boolean convertMessageIdToUuid) {
+ this.convertMessageIdToUuid = convertMessageIdToUuid;
+ }
+
/**
* Return the strategy to use if the queue is not found.
* @return the {@link QueueNotFoundStrategy}
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/MessageHeaderUtils.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/MessageHeaderUtils.java
index 5ee1e238c..6f167157c 100644
--- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/MessageHeaderUtils.java
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/MessageHeaderUtils.java
@@ -15,6 +15,7 @@
*/
package io.awspring.cloud.sqs;
+import io.awspring.cloud.sqs.listener.SqsHeaders;
import io.awspring.cloud.sqs.support.converter.MessagingMessageHeaders;
import java.util.Collection;
import java.util.Map;
@@ -30,6 +31,7 @@
* Utility class for extracting {@link MessageHeaders} from a {@link Message}.
*
* @author Tomaz Fernandes
+ * @author Jeongmin Kim
* @since 3.0
*/
public class MessageHeaderUtils {
@@ -150,4 +152,22 @@ public static Message removeHeaderIfPresent(Message message, String ke
return new GenericMessage<>(message.getPayload(), newHeaders);
}
+ /**
+ * Return the raw provider message ID, falling back to Spring message ID if not present.
+ * @param message the message.
+ * @return the raw provider ID or Spring ID.
+ */
+ public static String getRawMessageId(Message> message) {
+ String rawMessageId = message.getHeaders().get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, String.class);
+ return rawMessageId != null ? rawMessageId : getId(message);
+ }
+
+ /**
+ * Return the messages' raw provider IDs as a concatenated {@link String}.
+ * @param messages the messages.
+ * @return the raw provider IDs.
+ */
+ public static String getRawMessageId(Collection> messages) {
+ return messages.stream().map(MessageHeaderUtils::getRawMessageId).collect(Collectors.joining("; "));
+ }
}
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/listener/SqsHeaders.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/listener/SqsHeaders.java
index 41fcc166a..87101b1f2 100644
--- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/listener/SqsHeaders.java
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/listener/SqsHeaders.java
@@ -23,6 +23,7 @@
*
* @author Tomaz Fernandes
* @author Artem Bilan
+ * @author Jeongmin Kim
*
* @since 3.0
*
@@ -88,6 +89,11 @@ private SqsHeaders() {
*/
public static final String SQS_DEFAULT_TYPE_HEADER = "JavaType";
+ /**
+ * Header for the raw provider message ID when not using UUID conversion.
+ */
+ public static final String SQS_RAW_MESSAGE_ID_HEADER = SQS_HEADER_PREFIX + "RawMessageId";
+
public static class MessageSystemAttributes {
private MessageSystemAttributes() {
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplate.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplate.java
index dd577996c..bd790c5cd 100644
--- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplate.java
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplate.java
@@ -33,6 +33,7 @@
import io.awspring.cloud.sqs.support.converter.SqsMessagingMessageConverter;
import io.awspring.cloud.sqs.support.converter.legacy.LegacyJackson2SqsMessagingMessageConverter;
import io.awspring.cloud.sqs.support.observation.SqsTemplateObservation;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
@@ -77,6 +78,7 @@
* @author Tomaz Fernandes
* @author Zhong Xi Lu
* @author Hyunggeol Lee
+ * @author Jeongmin Kim
*
* @since 3.0
*/
@@ -327,16 +329,36 @@ private CompletableFuture handleAutoDeduplication(String endpointName)
protected CompletableFuture> doSendAsync(String endpointName, Message message,
org.springframework.messaging.Message originalMessage) {
return createSendMessageRequest(endpointName, message).thenCompose(this.sqsAsyncClient::sendMessage)
- .thenApply(response -> createSendResult(UUID.fromString(response.messageId()),
- response.sequenceNumber(), endpointName, originalMessage));
+ .thenApply(response -> createSendResult(response.messageId(), response.sequenceNumber(), endpointName,
+ originalMessage));
}
- private SendResult createSendResult(UUID messageId, @Nullable String sequenceNumber, String endpointName,
- org.springframework.messaging.Message originalMessage) {
+ private SendResult createSendResult(String rawMessageId, @Nullable String sequenceNumber,
+ String endpointName, org.springframework.messaging.Message originalMessage) {
+ Map additionalInfo = new HashMap<>();
+ if (sequenceNumber != null) {
+ additionalInfo.put(SqsTemplateParameters.SEQUENCE_NUMBER_PARAMETER_NAME, sequenceNumber);
+ }
+ UUID messageId;
+ if (isValidUuid(rawMessageId)) {
+ messageId = UUID.fromString(rawMessageId);
+ }
+ else {
+ messageId = UUID.nameUUIDFromBytes(rawMessageId.getBytes(StandardCharsets.UTF_8));
+ additionalInfo.put(SqsTemplateParameters.RAW_MESSAGE_ID_PARAMETER_NAME, rawMessageId);
+ }
return new SendResult<>(messageId, endpointName, originalMessage,
- sequenceNumber != null
- ? Collections.singletonMap(SqsTemplateParameters.SEQUENCE_NUMBER_PARAMETER_NAME, sequenceNumber)
- : Collections.emptyMap());
+ additionalInfo.isEmpty() ? Collections.emptyMap() : additionalInfo);
+ }
+
+ private static boolean isValidUuid(String value) {
+ try {
+ UUID.fromString(value);
+ return true;
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
}
private CompletableFuture createSendMessageRequest(String endpointName, Message message) {
@@ -358,8 +380,8 @@ protected CompletableFuture> doSendBatchAsync(String end
Collection messages, Collection> originalMessages) {
logger.debug("Sending messages {} to endpoint {}", messages, endpointName);
return createSendMessageBatchRequest(endpointName, messages).thenCompose(this.sqsAsyncClient::sendMessageBatch)
- .thenApply(response -> createSendResultBatch(response, endpointName,
- originalMessages.stream().collect(Collectors.toMap(MessageHeaderUtils::getId, msg -> msg))));
+ .thenApply(response -> createSendResultBatch(response, endpointName, originalMessages.stream()
+ .collect(Collectors.toMap(MessageHeaderUtils::getRawMessageId, msg -> msg))));
}
private SendResult.Batch createSendResultBatch(SendMessageBatchResponse response, String endpointName,
@@ -379,10 +401,8 @@ private Collection> createSendResultFailed(SendMessageB
private Collection> doCreateSendResultBatch(SendMessageBatchResponse response,
String endpointName, Map> originalMessagesById) {
- return response
- .successful().stream().map(entry -> createSendResult(UUID.fromString(entry.messageId()),
- entry.sequenceNumber(), endpointName, getOriginalMessage(originalMessagesById, entry)))
- .toList();
+ return response.successful().stream().map(entry -> createSendResult(entry.messageId(), entry.sequenceNumber(),
+ endpointName, getOriginalMessage(originalMessagesById, entry))).toList();
}
private org.springframework.messaging.Message getOriginalMessage(
@@ -540,7 +560,7 @@ private Map addMissingFifoReceiveHeaders(Map hea
private CompletableFuture deleteMessages(String endpointName,
Collection> messages) {
logger.trace("Acknowledging in queue {} messages {}", endpointName,
- MessageHeaderUtils.getId(addTypeToMessages(messages)));
+ MessageHeaderUtils.getRawMessageId(addTypeToMessages(messages)));
return getQueueAttributes(endpointName)
.thenCompose(attributes -> this.sqsAsyncClient.deleteMessageBatch(DeleteMessageBatchRequest.builder()
.queueUrl(attributes.getQueueUrl()).entries(createDeleteMessageEntries(messages)).build()))
@@ -559,7 +579,8 @@ private Collection> getFailedAckMessage
DeleteMessageBatchResponse response, Collection> messages,
String endpointName) {
return response.failed().stream().map(BatchResultErrorEntry::id)
- .map(id -> messages.stream().filter(msg -> MessageHeaderUtils.getId(msg).equals(id)).findFirst()
+ .map(id -> messages.stream().filter(msg -> MessageHeaderUtils.getRawMessageId(msg).equals(id))
+ .findFirst()
.orElseThrow(() -> new SqsAcknowledgementException(
"Could not correlate ids for acknowledgement failure", Collections.emptyList(),
messages, endpointName)))
@@ -570,7 +591,8 @@ private Collection> getSuccessfulAckMes
DeleteMessageBatchResponse response, Collection> messages,
String endpointName) {
return response.successful().stream().map(DeleteMessageBatchResultEntry::id)
- .map(id -> messages.stream().filter(msg -> MessageHeaderUtils.getId(msg).equals(id)).findFirst()
+ .map(id -> messages.stream().filter(msg -> MessageHeaderUtils.getRawMessageId(msg).equals(id))
+ .findFirst()
.orElseThrow(() -> new SqsAcknowledgementException(
"Could not correlate ids for acknowledgement failure", Collections.emptyList(),
messages, endpointName)))
@@ -588,7 +610,7 @@ private void logAcknowledgement(String endpointName, Collection createDeleteMessageEntries(
Collection> messages) {
return messages.stream()
- .map(message -> DeleteMessageBatchRequestEntry.builder().id(MessageHeaderUtils.getId(message))
+ .map(message -> DeleteMessageBatchRequestEntry.builder().id(MessageHeaderUtils.getRawMessageId(message))
.receiptHandle(
MessageHeaderUtils.getHeaderAsString(message, SqsHeaders.SQS_RECEIPT_HANDLE_HEADER))
.build())
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplateParameters.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplateParameters.java
index 6c3e3662e..801ad5ce3 100644
--- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplateParameters.java
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/SqsTemplateParameters.java
@@ -39,4 +39,9 @@ public class SqsTemplateParameters {
*/
public static final String ERROR_CODE_PARAMETER_NAME = "code";
+ /**
+ * The raw provider message ID when it is not a valid UUID.
+ */
+ public static final String RAW_MESSAGE_ID_PARAMETER_NAME = "rawMessageId";
+
}
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/MessagingMessageHeaders.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/MessagingMessageHeaders.java
index 99ad15a0b..883e419c4 100644
--- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/MessagingMessageHeaders.java
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/MessagingMessageHeaders.java
@@ -15,6 +15,7 @@
*/
package io.awspring.cloud.sqs.support.converter;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
import org.jspecify.annotations.Nullable;
@@ -23,6 +24,7 @@
/**
* {@link MessageHeaders} implementation that allows providing an external {@link UUID}.
* @author Tomaz Fernandes
+ * @author Jeongmin Kim
* @since 3.0
*/
public class MessagingMessageHeaders extends MessageHeaders {
@@ -53,4 +55,15 @@ public MessagingMessageHeaders(@Nullable Map headers, @Nullable
public MessagingMessageHeaders(@Nullable Map headers, @Nullable UUID id, @Nullable Long timestamp) {
super(headers, id, timestamp);
}
+
+ /**
+ * Create an instance with String ID converted to consistent UUID
+ */
+ public MessagingMessageHeaders(@Nullable Map headers, @Nullable String stringId) {
+ super(headers, stringId != null ? generateConsistentUuid(stringId) : null, null);
+ }
+
+ private static UUID generateConsistentUuid(String stringId) {
+ return UUID.nameUUIDFromBytes(stringId.getBytes(StandardCharsets.UTF_8));
+ }
}
diff --git a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapper.java b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapper.java
index c9f454616..fa58a7111 100644
--- a/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapper.java
+++ b/spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapper.java
@@ -51,6 +51,7 @@
* @author Tomaz Fernandes
* @author Alain Sahli
* @author Maciej Walkowiak
+ * @author Jeongmin Kim
*
* @since 3.0
* @see LegacyJackson2SqsMessagingMessageConverter
@@ -62,12 +63,18 @@ public class SqsHeaderMapper implements ContextAwareHeaderMapper {
private BiFunction additionalHeadersFunction = ((message,
accessor) -> accessor.toMessageHeaders());
+ private boolean convertMessageIdToUuid = true;
+
public void setAdditionalHeadersFunction(
BiFunction headerFunction) {
Assert.notNull(headerFunction, "headerFunction cannot be null");
this.additionalHeadersFunction = headerFunction;
}
+ public void setConvertMessageIdToUuid(boolean convertMessageIdToUuid) {
+ this.convertMessageIdToUuid = convertMessageIdToUuid;
+ }
+
@Override
public Message fromHeaders(MessageHeaders headers) {
Message.Builder builder = Message.builder();
@@ -157,9 +164,36 @@ public MessageHeaders toHeaders(Message source) {
accessor.copyHeadersIfAbsent(getMessageAttributesAsHeaders(source));
accessor.copyHeadersIfAbsent(createDefaultHeaders(source));
accessor.copyHeadersIfAbsent(createAdditionalHeaders(source));
- MessageHeaders messageHeaders = accessor.toMessageHeaders();
- logger.trace("Mapped headers {} for message {}", messageHeaders, source.messageId());
- return new MessagingMessageHeaders(messageHeaders, UUID.fromString(source.messageId()));
+
+ if (convertMessageIdToUuid) {
+ if (!isValidUuid(source.messageId())) {
+ throw new MessagingException(String.format(
+ "Message ID '%s' is not a valid UUID. To support non-UUID message IDs, "
+ + "set 'spring.cloud.aws.sqs.convert-message-id-to-uuid=false'. "
+ + "The raw message ID will be available via the '%s' header.",
+ source.messageId(), SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER));
+ }
+ MessageHeaders messageHeaders = accessor.toMessageHeaders();
+ logger.trace("Mapped headers {} for message {}", messageHeaders, source.messageId());
+ return new MessagingMessageHeaders(messageHeaders, UUID.fromString(source.messageId()));
+ }
+ else {
+ accessor.setHeader(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, source.messageId());
+ MessageHeaders messageHeaders = accessor.toMessageHeaders();
+ logger.trace("Mapped headers {} for message {}", messageHeaders, source.messageId());
+ return new MessagingMessageHeaders(messageHeaders, source.messageId());
+ }
+
+ }
+
+ private boolean isValidUuid(String messageId) {
+ try {
+ UUID.fromString(messageId);
+ return true;
+ }
+ catch (IllegalArgumentException e) {
+ return false;
+ }
}
private MessageHeaders createAdditionalHeaders(Message source) {
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java
index e156e9ec8..784bbf301 100644
--- a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/MessageHeaderUtilsTest.java
@@ -17,6 +17,9 @@
import static org.assertj.core.api.Assertions.assertThat;
+import io.awspring.cloud.sqs.listener.SqsHeaders;
+import java.util.Collection;
+import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
@@ -25,6 +28,7 @@
* Tests for {@link MessageHeaderUtils}.
*
* @author Tomaz Fernandes
+ * @author Jeongmin Kim
*/
class MessageHeaderUtilsTest {
@@ -93,4 +97,51 @@ void shouldPreserveOtherHeaders() {
assertThat(result.getHeaders().get("another-header")).isEqualTo("another-value");
assertThat(result.getHeaders().size()).isEqualTo(message.getHeaders().size() - 1);
}
+
+ @Test
+ void shouldReturnRawMessageIdWhenHeaderPresent() {
+ // given
+ String rawMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ Message message = MessageBuilder.withPayload("test-payload")
+ .setHeader(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, rawMessageId).build();
+
+ // when
+ String result = MessageHeaderUtils.getRawMessageId(message);
+
+ // then
+ assertThat(result).isEqualTo(rawMessageId);
+ }
+
+ @Test
+ void shouldFallbackToSpringMessageIdWhenRawHeaderNotPresent() {
+ // given
+ Message message = MessageBuilder.withPayload("test-payload").build();
+ String expectedId = message.getHeaders().getId().toString();
+
+ // when
+ String result = MessageHeaderUtils.getRawMessageId(message);
+
+ // then
+ assertThat(result).isEqualTo(expectedId);
+ }
+
+ @Test
+ void shouldConcatenateRawMessageIdsFromCollection() {
+ // given
+ String rawMessageId1 = "raw-id-1";
+ String rawMessageId2 = "raw-id-2";
+
+ Message message1 = MessageBuilder.withPayload("payload1")
+ .setHeader(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, rawMessageId1).build();
+ Message message2 = MessageBuilder.withPayload("payload2")
+ .setHeader(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER, rawMessageId2).build();
+
+ Collection> messages = List.of(message1, message2);
+
+ // when
+ String result = MessageHeaderUtils.getRawMessageId(messages);
+
+ // then
+ assertThat(result).isEqualTo("raw-id-1; raw-id-2");
+ }
}
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java
index a31d65fe1..bb297c702 100644
--- a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateTests.java
@@ -1192,6 +1192,56 @@ void shouldReceiveBatchFifo() {
}
+ @Test
+ void shouldHandleNonUuidMessageIdInSendResponse() {
+ String queue = "test-queue";
+ GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build();
+ given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(urlResponse));
+ mockQueueAttributes(mockClient, Map.of());
+ String nonUuidMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ SendMessageResponse response = SendMessageResponse.builder().messageId(nonUuidMessageId).build();
+ given(mockClient.sendMessage(any(SendMessageRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(response));
+ SqsOperations template = SqsTemplate.newTemplate(mockClient);
+ String payload = "test-payload";
+ SendResult result = template.send(to -> to.queue(queue).payload(payload));
+ assertThat(result.messageId())
+ .isEqualTo(UUID.nameUUIDFromBytes(nonUuidMessageId.getBytes(java.nio.charset.StandardCharsets.UTF_8)));
+ assertThat(result.additionalInformation().get(SqsTemplateParameters.RAW_MESSAGE_ID_PARAMETER_NAME))
+ .isEqualTo(nonUuidMessageId);
+ }
+
+ @Test
+ void shouldHandleNonUuidMessageIdInBatchSendResponse() {
+ String queue = "test-queue";
+ String payload1 = "test-payload-1";
+ String payload2 = "test-payload-2";
+ Message message1 = MessageBuilder.withPayload(payload1).build();
+ Message message2 = MessageBuilder.withPayload(payload2).build();
+ List> messages = List.of(message1, message2);
+
+ GetQueueUrlResponse urlResponse = GetQueueUrlResponse.builder().queueUrl(queue).build();
+ given(mockClient.getQueueUrl(any(GetQueueUrlRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(urlResponse));
+ mockQueueAttributes(mockClient, Map.of());
+ String nonUuidMessageId1 = "92898073-7bd6a160-5797b060-54a7e539";
+ String nonUuidMessageId2 = "a2898073-8bd6a160-6797b060-64a7e539";
+ SendMessageBatchResponse response = SendMessageBatchResponse.builder()
+ .successful(
+ builder -> builder.id(message1.getHeaders().getId().toString()).messageId(nonUuidMessageId1),
+ builder -> builder.id(message2.getHeaders().getId().toString()).messageId(nonUuidMessageId2))
+ .build();
+ given(mockClient.sendMessageBatch(any(SendMessageBatchRequest.class)))
+ .willReturn(CompletableFuture.completedFuture(response));
+ SqsOperations template = SqsTemplate.newSyncTemplate(mockClient);
+ SendResult.Batch results = template.sendMany(queue, messages);
+ assertThat(results.successful()).hasSize(2);
+ results.successful().forEach(result -> {
+ assertThat(result.additionalInformation()).containsKey(SqsTemplateParameters.RAW_MESSAGE_ID_PARAMETER_NAME);
+ });
+ }
+
@Test
void shouldPropagateTracingAsMessageSystemAttribute() {
String queue = "test-queue";
diff --git a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java
index ffbe0683a..5df507bd5 100644
--- a/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java
+++ b/spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/support/converter/SqsHeaderMapperTests.java
@@ -16,6 +16,7 @@
package io.awspring.cloud.sqs.support.converter;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import io.awspring.cloud.sqs.listener.SqsHeaders;
import java.math.BigDecimal;
@@ -27,6 +28,7 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.MessagingException;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.sqs.model.Message;
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
@@ -37,6 +39,7 @@
*
* @author Tomaz Fernandes
* @author Maciej Walkowiak
+ * @author Jeongmin Kim
*/
class SqsHeaderMapperTests {
@@ -177,6 +180,39 @@ void createsMessageWithNumberHeader(String value, String type, Number expected)
assertThat(headers.get(headerName)).isEqualTo(expected);
}
+ @Test
+ void shouldConvertUuidMessageIdWhenConvertMessageIdToUuidIsTrue() {
+ SqsHeaderMapper mapper = new SqsHeaderMapper();
+ mapper.setConvertMessageIdToUuid(true);
+ String uuidMessageId = "550e8400-e29b-41d4-a716-446655440000";
+ Message message = Message.builder().body("payload").messageId(uuidMessageId).build();
+ MessageHeaders headers = mapper.toHeaders(message);
+ assertThat(headers.getId()).isEqualTo(UUID.fromString(uuidMessageId));
+ assertThat(headers.get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isNull();
+ }
+
+ @Test
+ void shouldThrowWhenConvertMessageIdToUuidIsTrueAndMessageIdIsNotValidUuid() {
+ SqsHeaderMapper mapper = new SqsHeaderMapper();
+ mapper.setConvertMessageIdToUuid(true);
+ String nonUuidMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ Message message = Message.builder().body("payload").messageId(nonUuidMessageId).build();
+ assertThatThrownBy(() -> mapper.toHeaders(message)).isInstanceOf(MessagingException.class)
+ .hasMessageContaining("not a valid UUID").hasMessageContaining("convert-message-id-to-uuid");
+ }
+
+ @Test
+ void shouldStoreAwsMessageIdInHeaderWhenConvertMessageIdToUuidIsFalse() {
+ SqsHeaderMapper mapper = new SqsHeaderMapper();
+ mapper.setConvertMessageIdToUuid(false);
+ String nonUuidMessageId = "92898073-7bd6a160-5797b060-54a7e539";
+ Message message = Message.builder().body("payload").messageId(nonUuidMessageId).build();
+ MessageHeaders headers = mapper.toHeaders(message);
+ assertThat(headers.get(SqsHeaders.SQS_RAW_MESSAGE_ID_HEADER)).isEqualTo(nonUuidMessageId);
+ assertThat(headers.getId()).isNotEqualTo(nonUuidMessageId);
+ assertThat(headers.getId()).isNotNull();
+ }
+
private static Stream validArguments() {
return Stream.of(Arguments.of("10", "Number", BigDecimal.valueOf(10)),
Arguments.of("3", "Number.byte", (byte) 3), Arguments.of("3", "Number.Byte", (byte) 3),