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
19
Source : DecoratingMessageProcessorFactoryTest.java
with MIT License
from JaidenAshmore
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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