com.jashmore.sqs.QueueProperties

Here are the examples of the java api com.jashmore.sqs.QueueProperties taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

58 Examples 7

19 Source : DecoratingMessageProcessorFactoryTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced DecoratingMessageProcessorFactoryTest {

    @Mock
    SqsAsyncClient sqsAsyncClient;

    @Mock
    QueueProperties queueProperties;

    @Test
    void willAlwaysApplyGlobalDecoratorsIfPresent() throws Exception {
        // arrange
        final MessageProcessingDecorator decorator = mock(MessageProcessingDecorator.clreplaced);
        final DecoratingMessageProcessorFactory factory = new DecoratingMessageProcessorFactory(Collections.singletonList(decorator), Collections.emptyList());
        // act
        final MessageProcessor processor = factory.decorateMessageProcessor(sqsAsyncClient, "id", queueProperties, new MessageListener(), MessageListener.clreplaced.getMethod("actualListener"), (message, callback) -> CompletableFuture.completedFuture(null));
        processor.processMessage(Message.builder().build(), () -> CompletableFuture.completedFuture(null));
        // replacedert
        verify(decorator).onPreMessageProcessing(any(), any());
    }

    @Test
    void willApplyDecoratosWhenReturnedFrom() throws Exception {
        // arrange
        final MessageProcessingDecorator decorator = mock(MessageProcessingDecorator.clreplaced);
        final MessageProcessingDecoratorFactory<MessageProcessingDecorator> decoratorFactory = (sqsAsyncClient, queueProperties, identifier, bean, method) -> Optional.of(decorator);
        final DecoratingMessageProcessorFactory factory = new DecoratingMessageProcessorFactory(Collections.emptyList(), Collections.singletonList(decoratorFactory));
        // act
        final MessageProcessor processor = factory.decorateMessageProcessor(sqsAsyncClient, "id", queueProperties, new MessageListener(), MessageListener.clreplaced.getMethod("actualListener"), (message, callback) -> CompletableFuture.completedFuture(null));
        processor.processMessage(Message.builder().build(), () -> CompletableFuture.completedFuture(null));
        // replacedert
        verify(decorator).onPreMessageProcessing(any(), any());
    }

    private static clreplaced MessageListener {

        public void actualListener() {
        }
    }
}

19 Source : AutoVisibilityExtenderMessageProcessingDecoratorFactoryTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced AutoVisibilityExtenderMessageProcessingDecoratorFactoryTest {

    @Mock
    SqsAsyncClient sqsAsyncClient;

    @Mock
    QueueProperties queueProperties;

    @Mock
    Environment environment;

    AutoVisibilityExtenderMessageProcessingDecoratorFactory factory;

    @BeforeEach
    void setUp() {
        factory = new AutoVisibilityExtenderMessageProcessingDecoratorFactory(environment);
    }

    @Test
    void willBuildDecoratorWhenAnnotationPresent() throws Exception {
        // arrange
        final Method method = AutoVisibilityExtenderMessageProcessingDecoratorFactoryTest.clreplaced.getMethod("methodWithAnnotation");
        // act
        final Optional<AutoVisibilityExtenderMessageProcessingDecorator> optionalDecorator = factory.buildDecorator(sqsAsyncClient, queueProperties, "id", this, method);
        // replacedert
        replacedertThat(optionalDecorator).isPresent();
    }

    @Test
    void willNotBuildDecoratorWhenAnnotationNotPresent() throws Exception {
        // arrange
        final Method method = AutoVisibilityExtenderMessageProcessingDecoratorFactoryTest.clreplaced.getMethod("methodWithNoAnnotation");
        // act
        final Optional<AutoVisibilityExtenderMessageProcessingDecorator> optionalDecorator = factory.buildDecorator(sqsAsyncClient, queueProperties, "id", this, method);
        // replacedert
        replacedertThat(optionalDecorator).isEmpty();
    }

    @Test
    void willThrowExceptionAttemptingToWrapAsyncMethod() throws Exception {
        // arrange
        final Method method = AutoVisibilityExtenderMessageProcessingDecoratorFactoryTest.clreplaced.getMethod("asyncMethodWithAnnotation");
        // act
        final MessageProcessingDecoratorFactoryException exception = replacedertions.replacedertThrows(MessageProcessingDecoratorFactoryException.clreplaced, () -> factory.buildDecorator(sqsAsyncClient, queueProperties, "id", this, method));
        // replacedert
        replacedertThat(exception).hasMessage("AutoVisibilityExtenderMessageProcessingDecorator cannot be built around asynchronous message listeners");
    }

    @Test
    void builtDecoratorWillAutomaticallyExtendVisibility() throws Exception {
        // arrange
        final Method method = AutoVisibilityExtenderMessageProcessingDecoratorFactoryTest.clreplaced.getMethod("methodWithAnnotation");
        final AutoVisibilityExtenderMessageProcessingDecorator decorator = factory.buildDecorator(sqsAsyncClient, queueProperties, "id", this, method).orElseThrow(() -> new RuntimeException("Error"));
        final DecoratingMessageProcessor messageProcessor = new DecoratingMessageProcessor("id", queueProperties, Collections.singletonList(decorator), new LambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> {
            try {
                Thread.sleep(1500);
            } catch (InterruptedException interruptedException) {
            // do nothing
            }
        }));
        when(sqsAsyncClient.changeMessageVisibilityBatch(ArgumentMatchers.<Consumer<ChangeMessageVisibilityBatchRequest.Builder>>any())).thenAnswer(invocation -> {
            Consumer<ChangeMessageVisibilityBatchRequest.Builder> builder = invocation.getArgument(0);
            final ChangeMessageVisibilityBatchRequest.Builder requestBuilder = ChangeMessageVisibilityBatchRequest.builder();
            builder.accept(requestBuilder);
            return CompletableFuture.completedFuture(ChangeMessageVisibilityBatchResponse.builder().build());
        });
        // act
        messageProcessor.processMessage(Message.builder().receiptHandle("handle").build(), () -> CompletableFuture.completedFuture(null)).get(5, TimeUnit.SECONDS);
        // replacedert
        verify(sqsAsyncClient).changeMessageVisibilityBatch(ArgumentMatchers.<Consumer<ChangeMessageVisibilityBatchRequest.Builder>>any());
    }

    @Nested
    clreplaced BuildConfigurationProperties {

        @Test
        void shouldBeAbleToBuildPropertiesFromStrings() {
            // arrange
            when(environment.resolvePlaceholders(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
            final AutoVisibilityExtender annotation = new AutoVisibilityExtender() {

                @Override
                public Clreplaced<? extends Annotation> annotationType() {
                    return AutoVisibilityExtender.clreplaced;
                }

                @Override
                public int visibilityTimeoutInSeconds() {
                    return -1;
                }

                @Override
                public String visibilityTimeoutInSecondsString() {
                    return "10";
                }

                @Override
                public int maximumDurationInSeconds() {
                    return -1;
                }

                @Override
                public String maximumDurationInSecondsString() {
                    return "20";
                }

                @Override
                public int bufferTimeInSeconds() {
                    return -1;
                }

                @Override
                public String bufferTimeInSecondsString() {
                    return "5";
                }
            };
            // act
            final AutoVisibilityExtenderMessageProcessingDecoratorProperties properties = factory.buildConfigurationProperties(annotation);
            // replacedert
            replacedertThat(properties.visibilityTimeout()).isEqualTo(Duration.ofSeconds(10));
            replacedertThat(properties.maxDuration()).isEqualTo(Duration.ofSeconds(20));
            replacedertThat(properties.bufferDuration()).isEqualTo(Duration.ofSeconds(5));
        }

        @Test
        void noVisibilityTimeoutWillThrowException() {
            // arrange
            final AutoVisibilityExtender annotation = new AutoVisibilityExtender() {

                @Override
                public Clreplaced<? extends Annotation> annotationType() {
                    return AutoVisibilityExtender.clreplaced;
                }

                @Override
                public int visibilityTimeoutInSeconds() {
                    return -1;
                }

                @Override
                public String visibilityTimeoutInSecondsString() {
                    return "";
                }

                @Override
                public int maximumDurationInSeconds() {
                    return -1;
                }

                @Override
                public String maximumDurationInSecondsString() {
                    return "20";
                }

                @Override
                public int bufferTimeInSeconds() {
                    return -1;
                }

                @Override
                public String bufferTimeInSecondsString() {
                    return "5";
                }
            };
            // act
            final IllegalArgumentException exception = replacedertions.replacedertThrows(IllegalArgumentException.clreplaced, () -> factory.buildConfigurationProperties(annotation));
            // replacedert
            replacedertThat(exception).hasMessage("visibilityTimeoutInSeconds should be set/positive");
        }

        @Test
        void noMaximumDurationWillThrowException() {
            // arrange
            when(environment.resolvePlaceholders(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
            final AutoVisibilityExtender annotation = new AutoVisibilityExtender() {

                @Override
                public Clreplaced<? extends Annotation> annotationType() {
                    return AutoVisibilityExtender.clreplaced;
                }

                @Override
                public int visibilityTimeoutInSeconds() {
                    return -1;
                }

                @Override
                public String visibilityTimeoutInSecondsString() {
                    return "5";
                }

                @Override
                public int maximumDurationInSeconds() {
                    return -1;
                }

                @Override
                public String maximumDurationInSecondsString() {
                    return "";
                }

                @Override
                public int bufferTimeInSeconds() {
                    return -1;
                }

                @Override
                public String bufferTimeInSecondsString() {
                    return "5";
                }
            };
            // act
            final IllegalArgumentException exception = replacedertions.replacedertThrows(IllegalArgumentException.clreplaced, () -> factory.buildConfigurationProperties(annotation));
            // replacedert
            replacedertThat(exception).hasMessage("maximumDurationInSeconds should be set/positive");
        }

        @Test
        void noBufferTimeInSecondsWillThrowException() {
            // arrange
            when(environment.resolvePlaceholders(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
            final AutoVisibilityExtender annotation = new AutoVisibilityExtender() {

                @Override
                public Clreplaced<? extends Annotation> annotationType() {
                    return AutoVisibilityExtender.clreplaced;
                }

                @Override
                public int visibilityTimeoutInSeconds() {
                    return -1;
                }

                @Override
                public String visibilityTimeoutInSecondsString() {
                    return "5";
                }

                @Override
                public int maximumDurationInSeconds() {
                    return -1;
                }

                @Override
                public String maximumDurationInSecondsString() {
                    return "20";
                }

                @Override
                public int bufferTimeInSeconds() {
                    return -1;
                }

                @Override
                public String bufferTimeInSecondsString() {
                    return "";
                }
            };
            // act
            final IllegalArgumentException exception = replacedertions.replacedertThrows(IllegalArgumentException.clreplaced, () -> factory.buildConfigurationProperties(annotation));
            // replacedert
            replacedertThat(exception).hasMessage("bufferTimeInSeconds should be set/positive");
        }
    }

    @AutoVisibilityExtender(visibilityTimeoutInSeconds = 2, maximumDurationInSeconds = 99, bufferTimeInSeconds = 1)
    public void methodWithAnnotation() {
    }

    public void methodWithNoAnnotation() {
    }

    @AutoVisibilityExtender(visibilityTimeoutInSeconds = 30, maximumDurationInSeconds = 100, bufferTimeInSeconds = 1)
    public CompletableFuture<?> asyncMethodWithAnnotation() {
        return CompletableFuture.completedFuture(null);
    }
}

19 Source : DecoratingMessageProcessorFactory.java
with MIT License
from JaidenAshmore

public MessageProcessor decorateMessageProcessor(final SqsAsyncClient sqsAsyncClient, final String identifier, final QueueProperties queueProperties, final Object bean, final Method method, final MessageProcessor delegate) {
    final List<MessageProcessingDecorator> methodProcessingDecorators = decoratorFactories.stream().map(decoratorFactory -> decoratorFactory.buildDecorator(sqsAsyncClient, queueProperties, identifier, bean, method)).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
    if (globalDecorators.isEmpty() && methodProcessingDecorators.isEmpty()) {
        return delegate;
    }
    return new DecoratingMessageProcessor(identifier, queueProperties, CollectionUtils.immutableListFrom(globalDecorators, methodProcessingDecorators), delegate);
}

19 Source : FifoMessageListenerContainerFactory.java
with MIT License
from JaidenAshmore

@Override
protected MessageListenerContainer buildContainer(final String identifier, final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final FifoMessageListenerContainerProperties containerProperties, final Supplier<MessageProcessor> messageProcessorSupplier) {
    return new FifoMessageListenerContainer(identifier, queueProperties, sqsAsyncClient, messageProcessorSupplier, containerProperties);
}

19 Source : BasicMessageListenerContainerFactory.java
with MIT License
from JaidenAshmore

@Override
protected MessageListenerContainer buildContainer(final String identifier, final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final BatchingMessageListenerContainerProperties containerProperties, final Supplier<MessageProcessor> messageProcessorSupplier) {
    return new BatchingMessageListenerContainer(identifier, queueProperties, sqsAsyncClient, messageProcessorSupplier, containerProperties);
}

19 Source : AbstractCoreMessageListenerContainerFactory.java
with MIT License
from JaidenAshmore

@Override
protected MessageListenerContainer wrapMethodContainingAnnotation(final Object bean, final Method method, final A annotation) {
    final SqsAsyncClient sqsAsyncClient = getSqsAsyncClient(annotation);
    final QueueProperties queueProperties = getQueueProperties(sqsAsyncClient, annotation);
    final P properties = annotationParser.parse(annotation);
    final String identifier = IdentifierUtils.buildIdentifierForMethod(getIdentifier(annotation), bean.getClreplaced(), method);
    final Supplier<MessageProcessor> messageProcessorSupplier = () -> decoratingMessageProcessorFactory.decorateMessageProcessor(sqsAsyncClient, identifier, queueProperties, bean, method, new CoreMessageProcessor(argumentResolverService, queueProperties, sqsAsyncClient, method, bean));
    return buildContainer(identifier, sqsAsyncClient, queueProperties, properties, messageProcessorSupplier);
}

19 Source : SpringCloudSchemaArgumentResolver.java
with MIT License
from JaidenAshmore

@Override
public Object resolveArgumentForParameter(final QueueProperties queueProperties, final MethodParameter methodParameter, final Message message) throws ArgumentResolutionException {
    try {
        final Clreplaced<?> clazz = methodParameter.getParameter().getType();
        final SchemaReference schemaReference = schemaReferenceExtractor.extract(message);
        final T producerSchema = producerSchemaRetriever.getSchema(schemaReference);
        final T consumerSchema = consumerSchemaRetriever.getSchema(clazz);
        return messagePayloadDeserializer.deserialize(message, producerSchema, consumerSchema, clazz);
    } catch (RuntimeException runtimeException) {
        throw new ArgumentResolutionException(runtimeException);
    }
}

19 Source : PrefetchingMessageRetrieverTest.java
with MIT License
from JaidenAshmore

@Slf4j
@ExtendWith(MockitoExtension.clreplaced)
clreplaced PrefetchingMessageRetrieverTest {

    private static final String QUEUE_URL = "queueUrl";

    private static final QueueProperties QUEUE_PROPERTIES = QueueProperties.builder().queueUrl(QUEUE_URL).build();

    private static final CompletableFuture<ReceiveMessageResponse> RECEIVE_MESSAGES_INTERRUPTED = CompletableFutureUtils.completedExceptionally(SdkClientException.builder().cause(new SdkInterruptedException()).build());

    private static final StaticPrefetchingMessageRetrieverProperties DEFAULT_PREFETCHING_PROPERTIES = StaticPrefetchingMessageRetrieverProperties.builder().desiredMinPrefetchedMessages(1).maxPrefetchedMessages(2).build();

    @Mock
    private SqsAsyncClient sqsAsyncClient;

    private ExecutorService executorService;

    @BeforeEach
    void setUp() {
        executorService = Executors.newCachedThreadPool();
    }

    @AfterEach
    void tearDown() {
        executorService.shutdownNow();
    }

    @Test
    void desiredPrefetchedMessagesGreaterThanMaxPrefetchedMessagesThrowsException() {
        // arrange
        final PrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(10).maxPrefetchedMessages(5).build();
        // act
        final IllegalArgumentException exception = replacedertThrows(IllegalArgumentException.clreplaced, () -> new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties));
        // replacedert
        replacedertThat(exception).hasMessage("maxPrefetchedMessages should be greater than or equal to desiredMinPrefetchedMessages");
    }

    @Test
    void desiredMinPrefetchedMessagesLessThanOneThrowsErrorInConstruction() {
        // arrange
        final PrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(0).maxPrefetchedMessages(5).build();
        // act
        final IllegalArgumentException exception = replacedertThrows(IllegalArgumentException.clreplaced, () -> new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties));
        // replacedert
        replacedertThat(exception).hasMessage("desiredMinPrefetchedMessages must be greater than zero");
    }

    @Test
    void whenStartedMessagesWillBeRequestedForPrefetchedMessages() {
        // arrange
        final Message message = Message.builder().build();
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(mockReceiveMessageResponse(message)).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(2).maxPrefetchedMessages(2).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        final List<Message> leftOverMessages = retriever.run();
        // replacedert
        replacedertThat(leftOverMessages).containsExactly(message);
    }

    @Test
    void backgroundThreadWillRequestAsManyPrefetchedMessagesAsNeeded() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().maxPrefetchedMessages(2).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().maxNumberOfMessages()).isEqualTo(2);
    }

    @Test
    void multipleMessagesCanBeObtainedFromMultipleRequestsAndPlacedIntoInternalQueue() {
        final Message firstBatchMessageOne = Message.builder().build();
        final Message firstBatchMessageTwo = Message.builder().build();
        final Message secondBatchMessageOne = Message.builder().build();
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(mockReceiveMessageResponse(firstBatchMessageOne, firstBatchMessageTwo)).thenReturn(mockReceiveMessageResponse(secondBatchMessageOne)).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(4).maxPrefetchedMessages(5).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        final List<Message> leftOverMessages = retriever.run();
        // replacedert
        replacedertThat(leftOverMessages).containsExactly(firstBatchMessageOne, firstBatchMessageTwo, secondBatchMessageOne);
    }

    @Test
    void whenThereAreAlreadyPrefetchedMessagesItWillRequestUpToMaxBatchSize() {
        final Message firstBatchMessageOne = Message.builder().build();
        final Message firstBatchMessageTwo = Message.builder().build();
        final Message secondBatchMessageOne = Message.builder().build();
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(mockReceiveMessageResponse(firstBatchMessageOne, firstBatchMessageTwo)).thenReturn(mockReceiveMessageResponse(secondBatchMessageOne)).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(4).maxPrefetchedMessages(5).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient, times(3)).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getAllValues().get(0).maxNumberOfMessages()).isEqualTo(5);
        replacedertThat(receiveMessageRequestArgumentCaptor.getAllValues().get(1).maxNumberOfMessages()).isEqualTo(3);
    }

    @Test
    void backgroundThreadWillNotTryToRequestMoreMessagesThanTheAwsMaximum() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().maxPrefetchedMessages(AwsConstants.MAX_NUMBER_OF_MESSAGES_FROM_SQS + 1).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().maxNumberOfMessages()).isEqualTo(AwsConstants.MAX_NUMBER_OF_MESSAGES_FROM_SQS);
    }

    @Test
    void whenDesiredPrefetchedIsSameAsMaximumItWillNeverExceedDesiredMessages() {
        // arrange
        final CountDownLatch receiveMessageRequested = new CountDownLatch(1);
        final Message firstMessage = Message.builder().build();
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(triggerLatchAndReturnMessages(receiveMessageRequested, firstMessage));
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(1).maxPrefetchedMessages(1).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        startRunnableInThread(retriever::run, thread -> {
            // act
            replacedertThat(receiveMessageRequested.await(5, TimeUnit.SECONDS)).isTrue();
            // Wait a little bit to make sure that we have messages
            Thread.sleep(500);
            // replacedert
            verify(sqsAsyncClient, times(1)).receiveMessage(any(ReceiveMessageRequest.clreplaced));
        });
    }

    @Test
    void whenAMessageIsTakenToPutThePrefetchedCountBelowDesiredNewMessagesWillBeFetched() throws Exception {
        // arrange
        final CountDownLatch firstMessagesFetchedLatch = new CountDownLatch(1);
        final CountDownLatch secondMessageGroupRequestedLatch = new CountDownLatch(1);
        final Message firstMessage = Message.builder().build();
        final Message secondMessage = Message.builder().build();
        final Message thirdMessage = Message.builder().build();
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(triggerLatchAndReturnMessages(firstMessagesFetchedLatch, firstMessage, secondMessage)).thenAnswer(triggerLatchAndReturnMessages(secondMessageGroupRequestedLatch, thirdMessage)).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(2).maxPrefetchedMessages(3).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        final Future<List<Message>> future = Executors.newSingleThreadExecutor().submit(retriever::run);
        replacedertThat(firstMessagesFetchedLatch.await(5, TimeUnit.SECONDS));
        // wait a little bit to make sure that we have attempted to put messages onto the queue
        Thread.sleep(100);
        verify(sqsAsyncClient, times(1)).receiveMessage(any(ReceiveMessageRequest.clreplaced));
        replacedertThat(retriever.retrieveMessage()).isCompletedWithValue(firstMessage);
        // replacedert
        replacedertThat(secondMessageGroupRequestedLatch.await(5, TimeUnit.SECONDS)).isTrue();
        replacedertThat(retriever.retrieveMessage()).isCompletedWithValue(secondMessage);
        future.get(5, TimeUnit.SECONDS);
    }

    @Test
    void waitTimeForPrefetchingPropertiesWillBeSqsMaximum() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().desiredMinPrefetchedMessages(1).maxPrefetchedMessages(2).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().waitTimeSeconds()).isEqualTo(AwsConstants.MAX_SQS_RECEIVE_WAIT_TIME_IN_SECONDS);
    }

    @Test
    void whenNoMessageVisibilityTimeoutIncludedTheVisibilityTimeoutIsNullInReceiveMessageRequest() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().messageVisibilityTimeout(null).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isNull();
    }

    @Test
    void whenNegativeMessageVisibilityTimeoutIncludedTheVisibilityTimeoutIsNullInReceiveMessageRequest() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().messageVisibilityTimeout(Duration.ofSeconds(-1)).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isNull();
    }

    @Test
    void whenZeroMessageVisibilityTimeoutIncludedTheVisibilityTimeoutIsNullInReceiveMessageRequest() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().messageVisibilityTimeout(Duration.ZERO).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isNull();
    }

    @Test
    void whenMessageVisibilityTimeoutIncludedTheVisibilityTimeoutIsIncludedInReceiveMessageRequest() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().messageVisibilityTimeout(Duration.ofSeconds(2)).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isEqualTo(2);
    }

    @Test
    void allMessageAttributesAreIncludedInMessagesWhenRetrieved() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, DEFAULT_PREFETCHING_PROPERTIES);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().messageAttributeNames()).containsExactly(QueueAttributeName.ALL.toString());
    }

    @Test
    void allMessageSystemAttributesAreIncludedInMessagesWhenRetrieved() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, DEFAULT_PREFETCHING_PROPERTIES);
        // act
        retriever.run();
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().attributeNames()).contains(QueueAttributeName.ALL);
    }

    @Test
    void threadInterruptedPuttingMessageOnTheQueueWillNotRequestAnyMoreMessages() throws InterruptedException {
        // arrange
        final CountDownLatch receiveMessageRequested = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(triggerLatchAndReturnMessages(receiveMessageRequested, Message.builder().build(), Message.builder().build()));
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, DEFAULT_PREFETCHING_PROPERTIES);
        // act
        final Future<List<Message>> future = Executors.newSingleThreadExecutor().submit(retriever::run);
        receiveMessageRequested.await(5, TimeUnit.SECONDS);
        // Sleep a little bit to make sure we are blocked by the internal message queue
        Thread.sleep(100);
        future.cancel(true);
        // replacedert
        verify(sqsAsyncClient, times(1)).receiveMessage(any(ReceiveMessageRequest.clreplaced));
    }

    @Test
    void exceptionThrownObtainingMessagesWillBackOffForSpecifiedPeriod() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenThrow(new ExpectedTestException()).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final int backoffTimeInMilliseconds = 500;
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().errorBackoffTime(Duration.ofMillis(backoffTimeInMilliseconds)).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        final long startTime = System.currentTimeMillis();
        retriever.run();
        final long endTime = System.currentTimeMillis();
        // replacedert
        replacedertThat(endTime - startTime).isGreaterThanOrEqualTo(backoffTimeInMilliseconds);
    }

    @Test
    void threadInterruptedWhileBackingOffWillStopBackgroundThread() {
        // arrange
        final AtomicBoolean backoffOccurred = new AtomicBoolean(false);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenThrow(new ExpectedTestException());
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, new PrefetchingMessageRetrieverProperties() {

            @Override
            @Positive
            public int getDesiredMinPrefetchedMessages() {
                return 2;
            }

            @Override
            @Positive
            public int getMaxPrefetchedMessages() {
                return 4;
            }

            @Override
            @Nullable
            @Positive
            public Duration getMessageVisibilityTimeout() {
                return null;
            }

            @Override
            @Nullable
            @PositiveOrZero
            public Duration getErrorBackoffTime() {
                backoffOccurred.set(true);
                Thread.currentThread().interrupt();
                return Duration.ofMillis(500);
            }
        });
        // act
        retriever.run();
        // replacedert
        replacedertThat(backoffOccurred.get()).isTrue();
    }

    @Test
    void rejectedFutureFromSqsAsyncClientWillBackoff() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(CompletableFutureUtils.completedExceptionally(new ExpectedTestException())).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final int backoffTimeInMilliseconds = 500;
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().errorBackoffTime(Duration.ofMillis(backoffTimeInMilliseconds)).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        final long startTime = System.currentTimeMillis();
        retriever.run();
        final long endTime = System.currentTimeMillis();
        // replacedert
        replacedertThat(endTime - startTime).isGreaterThanOrEqualTo(backoffTimeInMilliseconds);
    }

    @Test
    void whenSqsAsyncClientThrowsSdkInterruptedExceptionTheBackgroundThreadShouldStop() {
        // arrange
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(RECEIVE_MESSAGES_INTERRUPTED);
        final int backoffTimeInMilliseconds = 500;
        final StaticPrefetchingMessageRetrieverProperties properties = DEFAULT_PREFETCHING_PROPERTIES.toBuilder().errorBackoffTime(Duration.ofMillis(backoffTimeInMilliseconds)).build();
        final PrefetchingMessageRetriever retriever = new PrefetchingMessageRetriever(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        // act
        retriever.run();
    }

    private Answer<CompletableFuture<ReceiveMessageResponse>> triggerLatchAndReturnMessages(final CountDownLatch latch, final Message... messages) {
        return invocation -> {
            latch.countDown();
            return mockReceiveMessageResponse(messages);
        };
    }

    private CompletableFuture<ReceiveMessageResponse> mockReceiveMessageResponse(final Message... messages) {
        return CompletableFuture.completedFuture(ReceiveMessageResponse.builder().messages(messages).build());
    }
}

19 Source : BatchingMessageRetrieverTest.java
with MIT License
from JaidenAshmore

@Slf4j
@ExtendWith(MockitoExtension.clreplaced)
clreplaced BatchingMessageRetrieverTest {

    private static final String QUEUE_URL = "queueUrl";

    private static final QueueProperties QUEUE_PROPERTIES = QueueProperties.builder().queueUrl(QUEUE_URL).build();

    private static final Duration POLLING_PERIOD = Duration.ofSeconds(1);

    private static final StaticBatchingMessageRetrieverProperties DEFAULT_PROPERTIES = StaticBatchingMessageRetrieverProperties.builder().messageVisibilityTimeout(Duration.ofSeconds(10)).batchSize(2).batchingPeriod(POLLING_PERIOD).build();

    @Mock
    private SqsAsyncClient sqsAsyncClient;

    @Test
    void threadIsWaitingWhileItWaitsForMessagesToDownload() {
        // arrange
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, DEFAULT_PROPERTIES);
        startRunnableInThread(retriever::run, thread -> {
            // act
            ThreadTestUtils.waitUntilThreadInState(thread, Thread.State.TIMED_WAITING);
            thread.interrupt();
            // replacedert
            ThreadTestUtils.waitUntilThreadInState(thread, Thread.State.TERMINATED);
        });
    }

    @Test
    void whenThereAreMoreRequestsForMessagesThanTheThresholdItWillRequestMessagesStraightAway() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(2).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        startRunnableInThread(retriever::run, thread -> {
            // act
            retriever.retrieveMessage();
            ThreadTestUtils.waitUntilThreadInState(thread, Thread.State.TIMED_WAITING);
            retriever.retrieveMessage();
            // replacedert
            replacedertThat(receiveMessageRequestLatch.await(1, TimeUnit.SECONDS)).isTrue();
        });
    }

    @Test
    void whenThePollingPeriodIreplacedTheBackgroundThreadWillRequestAsManyMessagesAsThoseWaiting() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(2).batchingPeriod(Duration.ofSeconds(1)).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        final long timeStarted = System.currentTimeMillis();
        startRunnableInThread(retriever::run, thread -> {
            // act
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
            // replacedert
            final long timeSendingBatch = System.currentTimeMillis();
            replacedertThat(timeSendingBatch - timeStarted).isGreaterThanOrEqualTo(retrieverProperties.getBatchingPeriod().toMillis());
        });
    }

    @Test
    void whenNoVisibilityTimeoutIncludedTheReceiveMessageRequestWillIncludeNullVisibilityTimeout() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).messageVisibilityTimeout(null).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isNull();
    }

    @Test
    void whenNegativeVisibilityTimeoutIncludedTheReceiveMessageRequestWillIncludeNullVisibilityTimeout() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).messageVisibilityTimeout(Duration.ofSeconds(-1)).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isNull();
    }

    @Test
    void whenZeroVisibilityTimeoutIncludedTheReceiveMessageRequestWillIncludeNullVisibilityTimeout() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).messageVisibilityTimeout(Duration.ZERO).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isNull();
    }

    @Test
    void whenValidVisibilityTimeoutIncludedTheReceiveMessageRequestWillIncludeVisibilityTimeout() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).messageVisibilityTimeout(Duration.ofSeconds(30)).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isEqualTo(30);
    }

    @Test
    void requestsForBatchesOfMessagesWillNotBeExecutedConcurrently() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(2).batchingPeriod(Duration.ofMillis(50L)).messageVisibilityTimeout(Duration.ofSeconds(30)).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch firstRequestMade = new CountDownLatch(1);
        final CountDownLatch waitUntilSecondRequestSubmitted = new CountDownLatch(1);
        final CountDownLatch secondRequestMade = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            firstRequestMade.countDown();
            waitUntilSecondRequestSubmitted.await();
            return mockReceiveMessageResponse();
        }).thenAnswer(invocation -> {
            secondRequestMade.countDown();
            return mockReceiveMessageResponse(Message.builder().build(), Message.builder().build());
        });
        startRunnableInThread(retriever::run, thread -> {
            // act
            final CompletableFuture<Message> firstMessageResponse = retriever.retrieveMessage();
            replacedertThat(firstRequestMade.await(2, TimeUnit.SECONDS)).isTrue();
            final CompletableFuture<Message> secondMessageResponse = retriever.retrieveMessage();
            waitUntilSecondRequestSubmitted.countDown();
            replacedertThat(secondRequestMade.await(2, TimeUnit.SECONDS)).isTrue();
            // replacedert
            firstMessageResponse.get(1, TimeUnit.SECONDS);
            secondMessageResponse.get(1, TimeUnit.SECONDS);
            verify(sqsAsyncClient, times(2)).receiveMessage(any(ReceiveMessageRequest.clreplaced));
        });
    }

    @Test
    void errorObtainingMessagesWillTryAgainAfterBackingOffPeriod() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().errorBackoffTime(Duration.ofMillis(200L)).batchSize(1).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch secondMessageRequestedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(CompletableFutureUtils.completedExceptionally(new ExpectedTestException())).thenAnswer(invocation -> {
            secondMessageRequestedLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        startRunnableInThread(retriever::run, thread -> {
            // act
            final long startTime = System.currentTimeMillis();
            retriever.retrieveMessage();
            replacedertThat(secondMessageRequestedLatch.await(1, TimeUnit.SECONDS)).isTrue();
            final long timeRequestedSecondMessageAfterBackoff = System.currentTimeMillis();
            // replacedert
            replacedertThat(timeRequestedSecondMessageAfterBackoff - startTime).isGreaterThanOrEqualTo(retrieverProperties.getErrorBackoffTime().toMillis());
        });
    }

    @Test
    void interruptedExceptionThrownWhenBackingOffWillEndBackgroundThread() {
        // arrange
        final long errorBackoffTimeInMilliseconds = 200L;
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().errorBackoffTime(Duration.ofMillis(errorBackoffTimeInMilliseconds)).batchSize(1).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenReturn(CompletableFutureUtils.completedExceptionally(new ExpectedTestException()));
        startRunnableInThread(retriever::run, thread -> {
            // act
            retriever.retrieveMessage();
            Thread.sleep(errorBackoffTimeInMilliseconds / 2);
            thread.interrupt();
            // replacedert
            waitUntilThreadInState(thread, Thread.State.TERMINATED);
        });
    }

    @Test
    void willNotExceedAwsMaxMessagesForRetrievalWhenRequestingMessages() {
        // arrange
        final StaticBatchingMessageRetrieverProperties retrieverProperties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).messageVisibilityTimeout(Duration.ofSeconds(30)).batchSize(MAX_NUMBER_OF_MESSAGES_FROM_SQS + 1).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        final CountDownLatch threadStoppedLatched = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            log.info("Requesting messages");
            receiveMessageRequestLatch.countDown();
            threadStoppedLatched.await();
            return mockReceiveMessageResponse();
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            for (int i = 0; i < MAX_NUMBER_OF_MESSAGES_FROM_SQS + 1; ++i) {
                retriever.retrieveMessage();
            }
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().maxNumberOfMessages()).isEqualTo(MAX_NUMBER_OF_MESSAGES_FROM_SQS);
    }

    @Test
    void waitTimeForMessageRetrievalWillSqsMaximum() {
        // arrange
        final StaticBatchingMessageRetrieverProperties properties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, properties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().waitTimeSeconds()).isEqualTo(AwsConstants.MAX_SQS_RECEIVE_WAIT_TIME_IN_SECONDS);
    }

    @Test
    void allMessageAttributesShouldBeDownloadedWhenRequestingMessages() {
        // arrange
        final StaticBatchingMessageRetrieverProperties properties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, properties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().messageAttributeNames()).containsExactly(QueueAttributeName.ALL.toString());
    }

    @Test
    void allMessageSystemAttributesShouldBeDownloadedWhenRequestingMessages() {
        // arrange
        final StaticBatchingMessageRetrieverProperties properties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, properties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().attributeNames()).containsExactly(QueueAttributeName.ALL);
    }

    @Test
    void nullPollingPeriodWillStillAllowMessagesToBeReceivedWhenLimitReached() {
        // arrange
        final StaticBatchingMessageRetrieverProperties properties = DEFAULT_PROPERTIES.toBuilder().batchSize(1).batchingPeriod(null).build();
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, properties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        startRunnableInThread(retriever::run, thread -> {
            // act
            retriever.retrieveMessage();
            // replacedert
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
    }

    @Test
    void errorGettingVisibilityTimeoutWillNotProvideOneInRequest() {
        // arrange
        final BatchingMessageRetrieverProperties retrieverProperties = mock(BatchingMessageRetrieverProperties.clreplaced);
        when(retrieverProperties.getBatchSize()).thenReturn(1);
        when(retrieverProperties.getBatchingPeriod()).thenReturn(Duration.ofSeconds(1));
        when(retrieverProperties.getMessageVisibilityTimeout()).thenThrow(new ExpectedTestException());
        final BatchingMessageRetriever retriever = new BatchingMessageRetriever(QUEUE_PROPERTIES, sqsAsyncClient, retrieverProperties);
        final CountDownLatch receiveMessageRequestLatch = new CountDownLatch(1);
        when(sqsAsyncClient.receiveMessage(any(ReceiveMessageRequest.clreplaced))).thenAnswer(invocation -> {
            receiveMessageRequestLatch.countDown();
            return mockReceiveMessageResponse(Message.builder().build());
        });
        // act
        startRunnableInThread(retriever::run, thread -> {
            retriever.retrieveMessage();
            replacedertThat(receiveMessageRequestLatch.await(2, TimeUnit.SECONDS)).isTrue();
        });
        // replacedert
        final ArgumentCaptor<ReceiveMessageRequest> receiveMessageRequestArgumentCaptor = ArgumentCaptor.forClreplaced(ReceiveMessageRequest.clreplaced);
        verify(sqsAsyncClient).receiveMessage(receiveMessageRequestArgumentCaptor.capture());
        replacedertThat(receiveMessageRequestArgumentCaptor.getValue().visibilityTimeout()).isNull();
    }

    private CompletableFuture<ReceiveMessageResponse> mockReceiveMessageResponse(final Message... messages) {
        return CompletableFuture.completedFuture(ReceiveMessageResponse.builder().messages(messages).build());
    }
}

19 Source : BatchingMessageResolverTest.java
with MIT License
from JaidenAshmore

@Slf4j
@ExtendWith(MockitoExtension.clreplaced)
clreplaced BatchingMessageResolverTest {

    private static final Duration BUFFERING_TIME = Duration.ofSeconds(1);

    private static final QueueProperties QUEUE_PROPERTIES = QueueProperties.builder().queueUrl("queueUrl").build();

    private static final StaticBatchingMessageResolverProperties DEFAULT_BATCHING_PROPERTIES = StaticBatchingMessageResolverProperties.builder().bufferingTime(BUFFERING_TIME).bufferingSizeLimit(1).build();

    @Mock
    private SqsAsyncClient sqsAsyncClient;

    private ExecutorService executorService;

    @BeforeEach
    void setUp() {
        executorService = Executors.newCachedThreadPool();
    }

    @AfterEach
    void tearDown() {
        executorService.shutdownNow();
    }

    @Test
    void messageShouldBeBufferedUntilTheTimeLimitIreplaced() throws Exception {
        // arrange
        final long bufferingTimeInMs = 100;
        final BatchingMessageResolverProperties batchingProperties = DEFAULT_BATCHING_PROPERTIES.toBuilder().bufferingTime(Duration.ofMillis(bufferingTimeInMs)).bufferingSizeLimit(2).build();
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, batchingProperties);
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            return CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().successful(DeleteMessageBatchResultEntry.builder().id("id").build()).build());
        });
        // act
        final long startTime = System.currentTimeMillis();
        executorService.submit(batchingMessageResolver::run);
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        final long endTime = System.currentTimeMillis();
        // replacedert
        replacedertThat(endTime - startTime).isGreaterThanOrEqualTo(bufferingTimeInMs);
    }

    @Test
    void whenBatchingSizeLimitReachedTheMessagesAreImmediatelySent() throws Exception {
        // arrange
        final long bufferingTimeInMs = 100_000;
        final BatchingMessageResolverProperties batchingProperties = DEFAULT_BATCHING_PROPERTIES.toBuilder().bufferingTime(Duration.ofMillis(bufferingTimeInMs)).bufferingSizeLimit(2).build();
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, batchingProperties);
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id2").receiptHandle("handle2").build());
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            return CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().build());
        });
        // act
        final long startTime = System.currentTimeMillis();
        executorService.submit(batchingMessageResolver::run);
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        final long endTime = System.currentTimeMillis();
        // replacedert
        replacedertThat(endTime - startTime).isLessThan(bufferingTimeInMs);
    }

    @Test
    void batchDeleteRequestShouldContainCorrectReceiptHandleForMessageRemoval() throws Exception {
        // arrange
        final long bufferingTimeInMs = 100_000;
        final BatchingMessageResolverProperties batchingProperties = DEFAULT_BATCHING_PROPERTIES.toBuilder().bufferingTime(Duration.ofMillis(bufferingTimeInMs)).bufferingSizeLimit(2).build();
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, batchingProperties);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            return CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().build());
        });
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id1").receiptHandle("receipt1").build());
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id2").receiptHandle("receipt2").build());
        // act
        executorService.submit(batchingMessageResolver::run);
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        // replacedert
        verify(sqsAsyncClient).deleteMessageBatch(DeleteMessageBatchRequest.builder().queueUrl("queueUrl").entries(DeleteMessageBatchRequestEntry.builder().id("id1").receiptHandle("receipt1").build(), DeleteMessageBatchRequestEntry.builder().id("id2").receiptHandle("receipt2").build()).build());
    }

    @Test
    void whenMessageIsSuccessfullyDeletedTheCompletableFutureIsResolved() throws Exception {
        // arrange
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, DEFAULT_BATCHING_PROPERTIES);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            return CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().successful(DeleteMessageBatchResultEntry.builder().id("id").build()).build());
        });
        final CompletableFuture<?> messageResolvedCompletableFuture = batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        // act
        executorService.submit(batchingMessageResolver::run);
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        // replacedert
        messageResolvedCompletableFuture.get();
        replacedertThat(messageResolvedCompletableFuture).isCompleted();
    }

    @Test
    void whenMessageFailsToBeDeletedTheCompletableFutureIsCompletedExceptionally() throws Exception {
        // arrange
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, DEFAULT_BATCHING_PROPERTIES);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            return CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().failed(BatchResultErrorEntry.builder().id("id").message("Expected Test Error").build()).build());
        });
        final CompletableFuture<?> messageResolvedCompletableFuture = batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        // act
        executorService.submit(batchingMessageResolver::run);
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        // replacedert
        final ExecutionException exception = replacedertThrows(ExecutionException.clreplaced, messageResolvedCompletableFuture::get);
        replacedertThat(exception).hasCauseInstanceOf(RuntimeException.clreplaced);
        replacedertThat(exception.getCause()).hasMessage("Expected Test Error");
    }

    @Test
    void whenMessageIsNotHandledInBatchDeletionItIsRejected() throws Exception {
        // arrange
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, DEFAULT_BATCHING_PROPERTIES);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            return CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().build());
        });
        final CompletableFuture<?> messageResolvedCompletableFuture = batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        // act
        executorService.submit(batchingMessageResolver::run);
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        // replacedert
        final ExecutionException exception = replacedertThrows(ExecutionException.clreplaced, messageResolvedCompletableFuture::get);
        replacedertThat(exception).hasCauseInstanceOf(RuntimeException.clreplaced);
        replacedertThat(exception.getCause()).hasMessage("Message not handled by batch delete. This should not happen");
    }

    @Test
    void notProvidingPropertiesWillResolveMessagesreplacedoonAsTheyAreRequested() throws Exception {
        // arrange
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            return CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().build());
        });
        executorService.submit(batchingMessageResolver::run);
        // act
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        // replacedert
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
    }

    @Test
    void interruptingThreadWhileWaitingForTotalMessageBatchWillStillPublishCurrentMessagesObtained() throws Exception {
        // arrange
        final StaticBatchingMessageResolverProperties batchingProperties = DEFAULT_BATCHING_PROPERTIES.toBuilder().bufferingTime(Duration.ofMillis(100_000L)).bufferingSizeLimit(2).build();
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, batchingProperties);
        final CompletableFuture<?> messageResolvedFuture = batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> CompletableFuture.completedFuture(DeleteMessageBatchResponse.builder().successful(DeleteMessageBatchResultEntry.builder().id("id").build()).build()));
        // act
        final Future<?> resolverThreadFuture = executorService.submit(batchingMessageResolver::run);
        // Just make sure we have drained the single message
        Thread.sleep(500);
        resolverThreadFuture.cancel(true);
        // replacedert
        messageResolvedFuture.get(30, TimeUnit.SECONDS);
        replacedertThat(messageResolvedFuture).isCompleted();
    }

    @Test
    void exceptionThrownInResponseToBatchRemovalWillRejectAllMessages() throws Exception {
        // arrange
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, DEFAULT_BATCHING_PROPERTIES);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            final CompletableFuture<DeleteMessageBatchResponse> completableFuture = new CompletableFuture<>();
            completableFuture.completeExceptionally(new ExpectedTestException());
            return completableFuture;
        });
        final CompletableFuture<?> messageResolvedFuture = batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        // act
        executorService.submit(batchingMessageResolver::run);
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        // replacedert
        final ExecutionException exception = replacedertThrows(ExecutionException.clreplaced, messageResolvedFuture::get);
        replacedertThat(exception).hasCauseInstanceOf(ExpectedTestException.clreplaced);
    }

    @Test
    void exceptionThrownSendingBatchRemovalWillRejectAllMessages() {
        // arrange
        final StaticBatchingMessageResolverProperties properties = DEFAULT_BATCHING_PROPERTIES.toBuilder().bufferingSizeLimit(1).build();
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, properties);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenThrow(new ExpectedTestException());
        final CompletableFuture<?> messageResolvedFuture = batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        // act
        executorService.submit(batchingMessageResolver::run);
        // replacedert
        final ExecutionException exception = replacedertThrows(ExecutionException.clreplaced, messageResolvedFuture::get);
        replacedertThat(exception).hasCauseInstanceOf(ExpectedTestException.clreplaced);
    }

    @Test
    void multipleBatchesOfDeletionsCanBeSentConcurrentlyIfManyResolveMessageCallsAreMadeAtOnce() throws Exception {
        // arrange
        final StaticBatchingMessageResolverProperties properties = DEFAULT_BATCHING_PROPERTIES.toBuilder().bufferingSizeLimit(1).build();
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, properties);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(2);
        final CountDownLatch blockDeleteMessage = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            log.debug("Received batch to delete");
            batchBeingDeletedLatch.countDown();
            blockDeleteMessage.await();
            throw new ExpectedTestException();
        });
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        // act
        executorService.submit(batchingMessageResolver::run);
        // replacedert
        replacedertThat(batchBeingDeletedLatch.await(2, TimeUnit.SECONDS)).isTrue();
    }

    @Test
    void shuttingDownMessageResolverWillWaitUntilEachMessageBatchCompletes() throws Exception {
        // arrange
        final StaticBatchingMessageResolverProperties properties = DEFAULT_BATCHING_PROPERTIES.toBuilder().bufferingSizeLimit(1).build();
        final BatchingMessageResolver batchingMessageResolver = new BatchingMessageResolver(QUEUE_PROPERTIES, sqsAsyncClient, properties);
        final CountDownLatch batchBeingDeletedLatch = new CountDownLatch(1);
        final CountDownLatch blockDeleteMessage = new CountDownLatch(1);
        when(sqsAsyncClient.deleteMessageBatch(any(DeleteMessageBatchRequest.clreplaced))).thenAnswer(invocation -> {
            batchBeingDeletedLatch.countDown();
            blockDeleteMessage.await();
            throw new ExpectedTestException();
        });
        batchingMessageResolver.resolveMessage(Message.builder().messageId("id").receiptHandle("handle").build());
        final Thread resolverThread = new Thread(batchingMessageResolver::run);
        resolverThread.start();
        replacedertThat(batchBeingDeletedLatch.await(1, TimeUnit.SECONDS)).isTrue();
        // act
        resolverThread.interrupt();
        // replacedert
        Thread.sleep(500);
        waitUntilThreadInState(resolverThread, Thread.State.WAITING);
        blockDeleteMessage.countDown();
        waitUntilThreadInState(resolverThread, Thread.State.TERMINATED);
    }
}

19 Source : LambdaMessageProcessorTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced LambdaMessageProcessorTest {

    private static final QueueProperties queueProperties = QueueProperties.builder().queueUrl("url").build();

    private static final Message message = Message.builder().receiptHandle("handle").build();

    @Mock
    private SqsAsyncClient sqsAsyncClient;

    @Mock
    private Supplier<CompletableFuture<?>> resolveMessage;

    @Nested
    clreplaced OnlyConsumeMessage {

        @Test
        void successfulExecutionWillResolveFuture() {
            // arrange
            when(resolveMessage.get()).thenReturn(CompletableFuture.completedFuture(null));
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> {
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(resolveMessage).get();
        }

        @Test
        void failureToProcessMessageWillRejectFuture() {
            // arrange
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> {
                throw new ExpectedTestException();
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            verify(resolveMessage, never()).get();
        }
    }

    @Nested
    clreplaced ConsumeMessageWithVisibility {

        @Test
        void visibilityExtenderCanBeUsedToExtendMessageVisibility() {
            // arrange
            when(resolveMessage.get()).thenReturn(CompletableFuture.completedFuture(null));
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, false, (message, visibilityExtender) -> visibilityExtender.extend());
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(sqsAsyncClient).changeMessageVisibility(ChangeMessageVisibilityRequest.builder().visibilityTimeout(DEFAULT_VISIBILITY_EXTENSION_IN_SECONDS).queueUrl("url").receiptHandle("handle").build());
        }
    }

    @Nested
    clreplaced ConsumeMessageWithAcknowledge {

        @Test
        void successfulExecutionWillResolveFutureButNotResolveMessage() {
            // arrange
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, (message, acknowledge) -> {
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(resolveMessage, never()).get();
        }

        @Test
        void failureExecutionWillRejectFutureAndNotResolveMessage() {
            // arrange
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, (message, acknowledge) -> {
                throw new ExpectedTestException();
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            verify(resolveMessage, never()).get();
        }
    }

    @Nested
    clreplaced ConsumeMessageWithAcknowledgeAndVisibilityExtender {

        @Test
        void successfulExecutionWillResolveFutureButNotResolveMessage() {
            // arrange
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, (message, acknowledge, visibilityExtender) -> {
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(resolveMessage, never()).get();
        }
    }

    @Nested
    clreplaced ResolvingMessage {

        @Test
        void exceptionThrownWhenResolvingMessageWillNotRejectProcessingFuture() {
            // arrange
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> {
            });
            when(resolveMessage.get()).thenThrow(new ExpectedTestException());
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
        }

        @Test
        void resolveMessageRejectedWillNotRejectProcessingFuture() {
            // arrange
            final LambdaMessageProcessor processor = new LambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> {
            });
            when(resolveMessage.get()).thenReturn(CompletableFutureUtils.completedExceptionally(new ExpectedTestException()));
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
        }
    }
}

19 Source : CoreMessageProcessorTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced CoreMessageProcessorTest {

    private static final QueueProperties QUEUE_PROPERTIES = QueueProperties.builder().queueUrl("queueUrl").build();

    private static final Message MESSAGE = Message.builder().body("test").build();

    private static final SynchronousMessageListenerScenarios syncMessageListener = new SynchronousMessageListenerScenarios();

    private static final Supplier<CompletableFuture<?>> NO_OP = () -> CompletableFuture.completedFuture(null);

    @Mock
    private ArgumentResolverService argumentResolverService;

    @Mock
    private SqsAsyncClient sqsAsyncClient;

    @Mock
    private ArgumentResolver<String> mockArgumentResolver;

    @Mock
    private Supplier<CompletableFuture<?>> mockMessageResolver;

    @Nested
    clreplaced Arguments {

        @Test
        void forEachParameterInMethodTheArgumentIsResolved() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithArguments", String.clreplaced, String.clreplaced);
            doReturn(mockArgumentResolver).when(argumentResolverService).getArgumentResolver(any(MethodParameter.clreplaced));
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            // act
            processor.processMessage(MESSAGE, NO_OP);
            // replacedert
            verify(argumentResolverService, times(2)).getArgumentResolver(any(MethodParameter.clreplaced));
        }

        @Test
        void anyParameterUnableToBeResolvedWillThrowAnError() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithArguments", String.clreplaced, String.clreplaced);
            when(argumentResolverService.getArgumentResolver(any(MethodParameter.clreplaced))).thenThrow(new UnsupportedArgumentResolutionException());
            // act
            replacedertThrows(UnsupportedArgumentResolutionException.clreplaced, () -> new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener));
        }

        @Test
        void methodWillBeInvokedWithArgumentsResolved() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithArguments", String.clreplaced, String.clreplaced);
            final SynchronousMessageListenerScenarios mockMessageListener = mock(SynchronousMessageListenerScenarios.clreplaced);
            doReturn(mockArgumentResolver).when(argumentResolverService).getArgumentResolver(any(MethodParameter.clreplaced));
            when(mockArgumentResolver.resolveArgumentForParameter(eq(QUEUE_PROPERTIES), any(), eq(MESSAGE))).thenReturn("payload").thenReturn("payload2");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, mockMessageListener);
            // act
            processor.processMessage(MESSAGE, NO_OP);
            // replacedert
            verify(mockMessageListener).methodWithArguments("payload", "payload2");
        }

        @Test
        void methodWithVisibilityExtenderWillBeCorrectlyResolved() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithVisibilityExtender", VisibilityExtender.clreplaced);
            final SynchronousMessageListenerScenarios mockMessageListener = mock(SynchronousMessageListenerScenarios.clreplaced);
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, mockMessageListener);
            // act
            processor.processMessage(MESSAGE, NO_OP);
            // replacedert
            verify(mockMessageListener).methodWithVisibilityExtender(any(VisibilityExtender.clreplaced));
        }

        @Test
        void anyArgumentThatFailsToBeResolvedForMessageWillThrowMessageProcessingException() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithArguments", String.clreplaced, String.clreplaced);
            doReturn(mockArgumentResolver).when(argumentResolverService).getArgumentResolver(any(MethodParameter.clreplaced));
            when(mockArgumentResolver.resolveArgumentForParameter(any(), any(), any())).thenThrow(new ExpectedTestException());
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            // act
            final ExecutionException exception = replacedertThrows(ExecutionException.clreplaced, () -> processor.processMessage(MESSAGE, NO_OP).get());
            // replacedert
            replacedertThat(exception).hasCauseInstanceOf(MessageProcessingException.clreplaced);
            replacedertThat(exception.getCause()).hasCauseInstanceOf(ExpectedTestException.clreplaced);
        }
    }

    @Nested
    clreplaced SynchronousMessageProcessing {

        @Test
        void willReturnCompletedFutureWhenTheMessageListenerDidNotThrowAnException() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithNoArguments");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFuture.completedFuture(null));
            // act
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(result).isCompleted();
        }

        @Test
        void willAttemptToResolveMessageWhenMessageListenerProcessedSuccessfully() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithNoArguments");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFuture.completedFuture(null));
            // act
            processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            verify(mockMessageResolver).get();
        }

        @Test
        void returnedCompletableFutureIsNotReliantOnMessageResolvingCompleting() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithNoArguments");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            when(mockMessageResolver.get()).thenReturn(new CompletableFuture<>());
            // act
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(result).isCompleted();
        }

        @Test
        void successfullyProcessingTheMessageWillAllowSubsequentFutureChainCallsToBeOnSameThread() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithNoArguments");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            when(mockMessageResolver.get()).thenReturn(new CompletableFuture<>());
            // act
            final AtomicReference<String> futureChainThreadName = new AtomicReference<>();
            final String messageListenerThreadName = Thread.currentThread().getName();
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver).thenAccept(ignored -> futureChainThreadName.set(Thread.currentThread().getName()));
            // replacedert
            replacedertThat(result).isCompleted();
            replacedertThat(futureChainThreadName.get()).isEqualTo(messageListenerThreadName);
        }

        @Test
        void willReturnRejectedCompletableFutureWhenTheMessageListenerThrewAnException() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodThatThrowsException");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            // act
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver);
            // arrange
            replacedertThat(result).isCompletedExceptionally();
        }

        @Test
        void unsuccessfullyProcessingTheMessageWillAllowSubsequentFutureChainCallsToBeOnSameThread() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodThatThrowsException");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            // act
            final AtomicReference<String> futureChainThreadName = new AtomicReference<>();
            final String messageListenerThreadName = Thread.currentThread().getName();
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver).whenComplete((ignored, throwable) -> futureChainThreadName.set(Thread.currentThread().getName()));
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            replacedertThat(futureChainThreadName.get()).isEqualTo(messageListenerThreadName);
        }

        @Test
        void failingToTriggerResolveMessageSupplierWillNotRejectFuture() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithNoArguments");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            when(mockMessageResolver.get()).thenThrow(ExpectedTestException.clreplaced);
            // act
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(result).isCompleted();
        }

        @Test
        void failingToResolveMessageWillNotRejectFuture() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithNoArguments");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFutureUtils.completedExceptionally(new ExpectedTestException()));
            // act
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(result).isCompleted();
        }

        @Test
        void failingToProcessDueToIllegalAccessExceptionWilRejectFuture() {
            // arrange
            final Method method = SynchronousMessageListenerScenarios.getMethod("privateMethod");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
            // act
            final CompletableFuture<?> result = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            final ExecutionException processingException = replacedertThrows(ExecutionException.clreplaced, result::get);
            replacedertThat(processingException).hasCauseInstanceOf(MessageProcessingException.clreplaced);
            replacedertThat(processingException.getCause()).hasCauseInstanceOf(IllegalAccessException.clreplaced);
        }

        @Nested
        clreplaced AcknowledgeArgument {

            @Test
            void methodWithAcknowledgeParameterWillNotDeleteMessageOnSuccess() {
                // arrange
                final Method method = SynchronousMessageListenerScenarios.getMethod("methodWithAcknowledge", Acknowledge.clreplaced);
                final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, syncMessageListener);
                // act
                processor.processMessage(MESSAGE, mockMessageResolver);
                // replacedert
                verify(mockMessageResolver, never()).get();
            }
        }
    }

    @Nested
    clreplaced AsynchronousMessageProcessing {

        private final AsynchronousMessageListenerScenarios asyncMessageListener = new AsynchronousMessageListenerScenarios();

        @Test
        void willReturnCompletedFutureWhenMessageListenerReturnsResolvedFuture() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodReturningResolvedFuture");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFuture.completedFuture(null));
            // act
            final CompletableFuture<?> completableFuture = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(completableFuture).isCompleted();
        }

        @Test
        void willAttemptToResolveMessageWhenMessageListenerReturnsCompletedFuture() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodReturningResolvedFuture");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFuture.completedFuture(null));
            // act
            final CompletableFuture<?> completableFuture = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(completableFuture).isCompleted();
            verify(mockMessageResolver).get();
        }

        @Test
        @SneakyThrows
        void whenTheMessageListenerReturnsCompletableFutureThatIsResolvedAsynchronouslyFutureChainIsNotOnSameThread() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodReturnFutureSubsequentlyResolved");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFuture.completedFuture(null));
            final String messageListenerThreadName = Thread.currentThread().getName();
            // act
            final AtomicReference<String> futureChainThreadName = new AtomicReference<>();
            processor.processMessage(MESSAGE, mockMessageResolver).whenComplete((ignored, throwable) -> futureChainThreadName.set(Thread.currentThread().getName())).get(5, TimeUnit.SECONDS);
            // replacedert
            replacedertThat(futureChainThreadName).isNotNull();
            replacedertThat(futureChainThreadName.get()).isNotEqualTo(messageListenerThreadName);
        }

        @Test
        void willReturnRejectedFutureWhenMessageListenerThrowsException() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodThatThrowsException");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            // act
            final CompletableFuture<?> completableFuture = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(completableFuture).isCompletedExceptionally();
        }

        @Test
        void willNotAttemptToResolveMessageWhenMessageListenerThrowsException() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodThatThrowsException");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            // act
            final CompletableFuture<?> completableFuture = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(completableFuture).isCompletedExceptionally();
            verify(mockMessageResolver, never()).get();
        }

        @Test
        @SneakyThrows
        void whenTheMessageListenerReturnsCompletableFutureThatIsRejectedAsynchronouslyFutureChainIsNotOnSameThread() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodReturnFutureSubsequentlyResolved");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFuture.completedFuture(null));
            final String messageListenerThreadName = Thread.currentThread().getName();
            // act
            final AtomicReference<String> futureChainThreadName = new AtomicReference<>();
            processor.processMessage(MESSAGE, mockMessageResolver).whenComplete((ignored, throwable) -> futureChainThreadName.set(Thread.currentThread().getName())).get(5, TimeUnit.SECONDS);
            // replacedert
            replacedertThat(futureChainThreadName).isNotNull();
            replacedertThat(futureChainThreadName.get()).isNotEqualTo(messageListenerThreadName);
        }

        @Test
        void messageListenerThatReturnsNullWillReturnRejectedFuture() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodThatReturnsNull");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            // act
            final CompletableFuture<?> completableFuture = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(completableFuture).isCompletedExceptionally();
        }

        @Test
        void thatCompletesButMessageResolvingFailsWillNotRejectFuture() {
            // arrange
            final Method method = AsynchronousMessageListenerScenarios.getMethod("methodReturningResolvedFuture");
            final MessageProcessor processor = new CoreMessageProcessor(argumentResolverService, QUEUE_PROPERTIES, sqsAsyncClient, method, asyncMessageListener);
            when(mockMessageResolver.get()).thenReturn(CompletableFutureUtils.completedExceptionally(new ExpectedTestException()));
            // act
            final CompletableFuture<?> completableFuture = processor.processMessage(MESSAGE, mockMessageResolver);
            // replacedert
            replacedertThat(completableFuture).isCompleted();
        }
    }
}

19 Source : AsyncLambdaMessageProcessorTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced AsyncLambdaMessageProcessorTest {

    private static final QueueProperties queueProperties = QueueProperties.builder().queueUrl("url").build();

    private static final Message message = Message.builder().receiptHandle("handle").build();

    @Mock
    private SqsAsyncClient sqsAsyncClient;

    @Mock
    private Supplier<CompletableFuture<?>> resolveMessage;

    @Nested
    clreplaced OnlyConsumeMessage {

        @Test
        void successfulExecutionWillResolveFuture() {
            // arrange
            when(resolveMessage.get()).thenReturn(CompletableFuture.completedFuture(null));
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> CompletableFuture.completedFuture(null));
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(resolveMessage).get();
        }

        @Test
        void failureToProcessMessageWillRejectFuture() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> {
                throw new ExpectedTestException();
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            verify(resolveMessage, never()).get();
        }

        @Test
        void rejectingFutureReturnedFromFutureWillRejectProcessingFuture() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> CompletableFutureUtils.completedExceptionally(new ExpectedTestException()));
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            verify(resolveMessage, never()).get();
        }

        @Test
        void methodThatReturnsNullWillRejectFuture() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> null);
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            verify(resolveMessage, never()).get();
        }
    }

    @Nested
    clreplaced ConsumeMessageWithVisibility {

        @Test
        void visibilityExtenderCanBeUsedToExtendMessageVisibility() {
            // arrange
            when(resolveMessage.get()).thenReturn(CompletableFuture.completedFuture(null));
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, false, (message, visibilityExtender) -> {
                visibilityExtender.extend();
                return CompletableFuture.completedFuture(null);
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(sqsAsyncClient).changeMessageVisibility(ChangeMessageVisibilityRequest.builder().visibilityTimeout(DEFAULT_VISIBILITY_EXTENSION_IN_SECONDS).queueUrl("url").receiptHandle("handle").build());
        }
    }

    @Nested
    clreplaced ConsumeMessageWithAcknowledge {

        @Test
        void successfulExecutionWillResolveFutureButNotResolveMessage() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, (message, acknowledge) -> CompletableFuture.completedFuture(null));
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(resolveMessage, never()).get();
        }

        @Test
        void failureExecutionWillRejectFutureAndNotResolveMessage() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, (message, acknowledge) -> {
                throw new ExpectedTestException();
            });
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompletedExceptionally();
            verify(resolveMessage, never()).get();
        }
    }

    @Nested
    clreplaced ConsumeMessageWithAcknowledgeAndVisibilityExtender {

        @Test
        void successfulExecutionWillResolveFutureButNotResolveMessage() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, (message, acknowledge, visibilityExtender) -> CompletableFuture.completedFuture(null));
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
            verify(resolveMessage, never()).get();
        }
    }

    @Nested
    clreplaced ResolvingMessage {

        @Test
        void exceptionThrownWhenResolvingMessageWillNotRejectProcessingFuture() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> CompletableFuture.completedFuture(null));
            when(resolveMessage.get()).thenThrow(new ExpectedTestException());
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
        }

        @Test
        void resolveMessageRejectedWillNotRejectProcessingFuture() {
            // arrange
            final AsyncLambdaMessageProcessor processor = new AsyncLambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> CompletableFuture.completedFuture(null));
            when(resolveMessage.get()).thenReturn(CompletableFutureUtils.completedExceptionally(new ExpectedTestException()));
            // act
            final CompletableFuture<?> result = processor.processMessage(message, resolveMessage);
            // replacedert
            replacedertThat(result).isCompleted();
        }
    }
}

19 Source : AutoVisibilityExtenderMessageProcessingDecoratorTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced AutoVisibilityExtenderMessageProcessingDecoratorTest {

    private static final QueueProperties QUEUE_PROPERTIES = QueueProperties.builder().queueUrl("url").build();

    @Mock
    SqsAsyncClient sqsAsyncClient;

    List<ChangeMessageVisibilityBatchRequest> changeVisibilityRequests;

    @BeforeEach
    void setUp() {
        changeVisibilityRequests = new ArrayList<>();
    }

    @Test
    void messageThatTakesLongerThanVisibilityTimeoutToProcessWillBeExtended() throws Exception {
        // arrange
        changingVisibilityIsSuccessful();
        final DecoratingMessageProcessor decoratingMessageProcessor = buildProcessor(new AutoVisibilityExtenderMessageProcessingDecoratorProperties() {

            @Override
            public Duration visibilityTimeout() {
                return Duration.ofSeconds(2);
            }

            @Override
            public Duration maxDuration() {
                return Duration.ofSeconds(10);
            }

            @Override
            public Duration bufferDuration() {
                return Duration.ofSeconds(1);
            }
        }, new LambdaMessageProcessor(sqsAsyncClient, QUEUE_PROPERTIES, message -> {
            try {
                Thread.sleep(Duration.ofSeconds(1).plusMillis(200).toMillis());
            } catch (InterruptedException interruptedException) {
                throw new RuntimeException("Unexpected interruption");
            }
        }));
        // act
        final Message message = Message.builder().messageId("a").receiptHandle("aHandle").build();
        decoratingMessageProcessor.processMessage(message, () -> CompletableFuture.completedFuture(null)).get(5, TimeUnit.SECONDS);
        // replacedert
        verifyVisibilityChangedOnce(message);
    }

    @Test
    void messageThatTakesLongerMaximumDurationWillBeInterrupted() throws Exception {
        // arrange
        final DecoratingMessageProcessor decoratingMessageProcessor = buildProcessor(new AutoVisibilityExtenderMessageProcessingDecoratorProperties() {

            @Override
            public Duration visibilityTimeout() {
                return Duration.ofSeconds(99);
            }

            @Override
            public Duration maxDuration() {
                return Duration.ofSeconds(2);
            }

            @Override
            public Duration bufferDuration() {
                return Duration.ofSeconds(1);
            }
        }, new LambdaMessageProcessor(sqsAsyncClient, QUEUE_PROPERTIES, message -> {
            try {
                Thread.sleep(Duration.ofSeconds(4).toMillis());
                throw new RuntimeException("Should have been interrupted!");
            } catch (InterruptedException interruptedException) {
            // do nothing
            }
        }));
        // act
        final Message message = Message.builder().messageId("a").receiptHandle("aHandle").build();
        decoratingMessageProcessor.processMessage(message, () -> CompletableFuture.completedFuture(null)).get(5, TimeUnit.SECONDS);
        // replacedert
        verifyVisibilityNeverChanged(message);
    }

    @Test
    void multipleMessagesCanBeExtendedDuringProcessing() throws InterruptedException, ExecutionException, TimeoutException {
        // arrange
        changingVisibilityIsSuccessful();
        final DecoratingMessageProcessor decoratingMessageProcessor = buildProcessor(new AutoVisibilityExtenderMessageProcessingDecoratorProperties() {

            @Override
            public Duration visibilityTimeout() {
                return Duration.ofSeconds(4);
            }

            @Override
            public Duration maxDuration() {
                return Duration.ofSeconds(5);
            }

            @Override
            public Duration bufferDuration() {
                return Duration.ofSeconds(1);
            }
        }, new LambdaMessageProcessor(sqsAsyncClient, QUEUE_PROPERTIES, message -> {
            try {
                Thread.sleep(Long.MAX_VALUE);
                throw new RuntimeException("Expected it to timeout");
            } catch (InterruptedException interruptedException) {
            // expected
            }
        }));
        final Message firstMessage = Message.builder().messageId("a").receiptHandle("aHandle").build();
        final Message secondMessage = Message.builder().messageId("b").receiptHandle("bHandle").build();
        final Message thirdMessage = Message.builder().messageId("c").receiptHandle("cHandle").build();
        // act
        final CompletableFuture<Void> firstMessageFuture = CompletableFuture.runAsync(() -> decoratingMessageProcessor.processMessage(firstMessage, () -> CompletableFuture.completedFuture(null)));
        Thread.sleep(1000);
        final CompletableFuture<Void> secondMessageFuture = CompletableFuture.runAsync(() -> decoratingMessageProcessor.processMessage(secondMessage, () -> CompletableFuture.completedFuture(null)));
        Thread.sleep(1000);
        final CompletableFuture<Void> thirdMessageFuture = CompletableFuture.runAsync(() -> decoratingMessageProcessor.processMessage(thirdMessage, () -> CompletableFuture.completedFuture(null)));
        final CompletableFuture<?> done = CompletableFutureUtils.allOf(CollectionUtils.immutableListOf(firstMessageFuture, secondMessageFuture, thirdMessageFuture));
        // replacedert
        done.get(20, TimeUnit.SECONDS);
        verifyVisibilityChangedOnce(firstMessage);
        verifyVisibilityChangedOnce(secondMessage);
        verifyVisibilityChangedOnce(thirdMessage);
    }

    @Test
    void messageThatUsesAcknowledgeParameterWillNotExtendAfterAcknowledged() throws Exception {
        // arrange
        final DecoratingMessageProcessor decoratingMessageProcessor = buildProcessor(new AutoVisibilityExtenderMessageProcessingDecoratorProperties() {

            @Override
            public Duration visibilityTimeout() {
                return Duration.ofSeconds(1);
            }

            @Override
            public Duration maxDuration() {
                return Duration.ofSeconds(10);
            }

            @Override
            public Duration bufferDuration() {
                return Duration.ofMillis(500);
            }
        }, new LambdaMessageProcessor(sqsAsyncClient, QUEUE_PROPERTIES, (message, acknowledge) -> {
            acknowledge.acknowledgeSuccessful();
            try {
                Thread.sleep(Duration.ofMillis(1500).toMillis());
            } catch (InterruptedException interruptedException) {
                throw new RuntimeException("Unexpected interruption");
            }
        }));
        // act
        final Message message = Message.builder().messageId("a").receiptHandle("aHandle").build();
        decoratingMessageProcessor.processMessage(message, () -> CompletableFuture.completedFuture(null)).get(5, TimeUnit.SECONDS);
        // replacedert
        verifyVisibilityNeverChanged(message);
    }

    @Test
    void visibilityExtensionThatFailsWillKeepProcessing() throws Exception {
        // arrange
        changingVisibilityIsSuccessful();
        final DecoratingMessageProcessor decoratingMessageProcessor = buildProcessor(new AutoVisibilityExtenderMessageProcessingDecoratorProperties() {

            @Override
            public Duration visibilityTimeout() {
                return Duration.ofSeconds(2);
            }

            @Override
            public Duration maxDuration() {
                return Duration.ofSeconds(10);
            }

            @Override
            public Duration bufferDuration() {
                return Duration.ofSeconds(1);
            }
        }, new LambdaMessageProcessor(sqsAsyncClient, QUEUE_PROPERTIES, message -> {
            try {
                Thread.sleep(Duration.ofSeconds(1).plusMillis(200).toMillis());
            } catch (InterruptedException interruptedException) {
                throw new RuntimeException("Unexpected interruption");
            }
        }));
        // act
        final Message message = Message.builder().messageId("a").receiptHandle("aHandle").build();
        decoratingMessageProcessor.processMessage(message, () -> CompletableFuture.completedFuture(null)).get(5, TimeUnit.SECONDS);
        // replacedert
        verifyVisibilityChangedOnce(message);
    }

    private DecoratingMessageProcessor buildProcessor(final AutoVisibilityExtenderMessageProcessingDecoratorProperties properties, final MessageProcessor delegate) {
        final AutoVisibilityExtenderMessageProcessingDecorator decorator = new AutoVisibilityExtenderMessageProcessingDecorator(sqsAsyncClient, QUEUE_PROPERTIES, properties);
        return new DecoratingMessageProcessor("identifier", QUEUE_PROPERTIES, Collections.singletonList(decorator), delegate);
    }

    private void changingVisibilityIsSuccessful() {
        when(sqsAsyncClient.changeMessageVisibilityBatch(ArgumentMatchers.<Consumer<ChangeMessageVisibilityBatchRequest.Builder>>any())).thenAnswer(invocation -> {
            Consumer<ChangeMessageVisibilityBatchRequest.Builder> builder = invocation.getArgument(0);
            final ChangeMessageVisibilityBatchRequest.Builder requestBuilder = ChangeMessageVisibilityBatchRequest.builder();
            builder.accept(requestBuilder);
            changeVisibilityRequests.add(requestBuilder.build());
            return CompletableFuture.completedFuture(ChangeMessageVisibilityBatchResponse.builder().build());
        });
    }

    private void verifyVisibilityNeverChanged(final Message message) {
        verifyVisibilityChanged(message, 0);
    }

    private void verifyVisibilityChangedOnce(final Message message) {
        verifyVisibilityChanged(message, 1);
    }

    private void verifyVisibilityChanged(final Message message, final int times) {
        final List<String> messagesWithVisibilityExtended = changeVisibilityRequests.stream().flatMap(request -> request.entries().stream()).filter(entry -> entry.id().equals(message.messageId())).map(ChangeMessageVisibilityBatchRequestEntry::id).collect(Collectors.toList());
        replacedertThat(messagesWithVisibilityExtended).hreplacedize(times);
    }
}

19 Source : DefaultVisibilityExtenderTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced DefaultVisibilityExtenderTest {

    private static final QueueProperties QUEUE_PROPERTIES = QueueProperties.builder().queueUrl("queueUrl").build();

    private static final String RECEIPT_HANDLE = "receipt_handle";

    @Mock
    private SqsAsyncClient sqsAsyncClient;

    @Mock
    private CompletableFuture<ChangeMessageVisibilityResponse> changeMessageVisibilityResultFuture;

    private final Message message = Message.builder().receiptHandle(RECEIPT_HANDLE).build();

    private DefaultVisibilityExtender defaultVisibilityExtender;

    @BeforeEach
    void setUp() {
        defaultVisibilityExtender = new DefaultVisibilityExtender(sqsAsyncClient, QUEUE_PROPERTIES, message);
    }

    @Test
    void defaultExtendShouldIncreaseVisibilityByDefaultAmount() {
        // act
        defaultVisibilityExtender.extend();
        // replacedert
        verify(sqsAsyncClient).changeMessageVisibility(ChangeMessageVisibilityRequest.builder().queueUrl("queueUrl").receiptHandle(RECEIPT_HANDLE).visibilityTimeout(DEFAULT_VISIBILITY_EXTENSION_IN_SECONDS).build());
    }

    @Test
    void extendShouldIncreaseVisibilityByAmountSet() {
        // act
        defaultVisibilityExtender.extend(10);
        // replacedert
        verify(sqsAsyncClient).changeMessageVisibility(ChangeMessageVisibilityRequest.builder().queueUrl("queueUrl").receiptHandle(RECEIPT_HANDLE).visibilityTimeout(10).build());
    }

    @Test
    void defaultExtendShouldReturnFutureFromAmazon() {
        // arrange
        when(sqsAsyncClient.changeMessageVisibility(ChangeMessageVisibilityRequest.builder().queueUrl("queueUrl").receiptHandle(RECEIPT_HANDLE).visibilityTimeout(DEFAULT_VISIBILITY_EXTENSION_IN_SECONDS).build())).thenReturn(changeMessageVisibilityResultFuture);
        // act
        final Future<?> extendFuture = defaultVisibilityExtender.extend();
        // replacedert
        replacedertThat(extendFuture).isEqualTo(changeMessageVisibilityResultFuture);
    }

    @Test
    void extendShouldReturnFutureFromAmazon() {
        // arrange
        when(sqsAsyncClient.changeMessageVisibility(ChangeMessageVisibilityRequest.builder().queueUrl("queueUrl").receiptHandle(RECEIPT_HANDLE).visibilityTimeout(10).build())).thenReturn(changeMessageVisibilityResultFuture);
        // act
        final Future<?> extendFuture = defaultVisibilityExtender.extend(10);
        // replacedert
        replacedertThat(extendFuture).isEqualTo(changeMessageVisibilityResultFuture);
    }
}

19 Source : PayloadArgumentResolverTest.java
with MIT License
from JaidenAshmore

@ExtendWith(MockitoExtension.clreplaced)
clreplaced PayloadArgumentResolverTest {

    @Mock
    private PayloadMapper payloadMapper;

    @Mock
    private QueueProperties queueProperties;

    private PayloadArgumentResolver payloadArgumentResolver;

    @BeforeEach
    void setUp() {
        payloadArgumentResolver = new PayloadArgumentResolver(payloadMapper);
    }

    @Test
    void parameterWithNoPayloadAnnotationCannotBeHandled() {
        // arrange
        final MethodParameter numberParameter = getParameter(2);
        // act
        final boolean canHandleParameter = payloadArgumentResolver.canResolveParameter(numberParameter);
        // replacedert
        replacedertThat(canHandleParameter).isFalse();
    }

    @Test
    void parameterWithPayloadAnnotationCanBeHandled() {
        // arrange
        final MethodParameter stringParameter = getParameter(0);
        // act
        final boolean canHandleParameter = payloadArgumentResolver.canResolveParameter(stringParameter);
        // replacedert
        replacedertThat(canHandleParameter).isTrue();
    }

    @Test
    void payloadThatFailsToBeBuiltThrowsArgumentResolutionException() {
        // arrange
        final MethodParameter stringParameter = getParameter(1);
        final Message message = Message.builder().build();
        when(payloadMapper.map(message, Pojo.clreplaced)).thenThrow(new PayloadMappingException("Error"));
        // act
        final ArgumentResolutionException exception = replacedertThrows(ArgumentResolutionException.clreplaced, () -> payloadArgumentResolver.resolveArgumentForParameter(queueProperties, stringParameter, message));
        // replacedert
        replacedertThat(exception.getCause()).isInstanceOf(PayloadMappingException.clreplaced);
    }

    @Test
    void payloadThatIsSuccessfullyBuiltIsReturnedInResolution() {
        final MethodParameter parameter = getParameter(1);
        final Message message = Message.builder().build();
        final Pojo parsedObject = new Pojo("test");
        when(payloadMapper.map(message, Pojo.clreplaced)).thenReturn(parsedObject);
        // act
        final Object argument = payloadArgumentResolver.resolveArgumentForParameter(queueProperties, parameter, message);
        // replacedert
        replacedertThat(argument).isEqualTo(parsedObject);
    }

    @SuppressWarnings({ "unused" })
    public void method(@Payload final String payloadString, @Payload final Pojo payloadPojo, final String parameterWithNoPayloadAnnotation) {
    }

    private MethodParameter getParameter(final int index) {
        try {
            final Method method = PayloadArgumentResolverTest.clreplaced.getMethod("method", String.clreplaced, Pojo.clreplaced, String.clreplaced);
            return DefaultMethodParameter.builder().method(method).parameter(method.getParameters()[index]).parameterIndex(index).build();
        } catch (final NoSuchMethodException noSuchMethodException) {
            throw new RuntimeException(noSuchMethodException);
        }
    }

    @SuppressWarnings({ "WeakerAccess", "unused" })
    public static clreplaced Pojo {

        private final String field;

        public Pojo(final String field) {
            this.field = field;
        }

        public String getField() {
            return field;
        }
    }
}

19 Source : PrefetchingMessageRetriever.java
with MIT License
from JaidenAshmore

/**
 * Message retriever that allows for the prefetching of messages for faster throughput by making sure that there are always messages in a queue locally to be
 * pulled from when one is needed.
 *
 * <p>The way this works is via the usage of the {@link PrefetchingMessageFutureConsumerQueue} that contains an internal queue of desired prefetched messages.
 * This retriever will keep trying to prefetch until this queue is filled or the {@link PrefetchingMessageRetriever#maxPrefetchedMessages} limit is reached.
 * If the number of prefetched messages is below the max but above the desired amount it will block until it is below the desired amount.
 *
 * <p>For the explanation, a {@link PrefetchingMessageRetriever} is built with a min prefetch size of 8, maximum prefetch size of 15 and a max of 10 messages
 * able to be obtained in one call to SQS. The events that occur for this retriever are:
 *
 * <ol>
 *     <li>{@link PrefetchingMessageRetriever} constructor is called</li>
 *     <li>To accommodate this the internal queue is only made to be of size 8.
 *     <li>The {@link #run()}} method is called on a new thread which starts the prefetching of messages from SQS process.</li>
 *     <li>A request is made out to retrieve 10 messages from SQS. This is the limit provided by AWS so this is the maximum messages that can be requested
 *         in a single request</li>
 *     <li>SQS responds with 10 messages.</li>
 *     <li>8 messages are placed into the queue but is blocked waiting for more messages to be placed due to the limit of the queue.  Therefore at this point
 *         the prefetching message retrieval is blocked until messages are consumed.  The other 2 messages will be waiting to be placed into this queue.
 *     <li>When two messages are consumed the rest of that prefetching batch is placed into the queue and the thread blocks until another message is
 *         consumed</li>
 *     <li>As the consumers begin to consume the messages from this retriever the queue begins to shrink in size until there are 7 messages in the queue.</li>
 *     <li>At this point the background thread is free to go out and obtain more messages from SQS and it will attempt to retrieve
 *         8 messages (this is due to the limit of 15 messages at maximum being prefetched)</li>
 *     <li>This process repeats as more messages are consumed and placed onto the queues.</li>
 * </ol>
 *
 * <p>Note that because these messages are being prefetched they could be in the internal queue for a long period and could even remain in the prefetched queue
 * after the visibility timeout for the message has expired. This could cause it to be placed in the dead letter queue or attempted again at a future time.
 */
@Slf4j
public clreplaced PrefetchingMessageRetriever implements MessageRetriever {

    private final SqsAsyncClient sqsAsyncClient;

    private final QueueProperties queueProperties;

    private final PrefetchingMessageRetrieverProperties properties;

    private final PrefetchingMessageFutureConsumerQueue pairConsumerQueue;

    private final int maxPrefetchedMessages;

    public PrefetchingMessageRetriever(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final PrefetchingMessageRetrieverProperties properties) {
        Preconditions.checkNotNull(sqsAsyncClient, "sqsAsyncClient");
        Preconditions.checkNotNull(queueProperties, "queueProperties");
        Preconditions.checkNotNull(properties, "properties");
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.properties = properties;
        this.maxPrefetchedMessages = properties.getMaxPrefetchedMessages();
        final int desiredMinPrefetchedMessages = properties.getDesiredMinPrefetchedMessages();
        Preconditions.checkArgument(maxPrefetchedMessages >= desiredMinPrefetchedMessages, "maxPrefetchedMessages should be greater than or equal to desiredMinPrefetchedMessages");
        Preconditions.checkArgument(desiredMinPrefetchedMessages > 0, "desiredMinPrefetchedMessages must be greater than zero");
        pairConsumerQueue = new PrefetchingMessageFutureConsumerQueue(desiredMinPrefetchedMessages);
    }

    @Override
    public CompletableFuture<Message> retrieveMessage() {
        final CompletableFuture<Message> completableFuture = new CompletableFuture<>();
        pairConsumerQueue.pushCompletableFuture(completableFuture);
        return completableFuture;
    }

    @Override
    public List<Message> run() {
        log.info("Started MessageRetriever");
        final List<Message> listsNotPublished = new LinkedList<>();
        while (!Thread.currentThread().isInterrupted()) {
            try {
                pairConsumerQueue.blockUntilFreeSlotForMessage();
                final List<Message> messages = CompletableFuture.supplyAsync(this::buildReceiveMessageRequest).thenCompose(sqsAsyncClient::receiveMessage).thenApply(ReceiveMessageResponse::messages).get();
                log.debug("Received {} messages", messages.size());
                final Lisreplacederator<Message> messageLisreplacederator = messages.lisreplacederator();
                while (messageLisreplacederator.hasNext()) {
                    final Message message = messageLisreplacederator.next();
                    try {
                        pairConsumerQueue.pushMessage(message);
                    } catch (final InterruptedException interruptedException) {
                        log.debug("Thread interrupted while adding messages into internal queue. Exiting...");
                        listsNotPublished.add(message);
                        messageLisreplacederator.forEachRemaining(listsNotPublished::add);
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            } catch (final InterruptedException exception) {
                log.debug("Thread interrupted while requesting messages. Exiting...");
                break;
            } catch (final ExecutionException | RuntimeException exception) {
                // Supposedly the SqsAsyncClient can get interrupted and this will remove the interrupted status from the thread and then wrap it
                // in it's own version of the interrupted exception...If this happens when the retriever is being shut down it will keep on processing
                // because it does not realise it is being shut down, therefore we have to check for this and quit if necessary
                if (exception instanceof ExecutionException) {
                    final Throwable executionExceptionCause = exception.getCause();
                    if (executionExceptionCause instanceof SdkClientException && executionExceptionCause.getCause() instanceof SdkInterruptedException) {
                        log.debug("Thread interrupted receiving messages");
                        break;
                    }
                }
                log.error("Exception thrown when retrieving messages", exception);
                performBackoff();
            }
        }
        final QueueDrain pairQueue = pairConsumerQueue.drain();
        pairQueue.getFuturesWaitingForMessages().forEach(future -> future.cancel(true));
        return CollectionUtils.immutableListFrom(pairQueue.getMessagesAvailableForProcessing(), listsNotPublished);
    }

    /**
     * Build the request that will download the messages from SQS.
     *
     * @return the request that will be sent to SQS
     */
    private ReceiveMessageRequest buildReceiveMessageRequest() {
        final int numberOfPrefetchSlotsLeft = maxPrefetchedMessages - pairConsumerQueue.getNumberOfBatchedMessages();
        final int numberOfMessagesToObtain = Math.min(AwsConstants.MAX_NUMBER_OF_MESSAGES_FROM_SQS, numberOfPrefetchSlotsLeft);
        log.debug("Retrieving {} messages asynchronously", numberOfMessagesToObtain);
        final ReceiveMessageRequest.Builder requestBuilder = ReceiveMessageRequest.builder().queueUrl(queueProperties.getQueueUrl()).attributeNames(QueueAttributeName.ALL).messageAttributeNames(QueueAttributeName.ALL.toString()).waitTimeSeconds(MAX_SQS_RECEIVE_WAIT_TIME_IN_SECONDS).maxNumberOfMessages(numberOfMessagesToObtain);
        final Duration visibilityTimeout = properties.getMessageVisibilityTimeout();
        if (visibilityTimeout != null && visibilityTimeout.getSeconds() > 0) {
            requestBuilder.visibilityTimeout((int) visibilityTimeout.getSeconds());
        }
        return requestBuilder.build();
    }

    private void performBackoff() {
        try {
            final Duration errorBackoffTime = safelyGetPositiveOrZeroDuration("errorBackoffTime", properties::getErrorBackoffTime, DEFAULT_ERROR_BACKOFF_TIMEOUT);
            log.debug("Backing off for {}ms", errorBackoffTime.toMillis());
            Thread.sleep(errorBackoffTime.toMillis());
        } catch (final InterruptedException interruptedException) {
            log.debug("Thread interrupted during backoff period");
            Thread.currentThread().interrupt();
        }
    }
}

19 Source : BatchingMessageRetriever.java
with MIT License
from JaidenAshmore

/**
 * This implementation of the {@link MessageRetriever} will group requests for messages into batches to reduce the number of times that messages are requested
 * from the SQS queue.
 *
 * <p>The advantage of this retriever is that the overall number of times that the SQS queue is queried are reduced but the overall throughput is reduced
 * because threads are waiting for the batch to be let through to get messages.
 */
@Slf4j
public clreplaced BatchingMessageRetriever implements MessageRetriever {

    private final QueueProperties queueProperties;

    private final SqsAsyncClient sqsAsyncClient;

    private final BatchingMessageRetrieverProperties properties;

    private final LinkedBlockingDeque<CompletableFuture<Message>> futuresWaitingForMessages;

    public BatchingMessageRetriever(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient, final BatchingMessageRetrieverProperties properties) {
        this.queueProperties = queueProperties;
        this.sqsAsyncClient = sqsAsyncClient;
        this.properties = properties;
        this.futuresWaitingForMessages = new LinkedBlockingDeque<>();
    }

    @Override
    public CompletableFuture<Message> retrieveMessage() {
        final CompletableFuture<Message> messageCompletableFuture = new CompletableFuture<>();
        futuresWaitingForMessages.add(messageCompletableFuture);
        return messageCompletableFuture;
    }

    @Override
    public List<Message> run() {
        log.info("Started MessageRetriever");
        while (!Thread.currentThread().isInterrupted()) {
            final Queue<CompletableFuture<Message>> messagesToObtain;
            try {
                messagesToObtain = obtainRequestForMessagesBatch();
            } catch (final InterruptedException interruptedException) {
                log.debug("Thread interrupted waiting for batch");
                break;
            }
            log.debug("Requesting {} messages", messagesToObtain.size());
            if (messagesToObtain.isEmpty()) {
                continue;
            }
            final List<Message> messages;
            try {
                messages = CompletableFuture.supplyAsync(messagesToObtain::size).thenApply(this::buildReceiveMessageRequest).thenComposeAsync(sqsAsyncClient::receiveMessage).thenApply(ReceiveMessageResponse::messages).get();
            } catch (final RuntimeException | ExecutionException exception) {
                // Supposedly the SqsAsyncClient can get interrupted and this will remove the interrupted status from the thread and then wrap it
                // in it's own version of the interrupted exception...If this happens when the retriever is being shut down it will keep on processing
                // because it does not realise it is being shut down, therefore we have to check for this and quit if necessary
                if (exception instanceof ExecutionException) {
                    final Throwable executionExceptionCause = exception.getCause();
                    if (executionExceptionCause instanceof SdkClientException) {
                        if (executionExceptionCause.getCause() instanceof SdkInterruptedException) {
                            log.debug("Thread interrupted while receiving messages");
                            break;
                        }
                    }
                }
                log.error("Error request messages", exception);
                // If there was an exception receiving messages we need to put these back into the queue
                futuresWaitingForMessages.addAll(messagesToObtain);
                performBackoff();
                continue;
            } catch (final InterruptedException interruptedException) {
                log.debug("Thread interrupted while waiting for batch of messages");
                break;
            }
            log.debug("Downloaded {} messages", messages.size());
            if (messages.size() > messagesToObtain.size()) {
                log.error("More messages were downloaded than requested, this shouldn't happen");
            }
            for (final Message message : messages) {
                final CompletableFuture<Message> completableFuture = messagesToObtain.poll();
                if (completableFuture != null) {
                    completableFuture.complete(message);
                }
            }
            // Any threads that weren't completed send back for processing again
            futuresWaitingForMessages.addAll(messagesToObtain);
        }
        futuresWaitingForMessages.forEach(future -> future.cancel(true));
        log.info("MessageRetriever has been successfully stopped");
        return Collections.emptyList();
    }

    private Queue<CompletableFuture<Message>> obtainRequestForMessagesBatch() throws InterruptedException {
        final Queue<CompletableFuture<Message>> messagesToObtain = new LinkedList<>();
        final int batchSize = getBatchSize();
        final Duration pollingPeriod = safelyGetPositiveOrZeroDuration("batchingPeriod", properties::getBatchingPeriod, Duration.ZERO);
        if (log.isDebugEnabled()) {
            log.debug("Waiting for {} requests for messages within {}ms. Total currently waiting: {}", batchSize, pollingPeriod.toMillis(), futuresWaitingForMessages.size());
        }
        QueueUtils.drain(futuresWaitingForMessages, messagesToObtain, batchSize, pollingPeriod);
        return messagesToObtain;
    }

    private void performBackoff() {
        try {
            final Duration errorBackoffTime = safelyGetPositiveOrZeroDuration("errorBackoffTime", properties::getErrorBackoffTime, DEFAULT_BACKOFF_TIME);
            log.debug("Backing off for {}ms", errorBackoffTime.toMillis());
            Thread.sleep(errorBackoffTime.toMillis());
        } catch (final InterruptedException interruptedException) {
            log.debug("Thread interrupted during backoff period");
            Thread.currentThread().interrupt();
        }
    }

    /**
     * Safely get the total number of threads requiring messages before it sends a batch request for messages.
     *
     * @return the total number of threads for the batching trigger
     */
    private int getBatchSize() {
        final int batchSize = PropertyUtils.safelyGetIntegerValue("batchSize", properties::getBatchSize, DEFAULT_BATCHING_TRIGGER);
        if (batchSize < 0) {
            return 0;
        }
        return Math.min(batchSize, AwsConstants.MAX_NUMBER_OF_MESSAGES_FROM_SQS);
    }

    /**
     * Build the request that will download the messages from SQS.
     *
     * @param numberOfMessagesToObtain the maximum number of messages to obtain
     * @return the request that will be sent to SQS
     */
    private ReceiveMessageRequest buildReceiveMessageRequest(final int numberOfMessagesToObtain) {
        final ReceiveMessageRequest.Builder requestBuilder = ReceiveMessageRequest.builder().queueUrl(queueProperties.getQueueUrl()).attributeNames(QueueAttributeName.ALL).messageAttributeNames(QueueAttributeName.ALL.toString()).maxNumberOfMessages(numberOfMessagesToObtain).waitTimeSeconds(MAX_SQS_RECEIVE_WAIT_TIME_IN_SECONDS);
        try {
            final Duration visibilityTimeout = properties.getMessageVisibilityTimeout();
            if (visibilityTimeout != null && visibilityTimeout.getSeconds() > 0) {
                requestBuilder.visibilityTimeout((int) visibilityTimeout.getSeconds());
            }
        } catch (final RuntimeException exception) {
            log.error("Error getting visibility timeout, none will be supplied in request", exception);
        }
        return requestBuilder.build();
    }
}

19 Source : BatchingMessageResolver.java
with MIT License
from JaidenAshmore

/**
 * {@link MessageResolver} that will batch the deletions of messages into a group to reduce the amount of messages that are being sent to SQS queue.
 *
 * <p>This uses a {@link BlockingQueue} to store all of the messages that need to be resolved and once the timeout provided by
 * {@link BatchingMessageResolverProperties#getBufferingTime()} is reached or the number of messages goes above
 * {@link BatchingMessageResolverProperties#getBufferingSizeLimit()}, the messages are sent out to be deleted.
 */
@Slf4j
@ThreadSafe
public clreplaced BatchingMessageResolver implements MessageResolver {

    private final QueueProperties queueProperties;

    private final SqsAsyncClient sqsAsyncClient;

    private final BatchingMessageResolverProperties properties;

    private final BlockingQueue<MessageResolutionBean> messagesToBeResolved;

    /**
     * Builds a {@link BatchingMessageResolver} that will perform a deletion of a message every time a single message is received.
     *
     * @param queueProperties details about the queue that the arguments will be resolved for
     * @param sqsAsyncClient  the client for connecting to the SQS queue
     */
    public BatchingMessageResolver(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient) {
        this(queueProperties, sqsAsyncClient, StaticBatchingMessageResolverProperties.builder().bufferingSizeLimit(1).bufferingTime(Duration.ofHours(1)).build());
    }

    /**
     * Builds a {@link BatchingMessageResolver} with the provided properties.
     *
     * @param queueProperties details about the queue that the arguments will be resolved for
     * @param sqsAsyncClient  the client for connecting to the SQS queue
     * @param properties      configuration properties for this resolver
     */
    public BatchingMessageResolver(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient, final BatchingMessageResolverProperties properties) {
        this.queueProperties = queueProperties;
        this.sqsAsyncClient = sqsAsyncClient;
        this.properties = properties;
        this.messagesToBeResolved = new LinkedBlockingQueue<>();
    }

    @Override
    public CompletableFuture<?> resolveMessage(final Message message) {
        final CompletableFuture<Object> completableFuture = new CompletableFuture<>();
        messagesToBeResolved.add(new MessageResolutionBean(message, completableFuture));
        return completableFuture;
    }

    @Override
    public void run() {
        log.info("Started MessageResolver background thread");
        boolean continueProcessing = true;
        final ExecutorService executorService = buildExecutorServiceForSendingBatchDeletion();
        // all of the batches currently being sent so that they can be waited on during shutdown
        final List<CompletableFuture<?>> batchesBeingPublished = new ArrayList<>();
        while (!Thread.currentThread().isInterrupted() && continueProcessing) {
            final List<MessageResolutionBean> batchOfMessagesToResolve = new LinkedList<>();
            try {
                final int batchSize = getBatchSize();
                final Duration bufferingTime = properties.getBufferingTime();
                log.trace("Waiting {}ms for {} messages to be submitted for deletion", bufferingTime.toMillis(), batchSize);
                QueueUtils.drain(messagesToBeResolved, batchOfMessagesToResolve, batchSize, bufferingTime);
            } catch (final InterruptedException interruptedException) {
                log.info("Shutting down MessageResolver");
                // Do nothing, we still want to send the current batch of messages
                continueProcessing = false;
            }
            if (!batchOfMessagesToResolve.isEmpty()) {
                log.debug("Sending batch deletion for {} messages", batchOfMessagesToResolve.size());
                final CompletableFuture<?> completableFuture = submitMessageDeletionBatch(batchOfMessagesToResolve, executorService);
                batchesBeingPublished.add(completableFuture);
                completableFuture.whenComplete((response, throwable) -> batchesBeingPublished.remove(completableFuture));
            }
        }
        try {
            log.debug("Waiting for {} batches to complete", batchesBeingPublished.size());
            CompletableFuture.allOf(batchesBeingPublished.toArray(new CompletableFuture<?>[0])).get();
            executorService.shutdownNow();
            log.info("MessageResolver has been successfully stopped");
        } catch (final InterruptedException interruptedException) {
            log.warn("Thread interrupted while waiting for message batches to be completed");
            Thread.currentThread().interrupt();
        } catch (final ExecutionException executionException) {
            log.error("Error waiting for all message batches to be published", executionException.getCause());
        }
    }

    /**
     * Build the {@link ExecutorService} to send the batch message delete messages.
     *
     * <p>This is needed because when a thread is interrupted while using the {@link SqsAsyncClient} a {@link SdkInterruptedException} is thrown which is
     * ultimately not what we want. We instead want to know that this has been done and wait for the delete requests to eventually finish. Therefore,
     * running it on extra threads provides this extra safety.
     *
     * <p>The extra service also allows for multiple batches to be sent concurrently.
     *
     * @return the service for running message deletion on a separate thread
     */
    private ExecutorService buildExecutorServiceForSendingBatchDeletion() {
        return Executors.newCachedThreadPool(ThreadUtils.multiNamedThreadFactory(Thread.currentThread().getName() + "-batch-delete"));
    }

    /**
     * Safely get the batch size for the number of messages to resolve as AWS has a limit for how many messages can be sent at once.
     *
     * @return the number of messages that should be resolved in a single batch
     */
    private int getBatchSize() {
        final int bufferingSizeLimit = properties.getBufferingSizeLimit();
        if (bufferingSizeLimit < 1) {
            return 1;
        }
        return Math.min(bufferingSizeLimit, MAX_NUMBER_OF_MESSAGES_IN_BATCH);
    }

    /**
     * Submit the batch of messages to be resolved asynchronously.
     *
     * <p>When the batch is completed successfully (or unsuccessfully), the futures for each message will be completed.
     *
     * @param batchOfMessagesToResolve the messages to resolve
     */
    private CompletableFuture<?> submitMessageDeletionBatch(final List<MessageResolutionBean> batchOfMessagesToResolve, final ExecutorService executorService) {
        final Map<String, CompletableFuture<Object>> messageCompletableFutures = batchOfMessagesToResolve.stream().map(bean -> new AbstractMap.SimpleImmutableEntry<>(bean.getMessage().messageId(), bean.getCompletableFuture())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        return CompletableFuture.supplyAsync(() -> buildBatchDeleteMessageRequest(batchOfMessagesToResolve)).thenComposeAsync(sqsAsyncClient::deleteMessageBatch, executorService).whenComplete((response, exception) -> {
            if (exception != null) {
                log.error("Error deleting messages", exception);
                messageCompletableFutures.values().forEach(completableFuture -> completableFuture.completeExceptionally(exception));
                return;
            }
            log.debug("{} messages successfully deleted, {} failed", response.successful().size(), response.failed().size());
            response.successful().stream().map(entry -> messageCompletableFutures.remove(entry.id())).forEach(completableFuture -> completableFuture.complete("completed"));
            response.failed().forEach(entry -> {
                final CompletableFuture<?> completableFuture = messageCompletableFutures.remove(entry.id());
                completableFuture.completeExceptionally(new RuntimeException(entry.message()));
            });
            if (!messageCompletableFutures.isEmpty()) {
                log.error("{} messages were not handled in the deletion. This could be a bug in the AWS SDK", messageCompletableFutures.size());
                messageCompletableFutures.values().forEach(completableFuture -> completableFuture.completeExceptionally(new RuntimeException("Message not handled by batch delete. This should not happen")));
            }
        });
    }

    private DeleteMessageBatchRequest buildBatchDeleteMessageRequest(final List<MessageResolutionBean> batchOfMessagesToResolve) {
        return DeleteMessageBatchRequest.builder().queueUrl(queueProperties.getQueueUrl()).entries(batchOfMessagesToResolve.stream().map(MessageResolutionBean::getMessage).map(messageToDelete -> DeleteMessageBatchRequestEntry.builder().id(messageToDelete.messageId()).receiptHandle(messageToDelete.receiptHandle()).build()).collect(Collectors.toSet())).build();
    }

    /**
     * Internal bean used for storing the message to be resolved in the internal queue.
     */
    @Value
    @AllArgsConstructor
    private static clreplaced MessageResolutionBean {

        /**
         * The message to be resolved.
         */
        Message message;

        /**
         * The future that should be resolved when the message is successfully or unsuccessfully deleted.
         */
        CompletableFuture<Object> completableFuture;
    }
}

19 Source : LambdaMessageProcessor.java
with MIT License
from JaidenAshmore

/**
 * {@link MessageProcessor} that takes a lambda/function for synchronous processing of a message.
 */
@Slf4j
public clreplaced LambdaMessageProcessor implements MessageProcessor {

    private final SqsAsyncClient sqsAsyncClient;

    private final QueueProperties queueProperties;

    private final boolean usesAcknowledgeParameter;

    private final MessageProcessingFunction messageProcessingFunction;

    /**
     * Constructor.
     *
     * @param sqsAsyncClient   the client to communicate with SQS
     * @param queueProperties  the properties of the queue
     * @param messageProcessor the function to consume a message and return the future
     */
    public LambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final Consumer<Message> messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = false;
        this.messageProcessingFunction = (message, acknowledge, visibilityExtender) -> messageProcessor.accept(message);
    }

    /**
     * Constructor.
     *
     * @param sqsAsyncClient   the client to communicate with SQS
     * @param queueProperties  the properties of the queue
     * @param messageProcessor the function to consume a message and acknowledge
     */
    public LambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final BiConsumer<Message, Acknowledge> messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = true;
        this.messageProcessingFunction = (message, acknowledge, visibilityExtender) -> messageProcessor.accept(message, acknowledge);
    }

    /**
     * Constructor.
     *
     * <p>As Java generics has type erasure and will convert <code>BiFunction<A, B, C></code> to <code>BiFunction</code> we need to change
     * the type signature to distinguish the function that consumes a message and an acknowledge compared to the function that consumes a message
     * and a visibility extender. As the visibility extender use case seems less common, this one has the unused parameter.
     *
     * @param sqsAsyncClient        the client to communicate with SQS
     * @param queueProperties       the properties of the queue
     * @param ignoredForTypeErasure field needed due to type erasure
     * @param messageProcessor      the function to consume a message and visibility extender and return the future
     */
    public LambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, @SuppressWarnings("unused") final boolean ignoredForTypeErasure, final BiConsumer<Message, VisibilityExtender> messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = false;
        this.messageProcessingFunction = (message, acknowledge, visibilityExtender) -> messageProcessor.accept(message, visibilityExtender);
    }

    /**
     * Constructor.
     *
     * @param sqsAsyncClient   the client to communicate with SQS
     * @param queueProperties  the properties of the queue
     * @param messageProcessor the function to consume a message, acknowledge and visibility extender and return the future
     */
    public LambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final MessageProcessingFunction messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = true;
        this.messageProcessingFunction = messageProcessor;
    }

    @Override
    public CompletableFuture<?> processMessage(Message message, Supplier<CompletableFuture<?>> resolveMessageCallback) {
        final Acknowledge acknowledge = resolveMessageCallback::get;
        final VisibilityExtender visibilityExtender = new DefaultVisibilityExtender(sqsAsyncClient, queueProperties, message);
        try {
            messageProcessingFunction.processMessage(message, acknowledge, visibilityExtender);
        } catch (final MessageProcessingException messageProcessingException) {
            return CompletableFutureUtils.completedExceptionally(messageProcessingException);
        } catch (final RuntimeException runtimeException) {
            return CompletableFutureUtils.completedExceptionally(new MessageProcessingException(runtimeException));
        }
        if (usesAcknowledgeParameter) {
            return CompletableFuture.completedFuture(null);
        }
        return CompletableFuture.completedFuture(null).thenAccept(ignored -> {
            try {
                resolveMessageCallback.get().handle((i, throwable) -> {
                    if (throwable != null) {
                        log.error("Error resolving successfully processed message", throwable);
                    }
                    return null;
                });
            } catch (final RuntimeException runtimeException) {
                log.error("Failed to trigger message resolving", runtimeException);
            }
        });
    }

    /**
     * Represents a message processing function that consumes the {@link Message}, {@link Acknowledge} and {@link VisibilityExtender}.
     */
    @FunctionalInterface
    public interface MessageProcessingFunction {

        void processMessage(Message message, Acknowledge acknowledge, VisibilityExtender visibilityExtender);
    }
}

19 Source : DecoratingMessageProcessor.java
with MIT License
from JaidenAshmore

/**
 * {@link MessageProcessor} that will decorate the processing of the message using the supplied {@link MessageProcessingDecorator}s.
 */
@Slf4j
public clreplaced DecoratingMessageProcessor implements MessageProcessor {

    private final String listenerIdentifier;

    private final QueueProperties queueProperties;

    private final List<MessageProcessingDecorator> decorators;

    private final MessageProcessor delegate;

    public DecoratingMessageProcessor(final String listenerIdentifier, final QueueProperties queueProperties, final List<MessageProcessingDecorator> decorators, final MessageProcessor delegate) {
        this.listenerIdentifier = listenerIdentifier;
        this.queueProperties = queueProperties;
        this.decorators = decorators;
        this.delegate = delegate;
    }

    @Override
    public CompletableFuture<?> processMessage(final Message message, final Supplier<CompletableFuture<?>> resolveMessageCallback) throws MessageProcessingException {
        final MessageProcessingContext context = MessageProcessingContext.builder().listenerIdentifier(listenerIdentifier).queueProperties(queueProperties).attributes(new HashMap<>()).build();
        decorators.forEach(decorator -> {
            try {
                decorator.onPreMessageProcessing(context, message);
            } catch (RuntimeException runtimeException) {
                throw new MessageProcessingException(runtimeException);
            }
        });
        try {
            final Supplier<CompletableFuture<?>> wrappedResolveMessageCallback = () -> {
                safelyRun(decorators, decorator -> decorator.onMessageResolve(context, message));
                return resolveMessageCallback.get().whenComplete((returnValue, throwable) -> {
                    if (throwable != null) {
                        safelyRun(decorators, decorator -> decorator.onMessageResolvedFailure(context, message, throwable));
                    } else {
                        safelyRun(decorators, decorator -> decorator.onMessageResolvedSuccess(context, message));
                    }
                });
            };
            return delegate.processMessage(message, wrappedResolveMessageCallback).whenComplete((returnValue, throwable) -> {
                if (throwable != null) {
                    safelyRun(decorators, decorator -> decorator.onMessageProcessingFailure(context, message, throwable));
                } else {
                    safelyRun(decorators, decorator -> decorator.onMessageProcessingSuccess(context, message, returnValue));
                }
            });
        } catch (RuntimeException runtimeException) {
            safelyRun(decorators, decorator -> decorator.onMessageProcessingFailure(context, message, runtimeException));
            throw runtimeException;
        } finally {
            safelyRun(decorators, decorator -> decorator.onMessageProcessingThreadComplete(context, message));
        }
    }

    /**
     * Used to run the {@link MessageProcessingDecorator} methods for each of the decorators, completing all regardless of whether a previous decorator
     * failed.
     *
     * @param messageProcessingDecorators the decorators to consume
     * @param decoratorConsumer           the consumer method that would be used to run one of the decorator methods
     */
    private void safelyRun(final List<MessageProcessingDecorator> messageProcessingDecorators, final Consumer<MessageProcessingDecorator> decoratorConsumer) {
        messageProcessingDecorators.forEach(decorator -> {
            try {
                decoratorConsumer.accept(decorator);
            } catch (RuntimeException runtimeException) {
                log.error("Error processing decorator: " + decorator.getClreplaced().getSimpleName(), runtimeException);
            }
        });
    }
}

19 Source : AsyncLambdaMessageProcessor.java
with MIT License
from JaidenAshmore

/**
 * {@link MessageProcessor} that takes a lambda/function for asynchronous processing of a message.
 */
@Slf4j
public clreplaced AsyncLambdaMessageProcessor implements MessageProcessor {

    private final SqsAsyncClient sqsAsyncClient;

    private final QueueProperties queueProperties;

    private final MessageProcessingFunction messageProcessingFunction;

    private final boolean usesAcknowledgeParameter;

    /**
     * Constructor.
     *
     * @param sqsAsyncClient   the client to communicate with SQS
     * @param queueProperties  the properties of the queue
     * @param messageProcessor the function to consume a message and return the future
     */
    public AsyncLambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final Function<Message, CompletableFuture<?>> messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = false;
        this.messageProcessingFunction = (message, acknowledge, visibilityExtender) -> messageProcessor.apply(message);
    }

    /**
     * Constructor.
     *
     * @param sqsAsyncClient   the client to communicate with SQS
     * @param queueProperties  the properties of the queue
     * @param messageProcessor the function to consume a message and acknowledge and return the future
     */
    public AsyncLambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final BiFunction<Message, Acknowledge, CompletableFuture<?>> messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = true;
        this.messageProcessingFunction = (message, acknowledge, visibilityExtender) -> messageProcessor.apply(message, acknowledge);
    }

    /**
     * Constructor.
     *
     * @param sqsAsyncClient   the client to communicate with SQS
     * @param queueProperties  the properties of the queue
     * @param messageProcessor the function to consume a message, acknowledge and visibility extender and return the future
     */
    public AsyncLambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final MessageProcessingFunction messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = true;
        this.messageProcessingFunction = messageProcessor;
    }

    /**
     * Constructor.
     *
     * <p>As Java generics has type erasure and will convert <code>BiFunction<A, B, C></code> to <code>BiFunction</code> we need to change
     * the type signature to distinguish the function that consumes a message and an acknowledge compared to the function that consumes a message
     * and a visibility extender. As the visibility extender use case seems less common, this one has the unused parameter.
     *
     * @param sqsAsyncClient        the client to communicate with SQS
     * @param queueProperties       the properties of the queue
     * @param ignoredForTypeErasure field needed due to type erasure
     * @param messageProcessor      the function to consume a message and visibility extender and return the future
     */
    public AsyncLambdaMessageProcessor(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, @SuppressWarnings("unused") final boolean ignoredForTypeErasure, final BiFunction<Message, VisibilityExtender, CompletableFuture<?>> messageProcessor) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.usesAcknowledgeParameter = false;
        this.messageProcessingFunction = (message, acknowledge, visibilityExtender) -> messageProcessor.apply(message, visibilityExtender);
    }

    @Override
    public CompletableFuture<?> processMessage(Message message, Supplier<CompletableFuture<?>> resolveMessageCallback) {
        final Acknowledge acknowledge = resolveMessageCallback::get;
        final VisibilityExtender visibilityExtender = new DefaultVisibilityExtender(sqsAsyncClient, queueProperties, message);
        final CompletableFuture<?> result;
        try {
            result = messageProcessingFunction.processMessage(message, acknowledge, visibilityExtender);
        } catch (RuntimeException runtimeException) {
            return CompletableFutureUtils.completedExceptionally(new MessageProcessingException(runtimeException));
        }
        if (result == null) {
            return CompletableFutureUtils.completedExceptionally(new MessageProcessingException("Method returns CompletableFuture but null was returned"));
        }
        if (usesAcknowledgeParameter) {
            return result;
        }
        final Runnable resolveCallbackLoggingErrorsOnly = () -> {
            try {
                resolveMessageCallback.get().handle((i, throwable) -> {
                    if (throwable != null) {
                        log.error("Error resolving successfully processed message", throwable);
                    }
                    return null;
                });
            } catch (RuntimeException runtimeException) {
                log.error("Failed to trigger message resolving", runtimeException);
            }
        };
        return result.thenAccept(ignored -> resolveCallbackLoggingErrorsOnly.run());
    }

    /**
     * Represents a message processing function that consumes the {@link Message}, {@link Acknowledge} and {@link VisibilityExtender}.
     */
    @FunctionalInterface
    public interface MessageProcessingFunction {

        CompletableFuture<?> processMessage(Message message, Acknowledge acknowledge, VisibilityExtender visibilityExtender);
    }
}

19 Source : AutoVisibilityExtenderMessageProcessingDecorator.java
with MIT License
from JaidenAshmore

/**
 * {@link MessageProcessingDecorator} that will continually extend the visibility of the message while it is being processed.
 *
 * <p>No effort is made to guarantee that a message is successfully extended and therefore if the request fails or partially fails (some messages are not
 * extended) it will not re-attempt to extend them and just replacedume that they preplaceded. Therefore if you desire a higher certainty that the visibility
 * extension will succeed you can configure the {@link AutoVisibilityExtenderMessageProcessingDecoratorProperties#bufferDuration()} to be a higher value.
 * For example, you could have the {@link AutoVisibilityExtenderMessageProcessingDecoratorProperties#visibilityTimeout()} to be 30 seconds but the
 * {@link AutoVisibilityExtenderMessageProcessingDecoratorProperties#bufferDuration()} to be 20 seconds and therefore you will have 3 attempts to successfully
 * extend the message.
 *
 * <p>Note that this only works with synchronous implementations of the message listener, e.g. functions that are executed using the
 * {@link com.jashmore.sqs.processor.LambdaMessageProcessor} or the {@link com.jashmore.sqs.processor.CoreMessageProcessor} where the function does not
 * return a {@link CompletableFuture}. This is because it is not easy to interrupt the processing of a message if it has been placed onto a different
 * thread to process.
 *
 * <p>This {@link MessageProcessingDecorator} is thread safe and will work safely when multiple messages are all being processed at once.
 *
 * @see AutoVisibilityExtenderMessageProcessingDecoratorProperties for configuration options
 */
@ThreadSafe
public clreplaced AutoVisibilityExtenderMessageProcessingDecorator implements MessageProcessingDecorator {

    private static final Logger log = LoggerFactory.getLogger(AutoVisibilityExtenderMessageProcessingDecorator.clreplaced);

    private final SqsAsyncClient sqsAsyncClient;

    private final QueueProperties queueProperties;

    private final AutoVisibilityExtenderMessageProcessingDecoratorProperties decoratorProperties;

    private final Map<Message, MessageProcessingState> currentMessagesProcessing;

    private final Object waitingLock = new Object();

    public AutoVisibilityExtenderMessageProcessingDecorator(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final AutoVisibilityExtenderMessageProcessingDecoratorProperties decoratorProperties) {
        this.sqsAsyncClient = sqsAsyncClient;
        this.queueProperties = queueProperties;
        this.decoratorProperties = decoratorProperties;
        this.currentMessagesProcessing = new HashMap<>();
    }

    @Override
    public void onPreMessageProcessing(final MessageProcessingContext context, final Message message) {
        synchronized (waitingLock) {
            final Instant timeNow = Instant.now();
            log.debug("Registering message {} with visibility auto extender", message.messageId());
            currentMessagesProcessing.put(message, ImmutableMessageProcessingState.builder().thread(Thread.currentThread()).startTime(timeNow).nextVisibilityExtensionTime(nextExtensionTime(timeNow, message, decoratorProperties.bufferDuration())).build());
            if (currentMessagesProcessing.size() == 1) {
                CompletableFuture.runAsync(this::performBackgroundThread, Executors.newSingleThreadExecutor(ThreadUtils.singleNamedThreadFactory(context.getListenerIdentifier() + "-auto-visibility-extender"))).whenComplete((ignored, throwable) -> {
                    if (throwable != null) {
                        log.error("Unexpected error with visibility timeout extender", throwable);
                    }
                });
            }
            // We need to notify the background thread to recalculate the updated time in case it has configured this message to have a smaller visibility
            // timeout then the current wait time
            waitingLock.notify();
        }
    }

    @Override
    public void onMessageProcessingThreadComplete(final MessageProcessingContext context, final Message message) {
        removeMessageFromAutoVisibilityExtender(message);
    }

    @Override
    public void onMessageResolve(MessageProcessingContext context, Message message) {
        // Needed in case the message listener is manually acknowledging the message
        removeMessageFromAutoVisibilityExtender(message);
    }

    private void removeMessageFromAutoVisibilityExtender(final Message message) {
        synchronized (waitingLock) {
            final MessageProcessingState valueStored = currentMessagesProcessing.remove(message);
            // Makes sure we only do this once for the message
            if (valueStored != null) {
                decoratorProperties.messageDoneProcessing(message);
                waitingLock.notify();
            }
        }
    }

    private void performBackgroundThread() {
        log.debug("Starting background thread for auto visibility extender");
        synchronized (waitingLock) {
            while (!currentMessagesProcessing.isEmpty()) {
                final Instant timeNow = Instant.now();
                final Duration maxDuration = decoratorProperties.maxDuration();
                final Duration bufferDuration = decoratorProperties.bufferDuration();
                interruptLongRunningThreads(timeNow, maxDuration);
                extendThreadsWithMoreTime(timeNow, bufferDuration);
                try {
                    waitUntilNexreplacederation(maxDuration);
                } catch (final InterruptedException interruptedException) {
                    break;
                }
            }
        }
        log.debug("Finished background thread for auto visibility extender");
    }

    private void interruptLongRunningThreads(final Instant timeNow, final Duration maxDuration) {
        final Map<Message, MessageProcessingState> messagesToInterrupt = currentMessagesProcessing.entrySet().stream().filter(messageStateEntry -> timeNow.compareTo(messageStateEntry.getValue().startTime().plus(maxDuration)) >= 0).collect(CollectionUtils.pairsToMap());
        messagesToInterrupt.forEach((message, state) -> {
            log.info("Interrupting message processing thread due to exceeded time for message {}", message.messageId());
            state.thread().interrupt();
            currentMessagesProcessing.remove(message);
        });
    }

    /**
     * For each message that has hit the visibility timeout extension time, attempt to extend the visibility.
     *
     * <p>This method does not wait for the response from the visibility timeout extension and just replacedumes that it works.
     *
     * @param timeNow the time that this iteration started at
     * @param bufferDuration the amount of buffer time for the next visibility timeout extension
     */
    private void extendThreadsWithMoreTime(final Instant timeNow, final Duration bufferDuration) {
        final Map<Message, MessageProcessingState> messagesToExtend = currentMessagesProcessing.entrySet().stream().filter(messageStateEntry -> timeNow.compareTo(messageStateEntry.getValue().nextVisibilityExtensionTime()) >= 0).collect(CollectionUtils.pairsToMap());
        List<Message> messageBatch = new ArrayList<>(AwsConstants.MAX_NUMBER_OF_MESSAGES_IN_BATCH);
        for (final Map.Entry<Message, MessageProcessingState> stateEntry : messagesToExtend.entrySet()) {
            final Message message = stateEntry.getKey();
            final MessageProcessingState state = stateEntry.getValue();
            log.info("Automatically extending visibility timeout of message {}", message.messageId());
            messageBatch.add(message);
            if (messageBatch.size() == AwsConstants.MAX_NUMBER_OF_MESSAGES_IN_BATCH) {
                extendMessageBatch(messageBatch);
                messageBatch.clear();
            }
            currentMessagesProcessing.put(message, ImmutableMessageProcessingState.builder().from(state).nextVisibilityExtensionTime(timeNow.plus(decoratorProperties.visibilityTimeout(message).minus(bufferDuration))).build());
        }
        if (!messageBatch.isEmpty()) {
            extendMessageBatch(messageBatch);
        }
    }

    private void extendMessageBatch(final List<Message> messageBatch) {
        sqsAsyncClient.changeMessageVisibilityBatch(builder -> builder.queueUrl(queueProperties.getQueueUrl()).entries(messageBatch.stream().map(message -> ChangeMessageVisibilityBatchRequestEntry.builder().id(message.messageId()).receiptHandle(message.receiptHandle()).visibilityTimeout((int) decoratorProperties.visibilityTimeout(message).getSeconds()).build()).collect(Collectors.toList()))).whenComplete((ignoredResponse, throwable) -> {
            if (throwable != null) {
                log.error("Error changing visibility timeout for message. The following messages were not extended: " + messageBatch.stream().map(Message::messageId).collect(Collectors.toList()), throwable);
            }
            if (ignoredResponse.hasFailed()) {
                log.error("Some messages failed to be have their visibility timeout changed: {}", ignoredResponse.failed().stream().map(BatchResultErrorEntry::id).collect(Collectors.toList()));
            }
        });
    }

    /**
     * If there are more messages that are currently processing, determine the next time that a message needs to be interrupted or extended and wait until
     * that.
     *
     * @param maxDuration the maximum amount of time to wait for a message
     * @throws InterruptedException if the thread was interrupted while waiting
     */
    private void waitUntilNexreplacederation(final Duration maxDuration) throws InterruptedException {
        final Optional<Instant> optionalEarliestNextUpdateTime = currentMessagesProcessing.values().stream().map(state -> determineEarliestTrigger(state, maxDuration)).min(Instant::compareTo);
        if (!optionalEarliestNextUpdateTime.isPresent()) {
            return;
        }
        final long nextTime = Instant.now().until(optionalEarliestNextUpdateTime.get(), ChronoUnit.MILLIS);
        if (nextTime <= 0) {
            return;
        }
        log.debug("Waiting {}ms to change visibility timeout", nextTime);
        waitingLock.wait(nextTime);
    }

    /**
     * Determines the next time that the message needs to be extended to stop its visibility from expiring.
     *
     * @param timeNow the time that this iteration started at
     * @param message the message to determine the visibility timeout for
     * @param bufferDuration the buffer to change the visibility timeout before it actually expires
     * @return the time to extend the message's visibility
     */
    private Instant nextExtensionTime(final Instant timeNow, final Message message, final Duration bufferDuration) {
        return timeNow.plus(decoratorProperties.visibilityTimeout(message)).minus(bufferDuration);
    }

    /**
     * Determines whether the earliest time for this message should be when it should be interrupted or the next visibility extension time.
     *
     * @param state the state of this message
     * @param maxDuration the maximum time the message should process
     * @return the next time that the message should be extended or interrupted
     */
    private static Instant determineEarliestTrigger(final MessageProcessingState state, final Duration maxDuration) {
        final Instant maxTime = state.startTime().plus(maxDuration);
        final Instant nextVisibilityExtensionTime = state.nextVisibilityExtensionTime();
        if (maxTime.isBefore(nextVisibilityExtensionTime)) {
            return maxTime;
        } else {
            return nextVisibilityExtensionTime;
        }
    }

    @Value.Immutable
    interface MessageProcessingState {

        /**
         * The thread that is processing this message.
         *
         * <p> This is used to interrupt the processing if has run too long.
         *
         * @return the thread processing the message
         */
        Thread thread();

        /**
         * The time that the message began processing.
         *
         * @return the start time for the message
         */
        Instant startTime();

        /**
         * The next time that the visibility of the message will need to be extended.
         *
         * <p> This includes the buffer time and therefore will occur before the message's timeout actually expires.
         *
         * @return the next visibility extension time
         */
        Instant nextVisibilityExtensionTime();
    }
}

19 Source : PrefetchingMessageListenerContainer.java
with MIT License
from JaidenAshmore

private Supplier<MessageRetriever> buildMessageRetrieverSupplier(final PrefetchingMessageListenerContainerProperties properties, final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient) {
    return () -> new PrefetchingMessageRetriever(sqsAsyncClient, queueProperties, new PrefetchingMessageRetrieverProperties() {

        @Positive
        @Override
        public int getDesiredMinPrefetchedMessages() {
            return properties.desiredMinPrefetchedMessages();
        }

        @Override
        @Positive
        public int getMaxPrefetchedMessages() {
            return properties.maxPrefetchedMessages();
        }

        @Nullable
        @Positive
        @Override
        public Duration getMessageVisibilityTimeout() {
            return properties.messageVisibilityTimeout();
        }

        @Nullable
        @PositiveOrZero
        @Override
        public Duration getErrorBackoffTime() {
            return properties.errorBackoffTime();
        }
    });
}

19 Source : PrefetchingMessageListenerContainer.java
with MIT License
from JaidenAshmore

private Supplier<MessageResolver> buildMessageResolverSupplier(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient) {
    return () -> new BatchingMessageResolver(queueProperties, sqsAsyncClient);
}

19 Source : FifoMessageListenerContainer.java
with MIT License
from JaidenAshmore

private Supplier<MessageRetriever> messageRetrieverSupplier(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient, final FifoMessageListenerContainerProperties properties) {
    return () -> new BatchingMessageRetriever(queueProperties, sqsAsyncClient, new BatchingMessageRetrieverProperties() {

        @Override
        @Positive
        @Max(AwsConstants.MAX_NUMBER_OF_MESSAGES_FROM_SQS)
        public int getBatchSize() {
            return properties.maximumMessagesInMessageGroup();
        }

        @Override
        @Nullable
        @Positive
        public Duration getBatchingPeriod() {
            return Duration.ofSeconds(5);
        }

        @Override
        @Nullable
        @Positive
        public Duration getMessageVisibilityTimeout() {
            return properties.messageVisibilityTimeout();
        }

        @Override
        @Nullable
        @PositiveOrZero
        public Duration getErrorBackoffTime() {
            return properties.errorBackoffTime();
        }
    });
}

19 Source : FifoMessageListenerContainer.java
with MIT License
from JaidenAshmore

private Supplier<MessageResolver> messageResolverSupplier(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient) {
    return () -> new BatchingMessageResolver(queueProperties, sqsAsyncClient);
}

19 Source : BatchingMessageListenerContainer.java
with MIT License
from JaidenAshmore

private Supplier<MessageResolver> buildMessageResolver(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient, final BatchingMessageListenerContainerProperties properties) {
    return () -> new BatchingMessageResolver(queueProperties, sqsAsyncClient, new BatchingMessageResolverProperties() {

        @Positive
        @Max(AwsConstants.MAX_NUMBER_OF_MESSAGES_IN_BATCH)
        @Override
        public int getBufferingSizeLimit() {
            return properties.batchSize();
        }

        @Nonnull
        @Positive
        @Override
        public Duration getBufferingTime() {
            return properties.getBatchingPeriod();
        }
    });
}

19 Source : BatchingMessageListenerContainer.java
with MIT License
from JaidenAshmore

private Supplier<MessageRetriever> buildMessageRetrieverSupplier(final QueueProperties queueProperties, final SqsAsyncClient sqsAsyncClient, final BatchingMessageListenerContainerProperties properties) {
    return () -> new BatchingMessageRetriever(queueProperties, sqsAsyncClient, new BatchingMessageRetrieverProperties() {

        @Positive
        @Max(AwsConstants.MAX_NUMBER_OF_MESSAGES_FROM_SQS)
        @Override
        public int getBatchSize() {
            return properties.batchSize();
        }

        @Nullable
        @Positive
        @Override
        public Duration getBatchingPeriod() {
            return properties.getBatchingPeriod();
        }

        @Nullable
        @Positive
        @Override
        public Duration getMessageVisibilityTimeout() {
            return properties.messageVisibilityTimeout();
        }

        @Nullable
        @PositiveOrZero
        @Override
        public Duration getErrorBackoffTime() {
            return properties.errorBackoffTime();
        }
    });
}

19 Source : DefaultVisibilityExtender.java
with MIT License
from JaidenAshmore

/**
 * Default implementation of the {@link VisibilityExtender} that increases the visibility of the message by sending a change message visibility request.
 *
 * @see SqsAsyncClient#changeMessageVisibility(ChangeMessageVisibilityRequest)
 */
@AllArgsConstructor
public clreplaced DefaultVisibilityExtender implements VisibilityExtender {

    private final SqsAsyncClient sqsAsyncClient;

    private final QueueProperties queueProperties;

    private final Message message;

    @Override
    public Future<?> extend() {
        return extend(DEFAULT_VISIBILITY_EXTENSION_IN_SECONDS);
    }

    @Override
    public Future<?> extend(final int visibilityExtensionInSeconds) {
        final ChangeMessageVisibilityRequest changeMessageVisibilityRequest = ChangeMessageVisibilityRequest.builder().queueUrl(queueProperties.getQueueUrl()).receiptHandle(message.receiptHandle()).visibilityTimeout(visibilityExtensionInSeconds).build();
        return sqsAsyncClient.changeMessageVisibility(changeMessageVisibilityRequest);
    }
}

19 Source : PayloadArgumentResolver.java
with MIT License
from JaidenAshmore

@Override
public Object resolveArgumentForParameter(final QueueProperties queueProperties, final MethodParameter methodParameter, final Message message) throws ArgumentResolutionException {
    try {
        return payloadMapper.map(message, methodParameter.getParameter().getType());
    } catch (final PayloadMappingException payloadMappingException) {
        throw new ArgumentResolutionException(payloadMappingException);
    }
}

19 Source : MessageIdArgumentResolver.java
with MIT License
from JaidenAshmore

@Override
public String resolveArgumentForParameter(final QueueProperties queueProperties, final MethodParameter methodParameter, final Message message) throws ArgumentResolutionException {
    return message.messageId();
}

19 Source : MessageArgumentResolver.java
with MIT License
from JaidenAshmore

@Override
public Message resolveArgumentForParameter(final QueueProperties queueProperties, final MethodParameter methodParameter, final Message message) throws ArgumentResolutionException {
    return message;
}

19 Source : FifoMessageListenerContainerIntegrationTest.java
with MIT License
from JaidenAshmore

private static MessageProcessor messageProcessor(final QueueProperties queueProperties, final Consumer<Message> lambda) {
    return new LambdaMessageProcessor(ELASTIC_MQ_SQS_ASYNC_CLIENT, queueProperties, lambda);
}

18 Source : PrefetchingMessageListenerContainerFactory.java
with MIT License
from JaidenAshmore

@Override
protected MessageListenerContainer buildContainer(final String identifier, final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final PrefetchingMessageListenerContainerProperties containerProperties, final Supplier<MessageProcessor> messageProcessorSupplier) {
    return new PrefetchingMessageListenerContainer(identifier, queueProperties, sqsAsyncClient, messageProcessorSupplier, containerProperties);
}

18 Source : ConcurrentMessageBrokerIntegrationTest.java
with MIT License
from JaidenAshmore

@Slf4j
clreplaced ConcurrentMessageBrokerIntegrationTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final PayloadMapper PAYLOAD_MAPPER = new JacksonPayloadMapper(OBJECT_MAPPER);

    private static final ElasticMqSqsAsyncClient elasticMQSqsAsyncClient = new ElasticMqSqsAsyncClient();

    private QueueProperties queueProperties;

    private ArgumentResolverService argumentResolverService;

    @BeforeEach
    void setUp() throws InterruptedException, ExecutionException, TimeoutException {
        queueProperties = elasticMQSqsAsyncClient.createRandomQueue().thenApply(CreateRandomQueueResponse::queueUrl).thenApply(url -> QueueProperties.builder().queueUrl(url).build()).get(5, SECONDS);
        argumentResolverService = new CoreArgumentResolverService(PAYLOAD_MAPPER, OBJECT_MAPPER);
    }

    @Test
    void allMessagesSentIntoQueueAreProcessed() throws Exception {
        // arrange
        final int concurrencyLevel = 10;
        final int numberOfMessages = 100;
        final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, elasticMQSqsAsyncClient, StaticBatchingMessageRetrieverProperties.builder().batchSize(1).build());
        final CountDownLatch messageReceivedLatch = new CountDownLatch(numberOfMessages);
        final MessageConsumer messageConsumer = new MessageConsumer(messageReceivedLatch);
        final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, elasticMQSqsAsyncClient);
        final MessageProcessor messageProcessor = new CoreMessageProcessor(argumentResolverService, queueProperties, elasticMQSqsAsyncClient, MessageConsumer.clreplaced.getMethod("consume", String.clreplaced), messageConsumer);
        final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(concurrencyLevel).build());
        SqsIntegrationTestUtils.sendNumberOfMessages(numberOfMessages, elasticMQSqsAsyncClient, queueProperties.getQueueUrl());
        final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
        // act
        coreMessageListenerContainer.start();
        // replacedert
        messageReceivedLatch.await(60, SECONDS);
        // cleanup
        coreMessageListenerContainer.stop();
        SqsIntegrationTestUtils.replacedertNoMessagesInQueue(elasticMQSqsAsyncClient, queueProperties.getQueueUrl());
    }

    @Test
    void usingPrefetchingMessageRetrieverCanConsumeAllMessages() throws Exception {
        // arrange
        final int concurrencyLevel = 10;
        final int numberOfMessages = 100;
        final MessageRetriever messageRetriever = new PrefetchingMessageRetriever(elasticMQSqsAsyncClient, queueProperties, StaticPrefetchingMessageRetrieverProperties.builder().messageVisibilityTimeout(Duration.ofSeconds(60)).desiredMinPrefetchedMessages(30).maxPrefetchedMessages(40).build());
        final CountDownLatch messageReceivedLatch = new CountDownLatch(numberOfMessages);
        final MessageConsumer messageConsumer = new MessageConsumer(messageReceivedLatch);
        final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, elasticMQSqsAsyncClient);
        final MessageProcessor messageProcessor = new CoreMessageProcessor(argumentResolverService, queueProperties, elasticMQSqsAsyncClient, MessageConsumer.clreplaced.getMethod("consume", String.clreplaced), messageConsumer);
        final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(concurrencyLevel).build());
        final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
        SqsIntegrationTestUtils.sendNumberOfMessages(numberOfMessages, elasticMQSqsAsyncClient, queueProperties.getQueueUrl());
        // act
        coreMessageListenerContainer.start();
        // replacedert
        messageReceivedLatch.await(1, MINUTES);
        // cleanup
        coreMessageListenerContainer.stop();
        SqsIntegrationTestUtils.replacedertNoMessagesInQueue(elasticMQSqsAsyncClient, queueProperties.getQueueUrl());
    }

    @Test
    void usingBatchingMessageRetrieverCanConsumeAllMessages() throws Exception {
        // arrange
        final int concurrencyLevel = 10;
        final int numberOfMessages = 100;
        final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, elasticMQSqsAsyncClient, StaticBatchingMessageRetrieverProperties.builder().batchSize(10).batchingPeriod(Duration.ofSeconds(3)).messageVisibilityTimeout(Duration.ofSeconds(60)).build());
        final CountDownLatch messageReceivedLatch = new CountDownLatch(numberOfMessages);
        final MessageConsumer messageConsumer = new MessageConsumer(messageReceivedLatch);
        final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, elasticMQSqsAsyncClient);
        final MessageProcessor messageProcessor = new CoreMessageProcessor(argumentResolverService, queueProperties, elasticMQSqsAsyncClient, MessageConsumer.clreplaced.getMethod("consume", String.clreplaced), messageConsumer);
        final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(concurrencyLevel).build());
        final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
        SqsIntegrationTestUtils.sendNumberOfMessages(numberOfMessages, elasticMQSqsAsyncClient, queueProperties.getQueueUrl());
        // act
        coreMessageListenerContainer.start();
        // replacedert
        messageReceivedLatch.await(1, MINUTES);
        // cleanup
        coreMessageListenerContainer.stop();
        SqsIntegrationTestUtils.replacedertNoMessagesInQueue(elasticMQSqsAsyncClient, queueProperties.getQueueUrl());
    }

    @SuppressWarnings("WeakerAccess")
    public static clreplaced MessageConsumer {

        private final CountDownLatch messagesReceivedLatch;

        public MessageConsumer(final CountDownLatch messagesReceivedLatch) {
            this.messagesReceivedLatch = messagesReceivedLatch;
        }

        @SuppressWarnings("unused")
        public void consume(@Payload final String messagePayload) {
            log.info("Consuming message: {}", messagePayload);
            messagesReceivedLatch.countDown();
        }
    }
}

18 Source : VisibilityExtenderIntegrationTest.java
with MIT License
from JaidenAshmore

@Slf4j
clreplaced VisibilityExtenderIntegrationTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final PayloadMapper PAYLOAD_MAPPER = new JacksonPayloadMapper(OBJECT_MAPPER);

    private static final ArgumentResolverService ARGUMENT_RESOLVER_SERVICE = new CoreArgumentResolverService(PAYLOAD_MAPPER, OBJECT_MAPPER);

    private static final int ORIGINAL_MESSAGE_VISIBILITY = 2;

    private static final ElasticMqSqsAsyncClient elasticMQSqsAsyncClient = new ElasticMqSqsAsyncClient(singletonList(SqsQueuesConfig.QueueConfig.builder().queueName("VisibilityExtenderIntegrationTest").visibilityTimeout(ORIGINAL_MESSAGE_VISIBILITY).maxReceiveCount(// make sure it will try multiple times
    2).build()));

    private QueueProperties queueProperties;

    @BeforeEach
    void setUp() throws InterruptedException, ExecutionException, TimeoutException {
        queueProperties = elasticMQSqsAsyncClient.createRandomQueue().thenApply(CreateRandomQueueResponse::queueUrl).thenApply(url -> QueueProperties.builder().queueUrl(url).build()).get(5, SECONDS);
    }

    @AfterAll
    static void tearDown() {
        elasticMQSqsAsyncClient.close();
    }

    @Test
    void messageAttributesCanBeConsumedInMessageProcessingMethods() throws Exception {
        // arrange
        final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, elasticMQSqsAsyncClient, StaticBatchingMessageRetrieverProperties.builder().batchSize(1).build());
        final CountDownLatch messageProcessedLatch = new CountDownLatch(1);
        final AtomicInteger numberTimesMessageProcessed = new AtomicInteger(0);
        final MessageConsumer messageConsumer = new MessageConsumer(messageProcessedLatch, numberTimesMessageProcessed);
        final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, elasticMQSqsAsyncClient);
        final MessageProcessor messageProcessor = new CoreMessageProcessor(ARGUMENT_RESOLVER_SERVICE, queueProperties, elasticMQSqsAsyncClient, MessageConsumer.clreplaced.getMethod("consume", VisibilityExtender.clreplaced), messageConsumer);
        final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(1).build());
        final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
        coreMessageListenerContainer.start();
        // act
        elasticMQSqsAsyncClient.sendMessage(SendMessageRequest.builder().queueUrl(queueProperties.getQueueUrl()).messageBody("test").build()).get(2, SECONDS);
        replacedertThat(messageProcessedLatch.await(ORIGINAL_MESSAGE_VISIBILITY * 3, SECONDS)).isTrue();
        coreMessageListenerContainer.stop();
        // replacedert
        replacedertThat(numberTimesMessageProcessed).hasValue(1);
    }

    @AllArgsConstructor
    public static clreplaced MessageConsumer {

        private final CountDownLatch latch;

        private final AtomicInteger numberTimesEntered;

        @SuppressWarnings("WeakerAccess")
        public void consume(VisibilityExtender visibilityExtender) throws Exception {
            numberTimesEntered.incrementAndGet();
            // Extend it past what the current available visibility is
            visibilityExtender.extend(3 * ORIGINAL_MESSAGE_VISIBILITY).get();
            Thread.sleep(2 * ORIGINAL_MESSAGE_VISIBILITY * 1000);
            latch.countDown();
        }
    }
}

18 Source : MessageSystemAttributeIntegrationTest.java
with MIT License
from JaidenAshmore

@Slf4j
clreplaced MessageSystemAttributeIntegrationTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final PayloadMapper PAYLOAD_MAPPER = new JacksonPayloadMapper(OBJECT_MAPPER);

    private static final ArgumentResolverService ARGUMENT_RESOLVER_SERVICE = new CoreArgumentResolverService(PAYLOAD_MAPPER, OBJECT_MAPPER);

    private static final ElasticMqSqsAsyncClient elasticMQSqsAsyncClient = new ElasticMqSqsAsyncClient();

    private QueueProperties queueProperties;

    @BeforeEach
    void setUp() throws InterruptedException, ExecutionException, TimeoutException {
        queueProperties = elasticMQSqsAsyncClient.createRandomQueue().thenApply(CreateRandomQueueResponse::queueUrl).thenApply(url -> QueueProperties.builder().queueUrl(url).build()).get(5, SECONDS);
    }

    @AfterAll
    static void tearDown() {
        elasticMQSqsAsyncClient.close();
    }

    @Test
    void messageAttributesCanBeConsumedInMessageProcessingMethods() throws Exception {
        // arrange
        final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, elasticMQSqsAsyncClient, StaticBatchingMessageRetrieverProperties.builder().batchSize(1).build());
        final CountDownLatch messageProcessedLatch = new CountDownLatch(1);
        final AtomicReference<OffsetDateTime> messageAttributeReference = new AtomicReference<>();
        final MessageConsumer messageConsumer = new MessageConsumer(messageProcessedLatch, messageAttributeReference);
        final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, elasticMQSqsAsyncClient);
        final MessageProcessor messageProcessor = new CoreMessageProcessor(ARGUMENT_RESOLVER_SERVICE, queueProperties, elasticMQSqsAsyncClient, MessageConsumer.clreplaced.getMethod("consume", OffsetDateTime.clreplaced), messageConsumer);
        final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(1).build());
        final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
        coreMessageListenerContainer.start();
        // act
        elasticMQSqsAsyncClient.sendMessage(SendMessageRequest.builder().queueUrl(queueProperties.getQueueUrl()).messageBody("test").build()).get(2, SECONDS);
        replacedertThat(messageProcessedLatch.await(5, SECONDS)).isTrue();
        coreMessageListenerContainer.stop();
        // replacedert
        replacedertThat(messageAttributeReference.get()).isCloseTo(OffsetDateTime.now(), within(2, MINUTES));
    }

    @SuppressWarnings("WeakerAccess")
    @AllArgsConstructor
    public static clreplaced MessageConsumer {

        private final CountDownLatch latch;

        private final AtomicReference<OffsetDateTime> valueAtomicReference;

        public void consume(@MessageSystemAttribute(MessageSystemAttributeName.APPROXIMATE_FIRST_RECEIVE_TIMESTAMP) final OffsetDateTime value) {
            log.info("Message processed with attribute: {}", value);
            valueAtomicReference.set(value);
            latch.countDown();
        }
    }
}

18 Source : MessageAttributeIntegrationTest.java
with MIT License
from JaidenAshmore

@Slf4j
clreplaced MessageAttributeIntegrationTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final PayloadMapper PAYLOAD_MAPPER = new JacksonPayloadMapper(OBJECT_MAPPER);

    private static final ArgumentResolverService ARGUMENT_RESOLVER_SERVICE = new CoreArgumentResolverService(PAYLOAD_MAPPER, OBJECT_MAPPER);

    private static final ElasticMqSqsAsyncClient elasticMQSqsAsyncClient = new ElasticMqSqsAsyncClient();

    private QueueProperties queueProperties;

    @BeforeEach
    void setUp() throws InterruptedException, ExecutionException, TimeoutException {
        queueProperties = elasticMQSqsAsyncClient.createRandomQueue().thenApply(CreateRandomQueueResponse::queueUrl).thenApply(url -> QueueProperties.builder().queueUrl(url).build()).get(5, SECONDS);
    }

    @AfterAll
    static void tearDown() {
        elasticMQSqsAsyncClient.close();
    }

    @Test
    void messageAttributesCanBeConsumedInMessageProcessingMethods() throws Exception {
        // arrange
        final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, elasticMQSqsAsyncClient, StaticBatchingMessageRetrieverProperties.builder().batchSize(1).build());
        final CountDownLatch messageProcessedLatch = new CountDownLatch(1);
        final AtomicReference<String> messageAttributeReference = new AtomicReference<>();
        final MessageConsumer messageConsumer = new MessageConsumer(messageProcessedLatch, messageAttributeReference);
        final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, elasticMQSqsAsyncClient);
        final MessageProcessor messageProcessor = new CoreMessageProcessor(ARGUMENT_RESOLVER_SERVICE, queueProperties, elasticMQSqsAsyncClient, MessageConsumer.clreplaced.getMethod("consume", String.clreplaced), messageConsumer);
        final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(1).build());
        final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
        coreMessageListenerContainer.start();
        // act
        elasticMQSqsAsyncClient.sendMessage(SendMessageRequest.builder().queueUrl(queueProperties.getQueueUrl()).messageBody("test").messageAttributes(singletonMap("key", MessageAttributeValue.builder().dataType(MessageAttributeDataTypes.STRING.getValue()).stringValue("expected value").build())).build()).get(2, SECONDS);
        replacedertThat(messageProcessedLatch.await(5, SECONDS)).isTrue();
        coreMessageListenerContainer.stop();
        // replacedert
        replacedertThat(messageAttributeReference).hasValue("expected value");
    }

    @SuppressWarnings("WeakerAccess")
    @AllArgsConstructor
    public static clreplaced MessageConsumer {

        private final CountDownLatch latch;

        private final AtomicReference<String> valueAtomicReference;

        public void consume(@MessageAttribute("key") final String value) {
            log.info("Message processed with attribute: {}", value);
            valueAtomicReference.set(value);
            latch.countDown();
        }
    }
}

18 Source : MessageArgumentResolutionIntegrationTest.java
with MIT License
from JaidenAshmore

clreplaced MessageArgumentResolutionIntegrationTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final PayloadMapper PAYLOAD_MAPPER = new JacksonPayloadMapper(OBJECT_MAPPER);

    private static final ArgumentResolverService ARGUMENT_RESOLVER_SERVICE = new CoreArgumentResolverService(PAYLOAD_MAPPER, OBJECT_MAPPER);

    private static final ElasticMqSqsAsyncClient elasticMQSqsAsyncClient = new ElasticMqSqsAsyncClient();

    private QueueProperties queueProperties;

    @BeforeEach
    void setUp() throws InterruptedException, ExecutionException, TimeoutException {
        queueProperties = elasticMQSqsAsyncClient.createRandomQueue().thenApply(CreateRandomQueueResponse::queueUrl).thenApply(url -> QueueProperties.builder().queueUrl(url).build()).get(5, SECONDS);
    }

    @AfterAll
    static void tearDown() {
        elasticMQSqsAsyncClient.close();
    }

    @Test
    void messageAttributesCanBeConsumedInMessageProcessingMethods() throws Exception {
        // arrange
        final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, elasticMQSqsAsyncClient, StaticBatchingMessageRetrieverProperties.builder().batchSize(1).build());
        final CountDownLatch messageProcessedLatch = new CountDownLatch(1);
        final AtomicReference<Message> messageAttributeReference = new AtomicReference<>();
        final MessageConsumer messageConsumer = new MessageConsumer(messageProcessedLatch, messageAttributeReference);
        final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, elasticMQSqsAsyncClient);
        final MessageProcessor messageProcessor = new CoreMessageProcessor(ARGUMENT_RESOLVER_SERVICE, queueProperties, elasticMQSqsAsyncClient, MessageConsumer.clreplaced.getMethod("consume", Message.clreplaced), messageConsumer);
        final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(1).build());
        final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
        coreMessageListenerContainer.start();
        // act
        elasticMQSqsAsyncClient.sendMessage(SendMessageRequest.builder().queueUrl(queueProperties.getQueueUrl()).messageBody("test").build()).get(2, SECONDS);
        replacedertThat(messageProcessedLatch.await(5, SECONDS)).isTrue();
        coreMessageListenerContainer.stop();
        // replacedert
        replacedertThat(messageAttributeReference.get().body()).isEqualTo("test");
    }

    @SuppressWarnings("WeakerAccess")
    @AllArgsConstructor
    public static clreplaced MessageConsumer {

        private final CountDownLatch latch;

        private final AtomicReference<Message> valueAtomicReference;

        public void consume(final Message message) {
            valueAtomicReference.set(message);
            latch.countDown();
        }
    }
}

18 Source : MessageProcessingContext.java
with MIT License
from JaidenAshmore

@Value
@Builder
public clreplaced MessageProcessingContext {

    /**
     * The unique identifier for this message listener.
     *
     * <p>For example: my-message-consumer or any other custom identifier.
     */
    @NonNull
    String listenerIdentifier;

    /**
     * The details about the queue that this message processor is running against.
     */
    @NonNull
    QueueProperties queueProperties;

    /**
     * Processing attributes that you can use to share objects through each stage of the processing.
     *
     * <p>For example, you may want to store the start time for when the message has begun to be processing
     * for usage by a later decorator method to determine the time to process the message.
     */
    @NonNull
    Map<String, Object> attributes;

    /**
     * Helper method to get an attribute with the given key.
     *
     * @param key the key of the attribute
     * @return the value of the attribute or null if it does not exist
     * @param <T> the type of the attribute to cast the value to
     */
    @Nullable
    @SuppressWarnings("unchecked")
    public <T> T getAttribute(final String key) {
        return (T) attributes.get(key);
    }

    /**
     *  Helper method to set an attribute with the given key.
     * MessageProcessingDecorat
     *  @param key   the key of the attribute
     *  @param value the value to set for the attribute
     *  @return this object for further chaining if necessary
     */
    public MessageProcessingContext setAttribute(final String key, @Nullable final Object value) {
        attributes.put(key, value);
        return this;
    }
}

17 Source : MessageAttributeArgumentResolver.java
with MIT License
from JaidenAshmore

@Override
public Object resolveArgumentForParameter(final QueueProperties queueProperties, final MethodParameter methodParameter, final Message message) throws ArgumentResolutionException {
    final MessageAttribute annotation = AnnotationUtils.findParameterAnnotation(methodParameter, MessageAttribute.clreplaced).orElseThrow(() -> new ArgumentResolutionException("Parameter preplaceded in does not contain the MessageAttribute annotation when it should"));
    final String attributeName = annotation.value();
    final Optional<MessageAttributeValue> optionalMessageAttributeValue = Optional.ofNullable(message.messageAttributes().get(attributeName));
    if (!optionalMessageAttributeValue.isPresent()) {
        if (annotation.required()) {
            throw new ArgumentResolutionException("Required Message Attribute '" + attributeName + "' is missing from message");
        }
        return null;
    }
    final MessageAttributeValue messageAttributeValue = optionalMessageAttributeValue.get();
    if (messageAttributeValue.dataType().startsWith(MessageAttributeDataTypes.STRING.getValue()) || messageAttributeValue.dataType().startsWith(MessageAttributeDataTypes.NUMBER.getValue())) {
        return handleStringParameterValue(methodParameter, messageAttributeValue, attributeName);
    } else if (messageAttributeValue.dataType().startsWith(MessageAttributeDataTypes.BINARY.getValue())) {
        return handleByteParameterValue(methodParameter, messageAttributeValue);
    }
    throw new ArgumentResolutionException("Cannot parse message attribute due to unknown data type '" + messageAttributeValue.dataType() + "'");
}

16 Source : MessageSystemAttributeArgumentResolver.java
with MIT License
from JaidenAshmore

@Override
public Object resolveArgumentForParameter(final QueueProperties queueProperties, final MethodParameter methodParameter, final Message message) throws ArgumentResolutionException {
    final MessageSystemAttribute annotation = AnnotationUtils.findParameterAnnotation(methodParameter, MessageSystemAttribute.clreplaced).orElseThrow(() -> new ArgumentResolutionException("Parameter preplaceded in does not contain the MessageSystemAttribute annotation when it should"));
    final MessageSystemAttributeName messageSystemAttributeName = annotation.value();
    final Optional<String> optionalAttributeValue = Optional.ofNullable(message.attributes().get(messageSystemAttributeName));
    if (!optionalAttributeValue.isPresent()) {
        if (annotation.required()) {
            throw new ArgumentResolutionException("Missing system attribute with name: " + messageSystemAttributeName.toString());
        }
        return null;
    }
    final String attributeValue = optionalAttributeValue.get();
    final Clreplaced<?> parameterType = methodParameter.getParameter().getType();
    try {
        if (parameterType == String.clreplaced) {
            return attributeValue;
        }
        if (parameterType == Integer.clreplaced || parameterType == int.clreplaced) {
            return Integer.parseInt(attributeValue);
        }
        if (parameterType == Long.clreplaced || parameterType == long.clreplaced) {
            return Long.parseLong(attributeValue);
        }
    } catch (final RuntimeException exception) {
        throw new ArgumentResolutionException("Error parsing message attribute: " + messageSystemAttributeName.toString(), exception);
    }
    if (messageSystemAttributeName == SENT_TIMESTAMP || messageSystemAttributeName == APPROXIMATE_FIRST_RECEIVE_TIMESTAMP) {
        return handleTimeStampAttributes(methodParameter.getParameter().getType(), messageSystemAttributeName, attributeValue);
    }
    throw new ArgumentResolutionException("Unsupported parameter type " + parameterType.getName() + " for system attribute " + messageSystemAttributeName.toString());
}

15 Source : FifoMessageListenerContainerIntegrationTest.java
with MIT License
from JaidenAshmore

private static void sendMessages(final QueueProperties queueProperties, @Max(10) final int numberOfGroups, @Min(1) final int numberOfMessages) throws Exception {
    for (int i = 0; i < numberOfMessages; ++i) {
        final int messageIndex = i;
        ELASTIC_MQ_SQS_ASYNC_CLIENT.sendMessageBatch(sendMessageBuilder -> {
            final List<SendMessageBatchRequestEntry> entries = IntStream.range(0, numberOfGroups).mapToObj(groupIndex -> {
                final String messageId = "" + messageIndex + "-" + groupIndex;
                return SendMessageBatchRequestEntry.builder().id(messageId).messageGroupId(String.valueOf(groupIndex)).messageBody("" + messageIndex).messageDeduplicationId(messageId).build();
            }).collect(Collectors.toList());
            sendMessageBuilder.queueUrl(queueProperties.getQueueUrl()).entries(entries);
        }).get(5, TimeUnit.SECONDS);
    }
}

14 Source : AutoVisibilityExtenderMessageProcessingDecoratorFactory.java
with MIT License
from JaidenAshmore

@Override
public Optional<AutoVisibilityExtenderMessageProcessingDecorator> buildDecorator(final SqsAsyncClient sqsAsyncClient, final QueueProperties queueProperties, final String identifier, final Object bean, final Method method) {
    final Optional<AutoVisibilityExtender> optionalAnnotation = AnnotationUtils.findMethodAnnotation(method, AutoVisibilityExtender.clreplaced);
    if (!optionalAnnotation.isPresent()) {
        return Optional.empty();
    }
    if (CompletableFuture.clreplaced.isreplacedignableFrom(method.getReturnType())) {
        throw new MessageProcessingDecoratorFactoryException(AutoVisibilityExtenderMessageProcessingDecorator.clreplaced.getSimpleName() + " cannot be built around asynchronous message listeners");
    }
    return optionalAnnotation.map(this::buildConfigurationProperties).map(properties -> new AutoVisibilityExtenderMessageProcessingDecorator(sqsAsyncClient, queueProperties, properties));
}

13 Source : LambdaMessageProcessorIntegrationTest.java
with MIT License
from JaidenAshmore

@Test
void canProcessMessageLambdas() throws ExecutionException, InterruptedException {
    // arrange
    final String queueUrl = client.createRandomQueue().get().getResponse().queueUrl();
    final QueueProperties queueProperties = QueueProperties.builder().queueUrl(queueUrl).build();
    final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, client);
    final CountDownLatch countDownLatch = new CountDownLatch(20);
    final MessageProcessor messageProcessor = new LambdaMessageProcessor(client, queueProperties, message -> countDownLatch.countDown());
    final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(1).build());
    final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, client, StaticBatchingMessageRetrieverProperties.builder().batchSize(1).build());
    final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
    coreMessageListenerContainer.start();
    // act
    SqsIntegrationTestUtils.sendNumberOfMessages(20, client, queueProperties.getQueueUrl());
    // replacedert
    replacedertThat(countDownLatch.await(20, TimeUnit.SECONDS)).isTrue();
    coreMessageListenerContainer.stop();
}

13 Source : AsyncLambdaMessageProcessorIntegrationTest.java
with MIT License
from JaidenAshmore

@Test
void canProcessMessageLambdas() throws ExecutionException, InterruptedException {
    // arrange
    final String queueUrl = client.createRandomQueue().get().getResponse().queueUrl();
    final QueueProperties queueProperties = QueueProperties.builder().queueUrl(queueUrl).build();
    final MessageResolver messageResolver = new BatchingMessageResolver(queueProperties, client);
    final CountDownLatch countDownLatch = new CountDownLatch(20);
    final MessageProcessor messageProcessor = new AsyncLambdaMessageProcessor(client, queueProperties, message -> CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(20);
        } catch (InterruptedException interruptedException) {
        // ignore
        }
        countDownLatch.countDown();
    }));
    final ConcurrentMessageBroker messageBroker = new ConcurrentMessageBroker(StaticConcurrentMessageBrokerProperties.builder().concurrencyLevel(1).build());
    final MessageRetriever messageRetriever = new BatchingMessageRetriever(queueProperties, client, StaticBatchingMessageRetrieverProperties.builder().batchSize(1).build());
    final CoreMessageListenerContainer coreMessageListenerContainer = new CoreMessageListenerContainer("id", () -> messageBroker, () -> messageRetriever, () -> messageProcessor, () -> messageResolver);
    coreMessageListenerContainer.start();
    // act
    SqsIntegrationTestUtils.sendNumberOfMessages(20, client, queueProperties.getQueueUrl());
    // replacedert
    replacedertThat(countDownLatch.await(20, TimeUnit.SECONDS)).isTrue();
    coreMessageListenerContainer.stop();
}

12 Source : CoreMessageProcessor.java
with MIT License
from JaidenAshmore

private static ArgumentResolvers determineArgumentResolvers(final ArgumentResolverService argumentResolverService, final QueueProperties queueProperties, final Method method) {
    final Parameter[] parameters = method.getParameters();
    List<InternalArgumentResolver> argumentResolvers = IntStream.range(0, parameters.length).<InternalArgumentResolver>mapToObj(parameterIndex -> {
        final Parameter parameter = parameters[parameterIndex];
        final MethodParameter methodParameter = DefaultMethodParameter.builder().method(method).parameter(parameter).parameterIndex(parameterIndex).build();
        if (isAcknowledgeParameter(parameter)) {
            return (message, acknowledge, visibilityExtender) -> acknowledge;
        }
        if (isVisibilityExtenderParameter(parameter)) {
            return (message, acknowledge, visibilityExtender) -> visibilityExtender;
        }
        final ArgumentResolver<?> argumentResolver = argumentResolverService.getArgumentResolver(methodParameter);
        return (message, acknowledge, visibilityExtender) -> argumentResolver.resolveArgumentForParameter(queueProperties, methodParameter, message);
    }).collect(toList());
    return (message, acknowledge, visibilityExtender) -> argumentResolvers.stream().map(argumentResolver -> argumentResolver.resolveArgument(message, acknowledge, visibilityExtender)).toArray(Object[]::new);
}

11 Source : PrefetchingMessageListenerContainerIntegrationTest.java
with MIT License
from JaidenAshmore

@Test
void canConsumeMessages() throws Exception {
    // arrange
    final CreateRandomQueueResponse response = sqsAsyncClient.createRandomQueue().get();
    final String queueUrl = response.getResponse().queueUrl();
    final QueueProperties queueProperties = QueueProperties.builder().queueUrl(queueUrl).build();
    final CountDownLatch latch = new CountDownLatch(1);
    final MessageListenerContainer container = new PrefetchingMessageListenerContainer("id", queueProperties, sqsAsyncClient, () -> new LambdaMessageProcessor(sqsAsyncClient, queueProperties, message -> latch.countDown()), ImmutablePrefetchingMessageListenerContainerProperties.builder().concurrencyLevel(2).desiredMinPrefetchedMessages(5).maxPrefetchedMessages(10).build());
    // act
    container.start();
    sqsAsyncClient.sendMessage(response.getQueueName(), "body");
    // replacedert
    replacedertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
    container.stop();
}

11 Source : FifoMessageListenerContainerIntegrationTest.java
with MIT License
from JaidenAshmore

@Test
void messagesInTheSameGroupAreNotProcessedConcurrently() throws Exception {
    // arrange
    final int numberOfMessageGroups = 5;
    final int numberOfMessages = 40;
    final CountDownLatch messagesProcessedLatch = new CountDownLatch(numberOfMessageGroups * numberOfMessages);
    final Set<String> currentMessages = new HashSet<>();
    final AtomicBoolean processedMessagesConcurrently = new AtomicBoolean();
    final QueueProperties queueProperties = createFifoQueue();
    final MessageListenerContainer container = new FifoMessageListenerContainer("identifier", queueProperties, ELASTIC_MQ_SQS_ASYNC_CLIENT, () -> messageProcessor(queueProperties, message -> {
        final String messageGroupId = message.attributes().get(MessageSystemAttributeName.MESSAGE_GROUP_ID);
        synchronized (currentMessages) {
            if (currentMessages.contains(messageGroupId)) {
                processedMessagesConcurrently.set(true);
            }
            currentMessages.add(messageGroupId);
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException interruptedException) {
        // do nothing
        }
        synchronized (currentMessages) {
            currentMessages.remove(messageGroupId);
        }
        messagesProcessedLatch.countDown();
    }), ImmutableFifoMessageListenerContainerProperties.builder().concurrencyLevel(10).maximumCachedMessageGroups(8).maximumMessagesInMessageGroup(2).build());
    sendMessages(queueProperties, numberOfMessageGroups, numberOfMessages);
    // act
    container.start();
    messagesProcessedLatch.await(60, TimeUnit.SECONDS);
    container.stop();
    // replacedert
    replacedertThat(processedMessagesConcurrently).isFalse();
}

See More Examples