Skip to content

Commit 1bb6225

Browse files
committed
Add SQSListener argument resolver to extract the subject of an SNS message
1 parent 425b6c8 commit 1bb6225

File tree

5 files changed

+185
-4
lines changed

5 files changed

+185
-4
lines changed

spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/ConfigStoreRuntimeHints.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
5252

5353
if (ClassUtils.isPresent("io.awspring.cloud.s3.S3PropertySource", classLoader)) {
5454
hints.reflection().registerType(TypeReference.of(S3PropertySources.class),
55-
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
56-
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
55+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
56+
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
5757

5858
hints.reflection().registerType(TypeReference.of(S3PropertySource.class),
59-
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
60-
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
59+
hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
60+
MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS));
6161
}
6262
}
6363
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2013-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.sqs.annotation;
17+
18+
import java.lang.annotation.ElementType;
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
import java.lang.annotation.Target;
22+
23+
/**
24+
* Annotation that is used to map SNS notification subject on an SQS Queue to a variable that is annotated. Used in
25+
* Controllers method for handling/receiving SQS notifications.
26+
*
27+
* @author Alexander Nebel
28+
* @since 3.3.0
29+
*/
30+
@Retention(RetentionPolicy.RUNTIME)
31+
@Target(ElementType.PARAMETER)
32+
public @interface SnsNotificationSubject {
33+
34+
}

spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/annotation/SqsListenerAnnotationBeanPostProcessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.awspring.cloud.sqs.listener.SqsHeaders;
2323
import io.awspring.cloud.sqs.support.resolver.BatchVisibilityHandlerMethodArgumentResolver;
2424
import io.awspring.cloud.sqs.support.resolver.NotificationMessageArgumentResolver;
25+
import io.awspring.cloud.sqs.support.resolver.NotificationSubjectArgumentResolver;
2526
import io.awspring.cloud.sqs.support.resolver.QueueAttributesMethodArgumentResolver;
2627
import io.awspring.cloud.sqs.support.resolver.SqsMessageMethodArgumentResolver;
2728
import io.awspring.cloud.sqs.support.resolver.VisibilityHandlerMethodArgumentResolver;
@@ -84,6 +85,7 @@ protected Collection<HandlerMethodArgumentResolver> createAdditionalArgumentReso
8485
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(createAdditionalArgumentResolvers());
8586
if (objectMapper != null) {
8687
argumentResolvers.add(new NotificationMessageArgumentResolver(messageConverter, objectMapper));
88+
argumentResolvers.add(new NotificationSubjectArgumentResolver(objectMapper));
8789
}
8890
return argumentResolvers;
8991
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2013-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.sqs.support.converter;
17+
18+
import com.fasterxml.jackson.databind.JsonNode;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import java.util.List;
21+
import org.springframework.lang.Nullable;
22+
import org.springframework.messaging.Message;
23+
import org.springframework.messaging.MessageHeaders;
24+
import org.springframework.messaging.converter.MessageConversionException;
25+
import org.springframework.messaging.converter.SmartMessageConverter;
26+
import org.springframework.util.Assert;
27+
28+
/**
29+
* @author Alexander Nebel
30+
* @since 3.3.0
31+
*/
32+
public class SnsSubjectConverter implements SmartMessageConverter {
33+
34+
private final ObjectMapper jsonMapper;
35+
36+
public SnsSubjectConverter(ObjectMapper jsonMapper) {
37+
Assert.notNull(jsonMapper, "jsonMapper must not be null");
38+
this.jsonMapper = jsonMapper;
39+
}
40+
41+
@Override
42+
@SuppressWarnings("unchecked")
43+
public Object fromMessage(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
44+
Assert.notNull(message, "message must not be null");
45+
Assert.notNull(targetClass, "target class must not be null");
46+
47+
Object payload = message.getPayload();
48+
49+
if (!targetClass.isAssignableFrom(String.class)) {
50+
throw new MessageConversionException("Subject can only be injected into String assignable Types", null);
51+
}
52+
if (payload instanceof List) {
53+
throw new MessageConversionException("Conversion of List is not supported", null);
54+
}
55+
56+
JsonNode jsonNode;
57+
try {
58+
jsonNode = this.jsonMapper.readTree(message.getPayload().toString());
59+
}
60+
catch (Exception e) {
61+
throw new MessageConversionException("Could not read JSON", e);
62+
}
63+
if (!jsonNode.has("Type")) {
64+
throw new MessageConversionException(
65+
"Payload: '" + message.getPayload() + "' does not contain a Type attribute", null);
66+
}
67+
68+
if (!"Notification".equals(jsonNode.get("Type").asText())) {
69+
throw new MessageConversionException("Payload: '" + message.getPayload() + "' is not a valid notification",
70+
null);
71+
}
72+
73+
if (!jsonNode.has("Subject")) {
74+
throw new MessageConversionException("Payload: '" + message.getPayload() + "' does not contain a subject",
75+
null);
76+
}
77+
78+
return jsonNode.get("Subject").asText();
79+
}
80+
81+
@Override
82+
public Object fromMessage(Message<?> message, Class<?> targetClass) {
83+
return fromMessage(message, targetClass, null);
84+
}
85+
86+
@Override
87+
public Message<?> toMessage(Object payload, MessageHeaders headers) {
88+
throw new UnsupportedOperationException(
89+
"This converter only supports reading a SNS notification and not writing them");
90+
}
91+
92+
@Override
93+
public Message<?> toMessage(Object payload, MessageHeaders headers, Object conversionHint) {
94+
throw new UnsupportedOperationException(
95+
"This converter only supports reading a SNS notification and not writing them");
96+
}
97+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2013-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.sqs.support.resolver;
17+
18+
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import io.awspring.cloud.sqs.annotation.SnsNotificationSubject;
20+
import io.awspring.cloud.sqs.support.converter.SnsSubjectConverter;
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.messaging.Message;
23+
import org.springframework.messaging.converter.SmartMessageConverter;
24+
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
25+
26+
/**
27+
* @author Alexander Nebel
28+
* @since 3.3.0
29+
*/
30+
public class NotificationSubjectArgumentResolver implements HandlerMethodArgumentResolver {
31+
32+
private final SmartMessageConverter converter;
33+
34+
public NotificationSubjectArgumentResolver(ObjectMapper jsonMapper) {
35+
this.converter = new SnsSubjectConverter(jsonMapper);
36+
}
37+
38+
@Override
39+
public boolean supportsParameter(MethodParameter parameter) {
40+
return parameter.hasParameterAnnotation(SnsNotificationSubject.class);
41+
}
42+
43+
@Override
44+
public Object resolveArgument(MethodParameter par, Message<?> msg) {
45+
return converter.fromMessage(msg, par.getParameterType(), par);
46+
}
47+
48+
}

0 commit comments

Comments
 (0)