diff --git a/README.md b/README.md
index 851e03ae..aa1cee4a 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ will not acknowledge the message automatically when the method executes without
The [Spring Cloud AWS Messaging](https://github.com/spring-cloud/spring-cloud-aws/tree/master/spring-cloud-aws-messaging) `@SqsListener` works by requesting
a set of messages from the SQS and when they are done it will request some more. There is one disadvantage with this approach in that if 9/10 of the messages
finish in 10 milliseconds but one takes 10 seconds no other messages will be picked up until that last message is complete. The
-[@BatchingQueueListener](./java-dynamic-sqs-listener-spring/java-dynamic-sqs-listener-spring-starter/src/main/java/com/jashmore/sqs/spring/container/batching/BatchingQueueListener.java)
+[@QueueListener](./java-dynamic-sqs-listener-spring/java-dynamic-sqs-listener-spring-core/src/main/java/com/jashmore/sqs/spring/container/basic/QueueListener.java)
provides the same basic functionality but it also provides a timeout where eventually it will request for more messages even for the threads that are
ready for another message. It will also batch the removal of messages from the queue and therefore with a concurrency level of 10, if there are a lot messages
on the queue, only 2 requests would be made to SQS for retrieval and deletion of messages. The usage is something like this:
@@ -109,7 +109,7 @@ on the queue, only 2 requests would be made to SQS for retrieval and deletion of
```java
@Service
public class MyMessageListener {
- @BatchingQueueListener(value = "${insert.queue.url.here}", concurrencyLevel = 10, maxPeriodBetweenBatchesInMs = 2000)
+ @QueueListener(value = "${insert.queue.url.here}", concurrencyLevel = 10, maxPeriodBetweenBatchesInMs = 2000)
public void processMessage(@Payload final String payload) {
// process the message payload here
}
@@ -121,7 +121,7 @@ before requesting messages for threads waiting for another message.
### Setting up a queue listener that prefetches messages
When the amount of messages for a service is extremely high, prefetching messages may be a way to optimise the throughput of the application. The
-[@PrefetchingQueueListener](./java-dynamic-sqs-listener-spring/java-dynamic-sqs-listener-spring-starter/src/main/java/com/jashmore/sqs/spring/container/prefetch/PrefetchingQueueListener.java)
+[@PrefetchingQueueListener](./java-dynamic-sqs-listener-spring/java-dynamic-sqs-listener-spring-core/src/main/java/com/jashmore/sqs/spring/container/prefetch/PrefetchingQueueListener.java)
annotation can be used to pretech messages in a background thread while messages are currently being processed. The usage is something like this:
```java
@@ -236,6 +236,12 @@ mvn clean install -DskipTests
(cd examples/java-dynamic-sqs-listener-core-examples && mvn exec:java)
```
+### Connecting to multiple AWS Accounts using the Spring Starter
+If the Spring Boot application needs to connect to SQS queues across multiple AWS Accounts, you will need to provide a
+[SqsAsyncClientProvider](./java-dynamic-sqs-listener-spring/java-dynamic-sqs-listener-spring-api/src/main/java/com/jashmore/sqs/spring/client/SqsAsyncClientProvider.java)
+which will be able to obtain a specific `SqsAsyncClient` based on an identifier. For more information on how to do this, take a look at the documentation
+at [How To Connect to Multiple AWS Accounts](doc/how-to-guides/spring/spring-how-to-connect-to-multiple-aws-accounts.md)
+
### Comparing Libraries
If you want to see the difference between this library and others like the
[Spring Cloud AWS Messaging](https://github.com/spring-cloud/spring-cloud-aws/tree/master/spring-cloud-aws-messaging) and
diff --git a/doc/documentation.md b/doc/documentation.md
index 9dd3f80a..389baab0 100644
--- a/doc/documentation.md
+++ b/doc/documentation.md
@@ -15,6 +15,7 @@ more in depth understanding take a look at the JavaDoc for the API.
extending the visibility of a message in the case of long processing so it does not get put back on the queue while processing
1. [How to manually acknowledge message](how-to-guides/core/core-how-to-manually-acknowledge-message.md): useful for when you want to mark the
message as successfully processed before the method has finished executing
+ 1. [How to Connect to an AWS SQS Queue](how-to-guides/how-to-connect-to-aws-sqs-queue.md): necessary for actually using this framework in live environments
1. Spring How To Guides
1. [How to add a custom ArgumentResolver to a Spring application](how-to-guides/spring/spring-how-to-add-custom-argument-resolver.md): useful for
integrating custom argument resolution code to be included in a Spring Application. See [How to implement a custom ArgumentResolver](how-to-guides/core/core-how-to-implement-a-custom-argument-resolver.md)
@@ -27,7 +28,8 @@ more in depth understanding take a look at the JavaDoc for the API.
writing right?
1. [How to Start/Stop Queue Listeners](how-to-guides/spring/spring-how-to-start-stop-queue-listeners.md): guide for starting and stopping the
processing of messages for specific queue listeners
- 1. [How to Connect to an AWS SQS Queue](how-to-guides/how-to-connect-to-aws-sqs-queue.md): necessary for actually using this framework in live environments
+ 1. [How to connect to multiple AWS Accounts](how-to-guides/spring/spring-how-to-connect-to-multiple-aws-accounts.md): guide for listening to queues
+ across multiple AWS Accounts
1. Local Development:
1. [Setting up IntelliJ](local-development/setting-up-intellij.md): steps for setting IntelliJ up for development,
e.g. configuring checkstyle, Lombok, etc
diff --git a/doc/how-to-guides/spring/spring-how-to-connect-to-multiple-aws-accounts.md b/doc/how-to-guides/spring/spring-how-to-connect-to-multiple-aws-accounts.md
new file mode 100644
index 00000000..d12ce3c9
--- /dev/null
+++ b/doc/how-to-guides/spring/spring-how-to-connect-to-multiple-aws-accounts.md
@@ -0,0 +1,60 @@
+# Spring - How to Connect to multiple AWS Accounts
+There may be a scenario where you need to connect to multiple queues across multiple AWS Accounts. In this scenario you would
+need to provide multiple `SqsAsyncClients` and for each queue listener you will need to indicate which one is desired. For a full
+example take a look at the [Multiple AWS Accounts Example](../../../examples/multiple-aws-account-example) which shows you how to
+connect to two locally running ElasticMQ servers.
+
+## Steps
+1. Create some queues that will use specific `SqsAsyncClient`s identified by an id.
+ ```java
+ public class MyMessageListeners {
+ // This uses the "default" SqsAsyncClient which may not be present
+ @QueueListener("queueNameForDefaultListener")
+ public void listenerForDefaultClient(@Payload String messageBody) {
+
+ }
+
+ // This uses the "firstClient" SqsAsyncClient
+ @QueueListener(value = "queueNameForFirstClient", sqsClient = "firstClient")
+ public void queueNameListenerForFirstClient(@Payload String messageBody) {
+
+ }
+
+ // This uses the "firstClient" SqsAsyncClient
+ @QueueListener(value = "anotherQueueNameForFirstClient", sqsClient = "firstClient")
+ public void anotherQueueNameListenerForFirstClient(@Payload String messageBody) {
+
+ }
+
+ // This uses the "secondClient" SqsAsyncClient
+ @QueueListener(value = "queueNameForSecondClient", sqsClient = "secondClient")
+ public void queueNameListenerForSecondClient(@Payload String messageBody) {
+
+ }
+ }
+ ```
+1. You will need to add to your `@Configuration` a bean of type
+[SqsAsyncClientProvider](../../../java-dynamic-sqs-listener-spring/java-dynamic-sqs-listener-spring-api/src/main/java/com/jashmore/sqs/spring/client/SqsAsyncClientProvider.java)
+which will provide all of the `SqsAsyncClient`s for the queues above.
+ ```java
+ @Configuration
+ public class MyConfig {
+ @Bean
+ public SqsAsyncClientProvider sqsAsyncClientProvider() {
+ // this client will be used if there is no client identifier for the listener. Note that this can be null
+ // and in this case listenerForDefaultClient above will fail to wrap
+ final SqsAsyncClient defaultClient = ...;
+
+ final SqsAsyncClient firstClient = ...;
+ final SqsAsyncClient secondClient = ...;
+
+ return new DefaultSqsAsyncClientProvider(
+ defaultClient,
+ ImmutableMap.of(
+ "firstClient", firstClient,
+ "secondClient", secondClient
+ )
+ );
+ }
+ }
+ ```
diff --git a/examples/java-dynamic-sqs-listener-core-examples/src/main/java/com/jashmore/sqs/examples/ConcurrentBrokerExample.java b/examples/java-dynamic-sqs-listener-core-examples/src/main/java/com/jashmore/sqs/examples/ConcurrentBrokerExample.java
index 06a242ba..4d4c1681 100644
--- a/examples/java-dynamic-sqs-listener-core-examples/src/main/java/com/jashmore/sqs/examples/ConcurrentBrokerExample.java
+++ b/examples/java-dynamic-sqs-listener-core-examples/src/main/java/com/jashmore/sqs/examples/ConcurrentBrokerExample.java
@@ -110,8 +110,9 @@ public static void main(final String[] args) throws Exception {
.bufferingTimeInMs(5000)
.build());
final MessageProcessor messageProcessor = new DefaultMessageProcessor(
- argumentResolverService(sqsAsyncClient),
+ argumentResolverService(),
queueProperties,
+ sqsAsyncClient,
messageResolver,
messageReceivedMethod,
messageConsumer
@@ -184,12 +185,11 @@ private static SqsAsyncClient startElasticMqServer() throws URISyntaxException {
/**
* Builds the {@link ArgumentResolverService} that will be used to parse the messages into arguments for the {@link MessageConsumer}.
*
- * @param sqsAsyncClient the client to communicate with the SQS queue
* @return the service to resolve arguments for the message consumer
*/
- private static ArgumentResolverService argumentResolverService(final SqsAsyncClient sqsAsyncClient) {
+ private static ArgumentResolverService argumentResolverService() {
final PayloadMapper payloadMapper = new JacksonPayloadMapper(OBJECT_MAPPER);
- return new CoreArgumentResolverService(payloadMapper, sqsAsyncClient, OBJECT_MAPPER);
+ return new CoreArgumentResolverService(payloadMapper, OBJECT_MAPPER);
}
/**
diff --git a/examples/multiple-aws-account-example/pom.xml b/examples/multiple-aws-account-example/pom.xml
new file mode 100644
index 00000000..503070f9
--- /dev/null
+++ b/examples/multiple-aws-account-example/pom.xml
@@ -0,0 +1,129 @@
+
+
+
+ examples
+ com.jashmore
+ 2.3.0-SNAPSHOT
+
+ 4.0.0
+
+ multiple-aws-account-example
+
+ Java Dynamic SQS Listener - Multiple AWS Accounts Example
+ Contains examples for listening to SQS queues on multiple AWS Accounts
+
+
+ ../../configuration/findbugs/bugsExcludeFilter.xml
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ ${spring.boot.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.boot.version}
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ com.jashmore
+ java-dynamic-sqs-listener-spring-starter
+ ${project.version}
+
+
+
+ com.jashmore
+ local-amazon-sqs
+ ${project.version}
+
+
+
+ org.elasticmq
+ elasticmq-rest-sqs_2.11
+ 0.13.9
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+
+
+
+ ${basedir}/src/main/resources
+ true
+
+ **/*.yml
+ **/application*.properties
+
+
+
+ ${basedir}/src/main/resources
+
+ **/*.yml
+ **/application*.properties
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.jashmore.sqs.examples.Application
+
+
+
+
+ repackage
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ ${resource.delimiter}
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/Application.java b/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/Application.java
new file mode 100644
index 00000000..32f528ff
--- /dev/null
+++ b/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/Application.java
@@ -0,0 +1,80 @@
+package com.jashmore.sqs.examples;
+
+import com.google.common.collect.ImmutableMap;
+
+import akka.http.scaladsl.Http;
+import com.jashmore.sqs.spring.client.DefaultSqsAsyncClientProvider;
+import com.jashmore.sqs.spring.client.SqsAsyncClientProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.elasticmq.rest.sqs.SQSRestServer;
+import org.elasticmq.rest.sqs.SQSRestServerBuilder;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sqs.SqsAsyncClient;
+import software.amazon.awssdk.services.sqs.model.CreateQueueRequest;
+
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+
+@SpringBootApplication
+@EnableScheduling
+@Slf4j
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class);
+ }
+
+ /**
+ * Creates the {@link SqsAsyncClientProvider} that will allow multiple AWS Account SQS queues to be queried.
+ *
+ * @return the provider to use
+ * @throws InterruptedException if it was interrupted while building the clients
+ */
+ @Bean
+ public SqsAsyncClientProvider sqsAsyncClientProvider() throws InterruptedException {
+ return new DefaultSqsAsyncClientProvider(null, ImmutableMap.of(
+ "firstClient", buildClient("firstClientQueue"),
+ "secondClient", buildClient("secondClientQueue")
+ ));
+ }
+
+ /**
+ * Starts an in-memory ElasticMQ server and returns a {@link SqsAsyncClient} pointing to it, each call to this represents a different AWS Account.
+ *
+ *
Note that the region and credentials are hardcoded to fake values as they are not checked but connecting to an actual AWS account here will involve
+ * reading environment variables or other properties.
+ *
+ * @param queueName the name of a queue to create in this SQS Server
+ * @return the {@link SqsAsyncClient} that points to this SQS Server
+ * @throws InterruptedException if it was interrupted while creating the queue
+ */
+ private static SqsAsyncClient buildClient(String queueName) throws InterruptedException {
+ log.info("Starting Local ElasticMQ SQS Server");
+ final SQSRestServer sqsRestServer = SQSRestServerBuilder
+ .withInterface("localhost")
+ .withDynamicPort()
+ .start();
+
+ final Http.ServerBinding serverBinding = sqsRestServer.waitUntilStarted();
+ final SqsAsyncClient client = SqsAsyncClient.builder()
+ .endpointOverride(URI.create("http://localhost:" + serverBinding.localAddress().getPort()))
+ .region(Region.of("localstack"))
+ .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("accessKeyId", "secretAccessKey")))
+ .build();
+ try {
+ client.createQueue(CreateQueueRequest.builder()
+ .queueName(queueName)
+ .build())
+ .get();
+ } catch (ExecutionException executionException) {
+ throw new RuntimeException(executionException.getCause());
+ }
+ return client;
+ }
+}
diff --git a/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/MessageListeners.java b/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/MessageListeners.java
new file mode 100644
index 00000000..5f092ef9
--- /dev/null
+++ b/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/MessageListeners.java
@@ -0,0 +1,31 @@
+package com.jashmore.sqs.examples;
+
+import com.jashmore.sqs.argument.payload.Payload;
+import com.jashmore.sqs.spring.container.basic.QueueListener;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@SuppressWarnings("unused")
+@Slf4j
+public class MessageListeners {
+ /**
+ * Basic queue listener.
+ *
+ * @param payload the payload of the SQS Message
+ */
+ @QueueListener(value = "firstClientQueue", sqsClient = "firstClient")
+ public void firstClientMessageProcessing(@Payload final String payload) {
+ log.info("Message Received from firstClient#firstClientQueue: {}", payload);
+ }
+
+ /**
+ * Basic queue listener.
+ *
+ * @param payload the payload of the SQS Message
+ */
+ @QueueListener(value = "secondClientQueue", sqsClient = "secondClient")
+ public void secondClientMessageProcessing(@Payload final String payload) {
+ log.info("Message Received from secondClient#secondClientQueue: {}", payload);
+ }
+}
diff --git a/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/ScheduledMessageProducer.java b/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/ScheduledMessageProducer.java
new file mode 100644
index 00000000..2c16d275
--- /dev/null
+++ b/examples/multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/ScheduledMessageProducer.java
@@ -0,0 +1,62 @@
+package com.jashmore.sqs.examples;
+
+import com.jashmore.sqs.spring.client.SqsAsyncClientProvider;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import software.amazon.awssdk.services.sqs.SqsAsyncClient;
+import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequest;
+import software.amazon.awssdk.services.sqs.model.SendMessageBatchRequestEntry;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Helper scheduled task that will place 10 messages onto each queue for processing by the message listeners.
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ScheduledMessageProducer {
+ private final SqsAsyncClientProvider sqsAsyncClientProvider;
+ private final AtomicInteger count = new AtomicInteger();
+
+ /**
+ * Scheduled job that sends messages to the queue for testing the listener.
+ *
+ * @throws Exception if there was an error placing messages on the queue
+ */
+ @Scheduled(initialDelay = 1000, fixedDelay = 1000)
+ public void addMessages() throws Exception {
+ log.info("Putting 10 messages onto each queue");
+ final int currentValue = count.incrementAndGet();
+
+ sendMessagesToQueue(getSqsAsyncClient("firstClient"), "firstClientQueue", currentValue);
+ sendMessagesToQueue(getSqsAsyncClient("secondClient"), "secondClientQueue", currentValue);
+ }
+
+ private SqsAsyncClient getSqsAsyncClient(final String clientId) {
+ return sqsAsyncClientProvider.getClient(clientId)
+ .orElseThrow(() -> new RuntimeException("Unknown client: " + clientId));
+ }
+
+ private void sendMessagesToQueue(final SqsAsyncClient sqsAsyncClient,
+ final String queueName,
+ final int currentValue) throws ExecutionException, InterruptedException {
+ final String queueUrl = sqsAsyncClient.getQueueUrl((request) -> request.queueName(queueName)).get().queueUrl();
+
+ final SendMessageBatchRequest.Builder batchRequestBuilder = SendMessageBatchRequest.builder().queueUrl(queueUrl);
+ batchRequestBuilder.entries(IntStream.range(0, 10)
+ .mapToObj(i -> {
+ final String messageId = "" + currentValue + "-" + i;
+ final String messageContent = "Message, loop: " + currentValue + " id: " + i;
+ return SendMessageBatchRequestEntry.builder().id(messageId).messageBody(messageContent).build();
+ })
+ .collect(Collectors.toSet()));
+
+ sqsAsyncClient.sendMessageBatch(batchRequestBuilder.build());
+ }
+}
diff --git a/examples/pom.xml b/examples/pom.xml
index 36f6a007..325f4979 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -24,6 +24,7 @@
java-dynamic-sqs-listener-spring-aws-examplejava-dynamic-sqs-listener-spring-integration-test-examplesqs-listener-library-comparison
+ multiple-aws-account-example
diff --git a/java-dynamic-sqs-listener-api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java b/java-dynamic-sqs-listener-api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java
index 30c0c8bf..1f5b32ad 100644
--- a/java-dynamic-sqs-listener-api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java
+++ b/java-dynamic-sqs-listener-api/src/main/java/com/jashmore/sqs/processor/MessageProcessor.java
@@ -3,6 +3,7 @@
import com.jashmore.sqs.argument.ArgumentResolverService;
import com.jashmore.sqs.broker.MessageBroker;
import com.jashmore.sqs.processor.argument.Acknowledge;
+import com.jashmore.sqs.processor.argument.VisibilityExtender;
import com.jashmore.sqs.resolver.MessageResolver;
import software.amazon.awssdk.services.sqs.model.Message;
@@ -34,6 +35,7 @@
*
{@link Acknowledge}: this argument can be used to acknowledge that the messages has been successfully processed and can be deleted from the
* queue. If no {@link Acknowledge} argument is included in the argument list of the method, the message will be deleted from the queue if the
* method processing the message completes without an exception being thrown.
+ *
{@link VisibilityExtender}: this argument can be used to extend the visibility of a message if it is taking a long time to process.
*
*
*
If you were to consider this library as similar to a pub-sub system, this could be considered the subscriber in that it will take messages provided
diff --git a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/VisibilityExtender.java b/java-dynamic-sqs-listener-api/src/main/java/com/jashmore/sqs/processor/argument/VisibilityExtender.java
similarity index 97%
rename from java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/VisibilityExtender.java
rename to java-dynamic-sqs-listener-api/src/main/java/com/jashmore/sqs/processor/argument/VisibilityExtender.java
index 5026e87d..0bfd58c2 100644
--- a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/VisibilityExtender.java
+++ b/java-dynamic-sqs-listener-api/src/main/java/com/jashmore/sqs/processor/argument/VisibilityExtender.java
@@ -1,4 +1,4 @@
-package com.jashmore.sqs.argument.visibility;
+package com.jashmore.sqs.processor.argument;
import java.util.concurrent.Future;
diff --git a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/CoreArgumentResolverService.java b/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/CoreArgumentResolverService.java
index ed82efbd..344578eb 100644
--- a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/CoreArgumentResolverService.java
+++ b/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/CoreArgumentResolverService.java
@@ -9,9 +9,7 @@
import com.jashmore.sqs.argument.messageid.MessageIdArgumentResolver;
import com.jashmore.sqs.argument.payload.PayloadArgumentResolver;
import com.jashmore.sqs.argument.payload.mapper.PayloadMapper;
-import com.jashmore.sqs.argument.visibility.VisibilityExtenderArgumentResolver;
import lombok.experimental.Delegate;
-import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import java.util.Set;
@@ -26,14 +24,12 @@ public class CoreArgumentResolverService implements ArgumentResolverService {
private final DelegatingArgumentResolverService delegatingArgumentResolverService;
public CoreArgumentResolverService(final PayloadMapper payloadMapper,
- final SqsAsyncClient sqsAsyncClient,
final ObjectMapper objectMapper) {
final Set> argumentResolvers = ImmutableSet.of(
new PayloadArgumentResolver(payloadMapper),
new MessageIdArgumentResolver(),
new MessageAttributeArgumentResolver(objectMapper),
new MessageSystemAttributeArgumentResolver(),
- new VisibilityExtenderArgumentResolver(sqsAsyncClient),
new MessageArgumentResolver()
);
this.delegatingArgumentResolverService = new DelegatingArgumentResolverService(argumentResolvers);
diff --git a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtender.java b/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtender.java
index ac1daf99..8203c787 100644
--- a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtender.java
+++ b/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtender.java
@@ -1,6 +1,7 @@
package com.jashmore.sqs.argument.visibility;
import com.jashmore.sqs.QueueProperties;
+import com.jashmore.sqs.processor.argument.VisibilityExtender;
import lombok.AllArgsConstructor;
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.model.ChangeMessageVisibilityRequest;
diff --git a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/VisibilityExtenderArgumentResolver.java b/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/VisibilityExtenderArgumentResolver.java
deleted file mode 100644
index faafe1f6..00000000
--- a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/argument/visibility/VisibilityExtenderArgumentResolver.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.jashmore.sqs.argument.visibility;
-
-import com.jashmore.sqs.QueueProperties;
-import com.jashmore.sqs.argument.ArgumentResolutionException;
-import com.jashmore.sqs.argument.ArgumentResolver;
-import com.jashmore.sqs.argument.MethodParameter;
-import com.jashmore.sqs.argument.payload.Payload;
-import software.amazon.awssdk.services.sqs.SqsAsyncClient;
-import software.amazon.awssdk.services.sqs.model.Message;
-
-/**
- * Resolves message consumer's with a parameter of type {@link VisibilityExtender} to an implementation that can be used
- * for the consumer to request the increase of visibility of the message.
- *
- *
Message consumer parameters with the {@link VisibilityExtender} type should not have any annotations that would result in another
- * {@link ArgumentResolver} from attempting to resolve the parameter. For example you should not have a method of type {@link VisibilityExtender} and also
- * with the {@link Payload} annotation.
- */
-public class VisibilityExtenderArgumentResolver implements ArgumentResolver {
- private final SqsAsyncClient sqsAsyncClient;
-
- public VisibilityExtenderArgumentResolver(final SqsAsyncClient sqsAsyncClient) {
- this.sqsAsyncClient = sqsAsyncClient;
- }
-
- @Override
- public boolean canResolveParameter(final MethodParameter methodParameter) {
- return methodParameter.getParameter().getType() == VisibilityExtender.class;
- }
-
- @Override
- public VisibilityExtender resolveArgumentForParameter(final QueueProperties queueProperties,
- final MethodParameter methodParameter,
- final Message message) throws ArgumentResolutionException {
- return new DefaultVisibilityExtender(sqsAsyncClient, queueProperties, message);
- }
-}
diff --git a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/processor/DefaultMessageProcessor.java b/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/processor/DefaultMessageProcessor.java
index c88c695d..f5b8e7c0 100644
--- a/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/processor/DefaultMessageProcessor.java
+++ b/java-dynamic-sqs-listener-core/src/main/java/com/jashmore/sqs/processor/DefaultMessageProcessor.java
@@ -7,9 +7,12 @@
import com.jashmore.sqs.argument.ArgumentResolverService;
import com.jashmore.sqs.argument.DefaultMethodParameter;
import com.jashmore.sqs.argument.MethodParameter;
+import com.jashmore.sqs.argument.visibility.DefaultVisibilityExtender;
import com.jashmore.sqs.processor.argument.Acknowledge;
+import com.jashmore.sqs.processor.argument.VisibilityExtender;
import com.jashmore.sqs.resolver.MessageResolver;
import lombok.AllArgsConstructor;
+import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.model.Message;
import java.lang.reflect.InvocationTargetException;
@@ -30,6 +33,7 @@
@AllArgsConstructor
public class DefaultMessageProcessor implements MessageProcessor {
private final QueueProperties queueProperties;
+ private final SqsAsyncClient sqsAsyncClient;
private final MessageResolver messageResolver;
private final Method messageConsumerMethod;
private final Object messageConsumerBean;
@@ -39,10 +43,12 @@ public class DefaultMessageProcessor implements MessageProcessor {
public DefaultMessageProcessor(final ArgumentResolverService argumentResolverService,
final QueueProperties queueProperties,
+ final SqsAsyncClient sqsAsyncClient,
final MessageResolver messageResolver,
final Method messageConsumerMethod,
final Object messageConsumerBean) {
this.queueProperties = queueProperties;
+ this.sqsAsyncClient = sqsAsyncClient;
this.messageResolver = messageResolver;
this.messageConsumerMethod = messageConsumerMethod;
this.messageConsumerBean = messageConsumerBean;
@@ -117,6 +123,10 @@ private List getArgumentResolvers(final ArgumentResolv
return message -> (Acknowledge) () -> messageResolver.resolveMessage(message);
}
+ if (isVisibilityExtenderParameter(parameter)) {
+ return message -> new DefaultVisibilityExtender(sqsAsyncClient, queueProperties, message);
+ }
+
final ArgumentResolver> argumentResolver = argumentResolverService.getArgumentResolver(methodParameter);
return message -> argumentResolver.resolveArgumentForParameter(queueProperties, methodParameter, message);
})
@@ -132,6 +142,10 @@ private static boolean isAcknowledgeParameter(final Parameter parameter) {
return Acknowledge.class.isAssignableFrom(parameter.getType());
}
+ private static boolean isVisibilityExtenderParameter(final Parameter parameter) {
+ return VisibilityExtender.class.isAssignableFrom(parameter.getType());
+ }
+
/**
* Internal resolver for resolving the argument given the message.
*/
diff --git a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/CoreArgumentResolverServiceTest.java b/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/CoreArgumentResolverServiceTest.java
index f6b0cbc9..bcd0d733 100644
--- a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/CoreArgumentResolverServiceTest.java
+++ b/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/CoreArgumentResolverServiceTest.java
@@ -17,10 +17,8 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.model.Message;
import software.amazon.awssdk.services.sqs.model.MessageSystemAttributeName;
@@ -35,14 +33,11 @@ public class CoreArgumentResolverServiceTest {
private PayloadMapper payloadMapper = new JacksonPayloadMapper(objectMapper);
- @Mock
- private SqsAsyncClient sqsAsyncClient;
-
private CoreArgumentResolverService service;
@Before
public void setUp() {
- service = new CoreArgumentResolverService(payloadMapper, sqsAsyncClient, objectMapper);
+ service = new CoreArgumentResolverService(payloadMapper, objectMapper);
}
@Test
diff --git a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtenderTest.java b/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtenderTest.java
index 708b3f74..d65eb070 100644
--- a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtenderTest.java
+++ b/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/visibility/DefaultVisibilityExtenderTest.java
@@ -1,6 +1,6 @@
package com.jashmore.sqs.argument.visibility;
-import static com.jashmore.sqs.argument.visibility.VisibilityExtender.DEFAULT_VISIBILITY_EXTENSION_IN_SECONDS;
+import static com.jashmore.sqs.processor.argument.VisibilityExtender.DEFAULT_VISIBILITY_EXTENSION_IN_SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/visibility/VisibilityExtenderArgumentResolverTest.java b/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/visibility/VisibilityExtenderArgumentResolverTest.java
deleted file mode 100644
index 4c48558e..00000000
--- a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/argument/visibility/VisibilityExtenderArgumentResolverTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.jashmore.sqs.argument.visibility;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.jashmore.sqs.QueueProperties;
-import com.jashmore.sqs.argument.DefaultMethodParameter;
-import com.jashmore.sqs.argument.MethodParameter;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import software.amazon.awssdk.services.sqs.SqsAsyncClient;
-import software.amazon.awssdk.services.sqs.model.Message;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Parameter;
-
-public class VisibilityExtenderArgumentResolverTest {
- @Rule
- public MockitoRule mockitoRule = MockitoJUnit.rule();
-
- @Mock
- private SqsAsyncClient sqsAsyncClient;
-
- @Mock
- private QueueProperties queueProperties;
-
- private final Message message = Message.builder().build();
-
- private VisibilityExtenderArgumentResolver visibilityExtenderArgumentResolver;
-
- @Before
- public void setUp() {
- visibilityExtenderArgumentResolver = new VisibilityExtenderArgumentResolver(sqsAsyncClient);
- }
-
- @Test
- public void canResolveParametersWithVisibilityExtenderType() {
- // arrange
- final MethodParameter parameter = getParameter(0);
-
- // act
- final boolean canResolveParameter = visibilityExtenderArgumentResolver.canResolveParameter(parameter);
-
- // assert
- assertThat(canResolveParameter).isTrue();
- }
-
- @Test
- public void canNotResolveParametersThatIsNotAVisibilityExtenderType() {
- // arrange
- final MethodParameter parameter = getParameter(1);
-
- // act
- final boolean canResolveParameter = visibilityExtenderArgumentResolver.canResolveParameter(parameter);
-
- // assert
- assertThat(canResolveParameter).isFalse();
- }
-
- @Test
- public void resolvingParameterReturnsVisibilityExtenderObject() {
- // arrange
- final MethodParameter parameter = getParameter(0);
-
- // act
- final Object resolvedArgument = visibilityExtenderArgumentResolver.resolveArgumentForParameter(queueProperties, parameter, message);
-
- // assert
- assertThat(resolvedArgument).isInstanceOf(VisibilityExtender.class);
- }
-
- @SuppressWarnings( {"WeakerAccess", "unused"})
- public void method(final VisibilityExtender visibilityExtender, final String string) {
-
- }
-
- private MethodParameter getParameter(final int index) {
- try {
- final Method method = VisibilityExtenderArgumentResolverTest.class.getMethod("method", VisibilityExtender.class, String.class);
- return DefaultMethodParameter.builder()
- .method(method)
- .parameter(method.getParameters()[index])
- .parameterIndex(index)
- .build();
- } catch (final NoSuchMethodException exception) {
- throw new RuntimeException("Unable to find method for testing against", exception);
- }
- }
-}
diff --git a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/processor/DefaultMessageProcessorTest.java b/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/processor/DefaultMessageProcessorTest.java
index 6201af16..2e00c71c 100644
--- a/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/processor/DefaultMessageProcessorTest.java
+++ b/java-dynamic-sqs-listener-core/src/test/java/com/jashmore/sqs/processor/DefaultMessageProcessorTest.java
@@ -27,6 +27,7 @@
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.model.Message;
import java.lang.reflect.Method;
@@ -54,6 +55,9 @@ public class DefaultMessageProcessorTest {
@Mock
private MessageResolver messageResolver;
+ @Mock
+ private SqsAsyncClient sqsAsyncClient;
+
@Mock
private ArgumentResolver mockArgumentResolver;
@@ -66,7 +70,8 @@ public void forEachParameterInMethodTheArgumentIsResolved() {
final Method method = getMethodWithAcknowledge();
final Message message = Message.builder().build();
doReturn(mockArgumentResolver).when(argumentResolverService).getArgumentResolver(any(MethodParameter.class));
- final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, messageResolver, method, BEAN);
+ final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES,
+ sqsAsyncClient, messageResolver, method, BEAN);
// act
processor.processMessage(message);
@@ -84,7 +89,7 @@ public void anyParameterUnableToBeResolvedWillThrowAnError() {
expectedException.expect(UnsupportedArgumentResolutionException.class);
// act
- new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, messageResolver, method, BEAN);
+ new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, messageResolver, method, BEAN);
}
@Test
@@ -97,7 +102,8 @@ public void methodWillBeInvokedWithArgumentsResolved() {
when(mockArgumentResolver.resolveArgumentForParameter(eq(QUEUE_PROPERTIES), any(), eq(message)))
.thenReturn("payload")
.thenReturn("payload2");
- final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, messageResolver, method, mockProcessor);
+ final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES,
+ sqsAsyncClient, messageResolver, method, mockProcessor);
// act
processor.processMessage(message);
@@ -114,7 +120,8 @@ public void methodWithAcknowledgeParameterWillNotDeleteMessageOnSuccess() {
doReturn(mockArgumentResolver).when(argumentResolverService).getArgumentResolver(any(MethodParameter.class));
when(mockArgumentResolver.resolveArgumentForParameter(eq(QUEUE_PROPERTIES), any(), eq(message)))
.thenReturn("payload");
- final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, messageResolver, method, BEAN);
+ final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES,
+ sqsAsyncClient, messageResolver, method, BEAN);
// act
processor.processMessage(message);
@@ -131,7 +138,8 @@ public void methodWithoutAcknowledgeParameterWillDeleteMessageOnSuccess() {
doReturn(mockArgumentResolver).when(argumentResolverService).getArgumentResolver(any(MethodParameter.class));
when(mockArgumentResolver.resolveArgumentForParameter(eq(QUEUE_PROPERTIES), any(), eq(message)))
.thenReturn("payload");
- final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, messageResolver, method, BEAN);
+ final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES,
+ sqsAsyncClient, messageResolver, method, BEAN);
// act
processor.processMessage(message);
@@ -149,7 +157,8 @@ public void methodWithoutAcknowledgeThatThrowsExceptionDoesNotDeleteMessage() {
doReturn(mockArgumentResolver).when(argumentResolverService).getArgumentResolver(any(MethodParameter.class));
when(mockArgumentResolver.resolveArgumentForParameter(eq(QUEUE_PROPERTIES), any(), eq(message)))
.thenReturn("payload");
- final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, messageResolver, method, BEAN);
+ final MessageProcessor processor = new DefaultMessageProcessor(argumentResolverService, QUEUE_PROPERTIES,
+ sqsAsyncClient, messageResolver, method, BEAN);
// act
try {
@@ -170,7 +179,8 @@ public void methodReturningCompletableFutureWillResolveMessageWhenFutureResolved
final CompletableFuture