• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JaidenAshmore / java-dynamic-sqs-listener / #2000

pending completion
#2000

push

github-actions

web-flow
#refs 395: upgrade to Java 17, Spring Boot 3 and other dependencies (#397)

236 of 236 new or added lines in 21 files covered. (100.0%)

2180 of 2263 relevant lines covered (96.33%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.37
/core/src/main/java/com/jashmore/sqs/argument/attribute/MessageAttributeArgumentResolver.java
1
package com.jashmore.sqs.argument.attribute;
2

3
import com.fasterxml.jackson.databind.ObjectMapper;
4
import com.jashmore.sqs.QueueProperties;
5
import com.jashmore.sqs.argument.ArgumentResolutionException;
6
import com.jashmore.sqs.argument.ArgumentResolver;
7
import com.jashmore.sqs.argument.MethodParameter;
8
import com.jashmore.sqs.util.annotation.AnnotationUtils;
9
import java.io.IOException;
10
import java.nio.charset.StandardCharsets;
11
import java.util.Optional;
12
import software.amazon.awssdk.services.sqs.model.Message;
13
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
14

15
/**
16
 * An {@link ArgumentResolver} that is able to handle the extraction of information from the attributes of the SQS message.
17
 *
18
 * <p>This will attempt to do its best in casting the contents of the message attribute to the type of tha parameter. For example, if the contents of
19
 * the message attribute is binary (byte[]) and the parameter is a POJO, it will attempt to serialise using the {@link ObjectMapper#readValue(byte[], Class)}.
20
 *
21
 * <p>It is the responsibility of the consumer to make sure that the type of the parameter is correct in regards to the content of the message attribute. For
22
 * example, this resolver ignores all helper data types after the main, e.g. Number.float data types will have the float ignored.
23
 *
24
 * <p>This current implementation uses the Jackson {@link ObjectMapper} to perform all of the parsing and there is the potential for a future version of this
25
 * library to split this out so that Jackson isn't a required dependency.
26
 *
27
 * @see <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-attributes.html">SQS Message Attributes</a>
28
 * @see MessageAttributeValue
29
 */
30
public class MessageAttributeArgumentResolver implements ArgumentResolver<Object> {
31

32
    private final ObjectMapper objectMapper;
33

34
    public MessageAttributeArgumentResolver(final ObjectMapper objectMapper) {
1✔
35
        this.objectMapper = objectMapper;
1✔
36
    }
1✔
37

38
    @Override
39
    public boolean canResolveParameter(final MethodParameter methodParameter) {
40
        return AnnotationUtils.findParameterAnnotation(methodParameter, MessageAttribute.class).isPresent();
1✔
41
    }
42

43
    @Override
44
    public Object resolveArgumentForParameter(
45
        final QueueProperties queueProperties,
46
        final MethodParameter methodParameter,
47
        final Message message
48
    ) throws ArgumentResolutionException {
49
        final MessageAttribute annotation = AnnotationUtils
1✔
50
            .findParameterAnnotation(methodParameter, MessageAttribute.class)
1✔
51
            .orElseThrow(() ->
1✔
52
                new ArgumentResolutionException("Parameter passed in does not contain the MessageAttribute annotation when it should")
×
53
            );
54

55
        final String attributeName = annotation.value();
1✔
56

57
        final Optional<MessageAttributeValue> optionalMessageAttributeValue = Optional.ofNullable(
1✔
58
            message.messageAttributes().get(attributeName)
1✔
59
        );
60

61
        if (!optionalMessageAttributeValue.isPresent()) {
1✔
62
            if (annotation.required()) {
1✔
63
                throw new ArgumentResolutionException("Required Message Attribute '" + attributeName + "' is missing from message");
1✔
64
            }
65

66
            return null;
1✔
67
        }
68

69
        final MessageAttributeValue messageAttributeValue = optionalMessageAttributeValue.get();
1✔
70

71
        if (
1✔
72
            messageAttributeValue.dataType().startsWith(MessageAttributeDataTypes.STRING.getValue()) ||
1✔
73
            messageAttributeValue.dataType().startsWith(MessageAttributeDataTypes.NUMBER.getValue())
1✔
74
        ) {
75
            return handleStringParameterValue(methodParameter, messageAttributeValue, attributeName);
1✔
76
        } else if (messageAttributeValue.dataType().startsWith(MessageAttributeDataTypes.BINARY.getValue())) {
1✔
77
            return handleByteParameterValue(methodParameter, messageAttributeValue);
1✔
78
        }
79

80
        throw new ArgumentResolutionException(
1✔
81
            "Cannot parse message attribute due to unknown data type '" + messageAttributeValue.dataType() + "'"
1✔
82
        );
83
    }
84

85
    /**
86
     * Handle resolving the argument from the string contents of the attribute.
87
     *
88
     * @param methodParameter       the parameter of the method to resolve
89
     * @param messageAttributeValue the value of the message attribute
90
     * @param attributeName         the name of the attribute that is being consumed
91
     * @return the resolved argument from the attribute
92
     */
93
    private Object handleStringParameterValue(
94
        final MethodParameter methodParameter,
95
        final MessageAttributeValue messageAttributeValue,
96
        final String attributeName
97
    ) {
98
        if (methodParameter.getParameter().getType().isAssignableFrom(String.class)) {
1✔
99
            return messageAttributeValue.stringValue();
1✔
100
        }
101

102
        try {
103
            return objectMapper.readValue(messageAttributeValue.stringValue(), methodParameter.getParameter().getType());
1✔
104
        } catch (final IOException ioException) {
1✔
105
            throw new ArgumentResolutionException("Error parsing Message Attribute '" + attributeName + "'", ioException);
1✔
106
        }
107
    }
108

109
    /**
110
     * Handle an attribute that contains the data as bytes.
111
     *
112
     * @param methodParameter       details about the parameter to parse the message attribute into
113
     * @param messageAttributeValue value of the message attribute
114
     * @return the argument resolved for this attribute
115
     */
116
    private Object handleByteParameterValue(final MethodParameter methodParameter, final MessageAttributeValue messageAttributeValue) {
117
        final byte[] byteArray = messageAttributeValue.binaryValue().asByteArray();
1✔
118
        final Class<?> parameterClass = methodParameter.getParameter().getType();
1✔
119
        if (parameterClass == byte[].class) {
1✔
120
            return byteArray;
1✔
121
        }
122

123
        if (parameterClass.isAssignableFrom(String.class)) {
1✔
124
            return new String(byteArray, StandardCharsets.UTF_8);
1✔
125
        }
126

127
        try {
128
            return objectMapper.readValue(byteArray, parameterClass);
1✔
129
        } catch (final IOException ioException) {
1✔
130
            throw new ArgumentResolutionException("Failure to parse binary bytes to '" + parameterClass.getName() + "'", ioException);
1✔
131
        }
132
    }
133
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc