Skip to content

Commit 57e94c2

Browse files
Enable SqsMessagingMessageConverter to handle JSON Strings with JSON Mime Type (#1195)
* Enable SqsMessagingMessageConverter to handle JSON strings configured with the Application_JSON header * Polishing Closes #1144 --------- Co-authored-by: daniel kim <[email protected]>
1 parent 8581ffa commit 57e94c2

File tree

6 files changed

+111
-25
lines changed

6 files changed

+111
-25
lines changed

spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfigurationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ void configuresFactoryComponentsAndOptions() {
178178
.asInstanceOf(type(CompositeMessageConverter.class))
179179
.extracting(CompositeMessageConverter::getConverters)
180180
.isInstanceOfSatisfying(List.class, converters ->
181-
assertThat(converters.get(1)).isInstanceOfSatisfying(
181+
assertThat(converters.get(2)).isInstanceOfSatisfying(
182182
MappingJackson2MessageConverter.class,
183183
jackson2MessageConverter ->
184184
assertThat(jackson2MessageConverter.getObjectMapper().getRegisteredModuleIds()).contains("jackson-datatype-jsr310")));
@@ -205,7 +205,7 @@ void configuresFactoryComponentsAndOptionsWithDefaults() {
205205
.asInstanceOf(type(CompositeMessageConverter.class))
206206
.extracting(CompositeMessageConverter::getConverters)
207207
.isInstanceOfSatisfying(List.class, converters ->
208-
assertThat(converters.get(1)).isInstanceOfSatisfying(
208+
assertThat(converters.get(2)).isInstanceOfSatisfying(
209209
MappingJackson2MessageConverter.class,
210210
jackson2MessageConverter ->
211211
assertThat(jackson2MessageConverter.getObjectMapper().getRegisteredModuleIds()).isEmpty()));

spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/AbstractMessagingMessageConverter.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,8 @@
3131
import org.springframework.messaging.converter.CompositeMessageConverter;
3232
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
3333
import org.springframework.messaging.converter.MessageConverter;
34+
import org.springframework.messaging.converter.SimpleMessageConverter;
35+
import org.springframework.messaging.converter.SmartMessageConverter;
3436
import org.springframework.messaging.converter.StringMessageConverter;
3537
import org.springframework.messaging.support.MessageBuilder;
3638
import org.springframework.util.Assert;
@@ -39,6 +41,8 @@
3941
* Base {@link MessagingMessageConverter} implementation.
4042
*
4143
* @author Tomaz Fernandes
44+
* @author Dongha Kim
45+
*
4246
* @since 3.0
4347
* @see SqsHeaderMapper
4448
* @see SqsMessageConversionContext
@@ -215,14 +219,17 @@ public MessageConversionContext createMessageConversionContext() {
215219
public S fromMessagingMessage(Message<?> message, @Nullable MessageConversionContext context) {
216220
// We must make sure the message id stays consistent throughout this process
217221
MessageHeaders headers = getMessageHeaders(message);
218-
Message<?> convertedMessage = Objects.requireNonNull(
219-
this.payloadMessageConverter.toMessage(message.getPayload(), message.getHeaders()),
220-
() -> "payloadMessageConverter returned null message for message " + message);
222+
Message<?> convertedMessage = convertPayload(message, message.getPayload());
221223
MessageHeaders completeHeaders = MessageHeaderUtils.addHeadersIfAbsent(headers, convertedMessage.getHeaders());
222224
S messageWithHeaders = this.headerMapper.fromHeaders(completeHeaders);
223225
return doConvertMessage(messageWithHeaders, convertedMessage.getPayload());
224226
}
225227

228+
private Message<?> convertPayload(Message<?> message, Object payload) {
229+
return Objects.requireNonNull(this.payloadMessageConverter.toMessage(payload, message.getHeaders()),
230+
() -> "payloadMessageConverter returned null message for message " + message);
231+
}
232+
226233
private MessageHeaders getMessageHeaders(Message<?> message) {
227234
String typeHeaderName = this.payloadTypeHeaderFunction.apply(message);
228235
return typeHeaderName != null
@@ -234,11 +241,18 @@ private MessageHeaders getMessageHeaders(Message<?> message) {
234241

235242
private CompositeMessageConverter createDefaultCompositeMessageConverter() {
236243
List<MessageConverter> messageConverters = new ArrayList<>();
244+
messageConverters.add(createClassMatchingMessageConverter());
237245
messageConverters.add(createStringMessageConverter());
238246
messageConverters.add(createDefaultMappingJackson2MessageConverter());
239247
return new CompositeMessageConverter(messageConverters);
240248
}
241249

250+
private SimpleClassMatchingMessageConverter createClassMatchingMessageConverter() {
251+
SimpleClassMatchingMessageConverter matchingMessageConverter = new SimpleClassMatchingMessageConverter();
252+
matchingMessageConverter.setSerializedPayloadClass(String.class);
253+
return matchingMessageConverter;
254+
}
255+
242256
private StringMessageConverter createStringMessageConverter() {
243257
StringMessageConverter stringMessageConverter = new StringMessageConverter();
244258
stringMessageConverter.setSerializedPayloadClass(String.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.awspring.cloud.sqs.support.converter;
2+
3+
import org.springframework.lang.Nullable;
4+
import org.springframework.messaging.Message;
5+
import org.springframework.messaging.MessageHeaders;
6+
import org.springframework.messaging.converter.AbstractMessageConverter;
7+
import org.springframework.messaging.converter.SmartMessageConverter;
8+
9+
/**
10+
* {@link SmartMessageConverter} implementation that returns the payload unchanged if the target class
11+
* for Serialization / Deserialization matches the payload class.
12+
*
13+
* @author Tomaz Fernandes
14+
* @since 3.3
15+
*/
16+
public class SimpleClassMatchingMessageConverter extends AbstractMessageConverter {
17+
18+
@Override
19+
protected boolean supports(Class<?> clazz) {
20+
return true;
21+
}
22+
23+
@Nullable
24+
@Override
25+
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
26+
Object payload = message.getPayload();
27+
return payload.getClass().isAssignableFrom(targetClass) ? payload : null;
28+
}
29+
30+
@Nullable
31+
@Override
32+
protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) {
33+
return payload.getClass().isAssignableFrom(getSerializedPayloadClass()) ? payload : null;
34+
}
35+
}

spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/support/converter/SqsMessagingMessageConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
* {@link software.amazon.awssdk.services.sqs.model.Message} instances to Spring Messaging {@link Message} instances.
2424
*
2525
* @author Tomaz Fernandes
26+
* @author Dongha kim
2627
* @since 3.0
2728
* @see SqsHeaderMapper
2829
* @see SqsMessageConversionContext

spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/integration/SqsMessageConversionIntegrationTests.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2022 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@
4646
import org.springframework.context.annotation.Configuration;
4747
import org.springframework.context.annotation.Import;
4848
import org.springframework.messaging.Message;
49+
import org.springframework.messaging.MessageHeaders;
4950
import org.springframework.messaging.handler.annotation.Header;
5051
import org.springframework.messaging.support.MessageBuilder;
5152
import org.springframework.util.Assert;
@@ -58,6 +59,7 @@
5859
*
5960
* @author Tomaz Fernandes
6061
* @author Mikhail Strokov
62+
* @author Dongha Kim
6163
* @author Wei Jiang
6264
*/
6365
@SpringBootTest
@@ -172,6 +174,20 @@ private Map<String, Object> getHeaderMapping(Class<?> clazz) {
172174
return Collections.singletonMap(SqsHeaders.SQS_DEFAULT_TYPE_HEADER, clazz.getName());
173175
}
174176

177+
@Test
178+
void shouldSendAndReceiveJsonString() throws Exception {
179+
String messageBody = """
180+
{
181+
"firstField": "hello",
182+
"secondField": "sqs!"
183+
}
184+
""";
185+
sqsTemplate.send(to -> to.queue(RESOLVES_POJO_TYPES_QUEUE_NAME).payload(messageBody).header(MessageHeaders.CONTENT_TYPE, "application/json"));
186+
logger.debug("Sent message to queue {} with messageBody {}", RESOLVES_POJO_TYPES_QUEUE_NAME, messageBody);
187+
assertThat(latchContainer.resolvesPojoLatch.await(10, TimeUnit.SECONDS)).isTrue();
188+
}
189+
190+
175191
static class ResolvesPojoListener {
176192

177193
@Autowired

spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/integration/SqsTemplateIntegrationTests.java

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2023 the original author or authors.
2+
* Copyright 2013-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,24 +15,10 @@
1515
*/
1616
package io.awspring.cloud.sqs.integration;
1717

18-
import static org.assertj.core.api.Assertions.assertThat;
19-
2018
import io.awspring.cloud.sqs.listener.SqsHeaders;
2119
import io.awspring.cloud.sqs.listener.acknowledgement.Acknowledgement;
22-
import io.awspring.cloud.sqs.operations.SendResult;
23-
import io.awspring.cloud.sqs.operations.SqsOperations;
24-
import io.awspring.cloud.sqs.operations.SqsTemplate;
25-
import io.awspring.cloud.sqs.operations.SqsTemplateParameters;
26-
import io.awspring.cloud.sqs.operations.TemplateAcknowledgementMode;
20+
import io.awspring.cloud.sqs.operations.*;
2721
import io.awspring.cloud.sqs.support.converter.AbstractMessagingMessageConverter;
28-
import java.time.Duration;
29-
import java.util.ArrayList;
30-
import java.util.Collection;
31-
import java.util.List;
32-
import java.util.Map;
33-
import java.util.Optional;
34-
import java.util.concurrent.CompletableFuture;
35-
import java.util.stream.IntStream;
3622
import org.assertj.core.api.InstanceOfAssertFactories;
3723
import org.junit.jupiter.api.BeforeAll;
3824
import org.junit.jupiter.api.Test;
@@ -41,13 +27,22 @@
4127
import org.springframework.context.annotation.Bean;
4228
import org.springframework.context.annotation.Configuration;
4329
import org.springframework.messaging.Message;
30+
import org.springframework.messaging.MessageHeaders;
4431
import org.springframework.messaging.support.MessageBuilder;
4532
import org.springframework.util.StopWatch;
4633
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
4734
import software.amazon.awssdk.services.sqs.model.QueueAttributeName;
4835

36+
import java.time.Duration;
37+
import java.util.*;
38+
import java.util.concurrent.CompletableFuture;
39+
import java.util.stream.IntStream;
40+
41+
import static org.assertj.core.api.Assertions.assertThat;
42+
4943
/**
5044
* @author Tomaz Fernandes
45+
* @author Dongha Kim
5146
*/
5247
@SpringBootTest
5348
public class SqsTemplateIntegrationTests extends BaseSqsIntegrationTest {
@@ -78,6 +73,8 @@ public class SqsTemplateIntegrationTests extends BaseSqsIntegrationTest {
7873

7974
private static final String HANDLES_CONTENT_DEDUPLICATION_QUEUE_NAME = "handles-content-deduplication-queue.fifo";
8075

76+
private static final String SENDS_AND_RECEIVES_JSON_MESSAGE_QUEUE_NAME = "send-receive-json-message-queue";
77+
8178
@Autowired
8279
private SqsAsyncClient asyncClient;
8380

@@ -92,7 +89,9 @@ static void beforeTests() {
9289
createQueue(client, RECORD_WITHOUT_TYPE_HEADER_QUEUE_NAME),
9390
createQueue(client, RETURNS_ON_PARTIAL_BATCH_QUEUE_NAME),
9491
createQueue(client, THROWS_ON_PARTIAL_BATCH_QUEUE_NAME),
95-
createQueue(client, SENDS_AND_RECEIVES_MANUAL_ACK_QUEUE_NAME), createQueue(client, EMPTY_QUEUE_NAME),
92+
createQueue(client, SENDS_AND_RECEIVES_JSON_MESSAGE_QUEUE_NAME),
93+
createQueue(client, SENDS_AND_RECEIVES_MANUAL_ACK_QUEUE_NAME),
94+
createQueue(client, EMPTY_QUEUE_NAME),
9695
createFifoQueue(client, SENDS_AND_RECEIVES_MESSAGE_FIFO_QUEUE_NAME),
9796
createFifoQueue(client, SENDS_AND_RECEIVES_BATCH_FIFO_QUEUE_NAME),
9897
createFifoQueue(client, HANDLES_CONTENT_DEDUPLICATION_QUEUE_NAME,
@@ -187,6 +186,27 @@ void shouldSendAndReceiveWithManualAcknowledgement() {
187186
assertThat(receivedMessage3).isEmpty();
188187
}
189188

189+
@Test
190+
void shouldSendAndReceiveJsonString() {
191+
SqsOperations template = SqsTemplate.builder()
192+
.sqsAsyncClient(this.asyncClient)
193+
.configureDefaultConverter(AbstractMessagingMessageConverter::doNotSendPayloadTypeHeader)
194+
.buildSyncTemplate();
195+
String jsonString = """
196+
{
197+
"propertyOne": "hello",
198+
"propertyTwo": "sqs!"
199+
}
200+
""";
201+
SampleRecord expectedPayload = new SampleRecord("hello", "sqs!");
202+
SendResult<Object> result = template.send(to -> to.queue(SENDS_AND_RECEIVES_JSON_MESSAGE_QUEUE_NAME)
203+
.payload(jsonString).header(MessageHeaders.CONTENT_TYPE, "application/json"));
204+
assertThat(result).isNotNull();
205+
Optional<Message<SampleRecord>> receivedMessage = template
206+
.receive(from -> from.queue(SENDS_AND_RECEIVES_JSON_MESSAGE_QUEUE_NAME), SampleRecord.class);
207+
assertThat(receivedMessage).isPresent().get().extracting(Message::getPayload).isEqualTo(expectedPayload);
208+
}
209+
190210
@Test
191211
void shouldSendAndReceiveBatch() {
192212
SqsOperations template = SqsTemplate.builder().sqsAsyncClient(this.asyncClient)

0 commit comments

Comments
 (0)