├── .github
└── dependabot.yml
├── .gitignore
├── LICENSE.txt
├── NOTICE.txt
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── amazon
│ └── sqs
│ └── javamessaging
│ ├── AmazonSQSMessagingClientWrapper.java
│ ├── PrefetchManager.java
│ ├── ProviderConfiguration.java
│ ├── SQSConnection.java
│ ├── SQSConnectionFactory.java
│ ├── SQSConnectionMetaData.java
│ ├── SQSMessageConsumer.java
│ ├── SQSMessageConsumerPrefetch.java
│ ├── SQSMessageProducer.java
│ ├── SQSMessagingClientConstants.java
│ ├── SQSQueueDestination.java
│ ├── SQSSession.java
│ ├── SQSSessionCallbackScheduler.java
│ ├── acknowledge
│ ├── AcknowledgeMode.java
│ ├── Acknowledger.java
│ ├── AutoAcknowledger.java
│ ├── BulkSQSOperation.java
│ ├── NegativeAcknowledger.java
│ ├── RangedAcknowledger.java
│ ├── SQSMessageIdentifier.java
│ └── UnorderedAcknowledger.java
│ ├── message
│ ├── SQSBytesMessage.java
│ ├── SQSMessage.java
│ ├── SQSObjectMessage.java
│ └── SQSTextMessage.java
│ └── util
│ ├── ExponentialBackoffStrategy.java
│ ├── SQSMessagingClientThreadFactory.java
│ └── SQSMessagingClientUtil.java
└── test
└── java
└── com
└── amazon
└── sqs
└── javamessaging
├── AcknowledgerCommon.java
├── AmazonSQSMessagingClientWrapperTest.java
├── AutoAcknowledgerTest.java
├── BulkSQSOperationTest.java
├── ExponentialBackoffStrategyTest.java
├── MessageListenerConcurrentOperationTest.java
├── ModifyWaitTimeSecondsTest.java
├── NegativeAcknowledgerTest.java
├── RangedAcknowledgerTest.java
├── SQSConnectionFactoryTest.java
├── SQSConnectionTest.java
├── SQSMessageConsumerPrefetchFifoTest.java
├── SQSMessageConsumerPrefetchTest.java
├── SQSMessageConsumerTest.java
├── SQSMessageProducerFifoTest.java
├── SQSMessageProducerTest.java
├── SQSMessagingClientUtilTest.java
├── SQSQueueDestinationTest.java
├── SQSSessionCallbackSchedulerTest.java
├── SQSSessionTest.java
├── UnorderedAcknowledgerTest.java
└── message
├── SQSBytesMessageTest.java
├── SQSMessageTest.java
├── SQSObjectMessageTest.java
└── SQSTextMessageTest.java
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | - package-ecosystem: "github-actions"
8 | directory: "/"
9 | schedule:
10 | interval: "daily"
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea/
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 |
4 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
5 |
6 | 1. Definitions.
7 |
8 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
9 |
10 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
11 |
12 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
13 |
14 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
15 |
16 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
17 |
18 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
19 |
20 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
21 |
22 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
23 |
24 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
25 |
26 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
27 |
28 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
29 |
30 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
31 |
32 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
33 |
34 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
35 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and
36 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
37 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
38 |
39 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
40 |
41 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
42 |
43 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
44 |
45 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
46 |
47 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
48 |
49 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
50 |
51 | END OF TERMS AND CONDITIONS
52 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Amazon SQS Java Messaging Library
2 | Copyright 2010-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 |
4 | This product includes software developed by
5 | Amazon Technologies, Inc (http://www.amazon.com/).
6 |
7 | **********************
8 | THIRD PARTY COMPONENTS
9 | **********************
10 | This software includes third party software subject to the following copyrights:
11 | - Apache ActiveMQ (see: org.apache.activemq.util.TypeConversionSupport, org.apache.activemq.command.ActiveMQStreamMessage). Copyright 2005-2013 Apache Software Foundation
12 |
13 | The licenses for these third party components are included in LICENSE.txt
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Amazon SQS Java Messaging Library
2 | ========================================
3 | The **Amazon SQS Java Messaging Library** holds the Java Message Service compatible classes, that are used
4 | for communicating with Amazon Simple Queue Service. This project builds on top of the AWS SDK for Java to use Amazon SQS as the JMS (as defined in Jakarta [3.1 specification](https://jakarta.ee/specifications/messaging/3.1/)) provider for the messaging applications without running any additional software.
5 |
6 | * You can download release builds through the [releases section of this](https://github.com/awslabs/amazon-sqs-java-messaging-lib) project.
7 | * For more information on using the amazon-sqs-java-messaging-lib, see our getting started guide to SQS [here](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/jmsclient.html).
8 |
9 | ## Getting Started
10 |
11 | * **Sign up for AWS** — Before you begin, you need an AWS account. For more information about creating an AWS account and retrieving your AWS credentials, see [AWS Account and Credentials](http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-setup.html) in the AWS SDK for Java Developer Guide.
12 | * **Minimum requirements** — To use the sample application, you'll need Java 8 (or later) and [Maven 3](http://maven.apache.org/). For more information about the requirements, see the [Getting Started](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/jmsclient.html) section of the Amazon SQS Developer Guide.
13 | * **Download** — Download the [latest release](https://github.com/awslabs/amazon-sqs-java-messaging-lib/releases) or pick it up from Maven:
14 | ```xml
15 |
16 | com.amazonaws
17 | amazon-sqs-java-messaging-lib
18 | 2.1.4
19 | jar
20 |
21 | ```
22 | * **Further information** - Read the [API documentation](http://aws.amazon.com/documentation/sqs/).
23 |
24 | ## Feedback
25 | * Give us feedback [here](https://github.com/awslabs/amazon-sqs-java-messaging-lib/issues).
26 | * If you'd like to contribute a new feature or bug fix, we'd love to see GitHub pull requests from you.
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.amazonaws
5 | amazon-sqs-java-messaging-lib
6 | 2.1.4
7 | Amazon SQS Java Messaging Library
8 | The Amazon SQS Java Messaging Library holds the Java Message Service compatible classes, that are used
9 | for communicating with Amazon Simple Queue Service.
10 |
11 | https://github.com/awslabs/amazon-sqs-java-messaging-lib
12 |
13 | https://github.com/awslabs/amazon-sqs-java-messaging-lib.git
14 |
15 |
16 |
17 | Apache License, Version 2.0
18 | https://aws.amazon.com/apache2.0
19 | repo
20 |
21 |
22 |
23 |
24 | amazonwebservices
25 | Amazon Web Services
26 | https://aws.amazon.com
27 |
28 | developer
29 |
30 |
31 |
32 |
33 | 2.29.24
34 | UTF-8
35 | UTF-8
36 |
37 |
38 |
39 |
40 | software.amazon.awssdk
41 | sqs
42 | ${aws-java-sdk.version}
43 |
44 |
45 | software.amazon.awssdk
46 | auth
47 | ${aws-java-sdk.version}
48 |
49 |
50 | software.amazon.awssdk
51 | utils
52 | ${aws-java-sdk.version}
53 |
54 |
55 |
56 | org.slf4j
57 | slf4j-api
58 | 2.0.13
59 |
60 |
61 | jakarta.jms
62 | jakarta.jms-api
63 | 3.1.0
64 |
65 |
66 | org.junit.jupiter
67 | junit-jupiter
68 | 5.10.3
69 | test
70 |
71 |
72 | org.assertj
73 | assertj-core
74 | 3.26.3
75 | test
76 |
77 |
78 | org.hamcrest
79 | hamcrest
80 | 2.2
81 | test
82 |
83 |
84 | org.mockito
85 | mockito-core
86 | 5.11.0
87 | test
88 |
89 |
90 | org.mockito
91 | mockito-junit-jupiter
92 | 5.11.0
93 | test
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | org.apache.maven.plugins
102 | maven-compiler-plugin
103 | 3.13.0
104 |
105 | 17
106 | 17
107 | UTF-8
108 |
109 |
110 |
111 | org.apache.maven.plugins
112 | maven-failsafe-plugin
113 | 3.2.5
114 |
115 |
116 |
117 | integration-test
118 | verify
119 |
120 |
121 |
122 |
123 |
124 | org.apache.maven.plugins
125 | maven-surefire-plugin
126 | 3.2.5
127 |
128 |
129 |
130 |
131 |
132 | org.apache.maven.plugins
133 | maven-source-plugin
134 | 3.3.0
135 |
136 |
137 | attach-sources
138 |
139 | jar-no-fork
140 |
141 |
142 |
143 |
144 |
145 | org.apache.maven.plugins
146 | maven-javadoc-plugin
147 | 3.6.3
148 |
149 |
150 | attach-javadocs
151 |
152 | jar
153 |
154 |
155 | none
156 |
157 |
158 |
159 |
160 |
161 | org.sonatype.plugins
162 | nexus-staging-maven-plugin
163 | 1.6.13
164 | true
165 |
166 | ossrh
167 | https://aws.oss.sonatype.org
168 | true
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | publishing
177 |
178 |
179 |
180 | ossrh
181 | https://oss.sonatype.org/content/repositories/snapshots
182 |
183 |
184 | ossrh
185 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
186 |
187 |
188 |
189 |
190 |
191 |
192 | org.apache.maven.plugins
193 | maven-gpg-plugin
194 | 3.1.0
195 |
196 |
197 | sign-artifacts
198 | verify
199 |
200 | sign
201 |
202 |
203 |
204 | --pinentry-mode
205 | loopback
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/PrefetchManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | /**
18 | * This interface is helper to notify when the prefetchThread should be resuming
19 | * messages.
20 | */
21 | public interface PrefetchManager {
22 |
23 | /**
24 | * Notify the prefetchThread that the message is dispatched from
25 | * messageQueue when user calls for receive or message listener onMessage is
26 | * called.
27 | */
28 | void messageDispatched();
29 |
30 | /**
31 | * Notify the prefetchThread that the message listener has finished with any
32 | * previous message and is ready to accept another.
33 | */
34 | void messageListenerReady();
35 |
36 | /**
37 | * This is used to determine the state of the consumer, when the message
38 | * listener scheduler is processing the messages.
39 | *
40 | * @return The message consumer, which owns the prefetchThread
41 | */
42 | SQSMessageConsumer getMessageConsumer();
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/ProviderConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | public class ProviderConfiguration {
18 | private int numberOfMessagesToPrefetch;
19 |
20 | public ProviderConfiguration() {
21 | // Set default numberOfMessagesToPrefetch to MIN_BATCH.
22 | this.numberOfMessagesToPrefetch = SQSMessagingClientConstants.MIN_BATCH;
23 | }
24 |
25 | public int getNumberOfMessagesToPrefetch() {
26 | return numberOfMessagesToPrefetch;
27 | }
28 |
29 | public void setNumberOfMessagesToPrefetch(int numberOfMessagesToPrefetch) {
30 | if (numberOfMessagesToPrefetch < SQSMessagingClientConstants.MIN_PREFETCH) {
31 | throw new IllegalArgumentException(String.format("Invalid prefetch size. Provided value '%1$s' cannot be smaller than '%2$s'", numberOfMessagesToPrefetch, SQSMessagingClientConstants.MIN_PREFETCH));
32 | }
33 | this.numberOfMessagesToPrefetch = numberOfMessagesToPrefetch;
34 | }
35 |
36 | public ProviderConfiguration withNumberOfMessagesToPrefetch(int numberOfMessagesToPrefetch) {
37 | setNumberOfMessagesToPrefetch(numberOfMessagesToPrefetch);
38 | return this;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/SQSConnectionFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import jakarta.jms.ConnectionFactory;
18 | import jakarta.jms.JMSContext;
19 | import jakarta.jms.JMSException;
20 | import jakarta.jms.JMSRuntimeException;
21 | import jakarta.jms.QueueConnection;
22 | import jakarta.jms.QueueConnectionFactory;
23 | import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
24 | import software.amazon.awssdk.auth.credentials.AwsCredentials;
25 | import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
26 | import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
27 | import software.amazon.awssdk.services.sqs.SqsClient;
28 | import software.amazon.awssdk.services.sqs.SqsClientBuilder;
29 |
30 | import java.util.function.Supplier;
31 |
32 | /**
33 | * A ConnectionFactory object encapsulates a set of connection configuration
34 | * parameters for SqsClient as well as setting
35 | * numberOfMessagesToPrefetch.
36 | *
37 | * The numberOfMessagesToPrefetch parameter is used to size of the
38 | * prefetched messages, which can be tuned based on the application workload. It
39 | * helps in returning messages from internal buffers(if there is any) instead of
40 | * waiting for the SQS receiveMessage call to return.
41 | *
42 | * If more physical connections than the default maximum value (that is 50 as of
43 | * today) are needed on the connection pool,
44 | * {@link software.amazon.awssdk.core.client.config.ClientOverrideConfiguration} needs to be configured.
45 | *
46 | * None of the createConnection methods set-up the physical
47 | * connection to SQS, so validity of credentials are not checked with those
48 | * methods.
49 | */
50 |
51 | public class SQSConnectionFactory implements ConnectionFactory, QueueConnectionFactory {
52 | private final ProviderConfiguration providerConfiguration;
53 | private final Supplier amazonSQSClientSupplier;
54 |
55 | /**
56 | * Creates a SQSConnectionFactory that uses default ProviderConfiguration
57 | * and SqsClientBuilder.standard() for creating SqsClient connections.
58 | * Every SQSConnection will have its own copy of SqsClient.
59 | */
60 | public SQSConnectionFactory() {
61 | this(new ProviderConfiguration());
62 | }
63 |
64 | /**
65 | * Creates a SQSConnectionFactory that uses SqsClientBuilder.standard() for creating SqsClient connections.
66 | * Every SQSConnection will have its own copy of SqsClient.
67 | */
68 | public SQSConnectionFactory(ProviderConfiguration providerConfiguration) {
69 | this(providerConfiguration, SqsClient.create());
70 | }
71 |
72 | /**
73 | * Creates a SQSConnectionFactory that uses the provided SqsClient connection.
74 | * Every SQSConnection will use the same provided SqsClient.
75 | */
76 | public SQSConnectionFactory(ProviderConfiguration providerConfiguration, final SqsClient client) {
77 | if (providerConfiguration == null) {
78 | throw new IllegalArgumentException("Provider configuration cannot be null");
79 | }
80 | if (client == null) {
81 | throw new IllegalArgumentException("AmazonSQS client cannot be null");
82 | }
83 | this.providerConfiguration = providerConfiguration;
84 | this.amazonSQSClientSupplier = () -> client;
85 | }
86 |
87 | /**
88 | * Creates a SQSConnectionFactory that uses the provided SqsClientBuilder for creating AmazonSQS client connections.
89 | * Every SQSConnection will have its own copy of AmazonSQS client created through the provided builder.
90 | */
91 | public SQSConnectionFactory(ProviderConfiguration providerConfiguration, final SqsClientBuilder clientBuilder) {
92 | if (providerConfiguration == null) {
93 | throw new IllegalArgumentException("Provider configuration cannot be null");
94 | }
95 | if (clientBuilder == null) {
96 | throw new IllegalArgumentException("AmazonSQS client builder cannot be null");
97 | }
98 | this.providerConfiguration = providerConfiguration;
99 | this.amazonSQSClientSupplier = clientBuilder::build;
100 | }
101 |
102 |
103 | @Override
104 | public SQSConnection createConnection() throws JMSException {
105 | try {
106 | SqsClient amazonSQS = amazonSQSClientSupplier.get();
107 | return createConnection(amazonSQS, null);
108 | } catch (RuntimeException e) {
109 | throw (JMSException) new JMSException("Error creating SQS client: " + e.getMessage()).initCause(e);
110 | }
111 | }
112 |
113 | @Override
114 | public SQSConnection createConnection(String awsAccessKeyId, String awsSecretKey) throws JMSException {
115 | AwsBasicCredentials basicAWSCredentials = AwsBasicCredentials.create(awsAccessKeyId, awsSecretKey);
116 | return createConnection(basicAWSCredentials);
117 | }
118 |
119 | @Override
120 | public JMSContext createContext() {
121 | throw new JMSRuntimeException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
122 | }
123 |
124 | @Override
125 | public JMSContext createContext(String userName, String password) {
126 | throw new JMSRuntimeException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
127 | }
128 |
129 | @Override
130 | public JMSContext createContext(String userName, String password, int sessionMode) {
131 | throw new JMSRuntimeException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
132 | }
133 |
134 | @Override
135 | public JMSContext createContext(int sessionMode) {
136 | throw new JMSRuntimeException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
137 | }
138 |
139 | public SQSConnection createConnection(AwsCredentials awsCredentials) throws JMSException {
140 | AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider.create(awsCredentials);
141 | return createConnection(awsCredentialsProvider);
142 | }
143 |
144 | public SQSConnection createConnection(AwsCredentialsProvider awsCredentialsProvider) throws JMSException {
145 | try {
146 | SqsClient amazonSQS = amazonSQSClientSupplier.get();
147 | return createConnection(amazonSQS, awsCredentialsProvider);
148 | } catch (Exception e) {
149 | throw (JMSException) new JMSException("Error creating SQS client: " + e.getMessage()).initCause(e);
150 | }
151 | }
152 |
153 | private SQSConnection createConnection(SqsClient amazonSQS, AwsCredentialsProvider awsCredentialsProvider) throws JMSException {
154 | AmazonSQSMessagingClientWrapper amazonSQSClientJMSWrapper = new AmazonSQSMessagingClientWrapper(amazonSQS, awsCredentialsProvider);
155 | return new SQSConnection(amazonSQSClientJMSWrapper, providerConfiguration.getNumberOfMessagesToPrefetch());
156 | }
157 |
158 | @Override
159 | public QueueConnection createQueueConnection() throws JMSException {
160 | return createConnection();
161 | }
162 |
163 | @Override
164 | public QueueConnection createQueueConnection(String userName, String password) throws JMSException {
165 | return createConnection(userName, password);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/SQSConnectionMetaData.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import jakarta.jms.ConnectionMetaData;
18 | import jakarta.jms.JMSException;
19 |
20 | import java.util.Collections;
21 | import java.util.Enumeration;
22 | import java.util.List;
23 |
24 |
25 | public class SQSConnectionMetaData implements ConnectionMetaData {
26 |
27 | private final String jmsVersion;
28 | private final int jmsMajorVersion;
29 | private final int jmsMinorVersion;
30 |
31 | private final String jmsProviderName;
32 | private final String providerVersion;
33 | private final int providerMajorVersion;
34 | private final int providerMinorVersion;
35 |
36 | private final List jmsxProperty;
37 |
38 | SQSConnectionMetaData() {
39 | this.jmsVersion = "1.1";
40 | this.jmsMajorVersion = 1;
41 | this.jmsMinorVersion = 1;
42 |
43 | this.jmsProviderName = "Amazon";
44 | this.providerVersion = "1.0";
45 | this.providerMajorVersion = 1;
46 | this.providerMinorVersion = 0;
47 |
48 | this.jmsxProperty = List.of(SQSMessagingClientConstants.JMSX_DELIVERY_COUNT,
49 | SQSMessagingClientConstants.JMSX_GROUP_ID, SQSMessagingClientConstants.JMSX_GROUP_SEC);
50 | }
51 |
52 | @Override
53 | public String getJMSVersion() throws JMSException {
54 | return jmsVersion;
55 | }
56 |
57 | @Override
58 | public int getJMSMajorVersion() throws JMSException {
59 | return jmsMajorVersion;
60 | }
61 |
62 | @Override
63 | public int getJMSMinorVersion() throws JMSException {
64 | return jmsMinorVersion;
65 | }
66 |
67 | @Override
68 | public String getJMSProviderName() throws JMSException {
69 | return jmsProviderName;
70 | }
71 |
72 | @Override
73 | public String getProviderVersion() throws JMSException {
74 | return providerVersion;
75 | }
76 |
77 | @Override
78 | public int getProviderMajorVersion() throws JMSException {
79 | return providerMajorVersion;
80 | }
81 |
82 | @Override
83 | public int getProviderMinorVersion() throws JMSException {
84 | return providerMinorVersion;
85 | }
86 |
87 | @Override
88 | public Enumeration getJMSXPropertyNames() throws JMSException {
89 | return Collections.enumeration(jmsxProperty);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/SQSMessageConsumer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
18 | import com.amazon.sqs.javamessaging.acknowledge.NegativeAcknowledger;
19 | import com.amazon.sqs.javamessaging.acknowledge.SQSMessageIdentifier;
20 | import jakarta.jms.IllegalStateException;
21 | import jakarta.jms.JMSException;
22 | import jakarta.jms.Message;
23 | import jakarta.jms.MessageConsumer;
24 | import jakarta.jms.MessageListener;
25 | import jakarta.jms.Queue;
26 | import jakarta.jms.QueueReceiver;
27 | import org.slf4j.Logger;
28 | import org.slf4j.LoggerFactory;
29 |
30 | import java.util.List;
31 | import java.util.Set;
32 | import java.util.concurrent.ExecutorService;
33 | import java.util.concurrent.Executors;
34 | import java.util.concurrent.ThreadFactory;
35 | import java.util.concurrent.TimeUnit;
36 |
37 | /**
38 | * A client uses a MessageConsumer object to receive messages from a
39 | * destination. A MessageConsumer object is created by passing a Destination
40 | * object to a message-consumer creation method supplied by a session.
41 | *
42 | * This message consumer does not support message selectors
43 | *
44 | * A client may either synchronously receive a message consumer's messages or
45 | * have the consumer asynchronously deliver them as they arrive via registering
46 | * a MessageListener object.
47 | *
48 | * The message consumer creates a background thread to prefetch the messages to
49 | * improve the receive turn-around times.
50 | */
51 | public class SQSMessageConsumer implements MessageConsumer, QueueReceiver {
52 | private static final Logger LOG = LoggerFactory.getLogger(SQSMessageConsumer.class);
53 | public static final int PREFETCH_EXECUTOR_GRACEFUL_SHUTDOWN_TIME = 30;
54 |
55 | protected volatile boolean closed = false;
56 |
57 | private final SQSQueueDestination sqsDestination;
58 | private final SQSSession parentSQSSession;
59 |
60 | private final SQSSessionCallbackScheduler sqsSessionRunnable;
61 |
62 | /**
63 | * Executor for prefetch thread.
64 | */
65 | private final ExecutorService prefetchExecutor;
66 |
67 | /**
68 | * Prefetch Runnable. This includes keeping internal message buffer filled and call MessageListener if set.
69 | */
70 | private final SQSMessageConsumerPrefetch sqsMessageConsumerPrefetch;
71 |
72 | SQSMessageConsumer(SQSConnection parentSQSConnection, SQSSession parentSQSSession,
73 | SQSSessionCallbackScheduler sqsSessionRunnable, SQSQueueDestination destination,
74 | Acknowledger acknowledger, NegativeAcknowledger negativeAcknowledger, ThreadFactory threadFactory) {
75 | this(parentSQSConnection, parentSQSSession,
76 | sqsSessionRunnable, destination,
77 | acknowledger, negativeAcknowledger, threadFactory,
78 | new SQSMessageConsumerPrefetch(sqsSessionRunnable, acknowledger, negativeAcknowledger, destination,
79 | parentSQSConnection.getWrappedAmazonSQSClient(),
80 | parentSQSConnection.getNumberOfMessagesToPrefetch()));
81 |
82 | }
83 |
84 | SQSMessageConsumer(SQSConnection parentSQSConnection, SQSSession parentSQSSession,
85 | SQSSessionCallbackScheduler sqsSessionRunnable, SQSQueueDestination destination,
86 | Acknowledger acknowledger, NegativeAcknowledger negativeAcknowledger, ThreadFactory threadFactory,
87 | SQSMessageConsumerPrefetch sqsMessageConsumerPrefetch) {
88 | this.parentSQSSession = parentSQSSession;
89 | this.sqsDestination = destination;
90 | this.sqsSessionRunnable = sqsSessionRunnable;
91 | this.sqsMessageConsumerPrefetch = sqsMessageConsumerPrefetch;
92 | this.sqsMessageConsumerPrefetch.setMessageConsumer(this);
93 |
94 | prefetchExecutor = Executors.newSingleThreadExecutor(threadFactory);
95 | prefetchExecutor.execute(sqsMessageConsumerPrefetch);
96 | }
97 |
98 |
99 |
100 | /**
101 | * Gets the queue destination associated with this queue receiver, where the
102 | * messages are delivered from.
103 | *
104 | * @return a queue destination
105 | */
106 | @Override
107 | public Queue getQueue() throws JMSException {
108 | return sqsDestination;
109 | }
110 |
111 | /**
112 | * Gets the message consumer's MessageListener.
113 | *
114 | * @return a message listener
115 | */
116 | @Override
117 | public MessageListener getMessageListener() throws JMSException {
118 | return sqsMessageConsumerPrefetch.getMessageListener();
119 | }
120 |
121 | /**
122 | * Sets the message consumer's MessageListener.
123 | *
124 | * @param listener
125 | * a message listener to use for asynchronous message delivery
126 | * @throws JMSException
127 | * If the message consumer is closed
128 | */
129 | @Override
130 | public void setMessageListener(MessageListener listener) throws JMSException {
131 | checkClosed();
132 | this.sqsMessageConsumerPrefetch.setMessageListener(listener);
133 | }
134 |
135 | /**
136 | * This call blocks indefinitely until a message is produced or until this
137 | * message consumer is closed. When ConnectionState is stopped receive is
138 | * paused.
139 | *
140 | * @return the next message produced for this message consumer, or null if
141 | * this message consumer is closed during the receive call
142 | * @throws JMSException
143 | * On internal error
144 | */
145 | @Override
146 | public Message receive() throws JMSException {
147 | checkClosed();
148 | return sqsMessageConsumerPrefetch.receive();
149 | }
150 |
151 | /**
152 | * This call blocks until a message arrives, the timeout expires, or this
153 | * message consumer is closed. A timeout of zero never expires, and the call
154 | * blocks indefinitely.
155 | *
156 | * @param timeout
157 | * the timeout value (in milliseconds)
158 | * @return the next message produced for this message consumer, or null if
159 | * the timeout expires or this message consumer is closed during the receive call
160 | * @throws JMSException
161 | * On internal error
162 | */
163 | @Override
164 | public Message receive(long timeout) throws JMSException {
165 | checkClosed();
166 | return sqsMessageConsumerPrefetch.receive(timeout);
167 | }
168 |
169 | /**
170 | * Receives the next message if one is immediately available.
171 | *
172 | * @return the next message produced for this message consumer, or null if
173 | * no message is available
174 | * @throws JMSException
175 | * On internal error
176 | */
177 | @Override
178 | public Message receiveNoWait() throws JMSException {
179 | checkClosed();
180 | return sqsMessageConsumerPrefetch.receiveNoWait();
181 | }
182 |
183 | /**
184 | * Closes the message consumer.
185 | *
186 | * This will not return until receives and/or message listeners in progress
187 | * have completed. A blocked message consumer receive call returns null when
188 | * this consumer is closed.
189 | *
190 | * Since consumer prefetch threads use SQS long-poll feature with 20 seconds
191 | * timeout, closing each consumer prefetch thread can take up to 20 seconds,
192 | * which in-turn will impact the time on consumer close.
193 | *
194 | * This method may be called from a message listener's onMessage method on
195 | * its own consumer. After this method returns the onMessage method will be
196 | * allowed to complete normally, and the callback scheduler thread will be
197 | * closing the message consumer.
198 | *
199 | * @throws JMSException
200 | * On internal error.
201 | */
202 | @Override
203 | public void close() throws JMSException {
204 | if (closed) {
205 | return;
206 | }
207 |
208 | if (parentSQSSession.isActiveCallbackSessionThread()) {
209 | sqsSessionRunnable.setConsumerCloseAfterCallback(this);
210 | return;
211 | }
212 |
213 | doClose();
214 | }
215 |
216 | void doClose() {
217 | if (closed) {
218 | return;
219 | }
220 |
221 | sqsMessageConsumerPrefetch.close();
222 |
223 | parentSQSSession.removeConsumer(this);
224 |
225 | try {
226 | if (!prefetchExecutor.isShutdown()) {
227 | LOG.debug("Shutting down {} executor", SQSSession.CONSUMER_PREFETCH_EXECUTOR_NAME);
228 | // Shut down executor.
229 | prefetchExecutor.shutdown();
230 | }
231 |
232 | parentSQSSession.waitForConsumerCallbackToComplete(this);
233 |
234 | if (!prefetchExecutor.awaitTermination(PREFETCH_EXECUTOR_GRACEFUL_SHUTDOWN_TIME, TimeUnit.SECONDS)) {
235 |
236 | LOG.warn("Can't terminate executor service {} after {} seconds, some running threads will be shutdown immediately",
237 | SQSSession.CONSUMER_PREFETCH_EXECUTOR_NAME, PREFETCH_EXECUTOR_GRACEFUL_SHUTDOWN_TIME);
238 | prefetchExecutor.shutdownNow();
239 | }
240 | } catch (InterruptedException e) {
241 | LOG.error("Interrupted while closing the consumer.", e);
242 | }
243 |
244 | closed = true;
245 | }
246 |
247 | boolean isClosed() {
248 | return closed;
249 | }
250 |
251 | /** This method is not supported. */
252 | @Override
253 | public String getMessageSelector() throws JMSException {
254 | throw new JMSException(SQSMessagingClientConstants.UNSUPPORTED_METHOD);
255 | }
256 |
257 | /** This stops the prefetching */
258 | protected void stopPrefetch() {
259 | if (!closed) {
260 | sqsMessageConsumerPrefetch.stop();
261 | }
262 | }
263 |
264 | /** This starts the prefetching */
265 | protected void startPrefetch() {
266 | if (!closed) {
267 | sqsMessageConsumerPrefetch.start();
268 | }
269 | }
270 |
271 | private void checkClosed() throws IllegalStateException {
272 | if (closed) {
273 | throw new IllegalStateException("Consumer is closed");
274 | }
275 | }
276 |
277 | List purgePrefetchedMessagesWithGroups(Set affectedGroups) throws JMSException {
278 | return sqsMessageConsumerPrefetch.purgePrefetchedMessagesWithGroups(affectedGroups);
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/SQSMessagingClientConstants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import jakarta.jms.ConnectionMetaData;
18 | import jakarta.jms.JMSException;
19 |
20 |
21 | public class SQSMessagingClientConstants {
22 |
23 | public static final String UNSUPPORTED_METHOD = "Unsupported Method";
24 |
25 | public static final ConnectionMetaData CONNECTION_METADATA = new SQSConnectionMetaData();
26 |
27 | public static final int MAX_BATCH = 10;
28 |
29 | public static final int MIN_BATCH = 1;
30 |
31 | public static final int MIN_PREFETCH = 0;
32 |
33 | /**
34 | * JMSMessage available user property types, which are mapped to message
35 | * attribute data types
36 | */
37 | public static final String STRING = "String";
38 |
39 | public static final String NUMBER = "Number";
40 |
41 | public static final String INT = "Number.int";
42 |
43 | public static final String BOOLEAN = "Number.Boolean";
44 |
45 | public static final String BYTE = "Number.byte";
46 |
47 | public static final String DOUBLE = "Number.double";
48 |
49 | public static final String FLOAT = "Number.float";
50 |
51 | public static final String LONG = "Number.long";
52 |
53 | public static final String SHORT = "Number.short";
54 |
55 | public static final String BINARY = "Binary";
56 |
57 | public static final String INT_FALSE = "0";
58 |
59 | public static final String INT_TRUE = "1";
60 |
61 | public static final String MESSAGE_ID_FORMAT = "ID:%s";
62 |
63 | public static final String JMSX_DELIVERY_COUNT = "JMSXDeliveryCount";
64 |
65 | public static final String JMSX_GROUP_ID = "JMSXGroupID";
66 |
67 | public static final String JMSX_GROUP_SEC = "JMSXGroupSeq";
68 |
69 | public static final String JMS_SQS_DEDUPLICATION_ID = "JMS_SQS_DeduplicationId";
70 |
71 | public static final String JMS_SQS_SEQUENCE_NUMBER = "JMS_SQS_SequenceNumber";
72 |
73 | public static final String APPROXIMATE_RECEIVE_COUNT = "ApproximateReceiveCount";
74 |
75 | public static final String SENT_TIMESTAMP = "SentTimestamp";
76 |
77 | public static final String MESSAGE_DEDUPLICATION_ID = "MessageDeduplicationId";
78 |
79 | public static final String MESSAGE_GROUP_ID = "MessageGroupId";
80 |
81 | public static final String SEQUENCE_NUMBER = "SequenceNumber";
82 |
83 | static final String APPENDED_USER_AGENT_HEADER_VERSION;
84 | static {
85 | try {
86 | APPENDED_USER_AGENT_HEADER_VERSION = String.format(
87 | "/SQS Java Messaging Client v%s", CONNECTION_METADATA.getProviderVersion());
88 | } catch (JMSException e) {
89 | throw new ExceptionInInitializerError(e);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/SQSQueueDestination.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import jakarta.jms.Destination;
18 | import jakarta.jms.Queue;
19 |
20 | /**
21 | * A SQSQueueDestination object encapsulates a queue name and SQS specific queue
22 | * URL. This is the way a client specifies the identity of a queue to JMS API
23 | * methods.
24 | */
25 | public class SQSQueueDestination implements Destination, Queue {
26 |
27 | private final String queueName;
28 |
29 | private final String queueUrl;
30 |
31 | private final boolean isFifo;
32 |
33 | SQSQueueDestination(String queueName, String queueUrl) {
34 | this.queueName = queueName;
35 | this.queueUrl = queueUrl;
36 | this.isFifo = this.queueName.endsWith(".fifo");
37 | }
38 |
39 | /**
40 | * Returns the name of this queue.
41 | *
42 | * @return queueName
43 | */
44 | @Override
45 | public String getQueueName() {
46 | return this.queueName;
47 | }
48 |
49 | /**
50 | * Returns the queueUrl of this queue.
51 | *
52 | * @return queueUrl
53 | */
54 | public String getQueueUrl() {
55 | return this.queueUrl;
56 | }
57 |
58 | public boolean isFifo() {
59 | return this.isFifo;
60 | }
61 |
62 | @Override
63 | public String toString() {
64 | return "SQSDestination [queueName=" + queueName + ", queueUrl=" + queueUrl + "]";
65 | }
66 |
67 | @Override
68 | public int hashCode() {
69 | final int prime = 31;
70 | int result = 1;
71 | result = prime * result + ((queueName == null) ? 0 : queueName.hashCode());
72 | result = prime * result + ((queueUrl == null) ? 0 : queueUrl.hashCode());
73 | return result;
74 | }
75 |
76 | @Override
77 | public boolean equals(Object obj) {
78 | if (this == obj)
79 | return true;
80 | if (obj == null)
81 | return false;
82 | if (getClass() != obj.getClass())
83 | return false;
84 | SQSQueueDestination other = (SQSQueueDestination) obj;
85 | if (queueName == null) {
86 | if (other.queueName != null)
87 | return false;
88 | } else if (!queueName.equals(other.queueName))
89 | return false;
90 | if (queueUrl == null) {
91 | return other.queueUrl == null;
92 | } else return queueUrl.equals(other.queueUrl);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/SQSSessionCallbackScheduler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.SQSMessageConsumerPrefetch.MessageManager;
18 | import com.amazon.sqs.javamessaging.SQSSession.CallbackEntry;
19 | import com.amazon.sqs.javamessaging.acknowledge.AcknowledgeMode;
20 | import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
21 | import com.amazon.sqs.javamessaging.acknowledge.NegativeAcknowledger;
22 | import com.amazon.sqs.javamessaging.acknowledge.SQSMessageIdentifier;
23 | import com.amazon.sqs.javamessaging.message.SQSMessage;
24 | import jakarta.jms.JMSException;
25 | import jakarta.jms.MessageListener;
26 | import jakarta.jms.Session;
27 | import org.slf4j.Logger;
28 | import org.slf4j.LoggerFactory;
29 |
30 | import java.util.ArrayDeque;
31 | import java.util.ArrayList;
32 | import java.util.Collections;
33 | import java.util.Iterator;
34 | import java.util.List;
35 | import java.util.Map;
36 | import java.util.Set;
37 |
38 | /**
39 | * Used internally to guarantee serial execution of message processing on
40 | * consumer message listeners.
41 | */
42 | public class SQSSessionCallbackScheduler implements Runnable {
43 | private static final Logger LOG = LoggerFactory.getLogger(SQSSessionCallbackScheduler.class);
44 |
45 | protected ArrayDeque callbackQueue;
46 |
47 | private final AcknowledgeMode acknowledgeMode;
48 |
49 | private final SQSSession session;
50 |
51 | private final NegativeAcknowledger negativeAcknowledger;
52 |
53 | private final Acknowledger acknowledger;
54 |
55 | /**
56 | * Only set from the callback thread to transfer the ownership of closing
57 | * the consumer. The message listener's onMessage method can call
58 | * close on its own consumer. After message consumer
59 | * close returns the onMessage method should be allowed to
60 | * complete normally and this thread will be responsible to finish the
61 | * close on message consumer.
62 | */
63 | private SQSMessageConsumer consumerCloseAfterCallback;
64 |
65 | private volatile boolean closed = false;
66 |
67 | SQSSessionCallbackScheduler(SQSSession session, AcknowledgeMode acknowledgeMode, Acknowledger acknowledger, NegativeAcknowledger negativeAcknowledger) {
68 | this.session = session;
69 | this.acknowledgeMode = acknowledgeMode;
70 | this.acknowledger = acknowledger;
71 | this.negativeAcknowledger = negativeAcknowledger;
72 | callbackQueue = new ArrayDeque<>();
73 | }
74 |
75 | /**
76 | * Used in case no consumers have started, and session needs to terminate
77 | * the thread
78 | */
79 | void close() {
80 | closed = true;
81 | /* Wake-up the thread in case it was blocked on empty queue */
82 | synchronized (callbackQueue) {
83 | callbackQueue.notify();
84 | }
85 | }
86 |
87 | @Override
88 | public void run() {
89 | CallbackEntry callbackEntry = null;
90 | try {
91 | while (true) {
92 | try {
93 | if (closed) {
94 | break;
95 | }
96 | synchronized (callbackQueue) {
97 | callbackEntry = callbackQueue.pollFirst();
98 | if (callbackEntry == null) {
99 | try {
100 | callbackQueue.wait();
101 | } catch (InterruptedException e) {
102 | /*
103 | * Will be retried on the next loop, and
104 | * break if the callback scheduler is closed.
105 | */
106 | LOG.debug("wait on empty callback queue interrupted: {}", e.getMessage());
107 | }
108 | continue;
109 | }
110 | }
111 |
112 | MessageListener messageListener = callbackEntry.messageListener();
113 | MessageManager messageManager = callbackEntry.messageManager();
114 | SQSMessage message = (SQSMessage) messageManager.message();
115 | SQSMessageConsumer messageConsumer = messageManager.prefetchManager().getMessageConsumer();
116 | if (messageConsumer.isClosed()) {
117 | nackReceivedMessage(message);
118 | continue;
119 | }
120 |
121 | try {
122 | // this takes care of start and stop
123 | session.startingCallback(messageConsumer);
124 | } catch (JMSException e) {
125 | LOG.debug("Not running callback: {}", e.getMessage());
126 | break;
127 | }
128 |
129 | try {
130 | /*
131 | * Notifying consumer prefetch thread so that it can
132 | * continue to prefetch
133 | */
134 | messageManager.prefetchManager().messageDispatched();
135 | int ackMode = acknowledgeMode.getOriginalAcknowledgeMode();
136 | boolean tryNack = true;
137 | try {
138 | if (messageListener != null) {
139 | if (ackMode != Session.AUTO_ACKNOWLEDGE) {
140 | acknowledger.notifyMessageReceived(message);
141 | }
142 | boolean callbackFailed = false;
143 | try {
144 | messageListener.onMessage(message);
145 | } catch (Throwable ex) {
146 | LOG.warn("Exception thrown from onMessage callback for message {}",
147 | message.getSQSMessageId(), ex);
148 | callbackFailed = true;
149 | } finally {
150 | if (!callbackFailed) {
151 | if (ackMode == Session.AUTO_ACKNOWLEDGE) {
152 | message.acknowledge();
153 | }
154 | tryNack = false;
155 | }
156 | }
157 | }
158 | } catch (JMSException ex) {
159 | LOG.warn("Unable to complete message dispatch for the message {}",
160 | message.getSQSMessageId(), ex);
161 | } finally {
162 | if (tryNack) {
163 | nackReceivedMessage(message);
164 | }
165 | }
166 |
167 | /*
168 | * The consumer close is delegated to the session thread
169 | * if consumer close is called by its message listener's
170 | * onMessage method on its own consumer.
171 | */
172 | if (consumerCloseAfterCallback != null) {
173 | consumerCloseAfterCallback.doClose();
174 | consumerCloseAfterCallback = null;
175 | }
176 | } finally {
177 | session.finishedCallback();
178 |
179 | // Let the prefetch manager know we're available to
180 | // process another message (if there is a still a listener attached).
181 | messageManager.prefetchManager().messageListenerReady();
182 | }
183 | } catch (Throwable ex) {
184 | LOG.error("Unexpected exception thrown during the run of the scheduled callback", ex);
185 | }
186 | }
187 | } finally {
188 | if (callbackEntry != null) {
189 | nackReceivedMessage((SQSMessage) callbackEntry.messageManager().message());
190 | }
191 | nackQueuedMessages();
192 | }
193 | }
194 |
195 | void setConsumerCloseAfterCallback(SQSMessageConsumer messageConsumer) {
196 | consumerCloseAfterCallback = messageConsumer;
197 | }
198 |
199 | void scheduleCallBacks(MessageListener messageListener, List messageManagers) {
200 | synchronized (callbackQueue) {
201 | try {
202 | for (MessageManager messageManager : messageManagers) {
203 | CallbackEntry callbackEntry = new CallbackEntry(messageListener, messageManager);
204 | callbackQueue.addLast(callbackEntry);
205 | }
206 | } finally {
207 | callbackQueue.notify();
208 | }
209 | }
210 | }
211 |
212 | void nackQueuedMessages() {
213 | synchronized (callbackQueue) {
214 | try {
215 | List nackMessageIdentifiers = new ArrayList<>();
216 | while (!callbackQueue.isEmpty()) {
217 | SQSMessage nackMessage = (SQSMessage) callbackQueue.pollFirst().messageManager().message();
218 | nackMessageIdentifiers.add(SQSMessageIdentifier.fromSQSMessage(nackMessage));
219 | }
220 |
221 | if (!nackMessageIdentifiers.isEmpty()) {
222 | negativeAcknowledger.bulkAction(nackMessageIdentifiers, nackMessageIdentifiers.size());
223 | }
224 | } catch (JMSException e) {
225 | LOG.warn("Caught exception while nacking the remaining messages on session callback queue", e);
226 | }
227 | }
228 | }
229 |
230 | private void nackReceivedMessage(SQSMessage message) {
231 | try {
232 | SQSMessageIdentifier messageIdentifier = SQSMessageIdentifier.fromSQSMessage(message);
233 | List nackMessageIdentifiers = new ArrayList<>();
234 | nackMessageIdentifiers.add(messageIdentifier);
235 |
236 | //failing to process a message with a specific group id means we have to nack all the pending messages with the same group id
237 | //to prevent processing messages out of order
238 | if (messageIdentifier.getGroupId() != null) {
239 | //prepare a map of single queueUrl with single group to purge
240 | Map> queueToGroupsMapping = Collections.singletonMap(messageIdentifier.getQueueUrl(), Collections.singleton(messageIdentifier.getGroupId()));
241 | nackMessageIdentifiers.addAll(purgeScheduledCallbacksForQueuesAndGroups(queueToGroupsMapping));
242 | }
243 |
244 | negativeAcknowledger.bulkAction(nackMessageIdentifiers, nackMessageIdentifiers.size());
245 | } catch (JMSException e) {
246 | LOG.warn("Unable to nack the message {}", message.getSQSMessageId(), e);
247 | }
248 | }
249 |
250 | List purgeScheduledCallbacksForQueuesAndGroups(Map> queueToGroupsMapping) throws JMSException {
251 | List purgedCallbacks = new ArrayList<>();
252 | synchronized (callbackQueue) {
253 | //let's walk over the callback queue
254 | Iterator callbackIterator = callbackQueue.iterator();
255 | while (callbackIterator.hasNext()) {
256 | CallbackEntry callbackEntry = callbackIterator.next();
257 | SQSMessageIdentifier pendingCallbackIdentifier = SQSMessageIdentifier.fromSQSMessage((SQSMessage) callbackEntry.messageManager().message());
258 |
259 | //is the callback entry for one of the affected queues?
260 | Set affectedGroupsInQueue = queueToGroupsMapping.get(pendingCallbackIdentifier.getQueueUrl());
261 | if (affectedGroupsInQueue != null) {
262 |
263 | //is the callback entry for one of the affected group ids?
264 | if (affectedGroupsInQueue.contains(pendingCallbackIdentifier.getGroupId())) {
265 | //we will purge this callback
266 | purgedCallbacks.add(pendingCallbackIdentifier);
267 | //remove from callback queue
268 | callbackIterator.remove();
269 | //notify prefetcher for that message that we are done with it and it can prefetch more messages
270 | callbackEntry.messageManager().prefetchManager().messageDispatched();
271 | }
272 | }
273 | }
274 | }
275 | return purgedCallbacks;
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/AcknowledgeMode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.AmazonSQSMessagingClientWrapper;
18 | import com.amazon.sqs.javamessaging.SQSSession;
19 |
20 | /**
21 | *
22 | * Specifies the different possible modes of acknowledgment:
23 | *
24 | *
In ACK_AUTO mode, every time the user receives a message, it is
25 | * acknowledged automatically. This is implemented by consumer and does not need
26 | * any further processing.
27 | *
In ACK_RANGE mode, all messages received before that message including
28 | * that one are acknowledged.
29 | *
In ACK_UNORDERED mode, messages can be acknowledged in any order one at a
30 | * time. Acknowledging the consumed message does not affect other message
31 | * acknowledges.
32 | *
33 | */
34 | public enum AcknowledgeMode {
35 | ACK_AUTO, ACK_UNORDERED, ACK_RANGE;
36 |
37 | private int originalAcknowledgeMode;
38 |
39 | /**
40 | * Sets the acknowledgment mode.
41 | */
42 | public AcknowledgeMode withOriginalAcknowledgeMode(int originalAcknowledgeMode) {
43 | this.originalAcknowledgeMode = originalAcknowledgeMode;
44 | return this;
45 | }
46 |
47 | /**
48 | * Returns the acknowledgment mode.
49 | */
50 | public int getOriginalAcknowledgeMode() {
51 | return originalAcknowledgeMode;
52 | }
53 |
54 | /**
55 | * Creates the acknowledger associated with the session, which will be used
56 | * to acknowledge the delivered messages on consumers of the session.
57 | *
58 | * @param amazonSQSClient
59 | * the SQS client to delete messages
60 | * @param parentSQSSession
61 | * the associated session for the acknowledger
62 | */
63 | public Acknowledger createAcknowledger(AmazonSQSMessagingClientWrapper amazonSQSClient, SQSSession parentSQSSession) {
64 | return switch (this) {
65 | case ACK_AUTO -> new AutoAcknowledger(amazonSQSClient, parentSQSSession);
66 | case ACK_RANGE -> new RangedAcknowledger(amazonSQSClient, parentSQSSession);
67 | case ACK_UNORDERED -> new UnorderedAcknowledger(amazonSQSClient, parentSQSSession);
68 | };
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/Acknowledger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.message.SQSMessage;
18 | import jakarta.jms.JMSException;
19 |
20 | import java.util.List;
21 |
22 | public interface Acknowledger {
23 |
24 | /**
25 | * Generic Acknowledge method. This method will delete message(s) in SQS Queue.
26 | *
27 | * @param message
28 | * message to acknowledge.
29 | * @throws JMSException
30 | */
31 | void acknowledge(SQSMessage message) throws JMSException;
32 |
33 | /**
34 | * Used when receiving messages. Depending on acknowledge mode this will
35 | * help create list of message backlog.
36 | *
37 | * @param message
38 | * notify acknowledger message is received
39 | * @throws JMSException
40 | */
41 | void notifyMessageReceived(SQSMessage message) throws JMSException;
42 |
43 | /**
44 | * Used in negative acknowledge. Gets all delivered but not acknowledged
45 | * messages.
46 | */
47 | List getUnAckMessages();
48 |
49 | /**
50 | * Deletes all not acknowledged delivered messages.
51 | */
52 | void forgetUnAckMessages();
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/AutoAcknowledger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.AmazonSQSMessagingClientWrapper;
18 | import com.amazon.sqs.javamessaging.SQSSession;
19 | import com.amazon.sqs.javamessaging.message.SQSMessage;
20 | import jakarta.jms.JMSException;
21 | import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
22 |
23 | import java.util.ArrayList;
24 | import java.util.List;
25 |
26 | /**
27 | * Used by session to automatically acknowledge a client's receipt of a message
28 | * either when the session has successfully returned from a call to receive or
29 | * when the message listener the session has called to process the message
30 | * successfully returns.
31 | */
32 | public class AutoAcknowledger implements Acknowledger {
33 |
34 | private final AmazonSQSMessagingClientWrapper amazonSQSClient;
35 | private final SQSSession session;
36 |
37 | public AutoAcknowledger(AmazonSQSMessagingClientWrapper amazonSQSClient, SQSSession session) {
38 | this.amazonSQSClient = amazonSQSClient;
39 | this.session = session;
40 | }
41 |
42 | /** Acknowledges the consumed message via calling deleteMessage */
43 | @Override
44 | public void acknowledge(SQSMessage message) throws JMSException {
45 | session.checkClosed();
46 | amazonSQSClient.deleteMessage(DeleteMessageRequest.builder()
47 | .queueUrl(message.getQueueUrl())
48 | .receiptHandle(message.getReceiptHandle())
49 | .build());
50 | }
51 |
52 | /**
53 | * When notify message is received, it will acknowledge the message.
54 | */
55 | @Override
56 | public void notifyMessageReceived(SQSMessage message) throws JMSException {
57 | acknowledge(message);
58 | }
59 |
60 | /**
61 | * AutoAcknowledge doesn't need to do anything in this method. Return an
62 | * empty list.
63 | */
64 | @Override
65 | public List getUnAckMessages() {
66 | return new ArrayList<>();
67 | }
68 |
69 | /** AutoAcknowledge doesn't need to do anything in this method. */
70 | @Override
71 | public void forgetUnAckMessages() {
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/BulkSQSOperation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.SQSMessagingClientConstants;
18 | import jakarta.jms.JMSException;
19 |
20 | import java.util.ArrayList;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.Map.Entry;
25 |
26 | /**
27 | * This is used by different acknowledgers that requires partitioning of the
28 | * list, and execute actions on the partitions
29 | */
30 | public abstract class BulkSQSOperation {
31 |
32 | /**
33 | * Bulk action on list of message identifiers up to the provided index
34 | *
35 | * @param messageIdentifierList
36 | * Container for the list of message identifiers
37 | * @param indexOfMessage
38 | * The action will apply to all messages up to this index
39 | * @throws JMSException
40 | * if action throws
41 | */
42 | public void bulkAction(List messageIdentifierList, int indexOfMessage)
43 | throws JMSException {
44 |
45 | assert indexOfMessage > 0;
46 | assert indexOfMessage <= messageIdentifierList.size();
47 |
48 | Map> receiptHandleWithSameQueueUrl = new HashMap<>();
49 |
50 | // Add all messages up to and including requested message into Map.
51 | // Map contains key as queueUrl and value as list receiptHandles from
52 | // that queueUrl.
53 |
54 | for (int i = 0; i < indexOfMessage; i++) {
55 | SQSMessageIdentifier messageIdentifier = messageIdentifierList.get(i);
56 | String queueUrl = messageIdentifier.getQueueUrl();
57 | List receiptHandles = receiptHandleWithSameQueueUrl.computeIfAbsent(queueUrl, k -> new ArrayList<>());
58 | // if value of queueUrl is null create new list.
59 | // add receiptHandle to the list.
60 | receiptHandles.add(messageIdentifier.getReceiptHandle());
61 | // Once there are 10 messages in messageBatch, apply the batch action
62 | if (receiptHandles.size() == SQSMessagingClientConstants.MAX_BATCH) {
63 | action(queueUrl, receiptHandles);
64 | receiptHandles.clear();
65 | }
66 | }
67 |
68 | // Flush rest of messages in map.
69 | for (Entry> entry : receiptHandleWithSameQueueUrl.entrySet()) {
70 | action(entry.getKey(), entry.getValue());
71 | }
72 | }
73 |
74 | /**
75 | * Action call block. This action can be applied on multiple messages for
76 | * the same queue.
77 | *
78 | * @param queueUrl
79 | * queueUrl of the queue, which the receipt handles belong
80 | * @param receiptHandles
81 | * the list of handles, which is be used to (negative)acknowledge
82 | * the messages.
83 | * @throws JMSException
84 | */
85 | public abstract void action(String queueUrl, List receiptHandles) throws JMSException;
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/NegativeAcknowledger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.AmazonSQSMessagingClientWrapper;
18 | import com.amazon.sqs.javamessaging.SQSMessageConsumerPrefetch.MessageManager;
19 | import com.amazon.sqs.javamessaging.SQSMessagingClientConstants;
20 | import com.amazon.sqs.javamessaging.message.SQSMessage;
21 | import jakarta.jms.JMSException;
22 | import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequest;
23 | import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequestEntry;
24 |
25 | import java.util.ArrayDeque;
26 | import java.util.ArrayList;
27 | import java.util.List;
28 |
29 | /**
30 | * Used to negative acknowledge of group of messages.
31 | *
32 | * Negative acknowledge resets the visibility timeout of a message, so that the
33 | * message can be immediately available to consume. This is mostly used on
34 | * recover and close methods.
35 | *
36 | * Negative acknowledge can potentially cause duplicate deliveries.
37 | */
38 | public class NegativeAcknowledger extends BulkSQSOperation {
39 |
40 | private static final int NACK_TIMEOUT = 0;
41 |
42 | private final AmazonSQSMessagingClientWrapper amazonSQSClient;
43 |
44 | public NegativeAcknowledger(AmazonSQSMessagingClientWrapper amazonSQSClient) {
45 | this.amazonSQSClient = amazonSQSClient;
46 | }
47 |
48 | /**
49 | * Bulk action for negative acknowledge on the list of messages of a
50 | * specific queue.
51 | *
52 | * @param messageQueue
53 | * Container for the list of message managers.
54 | * @param queueUrl
55 | * The queueUrl of the messages, which they received from.
56 | * @throws JMSException
57 | * If action throws.
58 | */
59 | public void bulkAction(ArrayDeque messageQueue, String queueUrl) throws JMSException {
60 | List receiptHandles = new ArrayList<>();
61 | while (!messageQueue.isEmpty()) {
62 | receiptHandles.add(((SQSMessage) (messageQueue.pollFirst().message())).getReceiptHandle());
63 |
64 | // If there is more than 10 stop can call action
65 | if (receiptHandles.size() == SQSMessagingClientConstants.MAX_BATCH) {
66 | action(queueUrl, receiptHandles);
67 | receiptHandles.clear();
68 | }
69 | }
70 | action(queueUrl, receiptHandles);
71 | }
72 |
73 | /**
74 | * Action call block for negative acknowledge for the list of receipt
75 | * handles. This action can be applied on multiple messages for the same
76 | * queue.
77 | *
78 | * @param queueUrl
79 | * The queueUrl of the queue, which the receipt handles belong.
80 | * @param receiptHandles
81 | * The list of handles, which is be used to negative acknowledge
82 | * the messages via using
83 | * changeMessageVisibilityBatch.
84 | * @throws JMSException
85 | * If changeMessageVisibilityBatch throws.
86 | */
87 | @Override
88 | public void action(String queueUrl, List receiptHandles) throws JMSException {
89 | if (receiptHandles == null || receiptHandles.isEmpty()) {
90 | return;
91 | }
92 |
93 | List nackEntries = new ArrayList<>(receiptHandles.size());
94 | int batchId = 0;
95 | for (String messageReceiptHandle : receiptHandles) {
96 | ChangeMessageVisibilityBatchRequestEntry changeMessageVisibilityBatchRequestEntry = ChangeMessageVisibilityBatchRequestEntry.builder()
97 | .id(Integer.toString(batchId))
98 | .receiptHandle(messageReceiptHandle)
99 | .visibilityTimeout(NACK_TIMEOUT)
100 | .build();
101 | nackEntries.add(changeMessageVisibilityBatchRequestEntry);
102 | batchId++;
103 | }
104 | amazonSQSClient.changeMessageVisibilityBatch(ChangeMessageVisibilityBatchRequest.builder()
105 | .queueUrl(queueUrl)
106 | .entries(nackEntries)
107 | .build());
108 | }
109 |
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/RangedAcknowledger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.AmazonSQSMessagingClientWrapper;
18 | import com.amazon.sqs.javamessaging.SQSSession;
19 | import com.amazon.sqs.javamessaging.message.SQSMessage;
20 | import jakarta.jms.JMSException;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 | import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequest;
24 | import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequestEntry;
25 |
26 | import java.util.ArrayList;
27 | import java.util.LinkedList;
28 | import java.util.List;
29 | import java.util.Queue;
30 |
31 | /**
32 | * Used to acknowledge group of messages. Acknowledging a consumed message
33 | * acknowledges all messages that the session has consumed before and including
34 | * that message.
35 | *
36 | * A big backlog of consumed messages can cause memory pressure, as well as an
37 | * increase on the probability of duplicates.
38 | *
39 | * This class is not safe for concurrent use.
40 | */
41 | public class RangedAcknowledger extends BulkSQSOperation implements Acknowledger {
42 | private static final Logger LOG = LoggerFactory.getLogger(RangedAcknowledger.class);
43 |
44 | private final AmazonSQSMessagingClientWrapper amazonSQSClient;
45 |
46 | private final SQSSession session;
47 |
48 | private final Queue unAckMessages;
49 |
50 | public RangedAcknowledger(AmazonSQSMessagingClientWrapper amazonSQSClient, SQSSession session) {
51 | this.amazonSQSClient = amazonSQSClient;
52 | this.session = session;
53 | this.unAckMessages = new LinkedList<>();
54 | }
55 |
56 | /**
57 | * Acknowledges all the consumed messages as well as the previously consumed
58 | * messages on the session via calling deleteMessageBatch until
59 | * all the messages are deleted.
60 | */
61 | @Override
62 | public void acknowledge(SQSMessage message) throws JMSException {
63 | session.checkClosed();
64 |
65 | SQSMessageIdentifier ackMessage = SQSMessageIdentifier.fromSQSMessage(message);
66 |
67 | int indexOfMessage = indexOf(ackMessage);
68 |
69 | /*
70 | * In case the message has already been deleted, warn user about it and
71 | * return. If not then it should continue with acknowledging all
72 | * the messages received before that
73 | */
74 | if (indexOfMessage == -1) {
75 | LOG.warn("SQSMessageID: {} with SQSMessageReceiptHandle: {} does not exist.", message.getSQSMessageId(),
76 | message.getReceiptHandle());
77 | } else {
78 | bulkAction(getUnAckMessages(), indexOfMessage);
79 | }
80 | }
81 |
82 | /**
83 | * Return the index of message if the message is in queue. Return -1 if
84 | * message does not exist in queue.
85 | */
86 | private int indexOf(SQSMessageIdentifier findMessage) {
87 | int i = 0;
88 | for (SQSMessageIdentifier sqsMessageIdentifier : unAckMessages) {
89 | i++;
90 | if (sqsMessageIdentifier.equals(findMessage)) {
91 | return i;
92 | }
93 | }
94 | return -1;
95 | }
96 |
97 | /**
98 | * Updates the internal queue for the consumed but not acknowledged
99 | * messages if the message was not already on queue.
100 | */
101 | @Override
102 | public void notifyMessageReceived(SQSMessage message) throws JMSException {
103 | SQSMessageIdentifier messageIdentifier = SQSMessageIdentifier.fromSQSMessage(message);
104 | if (!unAckMessages.contains(messageIdentifier)) {
105 | unAckMessages.add(messageIdentifier);
106 | }
107 | }
108 |
109 | /**
110 | * Returns the list of all consumed but not acknowledged messages.
111 | */
112 | @Override
113 | public List getUnAckMessages() {
114 | return new ArrayList<>(unAckMessages);
115 | }
116 |
117 | /**
118 | * Clears the list of not acknowledged messages.
119 | */
120 | @Override
121 | public void forgetUnAckMessages() {
122 | unAckMessages.clear();
123 | }
124 |
125 | /**
126 | * Acknowledges up to 10 messages via calling
127 | * deleteMessageBatch.
128 | */
129 | @Override
130 | public void action(String queueUrl, List receiptHandles) throws JMSException {
131 | if (receiptHandles == null || receiptHandles.isEmpty()) {
132 | return;
133 | }
134 |
135 | List deleteMessageBatchRequestEntries = new ArrayList<>();
136 | int batchId = 0;
137 | for (String receiptHandle : receiptHandles) {
138 | // Remove the message from queue of unAckMessages
139 | unAckMessages.poll();
140 |
141 | DeleteMessageBatchRequestEntry entry = DeleteMessageBatchRequestEntry.builder()
142 | .id( Integer.toString(batchId))
143 | .receiptHandle(receiptHandle)
144 | .build();
145 | deleteMessageBatchRequestEntries.add(entry);
146 | batchId++;
147 | }
148 |
149 | DeleteMessageBatchRequest deleteMessageBatchRequest = DeleteMessageBatchRequest
150 | .builder()
151 | .queueUrl(queueUrl)
152 | .entries(deleteMessageBatchRequestEntries)
153 | .build();
154 | /*
155 | * TODO: If one of the batch calls fail, then the remaining messages on
156 | * the batch will not be deleted, and will be visible and delivered as
157 | * duplicate after visibility timeout expires.
158 | */
159 | amazonSQSClient.deleteMessageBatch(deleteMessageBatchRequest);
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/SQSMessageIdentifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.message.SQSMessage;
18 | import jakarta.jms.JMSException;
19 |
20 | /**
21 | * Identifies an SQS message, when (negative)acknowledging the message
22 | */
23 | public class SQSMessageIdentifier {
24 |
25 | // The queueUrl where the message was sent or received from
26 | private final String queueUrl;
27 |
28 | // The receipt handle returned after the delivery of the message from SQS
29 | private final String receiptHandle;
30 |
31 | // The SQS message id assigned on send.
32 | private final String sqsMessageId;
33 |
34 | // The group id to which the message belongs
35 | private String groupId;
36 |
37 | public SQSMessageIdentifier(String queueUrl, String receiptHandle, String sqsMessageId) {
38 | this(queueUrl, receiptHandle, sqsMessageId, null);
39 | }
40 |
41 | public SQSMessageIdentifier(String queueUrl, String receiptHandle, String sqsMessageId, String groupId) {
42 | this.queueUrl = queueUrl;
43 | this.receiptHandle = receiptHandle;
44 | this.sqsMessageId = sqsMessageId;
45 | this.groupId = groupId;
46 | if (this.groupId != null && this.groupId.isEmpty()) {
47 | this.groupId = null;
48 | }
49 | }
50 |
51 | public static SQSMessageIdentifier fromSQSMessage(SQSMessage sqsMessage) throws JMSException {
52 | return new SQSMessageIdentifier(sqsMessage.getQueueUrl(), sqsMessage.getReceiptHandle(), sqsMessage.getSQSMessageId(), sqsMessage.getSQSMessageGroupId());
53 | }
54 |
55 | /**
56 | * Returns the queueUrl where the message was sent or received from.
57 | *
58 | * @return queueUrl
59 | */
60 | public String getQueueUrl() {
61 | return this.queueUrl;
62 | }
63 |
64 | /**
65 | * Returns the receipt handle returned after the delivery of the message
66 | * from SQS.
67 | *
68 | * @return receiptHandle
69 | */
70 | public String getReceiptHandle() {
71 | return this.receiptHandle;
72 | }
73 |
74 | /**
75 | * Returns the SQS message id assigned on send.
76 | *
77 | * @return sqsMessageId
78 | */
79 | public String getSQSMessageID() {
80 | return this.sqsMessageId;
81 | }
82 |
83 | /**
84 | * Returns the group id to which the message belongs. Non-null only for messages received from FIFO queues.
85 | *
86 | * @return groupId
87 | */
88 | public String getGroupId() {
89 | return this.groupId;
90 | }
91 |
92 | @Override
93 | public int hashCode() {
94 | final int prime = 31;
95 | int result = 1;
96 | result = prime * result + ((queueUrl == null) ? 0 : queueUrl.hashCode());
97 | result = prime * result + ((receiptHandle == null) ? 0 : receiptHandle.hashCode());
98 | result = prime * result + ((sqsMessageId == null) ? 0 : sqsMessageId.hashCode());
99 | return result;
100 | }
101 |
102 | @Override
103 | public boolean equals(Object obj) {
104 | if (this == obj)
105 | return true;
106 | if (obj == null)
107 | return false;
108 | if (getClass() != obj.getClass())
109 | return false;
110 | SQSMessageIdentifier other = (SQSMessageIdentifier) obj;
111 | if (queueUrl == null) {
112 | if (other.queueUrl != null)
113 | return false;
114 | } else if (!queueUrl.equals(other.queueUrl))
115 | return false;
116 | if (receiptHandle == null) {
117 | if (other.receiptHandle != null)
118 | return false;
119 | } else if (!receiptHandle.equals(other.receiptHandle))
120 | return false;
121 | if (sqsMessageId == null) {
122 | return other.sqsMessageId == null;
123 | } else return sqsMessageId.equals(other.sqsMessageId);
124 | }
125 |
126 | @Override
127 | public String toString() {
128 | return "SQSMessageIdentifier [queueUrl=" + queueUrl + ", receiptHandle=" + receiptHandle +
129 | ", sqsMessageId=" + sqsMessageId + "]";
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/acknowledge/UnorderedAcknowledger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.acknowledge;
16 |
17 | import com.amazon.sqs.javamessaging.AmazonSQSMessagingClientWrapper;
18 | import com.amazon.sqs.javamessaging.SQSSession;
19 | import com.amazon.sqs.javamessaging.message.SQSMessage;
20 | import jakarta.jms.JMSException;
21 | import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
22 |
23 | import java.util.ArrayList;
24 | import java.util.HashMap;
25 | import java.util.List;
26 | import java.util.Map;
27 |
28 | /**
29 | * Used to acknowledge messages in any order one at a time.
30 | *
31 | * This class is not safe for concurrent use.
32 | */
33 | public class UnorderedAcknowledger implements Acknowledger {
34 |
35 | private final AmazonSQSMessagingClientWrapper amazonSQSClient;
36 |
37 | private final SQSSession session;
38 |
39 | // key is the receipt handle of the message and value is the message
40 | // identifier
41 | private final Map unAckMessages;
42 |
43 | public UnorderedAcknowledger (AmazonSQSMessagingClientWrapper amazonSQSClient, SQSSession session) {
44 | this.amazonSQSClient = amazonSQSClient;
45 | this.session = session;
46 | this.unAckMessages = new HashMap<>();
47 | }
48 |
49 | /**
50 | * Acknowledges the consumed message via calling deleteMessage.
51 | */
52 | @Override
53 | public void acknowledge(SQSMessage message) throws JMSException {
54 | session.checkClosed();
55 | amazonSQSClient.deleteMessage(DeleteMessageRequest.builder()
56 | .queueUrl(message.getQueueUrl())
57 | .receiptHandle(message.getReceiptHandle())
58 | .build());
59 | unAckMessages.remove(message.getReceiptHandle());
60 | }
61 |
62 | /**
63 | * Updates the internal data structure for the consumed but not acknowledged
64 | * message.
65 | */
66 | @Override
67 | public void notifyMessageReceived(SQSMessage message) throws JMSException {
68 | SQSMessageIdentifier messageIdentifier = SQSMessageIdentifier.fromSQSMessage(message);
69 | unAckMessages.put(message.getReceiptHandle(), messageIdentifier);
70 | }
71 |
72 | /**
73 | * Returns the list of all consumed but not acknowledged messages.
74 | */
75 | @Override
76 | public List getUnAckMessages() {
77 | return new ArrayList<>(unAckMessages.values());
78 | }
79 |
80 | /**
81 | * Clears the list of not acknowledged messages.
82 | */
83 | @Override
84 | public void forgetUnAckMessages() {
85 | unAckMessages.clear();
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/message/SQSObjectMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.message;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
18 | import jakarta.jms.JMSException;
19 | import jakarta.jms.ObjectMessage;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import software.amazon.awssdk.services.sqs.model.Message;
23 | import software.amazon.awssdk.utils.BinaryUtils;
24 |
25 | import java.io.ByteArrayInputStream;
26 | import java.io.ByteArrayOutputStream;
27 | import java.io.IOException;
28 | import java.io.ObjectInputStream;
29 | import java.io.ObjectOutputStream;
30 | import java.io.Serializable;
31 |
32 | /**
33 | * An ObjectMessage object is used to send a message that contains a Java
34 | * serializable object.
35 | *
36 | * It inherits from the Message interface and adds a body containing a single
37 | * reference to an object. Only Serializable Java objects can be used.
38 | *
39 | * When a client receives an ObjectMessage, it is in read-only mode. If a client
40 | * attempts to write to the message at this point, a
41 | * MessageNotWriteableException is thrown. If clearBody is called, the message
42 | * can now be both read from and written to.
43 | */
44 | public class SQSObjectMessage extends SQSMessage implements ObjectMessage {
45 | private static final Logger LOG = LoggerFactory.getLogger(SQSObjectMessage.class);
46 |
47 | /**
48 | * Serialized message body
49 | */
50 | private String body;
51 |
52 | /**
53 | * Convert received SQSMessage into ObjectMessage
54 | */
55 | public SQSObjectMessage(Acknowledger acknowledger, String queueUrl, Message sqsMessage) throws JMSException {
56 | super(acknowledger, queueUrl, sqsMessage);
57 | body = sqsMessage.body();
58 | }
59 |
60 | /**
61 | * Create new empty ObjectMessage to send.
62 | */
63 | public SQSObjectMessage() throws JMSException {
64 | super();
65 | }
66 |
67 | /**
68 | * Create new ObjectMessage with payload to send.
69 | */
70 | public SQSObjectMessage(Serializable payload) throws JMSException {
71 | super();
72 | body = serialize(payload);
73 | }
74 |
75 | /**
76 | * Sets the Serializable containing this message's body
77 | *
78 | * @param payload
79 | * The Serializable containing the message's body
80 | * @throws jakarta.jms.MessageNotWriteableException
81 | * If the message is in read-only mode.
82 | * @throws jakarta.jms.MessageFormatException
83 | * If object serialization fails.
84 | */
85 | @Override
86 | public void setObject(Serializable payload) throws JMSException {
87 | checkBodyWritePermissions();
88 | body = serialize(payload);
89 | }
90 |
91 | /**
92 | * Gets the Serializable containing this message's body
93 | *
94 | * @throws jakarta.jms.MessageFormatException
95 | * If object deserialization fails.
96 | */
97 | @Override
98 | public Serializable getObject() throws JMSException {
99 | return deserialize(body);
100 | }
101 |
102 | /**
103 | * Sets the message body to write mode, and sets the object body to null
104 | */
105 | @Override
106 | public void clearBody() throws JMSException {
107 | body = null;
108 | setBodyWritePermissions(true);
109 | }
110 |
111 | /**
112 | * Deserialize the String into Serializable
113 | * object.
114 | */
115 | protected static Serializable deserialize(String serialized) throws JMSException {
116 | if (serialized == null) {
117 | return null;
118 | }
119 | Serializable deserializedObject;
120 | ObjectInputStream objectInputStream = null;
121 | try {
122 | byte[] bytes = BinaryUtils.fromBase64(serialized);
123 | objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
124 | deserializedObject = (Serializable) objectInputStream.readObject();
125 | } catch (IOException e) {
126 | LOG.error("IOException: Message cannot be written", e);
127 | throw convertExceptionToMessageFormatException(e);
128 | } catch (Exception e) {
129 | LOG.error("Unexpected exception: ", e);
130 | throw convertExceptionToMessageFormatException(e);
131 | } finally {
132 | if (objectInputStream != null) {
133 | try {
134 | objectInputStream.close();
135 | } catch (IOException e) {
136 | LOG.warn(e.getMessage());
137 | }
138 | }
139 | }
140 | return deserializedObject;
141 | }
142 |
143 | /**
144 | * Serialize the Serializable object to String.
145 | */
146 | protected static String serialize(Serializable serializable) throws JMSException {
147 | if (serializable == null) {
148 | return null;
149 | }
150 | String serializedString;
151 | ObjectOutputStream objectOutputStream = null;
152 | try {
153 | ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
154 | objectOutputStream = new ObjectOutputStream(bytesOut);
155 | objectOutputStream.writeObject(serializable);
156 | objectOutputStream.flush();
157 | serializedString = BinaryUtils.toBase64(bytesOut.toByteArray());
158 | } catch (IOException e) {
159 | LOG.error("IOException: cannot serialize objectMessage", e);
160 | throw convertExceptionToMessageFormatException(e);
161 | } finally {
162 | if (objectOutputStream != null) {
163 | try {
164 | objectOutputStream.close();
165 | } catch (IOException e) {
166 | LOG.warn(e.getMessage());
167 | }
168 | }
169 | }
170 | return serializedString;
171 | }
172 |
173 | public String getMessageBody() {
174 | return body;
175 | }
176 |
177 | void setMessageBody(String body) {
178 | this.body = body;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/message/SQSTextMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.message;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
18 | import jakarta.jms.JMSException;
19 | import jakarta.jms.TextMessage;
20 | import software.amazon.awssdk.services.sqs.model.Message;
21 |
22 | /**
23 | * A TextMessage object is used to send a message body containing a
24 | * java.lang.String. It inherits from the Message interface and adds a text
25 | * message body. SQS does not accept empty or null message body
26 | *
27 | * When a client receives a TextMessage, it is in read-only mode. If a client
28 | * attempts to write to the message at this point, a
29 | * MessageNotWriteableException is thrown. If clearBody is called, the message
30 | * can now be both read from and written to.
31 | */
32 | public class SQSTextMessage extends SQSMessage implements TextMessage {
33 |
34 | /**
35 | * Text of the message. Assume this is safe from SQS invalid characters.
36 | */
37 | private String text;
38 |
39 | /**
40 | * Convert received SQSMessage into TextMessage.
41 | */
42 | public SQSTextMessage(Acknowledger acknowledger, String queueUrl, Message sqsMessage) throws JMSException{
43 | super(acknowledger, queueUrl, sqsMessage);
44 | this.text = sqsMessage.body();
45 | }
46 |
47 | /**
48 | * Create new empty TextMessage to send.
49 | */
50 | public SQSTextMessage() throws JMSException {
51 | super();
52 | }
53 |
54 | /**
55 | * Create new TextMessage with payload to send.
56 | */
57 | public SQSTextMessage(String payload) throws JMSException {
58 | super();
59 | this.text = payload;
60 | }
61 |
62 | /**
63 | * Sets the text containing this message's body.
64 | *
65 | * @param string
66 | * The String containing the message's body
67 | * @throws jakarta.jms.MessageNotWriteableException
68 | * If the message is in read-only mode.
69 | */
70 | @Override
71 | public void setText(String string) throws JMSException {
72 | checkBodyWritePermissions();
73 | this.text = string;
74 | }
75 |
76 | /**
77 | * Gets the text containing this message's body.
78 | *
79 | * @return The String containing the message's body
80 | */
81 | @Override
82 | public String getText() throws JMSException {
83 | return text;
84 | }
85 |
86 | /**
87 | * Sets the message body to write mode, and sets the text to null
88 | */
89 | @Override
90 | public void clearBody() throws JMSException {
91 | text = null;
92 | setBodyWritePermissions(true);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/util/ExponentialBackoffStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.util;
16 |
17 | /**
18 | * Simple exponential back-off strategy, that is used for re-tries on SQS
19 | * interactions.
20 | */
21 | public class ExponentialBackoffStrategy {
22 |
23 | private final long delayInterval;
24 | private final long initialDelay;
25 | private final long maxDelay;
26 |
27 | public ExponentialBackoffStrategy(long delayInterval, long initialDelay, long maxDelay) {
28 | this.delayInterval = delayInterval;
29 | this.initialDelay = initialDelay;
30 | this.maxDelay = maxDelay;
31 | }
32 |
33 | /**
34 | * Returns the delay before the next attempt.
35 | *
36 | * @param retriesAttempted
37 | * @return The delay before the next attempt.
38 | */
39 | public long delayBeforeNextRetry(int retriesAttempted) {
40 | if (retriesAttempted < 1) {
41 | return initialDelay;
42 | }
43 |
44 |
45 | if (retriesAttempted > 63) {
46 | return maxDelay;
47 | }
48 |
49 | long multiplier = ((long)1 << (retriesAttempted - 1));
50 | if (multiplier > Long.MAX_VALUE / delayInterval) {
51 | return maxDelay;
52 | }
53 |
54 | long delay = multiplier * delayInterval;
55 | delay = Math.min(delay, maxDelay);
56 | return delay;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/util/SQSMessagingClientThreadFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.util;
16 |
17 | import java.util.concurrent.ThreadFactory;
18 | import java.util.concurrent.atomic.AtomicInteger;
19 |
20 | /** Simple thread factory that supports ThreadGroups */
21 |
22 | public class SQSMessagingClientThreadFactory implements ThreadFactory {
23 |
24 | private final String threadBaseName;
25 |
26 | private final AtomicInteger threadCounter;
27 |
28 | private final boolean isDaemon;
29 |
30 | private ThreadGroup threadGroup;
31 |
32 | public SQSMessagingClientThreadFactory(String taskName, boolean isDaemon) {
33 | this(taskName, isDaemon, false);
34 | }
35 |
36 | public SQSMessagingClientThreadFactory(String taskName, boolean isDaemon, boolean createWithThreadGroup) {
37 | this.threadBaseName = taskName + "Thread-";
38 | this.threadCounter = new AtomicInteger(0);
39 | this.isDaemon = isDaemon;
40 | if (createWithThreadGroup) {
41 | threadGroup = new ThreadGroup(taskName + "ThreadGroup");
42 | threadGroup.setDaemon(isDaemon);
43 | }
44 | }
45 |
46 | public SQSMessagingClientThreadFactory(String taskName, ThreadGroup threadGroup) {
47 | this.threadBaseName = taskName + "Thread-";
48 | this.threadCounter = new AtomicInteger(0);
49 | this.isDaemon = threadGroup.isDaemon();
50 | this.threadGroup = threadGroup;
51 | }
52 |
53 | /**
54 | * Constructs a new Thread. Initializes name, daemon status, and ThreadGroup
55 | * if there is any.
56 | *
57 | * @param r
58 | * A runnable to be executed by new thread instance
59 | * @return The constructed thread
60 | */
61 | public Thread newThread(Runnable r) {
62 | Thread t;
63 | if (threadGroup == null) {
64 | t = new Thread(r, threadBaseName + threadCounter.incrementAndGet());
65 | t.setDaemon(isDaemon);
66 | } else {
67 | t = new Thread(threadGroup, r, threadBaseName + threadCounter.incrementAndGet());
68 | t.setDaemon(isDaemon);
69 | }
70 | return t;
71 | }
72 |
73 | /**
74 | * Checks if the thread is member of the thread group
75 | *
76 | * @param thread
77 | * @return True If there is a thread group and the given thread is member of
78 | * the group
79 | */
80 | public boolean wasThreadCreatedWithThisThreadGroup(Thread thread) {
81 | if (threadGroup == null) {
82 | return false;
83 | }
84 | return thread.getThreadGroup() == threadGroup;
85 | }
86 |
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/src/main/java/com/amazon/sqs/javamessaging/util/SQSMessagingClientUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.util;
16 |
17 | import java.util.regex.Matcher;
18 | import java.util.regex.Pattern;
19 |
20 | /**
21 | * Includes utility classes to use when serializing property names to SQS
22 | * message attribute names. SQS message attribute names accept limited set of
23 | * characters, so if the property names include any characters that is not
24 | * accepted, they can be serialized through these basic utility classes.
25 | */
26 | public final class SQSMessagingClientUtil {
27 |
28 | private static final char HYPHEN = '-';
29 |
30 | private static final char UNDERSCORE = '_';
31 |
32 | private static final char DOT = '.';
33 |
34 | private static final Pattern ILLEGAL_ATTRIBUTE_NAME_PATTERN = Pattern.compile(UNDERSCORE + "([0-9]+)" +
35 | UNDERSCORE);
36 |
37 | /**
38 | * Keeping alphabet, numeric characters, hyphens, underscores, or dots.
39 | *
40 | * Changes everything to underscores Unicode number underscores, e.g.,
41 | * (*attr* -> _42_attr_42_).
42 | *
43 | * @param name
44 | * The name of the property to serialize.
45 | * @return The serialized name
46 | */
47 | public static String serializePropertyName(String name) {
48 |
49 | StringBuilder stringBuilder = new StringBuilder();
50 | for (char ch : name.toCharArray()) {
51 | if (Character.isLetterOrDigit(ch) || HYPHEN == ch || DOT == ch) {
52 | stringBuilder.append(ch);
53 | } else {
54 | stringBuilder.append(UNDERSCORE).append(Integer.toString(ch)).append(UNDERSCORE);
55 | }
56 | }
57 | return stringBuilder.toString();
58 | }
59 |
60 | /**
61 | * Changes everything from underscores Unicode number underscores back to
62 | * original character, e.g., (_42_attr_42_ -> *attr*).
63 | *
64 | * @param name
65 | * The name of the property to deserialize.
66 | * @return The deserialized name
67 | */
68 | public static String deserializePropertyName(String name) {
69 | String result = name;
70 | Matcher m = ILLEGAL_ATTRIBUTE_NAME_PATTERN.matcher(result);
71 |
72 | while (m.find()) {
73 | int charValue = Integer.parseInt(m.group(1));
74 | result = result.replace("_" + charValue + "_", Character.toString((char) charValue));
75 | }
76 | return result;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/AcknowledgerCommon.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
18 | import com.amazon.sqs.javamessaging.message.SQSMessage;
19 | import com.amazon.sqs.javamessaging.message.SQSTextMessage;
20 | import jakarta.jms.JMSException;
21 | import software.amazon.awssdk.services.sqs.model.Message;
22 | import software.amazon.awssdk.services.sqs.model.MessageSystemAttributeName;
23 |
24 | import java.util.ArrayList;
25 | import java.util.HashMap;
26 | import java.util.List;
27 | import java.util.Map;
28 |
29 | import static org.junit.jupiter.api.Assertions.assertEquals;
30 |
31 | /**
32 | * Parent class for the Acknowledger tests
33 | */
34 | public class AcknowledgerCommon {
35 |
36 | protected String baseQueueUrl = "queueUrl";
37 | protected Acknowledger acknowledger;
38 | protected AmazonSQSMessagingClientWrapper amazonSQSClient;
39 | protected List populatedMessages = new ArrayList<>();
40 |
41 | /*
42 | * Generate and populate the list with sqs message from different queues
43 | */
44 | public void populateMessage(int populateMessageSize) throws JMSException {
45 | String queueUrl = baseQueueUrl + 0;
46 | for (int i = 0; i < populateMessageSize; i++) {
47 | // Change queueUrl depending on how many messages there are.
48 | if (i == 11) {
49 | queueUrl = baseQueueUrl + 1;
50 | } else if (i == 22) {
51 | queueUrl = baseQueueUrl + 2;
52 | } else if (i == 33) {
53 | queueUrl = baseQueueUrl + 3;
54 | } else if (i == 44) {
55 | queueUrl = baseQueueUrl + 4;
56 | }
57 | // Add mock Attributes
58 | Map mockAttributes = new HashMap<>();
59 | mockAttributes.put(MessageSystemAttributeName.fromValue(SQSMessagingClientConstants.APPROXIMATE_RECEIVE_COUNT), "2");
60 |
61 | Message sqsMessage = Message.builder()
62 | .receiptHandle("ReceiptHandle" + i)
63 | .messageId("MessageId" + i)
64 | .attributes(mockAttributes)
65 | .build();
66 |
67 | SQSMessage message = new SQSTextMessage(acknowledger, queueUrl, sqsMessage);
68 |
69 | populatedMessages.add(message);
70 | acknowledger.notifyMessageReceived(message);
71 | }
72 | assertEquals(populateMessageSize, acknowledger.getUnAckMessages().size());
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/AutoAcknowledgerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.AcknowledgeMode;
18 | import com.amazon.sqs.javamessaging.acknowledge.AutoAcknowledger;
19 | import com.amazon.sqs.javamessaging.message.SQSMessage;
20 | import jakarta.jms.IllegalStateException;
21 | import org.junit.jupiter.api.BeforeEach;
22 | import org.junit.jupiter.api.Test;
23 | import org.mockito.ArgumentCaptor;
24 | import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
25 |
26 | import static org.assertj.core.api.Assertions.assertThat;
27 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
28 | import static org.junit.jupiter.api.Assertions.assertEquals;
29 | import static org.mockito.Mockito.doThrow;
30 | import static org.mockito.Mockito.mock;
31 | import static org.mockito.Mockito.spy;
32 | import static org.mockito.Mockito.verify;
33 | import static org.mockito.Mockito.when;
34 |
35 | /**
36 | * Test the AutoAcknowledger class
37 | */
38 | public class AutoAcknowledgerTest {
39 |
40 | private static final String QUEUE_URL = "QueueUrl";
41 | private static final String RECEIPT_HANDLE = "ReceiptHandle";
42 |
43 | private AutoAcknowledger acknowledger;
44 | private AmazonSQSMessagingClientWrapper amazonSQSClient;
45 | private SQSSession session;
46 |
47 | @BeforeEach
48 | public void before() throws Exception {
49 | amazonSQSClient = mock(AmazonSQSMessagingClientWrapper.class);
50 | session = mock(SQSSession.class);
51 | acknowledger = (AutoAcknowledger) spy(AcknowledgeMode.ACK_AUTO.createAcknowledger(amazonSQSClient, session));
52 | }
53 |
54 | /**
55 | * Test acknowledging message with auto acknowledger
56 | */
57 | @Test
58 | public void testAcknowledge() throws Exception {
59 | /*
60 | * Set up message mock
61 | */
62 | SQSMessage message = mock(SQSMessage.class);
63 | when(message.getQueueUrl())
64 | .thenReturn(QUEUE_URL);
65 | when(message.getReceiptHandle())
66 | .thenReturn(RECEIPT_HANDLE);
67 |
68 | /*
69 | * Use the acknowledger to ack the message
70 | */
71 | acknowledger.acknowledge(message);
72 |
73 | /*
74 | * Verify results
75 | */
76 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(DeleteMessageRequest.class);
77 | verify(amazonSQSClient).deleteMessage(argumentCaptor.capture());
78 | assertEquals(1, argumentCaptor.getAllValues().size());
79 |
80 | DeleteMessageRequest input = argumentCaptor.getAllValues().get(0);
81 | assertEquals(QUEUE_URL, input.queueUrl());
82 | assertEquals(RECEIPT_HANDLE, input.receiptHandle());
83 | }
84 |
85 | /**
86 | * Test attempt to acknowledge when the session is already closed
87 | */
88 | @Test
89 | public void testAcknowledgeWhenSessionClosed() throws Exception {
90 | /*
91 | * Set up mocks
92 | */
93 | doThrow(new IllegalStateException("ise"))
94 | .when(session).checkClosed();
95 |
96 | SQSMessage message = mock(SQSMessage.class);
97 | when(message.getQueueUrl())
98 | .thenReturn(QUEUE_URL);
99 | when(message.getReceiptHandle())
100 | .thenReturn(RECEIPT_HANDLE);
101 |
102 | /*
103 | * Use the acknowledger to ack the message
104 | */
105 | assertThatThrownBy(() -> acknowledger.acknowledge(message))
106 | .isInstanceOf(IllegalStateException.class);
107 | }
108 |
109 | /**
110 | * Test notify message received
111 | */
112 | @Test
113 | public void testNotifyMessageReceived() throws Exception {
114 | SQSMessage message = mock(SQSMessage.class);
115 | acknowledger.notifyMessageReceived(message);
116 | verify(acknowledger).acknowledge(message);
117 | }
118 |
119 | /**
120 | * Test get UnAckMessages
121 | */
122 | @Test
123 | public void testGetUnAckMessages() {
124 | assertThat(acknowledger.getUnAckMessages()).isEmpty();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/BulkSQSOperationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.BulkSQSOperation;
18 | import com.amazon.sqs.javamessaging.acknowledge.SQSMessageIdentifier;
19 | import org.junit.jupiter.api.BeforeEach;
20 | import org.junit.jupiter.api.Test;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
26 | import static org.junit.jupiter.api.Assertions.assertEquals;
27 | import static org.mockito.Mockito.spy;
28 | import static org.mockito.Mockito.verify;
29 |
30 | /**
31 | * Test the BulkSQSOperation class
32 | */
33 | public class BulkSQSOperationTest {
34 |
35 | private static final String QUEUE_URL = "queueUrl";
36 | private static final String MESSAGE_ID_PREFIX = "sqsMessageId";
37 | private static final String RECEIPT_HANDLE_PREFIX = "receiptHandle";
38 |
39 | private BulkSQSOperation bulkAction;
40 |
41 | @BeforeEach
42 | public void before() {
43 | bulkAction = spy(new BulkSQSOperation() {
44 | @Override
45 | public void action(String queueUrl, List receiptHandles) {
46 | }
47 | });
48 | }
49 |
50 | /**
51 | * Test illegal index value cases
52 | */
53 | @Test
54 | public void testBulkActionIllegalIndexValue() {
55 | List messageIdentifierList = List.of(
56 | new SQSMessageIdentifier(QUEUE_URL, "receiptHandle1", "sqsMessageId1"),
57 | new SQSMessageIdentifier(QUEUE_URL, "receiptHandle2", "sqsMessageId2"),
58 | new SQSMessageIdentifier(QUEUE_URL, "receiptHandle3", "sqsMessageId3"));
59 |
60 | int negativeSize = -10;
61 | assertThatThrownBy(() -> bulkAction.bulkAction(messageIdentifierList, negativeSize))
62 | .isInstanceOf(AssertionError.class);
63 |
64 | assertThatThrownBy(() -> bulkAction.bulkAction(messageIdentifierList, 0))
65 | .isInstanceOf(AssertionError.class);
66 |
67 | assertThatThrownBy(() -> bulkAction.bulkAction(messageIdentifierList, messageIdentifierList.size() + 1))
68 | .isInstanceOf(AssertionError.class);
69 | }
70 |
71 | /**
72 | * Test message flushed if number of message is below max batch size
73 | */
74 | @Test
75 | public void testBulkActionBelowBatchSize() throws Exception {
76 | List messageIdentifierList = new ArrayList<>();
77 |
78 | int numMessagesFromQueue = (SQSMessagingClientConstants.MAX_BATCH - 2) / 2;
79 |
80 | // Create message from the first queue
81 | int i = 0;
82 | List receiptHandles1 = new ArrayList<>();
83 | for (; i < numMessagesFromQueue; ++i) {
84 | messageIdentifierList.add(new SQSMessageIdentifier(QUEUE_URL + 1,
85 | RECEIPT_HANDLE_PREFIX + i, MESSAGE_ID_PREFIX + i));
86 | receiptHandles1.add(RECEIPT_HANDLE_PREFIX + i);
87 | }
88 |
89 | // Create message from the second queue
90 | List receiptHandles2 = new ArrayList<>();
91 | for (; i < numMessagesFromQueue * 2; ++i) {
92 | messageIdentifierList.add(new SQSMessageIdentifier(QUEUE_URL + 2,
93 | RECEIPT_HANDLE_PREFIX + i, MESSAGE_ID_PREFIX + i));
94 | receiptHandles2.add(RECEIPT_HANDLE_PREFIX + i);
95 | }
96 |
97 | bulkAction.bulkAction(messageIdentifierList, messageIdentifierList.size());
98 |
99 | verify(bulkAction).action(QUEUE_URL + 1, receiptHandles1);
100 | verify(bulkAction).action(QUEUE_URL + 2, receiptHandles2);
101 | }
102 |
103 |
104 | /**
105 | * Test message are send if number of message from a single queue is above max batch size
106 | */
107 | @Test
108 | public void testBulkActionAboveBatchSize() throws Exception {
109 | List messageIdentifierList = new ArrayList<>();
110 |
111 | int numMessagesFromQueue = SQSMessagingClientConstants.MAX_BATCH * 2 + 3;
112 |
113 | // Create messages from the first batch
114 | int i = 0;
115 | List firstBatchReceiptHandles = new ArrayList<>();
116 | for (; i < SQSMessagingClientConstants.MAX_BATCH; ++i) {
117 | messageIdentifierList.add(new SQSMessageIdentifier(QUEUE_URL + 1,
118 | RECEIPT_HANDLE_PREFIX + i, MESSAGE_ID_PREFIX + i));
119 | firstBatchReceiptHandles.add(RECEIPT_HANDLE_PREFIX + i);
120 | }
121 |
122 | // Create messages from the second batch
123 | List secondBatchReceiptHandles = new ArrayList<>();
124 | for (; i < SQSMessagingClientConstants.MAX_BATCH * 2; ++i) {
125 | messageIdentifierList.add(new SQSMessageIdentifier(QUEUE_URL + 1,
126 | RECEIPT_HANDLE_PREFIX + i, MESSAGE_ID_PREFIX + i));
127 | secondBatchReceiptHandles.add(RECEIPT_HANDLE_PREFIX + i);
128 | }
129 |
130 | // Create messages from the third batch
131 | List thirdBatchReceiptHandles = new ArrayList<>();
132 | for (; i < numMessagesFromQueue; ++i) {
133 | messageIdentifierList.add(new SQSMessageIdentifier(QUEUE_URL + 1,
134 | RECEIPT_HANDLE_PREFIX + i, MESSAGE_ID_PREFIX + i));
135 | thirdBatchReceiptHandles.add(RECEIPT_HANDLE_PREFIX + i);
136 | }
137 |
138 | // Create messages from a different queue
139 | List receiptHandles2 = new ArrayList<>();
140 | for (i = 0; i < SQSMessagingClientConstants.MAX_BATCH / 2; ++i) {
141 | messageIdentifierList.add(new SQSMessageIdentifier(QUEUE_URL + 2,
142 | RECEIPT_HANDLE_PREFIX + i, MESSAGE_ID_PREFIX + i));
143 | receiptHandles2.add(RECEIPT_HANDLE_PREFIX + i);
144 | }
145 |
146 | final List> receiptHandlesList = new ArrayList<>();
147 | final List queueUrlList = new ArrayList<>();
148 | bulkAction = new BulkSQSOperation() {
149 | @Override
150 | public void action(String queueUrl, List receiptHandles) {
151 | receiptHandlesList.add(new ArrayList<>(receiptHandles));
152 | queueUrlList.add(queueUrl);
153 | }
154 | };
155 |
156 | bulkAction.bulkAction(messageIdentifierList, messageIdentifierList.size());
157 |
158 | assertEquals(firstBatchReceiptHandles, receiptHandlesList.get(0));
159 | assertEquals(QUEUE_URL + 1, queueUrlList.get(0));
160 |
161 | assertEquals(secondBatchReceiptHandles, receiptHandlesList.get(1));
162 | assertEquals(QUEUE_URL + 1, queueUrlList.get(1));
163 |
164 | assertEquals(thirdBatchReceiptHandles, receiptHandlesList.get(2));
165 | assertEquals(QUEUE_URL + 1, queueUrlList.get(2));
166 |
167 | assertEquals(receiptHandles2, receiptHandlesList.get(3));
168 | assertEquals(QUEUE_URL + 2, queueUrlList.get(3));
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/ExponentialBackoffStrategyTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.util.ExponentialBackoffStrategy;
18 | import org.junit.jupiter.api.Test;
19 |
20 | import static org.junit.jupiter.api.Assertions.assertEquals;
21 |
22 | /**
23 | * Test the ExponentialBackoffStrategy class
24 | */
25 | public class ExponentialBackoffStrategyTest {
26 |
27 | private static final int DELAY_INTERVAL = 10;
28 | private static final int INITIAL_DELAY = 20;
29 | private static final int MAX_DELAY = 300;
30 |
31 | /**
32 | * test delay with illegal value of retry attempts
33 | */
34 | @Test
35 | public void testDelayBeforeNextRetryNonPositiveRetryAttempt() {
36 | ExponentialBackoffStrategy backoff = new ExponentialBackoffStrategy(DELAY_INTERVAL, INITIAL_DELAY, MAX_DELAY);
37 |
38 | assertEquals(INITIAL_DELAY, backoff.delayBeforeNextRetry(0));
39 | assertEquals(INITIAL_DELAY, backoff.delayBeforeNextRetry(-10));
40 | }
41 |
42 | /**
43 | * test first delay result in delay initial value
44 | */
45 | @Test
46 | public void testDelayBeforeNextRetryFirstAttempt() {
47 | ExponentialBackoffStrategy backoff = new ExponentialBackoffStrategy(DELAY_INTERVAL, INITIAL_DELAY, MAX_DELAY);
48 |
49 | assertEquals(DELAY_INTERVAL, backoff.delayBeforeNextRetry(1));
50 | }
51 |
52 | /**
53 | * test delay from 2 to 1000
54 | */
55 | @Test
56 | public void testDelayBeforeNextRetry() {
57 | ExponentialBackoffStrategy backoff = new ExponentialBackoffStrategy(DELAY_INTERVAL, INITIAL_DELAY, MAX_DELAY);
58 |
59 | assertEquals(20, backoff.delayBeforeNextRetry(2));
60 | assertEquals(40, backoff.delayBeforeNextRetry(3));
61 | assertEquals(80, backoff.delayBeforeNextRetry(4));
62 | assertEquals(160, backoff.delayBeforeNextRetry(5));
63 | for (int i = 6; i < 1000; i++) {
64 | assertEquals(MAX_DELAY, backoff.delayBeforeNextRetry(i));
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/MessageListenerConcurrentOperationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.AcknowledgeMode;
18 | import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
19 | import com.amazon.sqs.javamessaging.acknowledge.NegativeAcknowledger;
20 | import com.amazon.sqs.javamessaging.message.SQSMessage;
21 | import jakarta.jms.IllegalStateException;
22 | import jakarta.jms.JMSException;
23 | import jakarta.jms.Message;
24 | import jakarta.jms.MessageListener;
25 | import jakarta.jms.Session;
26 | import org.junit.jupiter.api.BeforeEach;
27 | import org.junit.jupiter.api.Test;
28 |
29 | import java.util.Collections;
30 | import java.util.concurrent.BrokenBarrierException;
31 | import java.util.concurrent.CountDownLatch;
32 | import java.util.concurrent.CyclicBarrier;
33 |
34 | import static org.junit.jupiter.api.Assertions.assertFalse;
35 | import static org.junit.jupiter.api.Assertions.assertTrue;
36 | import static org.mockito.Mockito.mock;
37 |
38 | /**
39 | * Test concurrent operation of message listener on session and connections
40 | */
41 | public class MessageListenerConcurrentOperationTest {
42 |
43 | private static final String QUEUE_URL = "queueUrl";
44 | private static final String QUEUE_NAME = "queueName";
45 | private static final int NUMBER_OF_MESSAGES_TO_PREFETCH = 10;
46 |
47 | private AmazonSQSMessagingClientWrapper amazonSQSClient;
48 | private SQSMessageConsumerPrefetch.MessageManager msgManager;
49 | private volatile SQSSession session;
50 | private volatile SQSConnection connection;
51 |
52 | /**
53 | * Message listener that creates a producer on the session
54 | */
55 | private final MessageListener msgListenerCreatesProducer = new MessageListener() {
56 | @Override
57 | public void onMessage(Message message) {
58 | try {
59 | session.createProducer(new SQSQueueDestination(QUEUE_NAME, QUEUE_URL));
60 | } catch (JMSException e) {
61 | e.printStackTrace();
62 | }
63 | }
64 | };
65 |
66 | /**
67 | * Message listener that creates a consumer on the session
68 | */
69 | private final MessageListener msgListenerCreatesConsumer = new MessageListener() {
70 | @Override
71 | public void onMessage(Message message) {
72 | try {
73 | session.createProducer(new SQSQueueDestination(QUEUE_NAME, QUEUE_URL));
74 | } catch (JMSException e) {
75 | e.printStackTrace();
76 | }
77 | }
78 | };
79 |
80 | @BeforeEach
81 | public void Setup() throws JMSException {
82 | Acknowledger acknowledger = mock(Acknowledger.class);
83 | NegativeAcknowledger negativeAcknowledger = mock(NegativeAcknowledger.class);
84 | SQSQueueDestination sqsDestination = new SQSQueueDestination(QUEUE_NAME, QUEUE_URL);
85 | amazonSQSClient = mock(AmazonSQSMessagingClientWrapper.class);
86 |
87 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
88 | session = new SQSSession(connection, AcknowledgeMode.ACK_AUTO);
89 | SQSSessionCallbackScheduler sqsSessionRunnable = new SQSSessionCallbackScheduler(session,
90 | AcknowledgeMode.ACK_AUTO, acknowledger, negativeAcknowledger);
91 |
92 | SQSMessageConsumer consumer = mock(SQSMessageConsumer.class);
93 |
94 | SQSMessageConsumerPrefetch preFetcher = new SQSMessageConsumerPrefetch(sqsSessionRunnable, acknowledger,
95 | negativeAcknowledger, sqsDestination, amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
96 | preFetcher.setMessageConsumer(consumer);
97 |
98 | msgManager = new SQSMessageConsumerPrefetch.MessageManager(preFetcher, mock(SQSMessage.class));
99 | }
100 |
101 | /**
102 | * Test concurrent operation on message listener creating producer and consumer when
103 | * session is concurrently closed
104 | */
105 | @Test
106 | public void testConcurrentSessionCloseOperation() throws JMSException, InterruptedException {
107 |
108 | ConcurrentOperation closeSessionOperation = new ConcurrentOperation() {
109 | @Override
110 | public void setup() throws IllegalStateException {
111 | session.start();
112 | }
113 |
114 | @Override
115 | public void applyOperation() throws JMSException {
116 | session.close();
117 | }
118 |
119 | @Override
120 | public void verify() {
121 | assertTrue(session.isClosed());
122 | assertFalse(session.isRunning());
123 | }
124 | };
125 |
126 | // Test session close operation with create producer operation
127 | for (int i = 0; i < 10; ++i) {
128 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
129 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
130 |
131 | testConcurrentExecution(msgListenerCreatesProducer, closeSessionOperation);
132 | connection.close();
133 | }
134 |
135 | // Test session close operation with create consumer operation
136 | for (int i = 0; i < 10; ++i) {
137 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
138 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
139 |
140 | testConcurrentExecution(msgListenerCreatesConsumer, closeSessionOperation);
141 | connection.close();
142 | }
143 | }
144 |
145 | /**
146 | * Test concurrent operation on message listener creating producer and consumer when
147 | * connection is concurrently started
148 | */
149 | @Test
150 | public void testConcurrentConnectionStartOperation() throws JMSException, InterruptedException {
151 |
152 | ConcurrentOperation startConnectionOperation = new ConcurrentOperation() {
153 | @Override
154 | public void setup() throws JMSException {
155 | session.start();
156 | }
157 |
158 | @Override
159 | public void applyOperation() throws JMSException {
160 | connection.start();
161 | }
162 |
163 | @Override
164 | public void verify() {
165 | assertFalse(connection.isClosed());
166 | assertTrue(connection.isRunning());
167 | }
168 | };
169 |
170 | // Test connection start operation with create producer operation
171 | for (int i = 0; i < 10; ++i) {
172 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
173 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
174 |
175 | testConcurrentExecution(msgListenerCreatesProducer, startConnectionOperation);
176 | connection.close();
177 | }
178 |
179 | // Test connection start operation with create consumer operation
180 | for (int i = 0; i < 10; ++i) {
181 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
182 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
183 |
184 | testConcurrentExecution(msgListenerCreatesConsumer, startConnectionOperation);
185 | connection.close();
186 | }
187 | }
188 |
189 | /**
190 | * Test concurrent operation on message listener creating producer and consumer when
191 | * connection is concurrently closed
192 | */
193 | @Test
194 | public void testConcurrentConnectionCloseOperation() throws JMSException, InterruptedException {
195 | ConcurrentOperation closeConnectionOperation = new ConcurrentOperation() {
196 | @Override
197 | public void setup() throws JMSException {
198 | connection.start();
199 | }
200 |
201 | @Override
202 | public void applyOperation() throws JMSException {
203 | connection.close();
204 | }
205 |
206 | @Override
207 | public void verify() {
208 | assertTrue(connection.isClosed());
209 | }
210 | };
211 |
212 | // Test connection close operation with create producer operation
213 | for (int i = 0; i < 10; ++i) {
214 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
215 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
216 |
217 | testConcurrentExecution(msgListenerCreatesProducer, closeConnectionOperation);
218 | }
219 |
220 | // Test connection close operation with create consumer operation
221 | for (int i = 0; i < 10; ++i) {
222 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
223 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
224 |
225 | testConcurrentExecution(msgListenerCreatesConsumer, closeConnectionOperation);
226 | }
227 | }
228 |
229 | /**
230 | * Test concurrent operation on message listener creating producer and consumer when
231 | * connection is concurrently stopped
232 | */
233 | @Test
234 | public void testConcurrentConnectionStopOperation() throws JMSException, InterruptedException {
235 | ConcurrentOperation stopConnectionOperation = new ConcurrentOperation() {
236 | @Override
237 | public void setup() throws JMSException {
238 | session.start();
239 | connection.start();
240 | }
241 |
242 | @Override
243 | public void applyOperation() throws JMSException {
244 | connection.stop();
245 | }
246 |
247 | @Override
248 | public void verify() {
249 | assertFalse(connection.isClosed());
250 | assertFalse(connection.isRunning());
251 | }
252 | };
253 |
254 |
255 | // Test connection stop operation with create producer operation
256 | for (int i = 0; i < 10; ++i) {
257 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
258 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
259 |
260 | testConcurrentExecution(msgListenerCreatesProducer, stopConnectionOperation);
261 | connection.close();
262 | }
263 |
264 | // Test connection stop operation with create consumer operation
265 | for (int i = 0; i < 10; ++i) {
266 | connection = new SQSConnection(amazonSQSClient, NUMBER_OF_MESSAGES_TO_PREFETCH);
267 | session = (SQSSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
268 |
269 | testConcurrentExecution(msgListenerCreatesConsumer, stopConnectionOperation);
270 | connection.close();
271 | }
272 | }
273 |
274 | public void testConcurrentExecution(final MessageListener msgListener, final ConcurrentOperation operation)
275 | throws JMSException, InterruptedException {
276 | // Start the session
277 | operation.setup();
278 |
279 | final CyclicBarrier startBarrier = new CyclicBarrier(2);
280 | final CountDownLatch finishLatch = new CountDownLatch(1);
281 |
282 | Thread t1 = new Thread(() -> {
283 | try {
284 | startBarrier.await();
285 | operation.applyOperation();
286 | } catch (JMSException | InterruptedException | BrokenBarrierException e) {
287 | e.printStackTrace();
288 | }
289 | finishLatch.countDown();
290 | });
291 |
292 | // Start the callback scheduler
293 | Thread t2 = new Thread(() -> {
294 | try {
295 | startBarrier.await();
296 | } catch (InterruptedException | BrokenBarrierException e) {
297 | e.printStackTrace();
298 | }
299 | session.getSqsSessionRunnable().scheduleCallBacks(msgListener, Collections.singletonList(msgManager));
300 | });
301 |
302 | t1.start();
303 | t2.start();
304 |
305 | finishLatch.await();
306 |
307 | operation.verify();
308 | }
309 |
310 | /**
311 | * Interface for the tested concurrent operation
312 | */
313 | private interface ConcurrentOperation {
314 |
315 | void setup() throws JMSException;
316 |
317 | void applyOperation() throws JMSException;
318 |
319 | void verify();
320 |
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/ModifyWaitTimeSecondsTest.java:
--------------------------------------------------------------------------------
1 | package com.amazon.sqs.javamessaging;
2 |
3 | import org.junit.jupiter.api.DisplayName;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.lang.reflect.Field;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 |
10 | class ModifyWaitTimeSecondsTest {
11 |
12 | @DisplayName("Should be able to modify SQSMessageConsumerPrefetch.WAIT_TIME_SECONDS via Reflection")
13 | @Test
14 | public void waitTimeSecondsShouldBeModifiableViaReflection() throws NoSuchFieldException, IllegalAccessException {
15 | Field wait_time_seconds = SQSMessageConsumerPrefetch.class.getDeclaredField("WAIT_TIME_SECONDS");
16 | wait_time_seconds.setAccessible(true);
17 | wait_time_seconds.setInt(null,5);
18 | assertEquals(5,SQSMessageConsumerPrefetch.WAIT_TIME_SECONDS);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/NegativeAcknowledgerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.NegativeAcknowledger;
18 | import com.amazon.sqs.javamessaging.message.SQSMessage;
19 | import jakarta.jms.JMSException;
20 | import org.junit.jupiter.api.BeforeEach;
21 | import org.junit.jupiter.api.Test;
22 | import org.mockito.ArgumentCaptor;
23 | import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequest;
24 | import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityBatchRequestEntry;
25 |
26 | import java.util.ArrayDeque;
27 | import java.util.Collections;
28 | import java.util.List;
29 |
30 | import static org.junit.jupiter.api.Assertions.assertEquals;
31 | import static org.junit.jupiter.api.Assertions.assertTrue;
32 | import static org.mockito.Mockito.any;
33 | import static org.mockito.Mockito.anyList;
34 | import static org.mockito.Mockito.eq;
35 | import static org.mockito.Mockito.mock;
36 | import static org.mockito.Mockito.never;
37 | import static org.mockito.Mockito.spy;
38 | import static org.mockito.Mockito.times;
39 | import static org.mockito.Mockito.verify;
40 | import static org.mockito.Mockito.when;
41 |
42 | /**
43 | * Test the NegativeAcknowledger class
44 | */
45 | public class NegativeAcknowledgerTest extends AcknowledgerCommon {
46 |
47 | private static final String QUEUE_URL = "queueUrl";
48 |
49 | private NegativeAcknowledger negativeAcknowledger;
50 |
51 | @BeforeEach
52 | public void setupRanded() {
53 | amazonSQSClient = mock(AmazonSQSMessagingClientWrapper.class);
54 | negativeAcknowledger = spy(new NegativeAcknowledger(amazonSQSClient));
55 | }
56 |
57 | /**
58 | * Test NegativeAcknowledger bulk action
59 | */
60 | @Test
61 | public void testNackBulkAction() throws JMSException {
62 | /*
63 | * Set up the message queue
64 | */
65 | List receiptHandles = List.of("r0", "r1", "r2");
66 |
67 | ArrayDeque messageQueue = addSQSMessageToQueue(3);
68 |
69 | /*
70 | * Nack the messages in bulk actions
71 | */
72 | negativeAcknowledger.bulkAction(messageQueue, QUEUE_URL);
73 |
74 | /*
75 | * Verify results
76 | */
77 | verify(negativeAcknowledger).action(QUEUE_URL, receiptHandles);
78 | }
79 |
80 | /**
81 | * Test NegativeAcknowledger bulk action with a large batch
82 | */
83 | @Test
84 | public void testNackBulkActionLargeBatch() throws JMSException {
85 | /*
86 | * Set up the message queue
87 | */
88 | ArrayDeque messageQueue = addSQSMessageToQueue(13);
89 |
90 | /*
91 | * Nack the messages in bulk actions
92 | */
93 | negativeAcknowledger.bulkAction(messageQueue, QUEUE_URL);
94 |
95 | /*
96 | * Verify results
97 | */
98 | verify(negativeAcknowledger, times(2)).action(eq(QUEUE_URL), anyList());
99 | }
100 |
101 | /**
102 | * Test NegativeAcknowledger action
103 | */
104 | @Test
105 | public void testAction() throws JMSException {
106 | List receiptHandles = List.of("r0", "r1", "r2");
107 |
108 | negativeAcknowledger.action(QUEUE_URL, receiptHandles);
109 |
110 | ArgumentCaptor argumentCaptor =
111 | ArgumentCaptor.forClass(ChangeMessageVisibilityBatchRequest.class);
112 | verify(amazonSQSClient).changeMessageVisibilityBatch(argumentCaptor.capture());
113 |
114 | assertEquals(1, argumentCaptor.getAllValues().size());
115 |
116 | assertEquals(QUEUE_URL, argumentCaptor.getAllValues().get(0).queueUrl());
117 | List captureList = argumentCaptor.getAllValues().get(0).entries();
118 | assertEquals(receiptHandles.size(), captureList.size());
119 |
120 | for (ChangeMessageVisibilityBatchRequestEntry item : captureList) {
121 | assertTrue(receiptHandles.contains(item.receiptHandle()));
122 | }
123 | }
124 |
125 | /**
126 | * Test NegativeAcknowledger action withe empty receipt handles
127 | */
128 | @Test
129 | public void testActionEmptyReceiptHandles() throws JMSException {
130 | negativeAcknowledger.action(QUEUE_URL, null);
131 |
132 | negativeAcknowledger.action(QUEUE_URL, Collections.emptyList());
133 |
134 | verify(amazonSQSClient, never()).changeMessageVisibilityBatch(any(ChangeMessageVisibilityBatchRequest.class));
135 | }
136 |
137 | /**
138 | * Utility function
139 | */
140 | private ArrayDeque addSQSMessageToQueue(int count) {
141 | ArrayDeque messageQueue = new ArrayDeque<>();
142 |
143 | PrefetchManager prefetchManager = mock(PrefetchManager.class);
144 | for (int i = 0; i < count; ++i) {
145 | SQSMessage msg = mock(SQSMessage.class);
146 | when(msg.getReceiptHandle()).thenReturn("r" + i);
147 | messageQueue.add(new SQSMessageConsumerPrefetch.MessageManager(prefetchManager, msg));
148 | }
149 |
150 | return messageQueue;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/RangedAcknowledgerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.AcknowledgeMode;
18 | import com.amazon.sqs.javamessaging.acknowledge.SQSMessageIdentifier;
19 | import com.amazon.sqs.javamessaging.message.SQSMessage;
20 | import jakarta.jms.JMSException;
21 | import org.junit.jupiter.api.BeforeEach;
22 | import org.junit.jupiter.api.Test;
23 | import org.mockito.ArgumentCaptor;
24 | import software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequest;
25 |
26 | import java.util.ArrayList;
27 | import java.util.HashMap;
28 | import java.util.List;
29 | import java.util.Map;
30 |
31 | import static org.junit.jupiter.api.Assertions.assertEquals;
32 | import static org.junit.jupiter.api.Assertions.assertFalse;
33 | import static org.junit.jupiter.api.Assertions.assertNotNull;
34 | import static org.junit.jupiter.api.Assertions.assertTrue;
35 | import static org.mockito.Mockito.mock;
36 | import static org.mockito.Mockito.times;
37 | import static org.mockito.Mockito.verify;
38 |
39 |
40 | /**
41 | * Test the RangedAcknowledger class
42 | */
43 | public class RangedAcknowledgerTest extends AcknowledgerCommon {
44 |
45 | @BeforeEach
46 | public void setupRanded() throws JMSException {
47 | amazonSQSClient = mock(AmazonSQSMessagingClientWrapper.class);
48 | acknowledger = AcknowledgeMode.ACK_RANGE.createAcknowledger(amazonSQSClient, mock(SQSSession.class));
49 | }
50 |
51 | /**
52 | * Test forget un-acknowledge messages
53 | */
54 | @Test
55 | public void testForget() throws JMSException {
56 | int populateMessageSize = 33;
57 | populateMessage(populateMessageSize);
58 |
59 | acknowledger.forgetUnAckMessages();
60 | assertEquals(0, acknowledger.getUnAckMessages().size());
61 | }
62 |
63 | /**
64 | * Test acknowledge all un-acknowledge messages
65 | */
66 | @Test
67 | public void testFullAck() throws JMSException {
68 | int populateMessageSize = 34;
69 | populateMessage(populateMessageSize);
70 | int ackMessage = 33;
71 |
72 | testAcknowledge(populateMessageSize, ackMessage);
73 |
74 | assertEquals(0, acknowledger.getUnAckMessages().size());
75 | }
76 |
77 | /**
78 | * Test acknowledge 25 first un-acknowledge messages
79 | */
80 | @Test
81 | public void testOneAck() throws JMSException {
82 | int populateMessageSize = 34;
83 | populateMessage(populateMessageSize);
84 | int ackMessage = 25;
85 |
86 | testAcknowledge(populateMessageSize, ackMessage);
87 |
88 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(DeleteMessageBatchRequest.class);
89 | verify(amazonSQSClient, times(5)).deleteMessageBatch(argumentCaptor.capture());
90 |
91 | //key is the queue url
92 | //value is the sequence of sizes of expected batches
93 | Map> expectedCalls = new HashMap<>();
94 | List queue0Calls = new ArrayList<>();
95 | queue0Calls.add(10);
96 | queue0Calls.add(1);
97 | expectedCalls.put(baseQueueUrl + 0, queue0Calls);
98 | List queue1Calls = new ArrayList<>();
99 | queue1Calls.add(10);
100 | queue1Calls.add(1);
101 | expectedCalls.put(baseQueueUrl + 1, queue1Calls);
102 | List queue2Calls = new ArrayList<>();
103 | queue2Calls.add(4);
104 | expectedCalls.put(baseQueueUrl + 2, queue2Calls);
105 |
106 | for (DeleteMessageBatchRequest request : argumentCaptor.getAllValues()) {
107 | String queueUrl = request.queueUrl();
108 | List expectedSequence = expectedCalls.get(queueUrl);
109 | assertNotNull(expectedSequence);
110 | assertTrue(expectedSequence.size() > 0);
111 | assertEquals(expectedSequence.get(0).intValue(), request.entries().size());
112 | expectedSequence.remove(0);
113 | if (expectedSequence.isEmpty()) {
114 | expectedCalls.remove(queueUrl);
115 | }
116 | }
117 |
118 | assertTrue(expectedCalls.isEmpty());
119 | }
120 |
121 | /**
122 | * Test two acknowledge calls
123 | */
124 | @Test
125 | public void testTwoAck() throws JMSException {
126 | int populateMessageSize = 44;
127 | populateMessage(populateMessageSize);
128 | int firstAckMessage = 10;
129 |
130 | testAcknowledge(populateMessageSize, firstAckMessage);
131 |
132 | int secondAckMessage = 20;
133 | testAcknowledge(populateMessageSize, secondAckMessage);
134 | }
135 |
136 | /**
137 | * Utility function to acknowledge the first indexOfMessageToAck un-acknowledge messages
138 | *
139 | * @param populateMessageSize current un-acknowledge messages size
140 | * @param indexOfMessageToAck index of message to acknowledge
141 | */
142 | public void testAcknowledge(int populateMessageSize, int indexOfMessageToAck) throws JMSException {
143 | SQSMessage messageToAck = populatedMessages.get(indexOfMessageToAck);
144 | messageToAck.acknowledge();
145 |
146 | for (int i = 0; i < indexOfMessageToAck; i++) {
147 | SQSMessage message = populatedMessages.get(i);
148 | SQSMessageIdentifier sqsMessageIdentifier = new SQSMessageIdentifier(
149 | message.getQueueUrl(), message.getReceiptHandle(), message.getSQSMessageId());
150 | assertFalse(acknowledger.getUnAckMessages().contains(sqsMessageIdentifier));
151 | }
152 |
153 | assertEquals((populateMessageSize - indexOfMessageToAck - 1), acknowledger.getUnAckMessages().size());
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/SQSConnectionFactoryTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import jakarta.jms.JMSException;
18 | import org.junit.jupiter.api.Test;
19 | import software.amazon.awssdk.regions.Region;
20 | import software.amazon.awssdk.services.sqs.SqsClient;
21 | import software.amazon.awssdk.services.sqs.SqsClientBuilder;
22 |
23 | import static org.junit.jupiter.api.Assertions.assertNotSame;
24 | import static org.junit.jupiter.api.Assertions.assertSame;
25 | import static org.mockito.Mockito.mock;
26 |
27 |
28 | public class SQSConnectionFactoryTest {
29 |
30 | @Test
31 | public void canCreateFactoryWithNoSetting() throws JMSException{
32 | System.setProperty("aws.region", "eu-central-1");
33 | SQSConnectionFactory factory= new SQSConnectionFactory();
34 | SQSConnection connection = factory.createConnection();
35 | connection.close();
36 | }
37 |
38 | @Test
39 | public void canCreateFactoryWithDefaultProviderSettings() {
40 | System.setProperty("aws.region", "eu-central-1");
41 | new SQSConnectionFactory(new ProviderConfiguration());
42 | //cannot actually attempt to create a connection because the default client builder depends on environment settings or instance configuration to be present
43 | //which we cannot guarantee on the builder fleet
44 | }
45 |
46 | @Test
47 | public void canCreateFactoryWithCustomClient() throws JMSException {
48 | SqsClient client = mock(SqsClient.class);
49 | SQSConnectionFactory factory = new SQSConnectionFactory(new ProviderConfiguration(), client);
50 | SQSConnection connection = factory.createConnection();
51 | connection.close();
52 | }
53 |
54 | @Test
55 | public void factoryWithCustomClientWillUseTheSameClient() throws JMSException {
56 | SqsClient client = mock(SqsClient.class);
57 | SQSConnectionFactory factory = new SQSConnectionFactory(new ProviderConfiguration(), client);
58 | SQSConnection connection1 = factory.createConnection();
59 | SQSConnection connection2 = factory.createConnection();
60 |
61 | assertSame(client, connection1.getAmazonSQSClient());
62 | assertSame(client, connection2.getAmazonSQSClient());
63 | assertSame(connection1.getAmazonSQSClient(), connection2.getAmazonSQSClient());
64 |
65 | connection1.close();
66 | connection2.close();
67 | }
68 |
69 | @Test
70 | public void canCreateFactoryWithCustomBuilder() throws JMSException {
71 | SqsClientBuilder clientBuilder = SqsClient.builder().region(Region.US_EAST_1);
72 | SQSConnectionFactory factory = new SQSConnectionFactory(new ProviderConfiguration(), clientBuilder);
73 | SQSConnection connection = factory.createConnection();
74 | connection.close();
75 | }
76 |
77 | @Test
78 | public void factoryWithCustomBuilderWillCreateNewClient() throws JMSException {
79 | SqsClientBuilder clientBuilder = SqsClient.builder().region(Region.US_EAST_1);
80 | SQSConnectionFactory factory = new SQSConnectionFactory(new ProviderConfiguration(), clientBuilder);
81 | SQSConnection connection1 = factory.createConnection();
82 | SQSConnection connection2 = factory.createConnection();
83 |
84 | assertNotSame(connection1.getAmazonSQSClient(), connection2.getAmazonSQSClient());
85 |
86 | connection1.close();
87 | connection2.close();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/SQSMessageConsumerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.AcknowledgeMode;
18 | import com.amazon.sqs.javamessaging.acknowledge.Acknowledger;
19 | import com.amazon.sqs.javamessaging.acknowledge.NegativeAcknowledger;
20 | import com.amazon.sqs.javamessaging.util.SQSMessagingClientThreadFactory;
21 | import jakarta.jms.JMSException;
22 | import jakarta.jms.MessageListener;
23 | import org.junit.jupiter.api.BeforeEach;
24 | import org.junit.jupiter.api.Test;
25 |
26 | import java.util.concurrent.CountDownLatch;
27 | import java.util.concurrent.Executors;
28 | import java.util.concurrent.ScheduledExecutorService;
29 | import java.util.concurrent.TimeUnit;
30 |
31 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
32 | import static org.junit.jupiter.api.Assertions.assertEquals;
33 | import static org.junit.jupiter.api.Assertions.assertFalse;
34 | import static org.junit.jupiter.api.Assertions.assertTrue;
35 | import static org.junit.jupiter.api.Assertions.fail;
36 | import static org.mockito.Mockito.any;
37 | import static org.mockito.Mockito.mock;
38 | import static org.mockito.Mockito.never;
39 | import static org.mockito.Mockito.spy;
40 | import static org.mockito.Mockito.verify;
41 | import static org.mockito.Mockito.verifyNoMoreInteractions;
42 | import static org.mockito.Mockito.when;
43 |
44 | /**
45 | * Test the SQSMessageConsumerPrefetchTest class
46 | */
47 | public class SQSMessageConsumerTest {
48 |
49 | private static final String QUEUE_URL_1 = "QueueUrl1";
50 | private static final String QUEUE_NAME = "QueueName";
51 |
52 | private SQSMessageConsumer consumer;
53 | private SQSConnection sqsConnection;
54 | private SQSSession sqsSession;
55 | private SQSSessionCallbackScheduler sqsSessionRunnable;
56 | private Acknowledger acknowledger;
57 |
58 | private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
59 | private SQSMessageConsumerPrefetch sqsMessageConsumerPrefetch;
60 | private NegativeAcknowledger negativeAcknowledger;
61 | private SQSMessagingClientThreadFactory threadFactory;
62 | private SQSQueueDestination destination;
63 |
64 | @BeforeEach
65 | public void setup() throws JMSException {
66 | sqsConnection = mock(SQSConnection.class);
67 |
68 | sqsSession = spy(new SQSSession(sqsConnection, AcknowledgeMode.ACK_AUTO));//mock(SQSSession.class);
69 | sqsSessionRunnable = mock(SQSSessionCallbackScheduler.class);
70 |
71 | acknowledger = mock(Acknowledger.class);
72 |
73 | negativeAcknowledger = mock(NegativeAcknowledger.class);
74 |
75 | threadFactory = new SQSMessagingClientThreadFactory("testTask", true);
76 |
77 | destination = new SQSQueueDestination(QUEUE_NAME, QUEUE_URL_1);
78 |
79 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
80 | destination, acknowledger, negativeAcknowledger, threadFactory));
81 |
82 |
83 | sqsMessageConsumerPrefetch = mock(SQSMessageConsumerPrefetch.class);
84 | }
85 |
86 | /**
87 | * Test the message selector is not supported
88 | */
89 | @Test
90 | public void testGetMessageSelectorNotSupported() {
91 | assertThatThrownBy(() -> consumer.getMessageSelector())
92 | .isInstanceOf(JMSException.class)
93 | .hasMessage("Unsupported Method");
94 | }
95 |
96 | /**
97 | * Test stop is a no op if already closed
98 | */
99 | @Test
100 | public void testStopNoOpIfAlreadyClosed() throws JMSException {
101 | /*
102 | * Set up consumer
103 | */
104 | consumer.close();
105 |
106 | /*
107 | * stop consumer
108 | */
109 | consumer.stopPrefetch();
110 |
111 | /*
112 | * Verify results
113 | */
114 | verifyNoMoreInteractions(sqsMessageConsumerPrefetch);
115 | }
116 |
117 | /**
118 | * Test close blocks on in progress callback
119 | */
120 | @Test
121 | public void testCloseBlocksInProgressCallback() throws InterruptedException, JMSException {
122 | /*
123 | * Set up the latches
124 | */
125 | final CountDownLatch beforeConsumerStopCall = new CountDownLatch(1);
126 | final CountDownLatch passedConsumerStopCall = new CountDownLatch(1);
127 |
128 | consumer = new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
129 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch);
130 |
131 | sqsSession.start();
132 | sqsSession.startingCallback(consumer);
133 |
134 | // Run another thread that tries to close the consumer while activeConsumerInCallback is set
135 | executorService.execute(() -> {
136 | beforeConsumerStopCall.countDown();
137 | try {
138 | consumer.close();
139 | } catch (JMSException e) {
140 | fail();
141 | }
142 | passedConsumerStopCall.countDown();
143 | });
144 |
145 | beforeConsumerStopCall.await();
146 | Thread.sleep(10);
147 | // Ensure that we wait on activeConsumerInCallback
148 | assertFalse(passedConsumerStopCall.await(2, TimeUnit.SECONDS));
149 |
150 | // Release the activeConsumerInCallback
151 | sqsSession.finishedCallback();
152 |
153 | // Ensure that the consumer close completed
154 | passedConsumerStopCall.await();
155 |
156 | assertTrue(consumer.closed);
157 | }
158 |
159 |
160 | /**
161 | * Test Start is a no op if already closed
162 | */
163 | @Test
164 | public void testStartNoOpIfAlreadyClosed() throws JMSException {
165 | /*
166 | * Set up consumer
167 | */
168 | consumer.close();
169 |
170 | /*
171 | * start consumer
172 | */
173 | consumer.startPrefetch();
174 |
175 | /*
176 | * Verify results
177 | */
178 | verifyNoMoreInteractions(sqsMessageConsumerPrefetch);
179 | }
180 |
181 | /**
182 | * Test do close results in no op when the consumer is already closed
183 | */
184 | @Test
185 | public void testDoCloseNoOpWhenAlreadyClosed() {
186 | /*
187 | * Set up consumer
188 | */
189 | consumer.closed = true;
190 |
191 | /*
192 | * Do close consumer
193 | */
194 | consumer.doClose();
195 |
196 | /*
197 | * Verify results
198 | */
199 | verifyNoMoreInteractions(sqsSession);
200 | }
201 |
202 |
203 | /**
204 | * Test do close
205 | */
206 | @Test
207 | public void testDoClose() {
208 | consumer = new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
209 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch);
210 |
211 | /*
212 | * Do close consumer
213 | */
214 | consumer.doClose();
215 |
216 | /*
217 | * Verify results
218 | */
219 | verify(sqsSession).removeConsumer(consumer);
220 | verify(sqsMessageConsumerPrefetch).close();
221 | }
222 |
223 |
224 | /**
225 | * Test close results in no op when the consumer is already closed
226 | */
227 | @Test
228 | public void testCloseNoOpWhenAlreadyClosed() throws JMSException {
229 | /*
230 | * Set up consumer
231 | */
232 | consumer.closed = true;
233 |
234 | /*
235 | * Close consumer
236 | */
237 | consumer.close();
238 |
239 | /*
240 | * Verify results
241 | */
242 |
243 | verify(consumer, never()).doClose();
244 | verify(sqsSessionRunnable, never()).setConsumerCloseAfterCallback(any(SQSMessageConsumer.class));
245 | }
246 |
247 | /**
248 | * Test when consumer is closed by the message listener that is running on the callback thread
249 | * we do not close but set a consumer close after callback
250 | */
251 | @Test
252 | public void testCloseCalledFromCallbackExecutionThread() throws JMSException {
253 | /*
254 | * Set up consumer
255 | */
256 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
257 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
258 |
259 | when(sqsSession.isActiveCallbackSessionThread())
260 | .thenReturn(true);
261 |
262 | /*
263 | * Close consumer
264 | */
265 | consumer.close();
266 |
267 | /*
268 | * Verify results
269 | */
270 | verify(consumer, never()).doClose();
271 | verify(sqsSessionRunnable).setConsumerCloseAfterCallback(consumer);
272 | }
273 |
274 | /**
275 | * Test consumer close
276 | */
277 | @Test
278 | public void testClose() throws JMSException {
279 | /*
280 | * Set up consumer
281 | */
282 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
283 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
284 |
285 | /*
286 | * Close consumer
287 | */
288 | consumer.close();
289 |
290 | /*
291 | * Verify results
292 | */
293 | verify(consumer).doClose();
294 | verify(sqsSessionRunnable, never()).setConsumerCloseAfterCallback(consumer);
295 | }
296 |
297 | /**
298 | * Test set message listener fails when consumer is already closed
299 | */
300 | @Test
301 | public void testSetMessageListenerAlreadyClosed() throws JMSException {
302 | /*
303 | * Set up consumer
304 | */
305 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
306 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
307 |
308 | consumer.close();
309 |
310 | MessageListener msgListener = mock(MessageListener.class);
311 |
312 | /*
313 | * Set message listener on a consumer
314 | */
315 | assertThatThrownBy(() -> consumer.setMessageListener(msgListener))
316 | .isInstanceOf(JMSException.class)
317 | .hasMessage("Consumer is closed");
318 | }
319 |
320 | /**
321 | * Test receive fails when consumer is already closed
322 | */
323 | @Test
324 | public void testReceiveAlreadyClosed() throws JMSException {
325 | /*
326 | * Set up consumer
327 | */
328 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
329 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
330 |
331 | consumer.close();
332 |
333 | /*
334 | * Call receive
335 | */
336 | assertThatThrownBy(() -> consumer.receive())
337 | .isInstanceOf(JMSException.class)
338 | .hasMessage("Consumer is closed");
339 |
340 | }
341 |
342 | /**
343 | * Test set message listener fails when consumer is already closed
344 | */
345 | @Test
346 | public void testReceiveWithTimeoutAlreadyClosed() throws JMSException {
347 | /*
348 | * Set up consumer
349 | */
350 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
351 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
352 | consumer.close();
353 |
354 | long timeout = 10;
355 |
356 | /*
357 | * Call receive with timeout
358 | */
359 | assertThatThrownBy(() -> consumer.receive(timeout))
360 | .isInstanceOf(JMSException.class)
361 | .hasMessage("Consumer is closed");
362 |
363 | }
364 |
365 | /**
366 | * Test set message listener fails when consumer is already closed
367 | */
368 | @Test
369 | public void testReceiveNoWaitAlreadyClosed() throws JMSException {
370 | /*
371 | * Set up consumer
372 | */
373 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
374 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
375 | consumer.close();
376 |
377 | /*
378 | * Call receive no wait
379 | */
380 | assertThatThrownBy(() -> consumer.receiveNoWait())
381 | .isInstanceOf(JMSException.class)
382 | .hasMessage("Consumer is closed");
383 | }
384 |
385 | /**
386 | * Test set message listener
387 | */
388 | @Test
389 | public void testSetMessageListener() throws JMSException {
390 | /*
391 | * Set up consumer
392 | */
393 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
394 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
395 | MessageListener msgListener = mock(MessageListener.class);
396 |
397 | /*
398 | * Set message listener on a consumer
399 | */
400 | consumer.setMessageListener(msgListener);
401 |
402 | /*
403 | * Verify results
404 | */
405 | verify(sqsMessageConsumerPrefetch).setMessageListener(msgListener);
406 | }
407 |
408 | /**
409 | * Test get message listener
410 | */
411 | @Test
412 | public void testGetMessageListener() throws JMSException {
413 | /*
414 | * Set up consumer
415 | */
416 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
417 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
418 |
419 | /*
420 | * Get message listener on a consumer
421 | */
422 | consumer.getMessageListener();
423 |
424 | /*
425 | * Verify results
426 | */
427 | verify(sqsMessageConsumerPrefetch).getMessageListener();
428 | }
429 |
430 | /**
431 | * Test get message listener
432 | */
433 | @Test
434 | public void testGetQueue() throws JMSException {
435 | /*
436 | * Set up consumer
437 | */
438 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
439 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
440 |
441 | assertEquals(destination, consumer.getQueue());
442 | }
443 |
444 | /**
445 | * Test receive
446 | */
447 | @Test
448 | public void testReceive() throws JMSException {
449 | /*
450 | * Set up consumer
451 | */
452 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
453 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
454 |
455 | /*
456 | * Call receive
457 | */
458 | consumer.receive();
459 |
460 | /*
461 | * Verify results
462 | */
463 | verify(sqsMessageConsumerPrefetch).receive();
464 | }
465 |
466 | /**
467 | * Test receive with timeout
468 | */
469 | @Test
470 | public void testReceiveWithTimeout() throws JMSException {
471 | /*
472 | * Set up consumer
473 | */
474 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
475 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
476 |
477 | long timeout = 10;
478 |
479 | /*
480 | * Call receive with timeout
481 | */
482 | consumer.receive(timeout);
483 |
484 | /*
485 | * Verify results
486 | */
487 | verify(sqsMessageConsumerPrefetch).receive(timeout);
488 | }
489 |
490 | /**
491 | * Test receive no wait
492 | */
493 | @Test
494 | public void testReceiveNoWait() throws JMSException {
495 | /*
496 | * Set up consumer
497 | */
498 | consumer = spy(new SQSMessageConsumer(sqsConnection, sqsSession, sqsSessionRunnable,
499 | destination, acknowledger, negativeAcknowledger, threadFactory, sqsMessageConsumerPrefetch));
500 |
501 | /*
502 | * Call receive no wait
503 | */
504 | consumer.receiveNoWait();
505 |
506 | /*
507 | * Verify results
508 | */
509 | verify(sqsMessageConsumerPrefetch).receiveNoWait();
510 | }
511 | }
512 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/SQSMessagingClientUtilTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.util.SQSMessagingClientUtil;
18 | import org.junit.jupiter.api.Test;
19 |
20 | import static org.junit.jupiter.api.Assertions.assertEquals;
21 |
22 | /**
23 | * Test the SQSMessagingClientUtilTest class
24 | */
25 | public class SQSMessagingClientUtilTest {
26 |
27 | String text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-.~!@#$%^&*()`][}{\\|\";'>?";
28 |
29 | /**
30 | * Test serialize and deserialize property name
31 | */
32 | @Test
33 | public void testSerializeDeserializePropertyName() {
34 | String serialize = SQSMessagingClientUtil.serializePropertyName(text);
35 | assertEquals(text, SQSMessagingClientUtil.deserializePropertyName(serialize));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/SQSQueueDestinationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import org.junit.jupiter.api.Test;
18 |
19 | import static org.junit.jupiter.api.Assertions.assertEquals;
20 | import static org.junit.jupiter.api.Assertions.assertFalse;
21 | import static org.junit.jupiter.api.Assertions.assertTrue;
22 |
23 | /**
24 | * Test the SQSDestinationTest class
25 | */
26 | public class SQSQueueDestinationTest {
27 |
28 | public static final String QUEUE_NAME = "QueueName";
29 | public static final String QUEUE_URL = "QueueUrl";
30 | public static final String FIFO_QUEUE_NAME = "QueueName.fifo";
31 | public static final String FIFO_QUEUE_URL = "QueueUrl.fifo";
32 |
33 | /**
34 | * Test SQSDestination property
35 | */
36 | @Test
37 | public void testProperty() throws Exception {
38 | SQSQueueDestination destination = new SQSQueueDestination(QUEUE_NAME, QUEUE_URL);
39 |
40 | assertFalse(destination.isFifo());
41 | assertEquals(QUEUE_NAME, destination.getQueueName());
42 | assertEquals(QUEUE_URL, destination.getQueueUrl());
43 | }
44 |
45 | /**
46 | * Test .fifo suffix on FIFO queues
47 | */
48 | @Test
49 | public void testFifoSuffix() throws Exception {
50 | SQSQueueDestination destination = new SQSQueueDestination(FIFO_QUEUE_NAME, FIFO_QUEUE_URL);
51 |
52 | assertTrue(destination.isFifo());
53 | assertEquals(FIFO_QUEUE_NAME, destination.getQueueName());
54 | assertEquals(FIFO_QUEUE_URL, destination.getQueueUrl());
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/UnorderedAcknowledgerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging;
16 |
17 | import com.amazon.sqs.javamessaging.acknowledge.AcknowledgeMode;
18 | import com.amazon.sqs.javamessaging.message.SQSMessage;
19 | import jakarta.jms.JMSException;
20 | import org.junit.jupiter.api.BeforeEach;
21 | import org.junit.jupiter.api.Test;
22 | import org.mockito.ArgumentCaptor;
23 | import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
24 |
25 | import java.util.ArrayList;
26 | import java.util.List;
27 | import java.util.Random;
28 |
29 | import static org.junit.jupiter.api.Assertions.assertEquals;
30 | import static org.junit.jupiter.api.Assertions.assertTrue;
31 | import static org.mockito.Mockito.mock;
32 | import static org.mockito.Mockito.times;
33 | import static org.mockito.Mockito.verify;
34 |
35 | /**
36 | * Test the UnorderedAcknowledgerTest class
37 | */
38 | public class UnorderedAcknowledgerTest extends AcknowledgerCommon {
39 |
40 | @BeforeEach
41 | public void setupUnordered() throws JMSException {
42 | amazonSQSClient = mock(AmazonSQSMessagingClientWrapper.class);
43 | acknowledger = AcknowledgeMode.ACK_UNORDERED.createAcknowledger(amazonSQSClient, mock(SQSSession.class));
44 | }
45 |
46 | /**
47 | * Test forgetUnAckMessages
48 | */
49 | @Test
50 | public void testForgetUnAckMessages() throws JMSException {
51 | int populateMessageSize = 30;
52 | populateMessage(populateMessageSize);
53 |
54 | acknowledger.forgetUnAckMessages();
55 | assertEquals(0, acknowledger.getUnAckMessages().size());
56 | }
57 |
58 | /**
59 | * Test acknowledge does not impact messages that were not specifically acknowledge
60 | */
61 | @Test
62 | public void testAcknowledge() throws JMSException {
63 | int populateMessageSize = 37;
64 | populateMessage(populateMessageSize);
65 | int counter = 0;
66 |
67 | List populatedMessagesCopy = new ArrayList<>(populatedMessages);
68 | while (!populatedMessagesCopy.isEmpty()) {
69 |
70 | int rand = new Random().nextInt(populatedMessagesCopy.size());
71 | SQSMessage message = populatedMessagesCopy.remove(rand);
72 | message.acknowledge();
73 | assertEquals(populateMessageSize - (++counter), acknowledger.getUnAckMessages().size());
74 | }
75 | assertEquals(0, acknowledger.getUnAckMessages().size());
76 |
77 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(DeleteMessageRequest.class);
78 | verify(amazonSQSClient, times(populateMessageSize)).deleteMessage(argumentCaptor.capture());
79 |
80 | for (SQSMessage msg : populatedMessages) {
81 | DeleteMessageRequest deleteRequest = DeleteMessageRequest.builder()
82 | .queueUrl(msg.getQueueUrl())
83 | .receiptHandle(msg.getReceiptHandle()).build();
84 | assertTrue(argumentCaptor.getAllValues().contains(deleteRequest));
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/message/SQSBytesMessageTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.message;
16 |
17 | import com.amazon.sqs.javamessaging.SQSMessagingClientConstants;
18 | import com.amazon.sqs.javamessaging.SQSSession;
19 | import jakarta.jms.JMSException;
20 | import jakarta.jms.MessageEOFException;
21 | import jakarta.jms.MessageFormatException;
22 | import jakarta.jms.MessageNotReadableException;
23 | import jakarta.jms.MessageNotWriteableException;
24 | import org.junit.jupiter.api.BeforeEach;
25 | import org.junit.jupiter.api.Test;
26 | import software.amazon.awssdk.services.sqs.model.Message;
27 | import software.amazon.awssdk.services.sqs.model.MessageSystemAttributeName;
28 | import software.amazon.awssdk.utils.BinaryUtils;
29 |
30 | import java.io.DataInputStream;
31 | import java.io.DataOutputStream;
32 | import java.io.IOException;
33 | import java.io.InputStream;
34 | import java.io.OutputStream;
35 | import java.util.Map;
36 | import java.util.Set;
37 |
38 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
39 | import static org.junit.jupiter.api.Assertions.assertEquals;
40 | import static org.junit.jupiter.api.Assertions.assertTrue;
41 | import static org.junit.jupiter.api.Assertions.fail;
42 | import static org.mockito.Mockito.any;
43 | import static org.mockito.Mockito.anyInt;
44 | import static org.mockito.Mockito.doNothing;
45 | import static org.mockito.Mockito.doThrow;
46 | import static org.mockito.Mockito.mock;
47 | import static org.mockito.Mockito.spy;
48 | import static org.mockito.Mockito.when;
49 |
50 | /**
51 | * Test the SQSBytesMessageTest class
52 | */
53 | public class SQSBytesMessageTest {
54 |
55 | private SQSSession mockSQSSession;
56 |
57 | private final static Map MESSAGE_SYSTEM_ATTRIBUTES = Map.of(
58 | MessageSystemAttributeName.fromValue(SQSMessagingClientConstants.APPROXIMATE_RECEIVE_COUNT), "1");
59 |
60 | @BeforeEach
61 | public void setUp() {
62 | mockSQSSession = mock(SQSSession.class);
63 | }
64 |
65 | /**
66 | * Test message ability to write and read different types
67 | */
68 | @Test
69 | public void testReadWrite() throws JMSException {
70 | when(mockSQSSession.createBytesMessage()).thenReturn(new SQSBytesMessage());
71 | SQSBytesMessage msg = (SQSBytesMessage) mockSQSSession.createBytesMessage();
72 |
73 | byte[] byteArray = new byte[]{1, 0, 'a', 65};
74 | byte byteData = 'a';
75 | short shortVal = 123;
76 |
77 | msg.writeBytes(byteArray);
78 | msg.writeChar('b');
79 | msg.writeBoolean(true);
80 | msg.writeInt(100);
81 | msg.writeDouble(2.1768);
82 | msg.writeFloat(3.1457f);
83 | msg.writeLong(1290772974281L);
84 | msg.writeShort(shortVal);
85 | msg.writeByte(byteData);
86 | msg.writeUTF("UTF-String");
87 | msg.writeObject("test");
88 |
89 | msg.reset();
90 |
91 | int length = (int) msg.getBodyLength();
92 | byte[] body = new byte[length];
93 | int i = msg.readBytes(body);
94 | if (i == -1) {
95 | fail("failed to readByte");
96 | }
97 |
98 | Message message = Message.builder()
99 | .body(BinaryUtils.toBase64(body))
100 | .attributes(MESSAGE_SYSTEM_ATTRIBUTES)
101 | .build();
102 |
103 | SQSBytesMessage receivedByteMsg = new SQSBytesMessage(null, "", message);
104 | byte[] byteArray1 = new byte[4];
105 | receivedByteMsg.readBytes(byteArray1);
106 |
107 | assertEquals(1, byteArray1[0]);
108 | assertEquals(0, byteArray1[1]);
109 | assertEquals('a', byteArray1[2]);
110 | assertEquals(65, byteArray1[3]);
111 | assertEquals('b', receivedByteMsg.readChar());
112 | assertTrue(receivedByteMsg.readBoolean());
113 | assertEquals(100, receivedByteMsg.readInt());
114 | assertEquals(2.1768, receivedByteMsg.readDouble(), 0);
115 | assertEquals(3.1457f, receivedByteMsg.readFloat(), 0);
116 | assertEquals(1290772974281L, receivedByteMsg.readLong());
117 | assertEquals(shortVal, receivedByteMsg.readShort());
118 | assertEquals(97, receivedByteMsg.readByte());
119 | assertEquals("UTF-String", receivedByteMsg.readUTF());
120 | assertEquals("test", receivedByteMsg.readUTF());
121 |
122 | assertThatThrownBy(receivedByteMsg::readBoolean).isInstanceOf(MessageEOFException.class);
123 | assertThatThrownBy(receivedByteMsg::readUnsignedByte).isInstanceOf(MessageEOFException.class);
124 |
125 | byte[] arr = new byte[10];
126 | assertEquals(-1, receivedByteMsg.readBytes(arr, 10));
127 |
128 | assertThatThrownBy(receivedByteMsg::readByte).isInstanceOf(MessageEOFException.class);
129 | assertThatThrownBy(receivedByteMsg::readShort).isInstanceOf(MessageEOFException.class);
130 | assertThatThrownBy(receivedByteMsg::readUnsignedShort).isInstanceOf(MessageEOFException.class);
131 | assertThatThrownBy(receivedByteMsg::readInt).isInstanceOf(MessageEOFException.class);
132 | assertThatThrownBy(receivedByteMsg::readLong).isInstanceOf(MessageEOFException.class);
133 | assertThatThrownBy(receivedByteMsg::readFloat).isInstanceOf(MessageEOFException.class);
134 | assertThatThrownBy(receivedByteMsg::readDouble).isInstanceOf(MessageEOFException.class);
135 | assertThatThrownBy(receivedByteMsg::readChar).isInstanceOf(MessageEOFException.class);
136 | assertThatThrownBy(receivedByteMsg::readUTF).isInstanceOf(MessageEOFException.class);
137 | }
138 |
139 | /**
140 | * Test byte message ability to handle io exception from the read operation
141 | */
142 | @Test
143 | public void testReadIOException() throws JMSException, IOException {
144 | /*
145 | * Set up mocks
146 | */
147 | Throwable ioException = new IOException();
148 |
149 | InputStream is = mock(InputStream.class);
150 | DataInputStream dis = new DataInputStream(is);
151 |
152 | when(is.read())
153 | .thenThrow(ioException);
154 |
155 | when(is.read(any(byte[].class), anyInt(), anyInt()))
156 | .thenThrow(ioException);
157 |
158 | SQSBytesMessage msg = spy(new SQSBytesMessage());
159 | doNothing()
160 | .when(msg).checkCanRead();
161 |
162 | msg.setDataIn(dis);
163 |
164 | assertThatThrownBy(msg::readBoolean).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
165 | assertThatThrownBy(msg::readByte).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
166 | assertThatThrownBy(msg::readUnsignedByte).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
167 | assertThatThrownBy(msg::readShort).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
168 | assertThatThrownBy(msg::readUnsignedShort).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
169 | assertThatThrownBy(msg::readInt).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
170 | assertThatThrownBy(msg::readLong).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
171 | assertThatThrownBy(msg::readFloat).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
172 | assertThatThrownBy(msg::readDouble).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
173 | assertThatThrownBy(msg::readChar).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
174 | assertThatThrownBy(msg::readUTF).isInstanceOf(JMSException.class).cause().isEqualTo(ioException);
175 | }
176 |
177 | /**
178 | * Test byte message ability to handle io exception from the write operation
179 | */
180 | @Test
181 | public void testWriteIOException() throws JMSException, IOException {
182 | /*
183 | * Set up mocks
184 | */
185 | Throwable ioException = new IOException();
186 |
187 | OutputStream os = mock(OutputStream.class);
188 | DataOutputStream dos = new DataOutputStream(os);
189 |
190 | SQSBytesMessage msg = spy(new SQSBytesMessage());
191 | doNothing()
192 | .when(msg).checkCanRead();
193 |
194 | msg.setDataOut(dos);
195 |
196 | doThrow(ioException)
197 | .when(os).write(anyInt());
198 |
199 | doThrow(ioException)
200 | .when(os).write(any(byte[].class), anyInt(), anyInt());
201 | try {
202 | msg.writeBoolean(true);
203 | fail();
204 | } catch (JMSException exception) {
205 | assertEquals(ioException, exception.getCause());
206 | }
207 |
208 | assertThatThrownBy(() -> msg.writeByte((byte) 1))
209 | .isInstanceOf(JMSException.class)
210 | .cause().isEqualTo(ioException);
211 |
212 | assertThatThrownBy(() -> msg.writeShort((short) 1))
213 | .isInstanceOf(JMSException.class)
214 | .cause().isEqualTo(ioException);
215 |
216 | assertThatThrownBy(() -> msg.writeInt(1))
217 | .isInstanceOf(JMSException.class)
218 | .cause().isEqualTo(ioException);
219 |
220 | assertThatThrownBy(() -> msg.writeLong(1290772974281L))
221 | .isInstanceOf(JMSException.class)
222 | .cause().isEqualTo(ioException);
223 |
224 | assertThatThrownBy(() -> msg.writeFloat(3.1457f))
225 | .isInstanceOf(JMSException.class)
226 | .cause().isEqualTo(ioException);
227 |
228 | assertThatThrownBy(() -> msg.writeDouble(2.1768))
229 | .isInstanceOf(JMSException.class)
230 | .cause().isEqualTo(ioException);
231 |
232 | assertThatThrownBy(() -> msg.writeChar('a'))
233 | .isInstanceOf(JMSException.class)
234 | .cause().isEqualTo(ioException);
235 |
236 | assertThatThrownBy(() -> msg.writeUTF("test"))
237 | .isInstanceOf(JMSException.class)
238 | .cause().isEqualTo(ioException);
239 | }
240 |
241 | /**
242 | * Test write object function
243 | */
244 | @Test
245 | public void testWriteObject() throws JMSException {
246 | SQSBytesMessage msg = new SQSBytesMessage();
247 |
248 | byte[] byteArray = new byte[]{1, 0, 'a', 65};
249 | byte byteData = 'a';
250 | short shortVal = 123;
251 |
252 | msg.writeObject(byteArray);
253 | msg.writeObject('b');
254 | msg.writeObject(true);
255 | msg.writeObject(100);
256 | msg.writeObject(2.1768);
257 | msg.writeObject(3.1457f);
258 | msg.writeObject(1290772974281L);
259 | msg.writeObject(shortVal);
260 | msg.writeObject(byteData);
261 | msg.writeObject("test");
262 |
263 | msg.reset();
264 |
265 | int length = (int) msg.getBodyLength();
266 | byte[] body = new byte[length];
267 | int i = msg.readBytes(body);
268 | if (i == -1) {
269 | fail("failed to readByte");
270 | }
271 |
272 | Message message = Message.builder()
273 | .attributes(MESSAGE_SYSTEM_ATTRIBUTES)
274 | .body(BinaryUtils.toBase64(body))
275 | .build();
276 | SQSBytesMessage receivedByteMsg = new SQSBytesMessage(null, "", message);
277 | byte[] byteArray1 = new byte[4];
278 | receivedByteMsg.readBytes(byteArray1);
279 |
280 | assertEquals(1, byteArray1[0]);
281 | assertEquals(0, byteArray1[1]);
282 | assertEquals('a', byteArray1[2]);
283 | assertEquals(65, byteArray1[3]);
284 | assertEquals('b', receivedByteMsg.readChar());
285 | assertTrue(receivedByteMsg.readBoolean());
286 | assertEquals(100, receivedByteMsg.readInt());
287 | assertEquals(2.1768, receivedByteMsg.readDouble(), 0);
288 | assertEquals(3.1457f, receivedByteMsg.readFloat(), 0);
289 | assertEquals(1290772974281L, receivedByteMsg.readLong());
290 | assertEquals(shortVal, receivedByteMsg.readShort());
291 | assertEquals(97, receivedByteMsg.readByte());
292 | assertEquals("test", receivedByteMsg.readUTF());
293 |
294 | /*
295 | * Check write object error cases
296 | */
297 | assertThatThrownBy(() -> msg.writeObject(Set.of())).isInstanceOf(MessageFormatException.class);
298 |
299 | /*
300 | * Check write object error cases
301 | */
302 | assertThatThrownBy(() -> msg.writeObject(null)).isInstanceOf(NullPointerException.class);
303 | }
304 |
305 | /**
306 | * Test clear body
307 | */
308 | @Test
309 | public void testClearBody() throws JMSException {
310 | SQSBytesMessage msg = new SQSBytesMessage();
311 |
312 | byte[] byteArray = new byte[]{1, 0, 'a', 65};
313 | msg.writeBytes(byteArray);
314 |
315 | msg.clearBody();
316 |
317 | byte[] readByteArray = new byte[4];
318 |
319 | /*
320 | * Verify message is in write-only mode
321 | */
322 | assertThatThrownBy(() -> msg.readBytes(readByteArray))
323 | .isInstanceOf(MessageNotReadableException.class)
324 | .hasMessage("Message is not readable");
325 |
326 | msg.writeBytes(byteArray);
327 | }
328 |
329 | /**
330 | * Test after reset the message is read only mode
331 | */
332 | @Test
333 | public void testNotWriteable() throws JMSException {
334 | SQSBytesMessage msg = new SQSBytesMessage();
335 |
336 | byte[] byteArray = new byte[]{'a', 0, 34, 65};
337 | msg.writeBytes(byteArray);
338 | msg.reset();
339 | assertEquals('a', msg.readByte());
340 |
341 | assertThatThrownBy(() -> msg.writeInt(10))
342 | .isInstanceOf(MessageNotWriteableException.class);
343 | }
344 |
345 | /**
346 | * Test before reset the message is not readable
347 | */
348 | @Test
349 | public void testReadable() throws JMSException {
350 | when(mockSQSSession.createBytesMessage()).thenReturn(new SQSBytesMessage());
351 | SQSBytesMessage msg = (SQSBytesMessage) mockSQSSession.createBytesMessage();
352 |
353 | byte[] byteArray = new byte[]{'a', 0, 34, 65};
354 | msg.writeBytes(byteArray);
355 |
356 | assertThatThrownBy(msg::readInt).isInstanceOf(MessageNotReadableException.class);
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/message/SQSObjectMessageTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.message;
16 |
17 | import jakarta.jms.JMSException;
18 | import jakarta.jms.ObjectMessage;
19 | import org.junit.jupiter.api.Test;
20 |
21 | import java.io.Serializable;
22 | import java.util.HashMap;
23 | import java.util.Map;
24 |
25 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
26 | import static org.junit.jupiter.api.Assertions.assertEquals;
27 | import static org.junit.jupiter.api.Assertions.assertNotEquals;
28 | import static org.junit.jupiter.api.Assertions.assertNotNull;
29 | import static org.junit.jupiter.api.Assertions.assertNull;
30 |
31 | /**
32 | * Test the SQSObjectMessageTest class
33 | */
34 | public class SQSObjectMessageTest {
35 |
36 | /**
37 | * Test set object
38 | */
39 | @Test
40 | public void testSetObject() throws JMSException {
41 | Map expectedPayload = Map.of("testKey", "testValue");
42 |
43 | ObjectMessage objectMessage = new SQSObjectMessage();
44 | objectMessage.setObject((Serializable) expectedPayload);
45 |
46 | Map actualPayload = (Map) objectMessage.getObject();
47 | assertEquals(expectedPayload, actualPayload);
48 | }
49 |
50 | /**
51 | * Test create message with object
52 | */
53 | @Test
54 | public void testCreateMessageWithObject() throws JMSException {
55 | Map expectedPayload = Map.of("testKey", "testValue");
56 |
57 | ObjectMessage objectMessage = new SQSObjectMessage((Serializable) expectedPayload);
58 |
59 | Map actualPayload = (Map) objectMessage.getObject();
60 | assertEquals(expectedPayload, actualPayload);
61 | }
62 |
63 | /**
64 | * Test object serialization
65 | */
66 | @Test
67 | public void testObjectSerialization() throws JMSException {
68 | Map expectedPayload = new HashMap<>();
69 | expectedPayload.put("testKey", "testValue");
70 |
71 | SQSObjectMessage sqsObjectMessage = new SQSObjectMessage();
72 |
73 | String serialized = SQSObjectMessage.serialize((Serializable) expectedPayload);
74 |
75 | assertNotNull(serialized, "Serialized object should not be null.");
76 | assertNotEquals("", serialized, "Serialized object should not be empty.");
77 |
78 | Map deserialized = (Map) SQSObjectMessage.deserialize(serialized);
79 |
80 | assertNotNull(deserialized, "Deserialized object should not be null.");
81 | assertEquals(expectedPayload, deserialized, "Serialized object should be equal to original object.");
82 |
83 | sqsObjectMessage.clearBody();
84 |
85 | assertNull(sqsObjectMessage.getMessageBody());
86 |
87 | }
88 |
89 | /**
90 | * Test serialization and deserialization with illegal input
91 | */
92 | @Test
93 | public void testDeserializeIllegalInput() throws JMSException {
94 | assertThatThrownBy(() -> SQSObjectMessage.deserialize("Wrong String"))
95 | .isInstanceOf(JMSException.class);
96 |
97 | assertNull(SQSObjectMessage.deserialize(null));
98 | assertNull(SQSObjectMessage.serialize(null));
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/test/java/com/amazon/sqs/javamessaging/message/SQSTextMessageTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 | * A copy of the License is located at
7 | *
8 | * http://aws.amazon.com/apache2.0
9 | *
10 | * or in the "license" file accompanying this file. This file is distributed
11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 | * express or implied. See the License for the specific language governing
13 | * permissions and limitations under the License.
14 | */
15 | package com.amazon.sqs.javamessaging.message;
16 |
17 | import jakarta.jms.JMSException;
18 | import org.junit.jupiter.api.Test;
19 |
20 | import static org.junit.jupiter.api.Assertions.assertEquals;
21 | import static org.junit.jupiter.api.Assertions.assertNull;
22 |
23 | /**
24 | * Test the SQSTextMessageTest class
25 | */
26 | public class SQSTextMessageTest {
27 |
28 | @Test
29 | public void testSetText() throws JMSException {
30 | String expectedPayload = "testing set payload";
31 | SQSTextMessage sqsTextMessage = new SQSTextMessage();
32 | sqsTextMessage.setText(expectedPayload);
33 | String actualPayload = sqsTextMessage.getText();
34 | assertEquals(expectedPayload, actualPayload);
35 | }
36 |
37 | /**
38 | * Test create message with text
39 | */
40 | @Test
41 | public void testCreateMessageWithText() throws JMSException {
42 | String expectedPayload = "testing message with payload";
43 | SQSTextMessage sqsTextMessage = new SQSTextMessage(expectedPayload);
44 |
45 | String actualPayload = sqsTextMessage.getText();
46 | assertEquals(expectedPayload, actualPayload);
47 | }
48 |
49 | /**
50 | * Test create message and setting text
51 | */
52 | @Test
53 | public void testClearBody() throws JMSException {
54 | String expectedPayload = "testing set payload";
55 | SQSTextMessage sqsTextMessage = new SQSTextMessage();
56 | sqsTextMessage.setText(expectedPayload);
57 | assertEquals(expectedPayload, sqsTextMessage.getText());
58 |
59 | sqsTextMessage.clearBody();
60 | assertNull(sqsTextMessage.getText());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------