├── .github └── workflows │ ├── build-and-test.yml │ ├── release-snapshot.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .markdownlinkcheck.json ├── .prettierignore ├── .prettierrc ├── .yarn └── releases │ └── yarn-3.6.1.cjs ├── .yarnrc.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── annotations ├── README.md ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── annotations │ │ ├── container │ │ └── AnnotationMessageListenerContainerFactory.java │ │ ├── core │ │ ├── basic │ │ │ ├── BasicAnnotationMessageListenerContainerFactory.java │ │ │ ├── QueueListener.java │ │ │ └── QueueListenerParser.java │ │ ├── fifo │ │ │ ├── FifoAnnotationMessageListenerContainerFactory.java │ │ │ ├── FifoQueueListener.java │ │ │ └── FifoQueueListenerParser.java │ │ └── prefetch │ │ │ ├── PrefetchingAnnotationMessageListenerContainerFactory.java │ │ │ ├── PrefetchingQueueListener.java │ │ │ └── PrefetchingQueueListenerParser.java │ │ └── decorator │ │ └── visibilityextender │ │ ├── AutoVisibilityExtender.java │ │ └── AutoVisibilityExtenderMessageProcessingDecoratorFactory.java │ └── test │ └── java │ └── com │ └── jashmore │ └── sqs │ └── annotations │ ├── core │ ├── basic │ │ ├── BasicMessageListenerContainerFactoryTest.java │ │ └── QueueListenerParserTest.java │ ├── fifo │ │ └── FifoQueueListenerParserTest.java │ └── prefetch │ │ ├── PrefetchingMessageListenerContainerFactoryTest.java │ │ └── PrefetchingQueueListenerParserTest.java │ └── decorator │ └── visibilityextender │ └── AutoVisibilityExtenderMessageProcessingDecoratorFactoryTest.java ├── api ├── build.gradle.kts └── src │ └── main │ └── java │ └── com │ └── jashmore │ └── sqs │ ├── QueueProperties.java │ ├── argument │ ├── ArgumentResolutionException.java │ ├── ArgumentResolver.java │ ├── ArgumentResolverService.java │ ├── DefaultMethodParameter.java │ ├── MethodParameter.java │ └── UnsupportedArgumentResolutionException.java │ ├── aws │ └── AwsConstants.java │ ├── broker │ └── MessageBroker.java │ ├── client │ ├── QueueResolutionException.java │ ├── QueueResolver.java │ └── SqsAsyncClientProvider.java │ ├── container │ ├── MessageListenerContainer.java │ ├── MessageListenerContainerCoordinator.java │ ├── MessageListenerContainerFactory.java │ └── MessageListenerContainerInitialisationException.java │ ├── decorator │ ├── MessageProcessingContext.java │ ├── MessageProcessingDecorator.java │ ├── MessageProcessingDecoratorFactory.java │ └── MessageProcessingDecoratorFactoryException.java │ ├── processor │ ├── MessageProcessingException.java │ ├── MessageProcessor.java │ └── argument │ │ ├── Acknowledge.java │ │ └── VisibilityExtender.java │ ├── resolver │ └── MessageResolver.java │ └── retriever │ └── MessageRetriever.java ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── jashmore │ │ └── gradle │ │ ├── JacocoCoverallsPlugin.kt │ │ ├── ReleasePlugin.kt │ │ └── utils │ │ └── VersionUtils.kt │ └── resources │ └── META-INF │ └── gradle-plugins │ ├── com.jashmore.gradle.jacoco-coveralls.properties │ └── com.jashmore.gradle.release.properties ├── configuration └── spotbugs │ └── bugsExcludeFilter.xml ├── core ├── README.md ├── build.gradle.kts └── src │ ├── integrationTest │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ ├── argument │ │ │ ├── MessageArgumentResolutionIntegrationTest.java │ │ │ ├── MessageAttributeIntegrationTest.java │ │ │ ├── MessageSystemAttributeIntegrationTest.java │ │ │ └── VisibilityExtenderIntegrationTest.java │ │ │ ├── container │ │ │ ├── batching │ │ │ │ └── BatchingMessageListenerContainerIntegrationTest.java │ │ │ ├── fifo │ │ │ │ └── FifoMessageListenerContainerIntegrationTest.java │ │ │ └── prefetching │ │ │ │ └── PrefetchingMessageListenerContainerIntegrationTest.java │ │ │ ├── decorator │ │ │ └── AutoVisibilityExtenderMessageProcessingDecoratorIntegrationTest.java │ │ │ ├── listener │ │ │ └── concurrent │ │ │ │ └── ConcurrentMessageBrokerIntegrationTest.java │ │ │ ├── processor │ │ │ ├── AsyncLambdaMessageProcessorIntegrationTest.java │ │ │ └── LambdaMessageProcessorIntegrationTest.java │ │ │ └── util │ │ │ └── SqsIntegrationTestUtils.java │ └── resources │ │ └── logback.xml │ ├── main │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ ├── argument │ │ ├── CoreArgumentResolverService.java │ │ ├── DelegatingArgumentResolverService.java │ │ ├── attribute │ │ │ ├── MessageAttribute.java │ │ │ ├── MessageAttributeArgumentResolver.java │ │ │ ├── MessageAttributeDataTypes.java │ │ │ ├── MessageSystemAttribute.java │ │ │ └── MessageSystemAttributeArgumentResolver.java │ │ ├── message │ │ │ └── MessageArgumentResolver.java │ │ ├── messageid │ │ │ ├── MessageId.java │ │ │ └── MessageIdArgumentResolver.java │ │ ├── payload │ │ │ ├── Payload.java │ │ │ ├── PayloadArgumentResolver.java │ │ │ └── mapper │ │ │ │ ├── JacksonPayloadMapper.java │ │ │ │ ├── PayloadMapper.java │ │ │ │ └── PayloadMappingException.java │ │ └── visibility │ │ │ └── DefaultVisibilityExtender.java │ │ ├── broker │ │ ├── concurrent │ │ │ ├── ConcurrentMessageBroker.java │ │ │ ├── ConcurrentMessageBrokerConstants.java │ │ │ ├── ConcurrentMessageBrokerProperties.java │ │ │ └── StaticConcurrentMessageBrokerProperties.java │ │ └── grouping │ │ │ ├── GroupingMessageBroker.java │ │ │ └── GroupingMessageBrokerProperties.java │ │ ├── client │ │ ├── DefaultPlaceholderQueueResolver.java │ │ └── DefaultSqsAsyncClientProvider.java │ │ ├── container │ │ ├── CoreMessageListenerContainer.java │ │ ├── CoreMessageListenerContainerConstants.java │ │ ├── CoreMessageListenerContainerProperties.java │ │ ├── StaticCoreMessageListenerContainerProperties.java │ │ ├── batching │ │ │ ├── BatchingMessageListenerContainer.java │ │ │ └── BatchingMessageListenerContainerProperties.java │ │ ├── fifo │ │ │ ├── FifoMessageListenerContainer.java │ │ │ └── FifoMessageListenerContainerProperties.java │ │ └── prefetching │ │ │ ├── PrefetchingMessageListenerContainer.java │ │ │ └── PrefetchingMessageListenerContainerProperties.java │ │ ├── decorator │ │ ├── AutoVisibilityExtenderMessageProcessingDecorator.java │ │ └── AutoVisibilityExtenderMessageProcessingDecoratorProperties.java │ │ ├── placeholder │ │ ├── PlaceholderResolver.java │ │ └── StaticPlaceholderResolver.java │ │ ├── processor │ │ ├── AsyncLambdaMessageProcessor.java │ │ ├── CoreMessageProcessor.java │ │ ├── DecoratingMessageProcessor.java │ │ ├── DecoratingMessageProcessorFactory.java │ │ └── LambdaMessageProcessor.java │ │ ├── resolver │ │ └── batching │ │ │ ├── BatchingMessageResolver.java │ │ │ ├── BatchingMessageResolverProperties.java │ │ │ └── StaticBatchingMessageResolverProperties.java │ │ └── retriever │ │ ├── batching │ │ ├── BatchingMessageRetriever.java │ │ ├── BatchingMessageRetrieverConstants.java │ │ ├── BatchingMessageRetrieverProperties.java │ │ └── StaticBatchingMessageRetrieverProperties.java │ │ └── prefetch │ │ ├── PrefetchingMessageFutureConsumerQueue.java │ │ ├── PrefetchingMessageRetriever.java │ │ ├── PrefetchingMessageRetrieverConstants.java │ │ ├── PrefetchingMessageRetrieverProperties.java │ │ ├── QueueDrain.java │ │ └── StaticPrefetchingMessageRetrieverProperties.java │ └── test │ ├── java │ └── com │ │ └── jashmore │ │ └── sqs │ │ ├── argument │ │ ├── CoreArgumentResolverServiceTest.java │ │ ├── DelegatingArgumentResolverServiceTest.java │ │ ├── attribute │ │ │ ├── MessageAttributeArgumentResolverTest.java │ │ │ └── MessageSystemAttributeArgumentResolverTest.java │ │ ├── message │ │ │ └── MessageArgumentResolverTest.java │ │ ├── messageid │ │ │ └── MessageIdArgumentResolverTest.java │ │ ├── payload │ │ │ ├── PayloadArgumentResolverTest.java │ │ │ ├── PayloadArgumentResolver_ProxyClassTest.java │ │ │ └── mapper │ │ │ │ └── JacksonPayloadMapperTest.java │ │ └── visibility │ │ │ └── DefaultVisibilityExtenderTest.java │ │ ├── broker │ │ ├── concurrent │ │ │ ├── ConcurrentMessageBrokerTest.java │ │ │ └── StaticConcurrentMessageBrokerPropertiesTest.java │ │ ├── grouping │ │ │ └── GroupingMessageBrokerTest.java │ │ └── util │ │ │ └── MessageBrokerTestUtils.java │ │ ├── client │ │ ├── DefaultPlaceholderQueueResolverTest.java │ │ └── DefaultSqsAsyncClientProviderTest.java │ │ ├── container │ │ └── CoreMessageListenerContainerTest.java │ │ ├── decorator │ │ └── AutoVisibilityExtenderMessageProcessingDecoratorTest.java │ │ ├── placeholder │ │ └── StaticPlaceholderResolverTest.java │ │ ├── processor │ │ ├── AsyncLambdaMessageProcessorTest.java │ │ ├── AsynchronousMessageListenerScenarios.java │ │ ├── CoreMessageProcessorTest.java │ │ ├── DecoratingMessageProcessorTest.java │ │ ├── LambdaMessageProcessorTest.java │ │ └── SynchronousMessageListenerScenarios.java │ │ ├── resolver │ │ └── batching │ │ │ └── BatchingMessageResolverTest.java │ │ ├── retriever │ │ ├── batching │ │ │ └── BatchingMessageRetrieverTest.java │ │ └── prefetch │ │ │ ├── PrefetchingMessageFutureConsumerQueueTest.java │ │ │ ├── PrefetchingMessageRetrieverTest.java │ │ │ └── StaticPrefetchingMessageRetrieverPropertiesTest.java │ │ ├── spring │ │ └── processor │ │ │ └── DecoratingMessageProcessorFactoryTest.java │ │ └── util │ │ └── thread │ │ └── ThreadTestUtils.java │ └── resources │ └── logback.xml ├── doc ├── core-implementations-overview.md ├── how-to-guides │ ├── core │ │ ├── core-how-to-add-aws-xray-tracing.md │ │ ├── core-how-to-add-brave-tracing.md │ │ ├── core-how-to-create-a-message-processing-decorator.md │ │ ├── core-how-to-extend-message-visibility-during-processing.md │ │ ├── core-how-to-implement-a-custom-argument-resolver.md │ │ ├── core-how-to-implement-a-custom-message-retrieval.md │ │ ├── core-how-to-mark-message-as-successfully-processed.md │ │ └── core-how-to-use-kotlin-dsl.md │ ├── how-to-connect-to-aws-sqs-queue.md │ ├── ktor │ │ └── ktor-how-to-register-message-listeners.md │ └── spring │ │ ├── spring-how-to-add-aws-xray-tracing.md │ │ ├── spring-how-to-add-brave-tracing.md │ │ ├── spring-how-to-add-custom-argument-resolver.md │ │ ├── spring-how-to-add-custom-message-processing-decorators.md │ │ ├── spring-how-to-add-own-queue-listener.md │ │ ├── spring-how-to-change-message-visibility.md │ │ ├── spring-how-to-connect-to-multiple-aws-accounts.md │ │ ├── spring-how-to-customise-argument-resolution.md │ │ ├── spring-how-to-extend-message-visibility-during-processing.md │ │ ├── spring-how-to-have-listener-dynamic-properties.md │ │ ├── spring-how-to-prevent-containers-starting-on-startup.md │ │ ├── spring-how-to-set-queue-listener-props-from-the-environment.md │ │ ├── spring-how-to-start-stop-message-listener-containers.md │ │ ├── spring-how-to-version-payload-schemas-using-spring-cloud-schema-registry.md │ │ └── spring-how-to-write-integration-tests.md ├── local-development │ └── setting-up-intellij.md └── resources │ ├── README.md │ ├── architecture_diagram.png │ └── architecture_diagram.xml ├── examples ├── auto-visibility-extender-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ ├── Application.java │ │ │ ├── MessageListeners.java │ │ │ └── ScheduledMessageProducer.java │ │ └── resources │ │ └── logback.xml ├── aws-xray-spring-example │ ├── README.md │ ├── assets │ │ ├── iam_access_key.png │ │ ├── iam_permissions.png │ │ └── sns_subscription.png │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ ├── Application.java │ │ │ ├── MessageListeners.java │ │ │ ├── ScheduledMessageProducer.java │ │ │ └── SomeService.java │ │ └── resources │ │ ├── application.yml │ │ └── logback.xml ├── core-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ └── ConcurrentBrokerExample.java │ │ └── resources │ │ └── logback.xml ├── core-kotlin-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ └── KotlinConcurrentBrokerExample.kt │ │ └── resources │ │ └── logback.xml ├── fifo-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ ├── Application.java │ │ │ ├── MessageListeners.java │ │ │ └── ScheduledMessageProducer.java │ │ └── resources │ │ └── logback.xml ├── ktor-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ └── KtorApplicationExample.kt │ │ └── resources │ │ └── logback.xml ├── micronaut-integration-test-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── examples │ │ │ │ └── integrationtests │ │ │ │ └── TestApplication.java │ │ └── resources │ │ │ ├── application.yaml │ │ │ └── logback.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── examples │ │ └── integrationtests │ │ └── SqsListenerExampleIntegrationTest.java ├── spring-aws-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── examples │ │ │ └── spring │ │ │ └── aws │ │ │ └── Application.java │ │ └── resources │ │ ├── application.yml │ │ └── logback.xml ├── spring-cloud-schema-registry-example │ ├── README.md │ ├── spring-cloud-schema-registry-consumer │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── examples │ │ │ │ └── schemaregistry │ │ │ │ └── ConsumerApplication.java │ │ │ └── resources │ │ │ ├── application.yml │ │ │ ├── avro │ │ │ └── sensor.avsc │ │ │ └── logback.xml │ ├── spring-cloud-schema-registry-producer-two │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── examples │ │ │ │ └── schemaregistry │ │ │ │ ├── MessageProducers.java │ │ │ │ └── ProducerTwoApplication.java │ │ │ └── resources │ │ │ ├── application.yml │ │ │ └── avro │ │ │ └── sensor.avsc │ └── spring-cloud-schema-registry-producer │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ └── schemaregistry │ │ │ ├── MessageProducers.java │ │ │ └── ProducerApplication.java │ │ └── resources │ │ ├── application.yml │ │ └── avro │ │ └── sensor.avsc ├── spring-integration-test-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── examples │ │ │ │ └── integrationtests │ │ │ │ └── IntegrationTestExampleApplication.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── logback.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── examples │ │ └── integrationtests │ │ └── SqsListenerExampleIntegrationTest.java ├── spring-multiple-aws-account-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── examples │ │ ├── Application.java │ │ ├── MessageListeners.java │ │ └── ScheduledMessageProducer.java ├── spring-sleuth-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ └── sleuth │ │ │ ├── Application.java │ │ │ └── MessageListeners.java │ │ └── resources │ │ ├── application.yml │ │ └── logback-spring.xml ├── spring-starter-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── examples │ │ │ ├── Application.java │ │ │ ├── MessageListeners.java │ │ │ ├── ScheduledMessageProducer.java │ │ │ └── ScheduledQueueListenerEnabler.java │ │ └── resources │ │ └── logback.xml └── spring-starter-minimal-example │ ├── README.md │ ├── build.gradle.kts │ └── src │ └── main │ ├── java │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── examples │ │ ├── Application.java │ │ ├── MessageListeners.java │ │ └── ScheduledMessageProducer.java │ └── resources │ └── logback.xml ├── extensions ├── aws-xray-extension │ ├── core │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── extensions │ │ │ │ └── xray │ │ │ │ ├── client │ │ │ │ ├── ClientSegmentMutator.java │ │ │ │ ├── ClientSegmentNamingStrategy.java │ │ │ │ ├── StaticClientSegmentNamingStrategy.java │ │ │ │ ├── UnsampledClientSegmentMutator.java │ │ │ │ └── XrayWrappedSqsAsyncClient.java │ │ │ │ └── decorator │ │ │ │ ├── BasicXrayMessageProcessingDecorator.java │ │ │ │ ├── DecoratorSegmentMutator.java │ │ │ │ ├── DecoratorSegmentNamingStrategy.java │ │ │ │ ├── DecoratorSubsegmentMutator.java │ │ │ │ ├── DecoratorSubsegmentNamingStrategy.java │ │ │ │ └── StaticDecoratorSegmentNamingStrategy.java │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── extensions │ │ │ └── xray │ │ │ ├── client │ │ │ ├── StaticClientSegmentNamingStrategyTest.java │ │ │ └── XrayWrappedSqsAsyncClientTest.java │ │ │ └── decorator │ │ │ └── BasicXrayMessageProcessingDecoratorTest.java │ └── spring-boot │ │ ├── build.gradle.kts │ │ └── src │ │ ├── integrationTest │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── extensions │ │ │ └── xray │ │ │ └── XrayExtensionIntegrationTest.java │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── extensions │ │ │ │ └── xray │ │ │ │ └── spring │ │ │ │ └── SqsListenerXrayConfiguration.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── extensions │ │ └── xray │ │ └── decorator │ │ └── SqsListenerXrayConfigurationTest.java ├── brave-extension │ ├── core │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── extensions │ │ │ │ └── brave │ │ │ │ └── decorator │ │ │ │ └── BraveMessageProcessingDecorator.java │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── extensions │ │ │ └── brave │ │ │ └── decorator │ │ │ └── BraveMessageProcessingDecoratorTest.java │ └── spring-boot │ │ ├── build.gradle.kts │ │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── extensions │ │ │ │ └── brave │ │ │ │ └── spring │ │ │ │ └── BraveMessageProcessingDecoratorConfiguration.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── spring │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── extensions │ │ └── brave │ │ ├── BraveMessageProcessingDecoratorAsynchronousIntegrationTest.java │ │ └── BraveMessageProcessingDecoratorIntegrationTest.java ├── core-kotlin-dsl │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── core │ │ │ └── kotlin │ │ │ └── dsl │ │ │ ├── MessageListenerDsl.kt │ │ │ ├── argument │ │ │ ├── CoreArgumentResolverServiceDslBuilder.kt │ │ │ └── DelegatingArgumentResolverServiceDslBuilder.kt │ │ │ ├── broker │ │ │ ├── ConcurrentMessageBrokerDslBuilder.kt │ │ │ └── GroupingMessageBrokerDslBuilder.kt │ │ │ ├── container │ │ │ ├── AbstractMessageListenerContainerDslBuilder.kt │ │ │ ├── BatchingMessageListenerContainerDslBuilder.kt │ │ │ ├── CoreMessageListenerContainerDslBuilder.kt │ │ │ ├── FifoMessageListenerContainerDslBuilder.kt │ │ │ └── PrefetchingMessageListenerContainerDslBuilder.kt │ │ │ ├── processor │ │ │ ├── AsyncLambdaMessageProcessorDslBuilder.kt │ │ │ ├── CoreMessageProcessorDslBuilder.kt │ │ │ ├── DecoratedMessageProcessorDslBuilder.kt │ │ │ └── LambdaMessageProcessorDslBuilder.kt │ │ │ ├── resolver │ │ │ └── BatchingMessageResolverDslBuilder.kt │ │ │ ├── retriever │ │ │ ├── BatchingMessageRetrieverDslBuilder.kt │ │ │ └── PrefetchingMessageRetrieverDslBuilder.kt │ │ │ └── utils │ │ │ ├── CachingDslUtils.kt │ │ │ └── RequiredFieldException.kt │ │ └── test │ │ ├── kotlin │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── core │ │ │ └── kotlin │ │ │ └── dsl │ │ │ ├── argument │ │ │ └── DelegatingArgumentResolverServiceDslBuilderTest.kt │ │ │ ├── broker │ │ │ ├── ConcurrentMessageBrokerDslBuilderTest.kt │ │ │ └── GroupingMessageBrokerDslBuilderTest.kt │ │ │ ├── container │ │ │ ├── BatchingMessageListenerContainerDslBuilderTest.kt │ │ │ ├── CoreMessageListenerContainerDslBuilderTest.kt │ │ │ ├── FifoMessageListenerContainerDslBuilderTest.kt │ │ │ └── PrefetchingMessageListenerContainerDslBuilderTest.kt │ │ │ ├── processor │ │ │ ├── AsyncLambdaMessageProcessorDslBuilderTest.kt │ │ │ ├── CoreMessageProcessorDslBuilderTest.kt │ │ │ └── LambdaMessageProcessorDslBuilderTest.kt │ │ │ ├── resolver │ │ │ └── BatchingMessageResolverDslBuilderTest.kt │ │ │ ├── retriever │ │ │ ├── BatchingMessageRetrieverDslBuilderTest.kt │ │ │ └── PrefetchingMessageRetrieverDslBuilderTest.kt │ │ │ └── utils │ │ │ └── CachingDslUtilsTest.kt │ │ └── resources │ │ └── logback.xml └── spring-cloud-schema-registry-extension │ ├── README.md │ ├── avro-spring-cloud-schema-registry-extension │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── extensions │ │ │ └── registry │ │ │ └── avro │ │ │ ├── AvroClasspathConsumerSchemaRetriever.java │ │ │ ├── AvroMessagePayloadDeserializer.java │ │ │ ├── AvroSchemaProcessingException.java │ │ │ ├── AvroSchemaRegistryProducerSchemaRetriever.java │ │ │ ├── AvroSpringCloudSchemaProperties.java │ │ │ ├── AvroSqsSpringCloudSchemaRegistryConfiguration.java │ │ │ └── EnableSchemaRegistrySqsExtension.java │ │ └── test │ │ ├── java │ │ ├── com │ │ │ └── jashmore │ │ │ │ └── sqs │ │ │ │ └── extensions │ │ │ │ └── registry │ │ │ │ └── avro │ │ │ │ ├── AvroClasspathConsumerSchemaRetrieverTest.java │ │ │ │ ├── AvroMessagePayloadDeserializerTest.java │ │ │ │ └── AvroSchemaRegistryProducerSchemaRetrieverTest.java │ │ └── it │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── extensions │ │ │ └── registry │ │ │ └── avro │ │ │ └── AvroSpringCloudSchemaRegistryIntegrationTest.java │ │ └── resources │ │ ├── application.yml │ │ ├── avro-non-generated-test-schemas │ │ └── non-built-schema.avsc │ │ └── avro-test-schemas │ │ ├── import │ │ └── author.avsc │ │ └── schema │ │ └── book.avsc │ ├── in-memory-spring-cloud-schema-registry │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── extensions │ │ └── registry │ │ └── InMemorySchemaRegistryClient.java │ └── spring-cloud-schema-registry-extension-api │ ├── README.md │ ├── build.gradle.kts │ └── src │ ├── main │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── extensions │ │ └── registry │ │ ├── ConsumerSchemaRetriever.java │ │ ├── ConsumerSchemaRetrieverException.java │ │ ├── InMemoryCachingProducerSchemaRetriever.java │ │ ├── MessageAttributeSchemaReferenceExtractor.java │ │ ├── MessagePayloadDeserializer.java │ │ ├── MessagePayloadDeserializerException.java │ │ ├── ProducerSchemaRetriever.java │ │ ├── ProducerSchemaRetrieverException.java │ │ ├── SchemaReferenceExtractor.java │ │ ├── SchemaReferenceExtractorException.java │ │ ├── SpringCloudSchemaArgumentResolver.java │ │ ├── SpringCloudSchemaRegistryPayload.java │ │ └── SpringCloudSchemaSqsConfiguration.java │ └── test │ └── java │ └── com │ └── jashmore │ └── sqs │ └── extensions │ └── registry │ ├── InMemoryCachingProducerSchemaRetrieverTest.java │ ├── MessageAttributeSchemaReferenceExtractorTest.java │ └── SpringCloudSchemaArgumentResolverTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ktor └── core │ ├── build.gradle.kts │ └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── ktor │ │ └── container │ │ └── KtorCoreExtension.kt │ └── test │ ├── kotlin │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── ktor │ │ └── container │ │ └── KtorCoreExtensionTest.kt │ └── resources │ └── logback.xml ├── lombok.config ├── micronaut ├── micronaut-core │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ ├── argument │ │ │ ├── MessageArgumentResolutionMicronautIntegrationTest.java │ │ │ ├── MessageAttributeMicronautIntegrationTest.java │ │ │ └── MessageSystemAttributeMicronautIntegrationTest.java │ │ │ ├── client │ │ │ └── MultipleSqsAsyncClientIntegrationTest.java │ │ │ ├── container │ │ │ ├── basic │ │ │ │ ├── QueueListenerEnvironmentIntegrationTest.java │ │ │ │ ├── QueueListenerIntegrationTest.java │ │ │ │ ├── QueueListenerMessageDecoratorIntegrationTest.java │ │ │ │ └── QueueListenerVisibilityIntegrationTest.java │ │ │ ├── fifo │ │ │ │ └── FifoMessageListenerContainerFactoryTest.java │ │ │ └── prefetch │ │ │ │ ├── PrefetchingQueueListenerEnvironmentIntegrationTest.java │ │ │ │ ├── PrefetchingQueueListenerIntegrationTest.java │ │ │ │ ├── PrefetchingQueueListenerMessageDecoratorIntegrationTest.java │ │ │ │ └── PrefetchingQueueListenerVisibilityIntegrationTest.java │ │ │ ├── decorator │ │ │ └── visibilityextender │ │ │ │ └── AutoVisibilityExtenderMessageProcessingDecoratorFactoryIntegrationTest.java │ │ │ ├── proxy │ │ │ └── MicronautProxyBeanQueueListenerResolutionIntegrationTest.java │ │ │ ├── queue │ │ │ └── EnvironmentQueueResolverIntegrationTest.java │ │ │ └── service │ │ │ └── MicronautMessageListenerContainerCoordinatorIntegrationTest.java │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── micronaut │ │ │ ├── configuration │ │ │ └── QueueListenerConfiguration.java │ │ │ ├── container │ │ │ ├── MicronautListenerMethodProcessor.java │ │ │ ├── MicronautMessageListenerContainerCoordinator.java │ │ │ ├── MicronautMessageListenerContainerCoordinatorProperties.java │ │ │ └── MicronautMessageListenerContainerRegistry.java │ │ │ ├── jackson │ │ │ └── SqsListenerObjectMapperSupplier.java │ │ │ ├── placeholder │ │ │ └── MicronautPlaceholderResolver.java │ │ │ └── queue │ │ │ └── DefaultQueueResolver.java │ │ └── test │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── micronaut │ │ ├── container │ │ ├── MicronautListenerMethodProcessorTest.java │ │ ├── MicronautMessageListenerContainerCoordinatorTest.java │ │ └── MicronautMessageListenerContainerRegistryTest.java │ │ └── queue │ │ └── DefaultQueueResolverTest.java └── micronaut-inject-java │ ├── build.gradle.kts │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── micronaut │ │ │ └── service │ │ │ ├── FifoQueueListenerAnnotationTransformer.java │ │ │ ├── PrefetchingQueueListenerAnnotationTransformer.java │ │ │ └── QueueListenerAnnotationTransformer.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.micronaut.inject.annotation.AnnotationTransformer │ └── test │ └── java │ └── com │ └── jashmore │ └── sqs │ └── micronaut │ └── service │ ├── FifoQueueListenerAnnotationTransformerTest.java │ ├── PrefetchingQueueListenerAnnotationTransformerTest.java │ └── QueueListenerAnnotationTransformerTest.java ├── package.json ├── renovate.json ├── settings.gradle.kts ├── spring ├── spring-core │ ├── build.gradle.kts │ └── src │ │ ├── integrationTest │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ ├── argument │ │ │ ├── MessageArgumentResolutionSpringIntegrationTest.java │ │ │ ├── MessageAttributeSpringIntegrationTest.java │ │ │ └── MessageSystemAttributeSpringIntegrationTest.java │ │ │ ├── client │ │ │ └── MultipleSqsAsyncClientIntegrationTest.java │ │ │ ├── container │ │ │ ├── basic │ │ │ │ ├── QueueListenerEnvironmentIntegrationTest.java │ │ │ │ ├── QueueListenerIntegrationTest.java │ │ │ │ ├── QueueListenerMessageDecoratorIntegrationTest.java │ │ │ │ └── QueueListenerVisibilityIntegrationTest.java │ │ │ ├── fifo │ │ │ │ └── FifoMessageListenerContainerFactoryTest.java │ │ │ └── prefetch │ │ │ │ ├── PrefetchingQueueListenerEnvironmentIntegrationTest.java │ │ │ │ ├── PrefetchingQueueListenerIntegrationTest.java │ │ │ │ ├── PrefetchingQueueListenerMessageDecoratorIntegrationTest.java │ │ │ │ └── PrefetchingQueueListenerVisibilityIntegrationTest.java │ │ │ ├── decorator │ │ │ └── visibilityextender │ │ │ │ └── AutoVisibilityExtenderMessageProcessingDecoratorFactoryIntegrationTest.java │ │ │ ├── proxy │ │ │ └── ProxyBeanQueueListenerResolutionIntegrationTest.java │ │ │ ├── queue │ │ │ └── EnvironmentQueueResolverIntegrationTest.java │ │ │ └── service │ │ │ └── SpringMessageListenerContainerCoordinatorIntegrationTest.java │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── spring │ │ │ ├── config │ │ │ └── QueueListenerConfiguration.java │ │ │ ├── container │ │ │ ├── SpringMessageListenerContainerCoordinator.java │ │ │ ├── SpringMessageListenerContainerCoordinatorProperties.java │ │ │ └── StaticSpringMessageListenerContainerCoordinatorProperties.java │ │ │ ├── jackson │ │ │ └── SqsListenerObjectMapperSupplier.java │ │ │ └── placeholder │ │ │ └── SpringPlaceholderResolver.java │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── spring │ │ │ ├── config │ │ │ └── QueueListenerConfigurationTest.java │ │ │ ├── container │ │ │ └── SpringMessageListenerContainerCoordinatorTest.java │ │ │ └── placeholder │ │ │ └── SpringPlaceholderResolverTest.java │ │ └── resources │ │ ├── logback.xml │ │ └── slf4jtest.properties └── spring-starter │ ├── build.gradle.kts │ └── src │ ├── main │ └── resources │ │ └── META-INF │ │ └── spring │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ └── test │ └── java │ └── com │ └── jashmore │ └── sqs │ ├── SpringObjectMapperIntegrationTest.java │ └── SqsSpringStarterIntegrationTest.java ├── util ├── annotation-utils │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── util │ │ │ └── annotation │ │ │ └── AnnotationUtils.java │ │ └── test │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── util │ │ └── annotation │ │ ├── AnnotationUtils_MethodAnnotationsTest.java │ │ └── AnnotationUtils_ParameterAnnotationsTest.java ├── avro-spring-cloud-schema-registry-sqs-client │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── registry │ │ └── AvroSchemaRegistrySqsAsyncClient.java ├── common-utils │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── util │ │ │ ├── Preconditions.java │ │ │ ├── ResizableSemaphore.java │ │ │ ├── collections │ │ │ ├── CollectionUtils.java │ │ │ └── QueueUtils.java │ │ │ ├── concurrent │ │ │ └── CompletableFutureUtils.java │ │ │ ├── identifier │ │ │ └── IdentifierUtils.java │ │ │ ├── properties │ │ │ └── PropertyUtils.java │ │ │ ├── string │ │ │ └── StringUtils.java │ │ │ └── thread │ │ │ └── ThreadUtils.java │ │ └── test │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── util │ │ ├── PreconditionsTest.java │ │ ├── ResizableSemaphoreTest.java │ │ ├── collections │ │ ├── CollectionUtilsTest.java │ │ └── QueueUtilsTest.java │ │ ├── concurrent │ │ └── CompletableFutureUtilsTest.java │ │ ├── identifier │ │ └── IdentifierUtilsTest.java │ │ ├── properties │ │ └── PropertyUtilsTest.java │ │ ├── string │ │ └── StringUtilsTest.java │ │ └── thread │ │ └── ThreadUtilsTest.java ├── documentation-annotations │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── documentation │ │ └── annotations │ │ ├── GuardedBy.java │ │ ├── Max.java │ │ ├── Min.java │ │ ├── Nonnull.java │ │ ├── NotThreadSafe.java │ │ ├── Nullable.java │ │ ├── Positive.java │ │ ├── PositiveOrZero.java │ │ ├── ThreadSafe.java │ │ └── VisibleForTesting.java ├── elasticmq-sqs-client │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── elasticmq │ │ └── ElasticMqSqsAsyncClient.java ├── expected-test-exception │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── util │ │ └── ExpectedTestException.java ├── local-sqs-async-client │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── util │ │ │ ├── CreateRandomQueueResponse.java │ │ │ ├── LocalSqsAsyncClient.java │ │ │ ├── LocalSqsAsyncClientImpl.java │ │ │ └── SqsQueuesConfig.java │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── jashmore │ │ │ └── sqs │ │ │ └── util │ │ │ └── LocalSqsAsyncClientImplTest.java │ │ └── resources │ │ └── logback.xml ├── proxy-method-interceptor │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── util │ │ └── ProxyMethodInterceptor.java └── sqs-brave-tracing │ ├── build.gradle.kts │ └── src │ ├── integrationTest │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── brave │ │ ├── SendMessageBatchTracingExecutionInterceptorIntegrationTest.java │ │ └── SendMessageTracingExecutionInterceptorIntegrationTest.java │ ├── main │ └── java │ │ └── com │ │ └── jashmore │ │ └── sqs │ │ └── brave │ │ ├── SendMessageBatchTracingExecutionInterceptor.java │ │ ├── SendMessageTracingExecutionInterceptor.java │ │ └── propogation │ │ ├── SendMessageRemoteGetter.java │ │ └── SendMessageRemoteSetter.java │ └── test │ └── java │ └── com │ └── jashmore │ └── sqs │ └── brave │ ├── SendMessageBatchTracingExecutionInterceptorTest.java │ ├── SendMessageTracingExecutionInterceptorTest.java │ └── propogation │ ├── SendMessageRemoteGetterTest.java │ └── SendMessageRemoteSetterTest.java └── yarn.lock /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: [push, pull_request] # pull_request needed for coveralls 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Install Yarn 11 | uses: borales/actions-yarn@v4 12 | with: 13 | cmd: install 14 | - name: Markdown link checks and code formatting 15 | uses: borales/actions-yarn@v4 16 | with: 17 | cmd: test 18 | - name: Set up JDK 17 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 17 22 | - name: Build with Gradle 23 | uses: eskatos/gradle-command-action@v1 24 | env: 25 | CI: true 26 | with: 27 | arguments: build 28 | - name: Coverage Report 29 | uses: eskatos/gradle-command-action@v1 30 | env: 31 | CI: true 32 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 33 | with: 34 | arguments: jacocoRootReport coveralls 35 | -------------------------------------------------------------------------------- /.github/workflows/release-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Release Snapshot 2 | on: [workflow_dispatch] 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Set up JDK 17 10 | uses: actions/setup-java@v1 11 | with: 12 | java-version: 17 13 | - name: Build with Gradle 14 | uses: eskatos/gradle-command-action@v1 15 | env: 16 | CI: true 17 | OSS_SONATYPE_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }} 18 | OSS_SONATYPE_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }} 19 | GPG_SIGNING_KEY_ASCII_ARMORED_FORMAT: ${{ secrets.GPG_SIGNING_KEY_ASCII_ARMORED_FORMAT }} 20 | GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }} 21 | with: 22 | arguments: assemble publishMavenJavaPublicationToMavenRepository 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | target/ 4 | 5 | # Gradle 6 | build 7 | .gradle 8 | out 9 | target 10 | 11 | # Node 12 | node_modules 13 | 14 | # Log file 15 | *.log 16 | 17 | # BlueJ files 18 | *.ctxt 19 | 20 | # Mobile Tools for Java (J2ME) 21 | .mtj.tmp/ 22 | 23 | # Package Files # 24 | *.jar 25 | *.war 26 | *.nar 27 | *.ear 28 | *.zip 29 | *.tar.gz 30 | *.rar 31 | 32 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 33 | hs_err_pid* 34 | 35 | # IntelliJ 36 | .idea 37 | *.iml 38 | 39 | # Java Dumps 40 | *.hprof 41 | 42 | # Yarn 43 | .yarn/* 44 | !.yarn/cache 45 | !.yarn/patches 46 | !.yarn/plugins 47 | !.yarn/releases 48 | !.yarn/sdks 49 | !.yarn/versions 50 | 51 | !gradle-wrapper.jar 52 | 53 | .micronaut/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.markdownlinkcheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "http://localhost.*" 5 | }, 6 | { 7 | "pattern": "mailto:*" 8 | }, 9 | { 10 | "pattern": "https://coveralls.io.*" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | out 3 | *.iml 4 | .idea 5 | gradle/**/* 6 | .gradle 7 | bin 8 | target 9 | gradlew 10 | gradlew.bat 11 | LICENSE 12 | yarn.lock 13 | spring.factories 14 | lombok.config 15 | *.png 16 | *.properties 17 | .prettierignore 18 | .gitignore 19 | 20 | # Kotlin is handled by ktlint plugin 21 | *.kt 22 | *.kts 23 | 24 | .husky 25 | 26 | org.springframework.boot.autoconfigure.AutoConfiguration.imports 27 | io.micronaut.inject.annotation.AnnotationTransformer 28 | 29 | doc/resources/**/*.xml 30 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-java", "@prettier/plugin-xml"], 3 | "printWidth": 140, 4 | "tabWidth": 4, 5 | "useTabs": false, 6 | "trailingComma": "none" 7 | } 8 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.6.1.cjs 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | You are welcome to contribute to the project by forking the repository and sending pull requests to the master branch. Please follow the style of the 4 | surrounding code to keep it consistent. 5 | 6 | ## Requirements for the PR 7 | 8 | 1. Please include in the description a thorough explanation of what you are changing and why this change is necessary. 9 | 1. I would prefer to see more comments than less as comments that may not be deemed necessary now could be extremely beneficial for the future for 10 | understanding why it was done a certain way. 11 | 1. When writing tests, make sure to: 12 | a. Only test one bit of functionality per test and make sure the name of the test aligns with this 13 | b. Tests should be split into arrange, act and assert for readability if possible 14 | c. Unit test coverage should be high and try to cover all edge cases and race conditions 15 | 16 | ## License 17 | 18 | By contributing to this code base you are agreeing to the terms of the [MIT license](./LICENSE). All files included in this project 19 | are included within this license. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 JaidenAshmore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.x | :white_check_mark: | 11 | | 4.x | :x: | 12 | | 3.x | :x: | 13 | 14 | ## Reporting a Vulnerability 15 | 16 | Please email any security vulnerabilities to . 17 | -------------------------------------------------------------------------------- /annotations/README.md: -------------------------------------------------------------------------------- 1 | # Java Dynamic SQS Listener Annotations 2 | 3 | Wrapper around the core library that allows for setting up using annotations to simplify the usage. 4 | 5 | ## More Information 6 | 7 | For more information you can look at the root project [README.md](../README.md) which provides more information about the architecture 8 | of the application. The [API](../api) is also a good location to find more information about what each part of the framework is how 9 | they interact with each other. 10 | -------------------------------------------------------------------------------- /annotations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains a way to attach message listeners via annotations" 3 | 4 | dependencies { 5 | api(project(":java-dynamic-sqs-listener-core")) 6 | implementation(project(":common-utils")) 7 | implementation(project(":annotation-utils")) 8 | compileOnly(project(":documentation-annotations")) 9 | 10 | testImplementation(project(":elasticmq-sqs-client")) 11 | } 12 | -------------------------------------------------------------------------------- /api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "This contains the api functionality for the SQS Listener. This can be used for consumer to implement themselves" 2 | 3 | val awsVersion: String by project 4 | 5 | dependencies { 6 | api(platform("software.amazon.awssdk:bom:${awsVersion}")) 7 | api("software.amazon.awssdk:sqs") 8 | compileOnly(project(":documentation-annotations")) 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/QueueProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs; 2 | 3 | import lombok.Builder; 4 | import lombok.Value; 5 | import lombok.experimental.NonFinal; 6 | 7 | /** 8 | * Contains information about the queue that will be listened against. 9 | */ 10 | @Value 11 | @NonFinal 12 | @Builder 13 | public class QueueProperties { 14 | 15 | /** 16 | * The URL of the queue that can be used by the Amazon clients to add, remove messages etc. 17 | */ 18 | String queueUrl; 19 | } 20 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/argument/ArgumentResolutionException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument; 2 | 3 | /** 4 | * Exception thrown when there was an error resolving an argument for the method. 5 | * 6 | *

This can occur if a payload is trying to be resolved but the message does not conform and cannot be correctly created. For example, 7 | * you have a Java Bean being built from the message body but the message is not in the correct format. 8 | */ 9 | public class ArgumentResolutionException extends RuntimeException { 10 | 11 | public ArgumentResolutionException(final String message) { 12 | super(message); 13 | } 14 | 15 | public ArgumentResolutionException(final String message, final Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public ArgumentResolutionException(final Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/argument/ArgumentResolverService.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | import com.jashmore.sqs.processor.MessageProcessor; 5 | import com.jashmore.sqs.processor.argument.Acknowledge; 6 | import com.jashmore.sqs.processor.argument.VisibilityExtender; 7 | 8 | /** 9 | * Service used to determine the {@link ArgumentResolver} that can be applied to a parameter of a method when it is executed during processing of 10 | * a message. 11 | * 12 | *

As there could be multiple messages all being processed at once, and therefore resolving many arguments concurrently, 13 | * the implementations of this class must be thread safe. 14 | */ 15 | @ThreadSafe 16 | public interface ArgumentResolverService { 17 | /** 18 | * Determine the {@link ArgumentResolver} that should be used for processing an argument of the method. 19 | * 20 | *

Note that this does not resolve the {@link Acknowledge} or {@link VisibilityExtender} argument as that is provided by the {@link MessageProcessor}. 21 | * 22 | * @param methodParameter details about the method parameter 23 | * @return the resolver that should be used to resolve this parameter 24 | * @throws UnsupportedArgumentResolutionException if there is no available {@link ArgumentResolver} 25 | */ 26 | ArgumentResolver getArgumentResolver(MethodParameter methodParameter) throws UnsupportedArgumentResolutionException; 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/argument/DefaultMethodParameter.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument; 2 | 3 | import com.jashmore.documentation.annotations.PositiveOrZero; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Parameter; 6 | import lombok.Builder; 7 | 8 | /** 9 | * Default implementation of the {@link MethodParameter}, nothing special about it. 10 | */ 11 | @Builder 12 | public class DefaultMethodParameter implements MethodParameter { 13 | 14 | private final Method method; 15 | private final Parameter parameter; 16 | private final int parameterIndex; 17 | 18 | public DefaultMethodParameter(final Method method, final Parameter parameter, @PositiveOrZero final int parameterIndex) { 19 | this.method = method; 20 | this.parameter = parameter; 21 | this.parameterIndex = parameterIndex; 22 | } 23 | 24 | @Override 25 | public Method getMethod() { 26 | return method; 27 | } 28 | 29 | @Override 30 | public Parameter getParameter() { 31 | return parameter; 32 | } 33 | 34 | @Override 35 | @PositiveOrZero 36 | public int getParameterIndex() { 37 | return parameterIndex; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/argument/MethodParameter.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument; 2 | 3 | import com.jashmore.documentation.annotations.PositiveOrZero; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Parameter; 6 | 7 | /** 8 | * Represents details about the method parameter that needs to have the argument resolved. 9 | */ 10 | public interface MethodParameter { 11 | /** 12 | * Get the method that this parameter exists on. 13 | * 14 | * @return the method for the parameter 15 | */ 16 | Method getMethod(); 17 | 18 | /** 19 | * The parameter for the method. 20 | * 21 | * @return the parameter for the method 22 | */ 23 | Parameter getParameter(); 24 | 25 | /** 26 | * The index of the parameter in the method parameter list. 27 | * 28 | * @return the index of the parameter for the method parameter list 29 | */ 30 | @PositiveOrZero 31 | int getParameterIndex(); 32 | } 33 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/argument/UnsupportedArgumentResolutionException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument; 2 | 3 | /** 4 | * Exception thrown if there is no available {@link ArgumentResolver} that is able to resolve the provided parameter for the method. 5 | */ 6 | public class UnsupportedArgumentResolutionException extends RuntimeException { 7 | 8 | public UnsupportedArgumentResolutionException() { 9 | super("Unable to resolve message argument"); 10 | } 11 | 12 | public UnsupportedArgumentResolutionException(final MethodParameter methodParameter) { 13 | super( 14 | String.format( 15 | "No eligible ArgumentResolver for parameter[%d] for method: %s", 16 | methodParameter.getParameterIndex(), 17 | methodParameter.getMethod().getDeclaringClass().getName() + "#" + methodParameter.getMethod().getName() 18 | ) 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/aws/AwsConstants.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.aws; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | @UtilityClass 6 | public final class AwsConstants { 7 | 8 | /** 9 | * AWS has a maximum wait time of for receiving a message and some implementations like ElasticMQ will 10 | * throw errors for any value greater than this. 11 | */ 12 | public static final int MAX_SQS_RECEIVE_WAIT_TIME_IN_SECONDS = 20; 13 | 14 | /** 15 | * This is the limit imposed by SQS for the maximum number of messages that can be obtained from a single request. 16 | */ 17 | public static final int MAX_NUMBER_OF_MESSAGES_FROM_SQS = 10; 18 | 19 | /** 20 | * This is the limit imposed by SQS for the maximum number of messages that can be handled in a batch request, like batch delete. 21 | * 22 | * @see software.amazon.awssdk.services.sqs.SqsAsyncClient#deleteMessageBatch(software.amazon.awssdk.services.sqs.model.DeleteMessageBatchRequest) 23 | * for an example batching endpoint 24 | */ 25 | public static final int MAX_NUMBER_OF_MESSAGES_IN_BATCH = 10; 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/client/QueueResolutionException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.client; 2 | 3 | /** 4 | * Exception thrown when there was a problem resolving the URL for a queue. 5 | */ 6 | public class QueueResolutionException extends RuntimeException { 7 | 8 | public QueueResolutionException(final Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/client/QueueResolver.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.client; 2 | 3 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 4 | 5 | /** 6 | * Used to determine the Queue URL for a SQS client based on the name or URL. 7 | */ 8 | public interface QueueResolver { 9 | /** 10 | * Resolve the queue URL for a queue. 11 | * 12 | *

This takes in a string that can contain parameters that can be resolved by the environment, for example properties in the 13 | * application.yml. If the resolved string is a URL that will be returned, otherwise it will assume it is the queue name and will call into SQS 14 | * to determine the queue URL for that queue. 15 | * 16 | *

Some example queueNameOrUrls that can be passed in are: 17 | *

22 | * 23 | * @param client the client that can be used to get information about the queue 24 | * @param queueNameOrUrl queueName or queueUrl that may have parameterised placeholders in it. 25 | * @return the resolved url of the queue if it exists 26 | * @throws QueueResolutionException if there was an error resolving the queue URL 27 | */ 28 | String resolveQueueUrl(SqsAsyncClient client, String queueNameOrUrl) throws QueueResolutionException; 29 | } 30 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/container/MessageListenerContainerCoordinator.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.container; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * Service that can be injected into the Spring Application to start and stop the containers that are controlling 7 | * the consumption of queue messages. 8 | */ 9 | public interface MessageListenerContainerCoordinator { 10 | /** 11 | * Get all the containers that have been configured with this coordinator. 12 | * 13 | * @return the set of containers 14 | */ 15 | Set getContainers(); 16 | 17 | /** 18 | * Start all of the containers that have been built for the application. 19 | */ 20 | void startAllContainers(); 21 | 22 | /** 23 | * Start a container with the given identifier. 24 | * 25 | * @param queueIdentifier the identifier for the queue 26 | * @throws IllegalArgumentException if there is not a container with the given identifier 27 | */ 28 | void startContainer(String queueIdentifier); 29 | 30 | /** 31 | * Stop all the containers that have been built for the application. 32 | */ 33 | void stopAllContainers(); 34 | 35 | /** 36 | * Stop a container with the given identifier. 37 | * 38 | * @param queueIdentifier the identifier for the queue 39 | * @throws IllegalArgumentException if there is not a container with the given identifier 40 | */ 41 | void stopContainer(String queueIdentifier); 42 | } 43 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/container/MessageListenerContainerFactory.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.container; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Optional; 5 | 6 | /** 7 | * Wrapper used to analyse methods in the application and determine if the method should be included in the messaging framework by wrapping that method in a 8 | * queue listener so that each message received will run the method. 9 | */ 10 | public interface MessageListenerContainerFactory { 11 | /** 12 | * Builds a {@link MessageListenerContainer} that will wrap the method and all messages for a queue will execute this method. 13 | * 14 | * @param bean the specific bean for this method 15 | * @param method the method of the bean that will be run for each message 16 | * @return the container that will wrap this method if it can 17 | * @throws MessageListenerContainerInitialisationException if there was a known error building this container 18 | */ 19 | Optional buildContainer(Object bean, Method method) throws MessageListenerContainerInitialisationException; 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/container/MessageListenerContainerInitialisationException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.container; 2 | 3 | /** 4 | * Exception that is thrown when there is a known error initialising a {@link com.jashmore.sqs.container.MessageListenerContainer}. 5 | */ 6 | public class MessageListenerContainerInitialisationException extends RuntimeException { 7 | 8 | public MessageListenerContainerInitialisationException(final String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/decorator/MessageProcessingDecoratorFactory.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.decorator; 2 | 3 | import com.jashmore.sqs.QueueProperties; 4 | import java.lang.reflect.Method; 5 | import java.util.Optional; 6 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 7 | 8 | /** 9 | * Factory used to build a {@link MessageProcessingDecorator} that will be attached to a specific message listener method. 10 | * 11 | *

This is separate to any global {@link MessageProcessingDecorator}s, which can be defined as beans in the Spring Application and attached to all the 12 | * message listeners. 13 | */ 14 | public interface MessageProcessingDecoratorFactory { 15 | /** 16 | * Build the optional decorator for the provided method. 17 | * 18 | * @param sqsAsyncClient the client that is used for this message listener 19 | * @param queueProperties details about the queue 20 | * @param bean the bean object that the message listener is running on 21 | * @param method the message listener method 22 | * @return an optional decorator to attach to the message listener 23 | * @throws MessageProcessingDecoratorFactoryException if the decorator cannot be built due to the method being in the incorrect format 24 | * or if an error occurred building the decorator 25 | */ 26 | Optional buildDecorator( 27 | SqsAsyncClient sqsAsyncClient, 28 | QueueProperties queueProperties, 29 | String identifier, 30 | Object bean, 31 | Method method 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/decorator/MessageProcessingDecoratorFactoryException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.decorator; 2 | 3 | /** 4 | * An exception thrown if there was an error building the {@link MessageProcessingDecorator} or if the method being 5 | * wrapped is not in the correct shape. 6 | */ 7 | @SuppressWarnings("unused") 8 | public class MessageProcessingDecoratorFactoryException extends RuntimeException { 9 | 10 | public MessageProcessingDecoratorFactoryException() {} 11 | 12 | public MessageProcessingDecoratorFactoryException(final String message) { 13 | super(message); 14 | } 15 | 16 | public MessageProcessingDecoratorFactoryException(final String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public MessageProcessingDecoratorFactoryException(final Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | public MessageProcessingDecoratorFactoryException( 25 | final String message, 26 | final Throwable cause, 27 | final boolean enableSuppression, 28 | final boolean writableStackTrace 29 | ) { 30 | super(message, cause, enableSuppression, writableStackTrace); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/processor/MessageProcessingException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.processor; 2 | 3 | /** 4 | * Exception thrown when the {@link MessageProcessor} has an exception processing a message. 5 | */ 6 | public class MessageProcessingException extends RuntimeException { 7 | 8 | public MessageProcessingException(final String message) { 9 | super(message); 10 | } 11 | 12 | public MessageProcessingException(final String message, final Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public MessageProcessingException(final Throwable cause) { 17 | super(cause); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/src/main/java/com/jashmore/sqs/processor/argument/Acknowledge.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.processor.argument; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | /** 6 | * Used to acknowledge the completion of a message being processed by the message consumer. 7 | * 8 | *

If the message consumer has a parameter with this type it will indicate to the {@link com.jashmore.sqs.processor.MessageProcessor} that it should 9 | * not automatically mark the message as a success on the completion of the method without an exception being thrown. Instead the message consumer must call 10 | * {@link Acknowledge#acknowledgeSuccessful()} to mark the message as completed. 11 | */ 12 | public interface Acknowledge { 13 | /** 14 | * Acknowledge that the message was successfully completed, which will result in it being removed from the queue. 15 | * 16 | *

Multiple calls to this has indeterminate behaviour and should not be done. 17 | * 18 | * @return the future that will be resolved if the message was successfully acknowledged 19 | */ 20 | CompletableFuture acknowledgeSuccessful(); 21 | } 22 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenCentral() 3 | jcenter() 4 | maven(url ="https://plugins.gradle.org/m2/") 5 | } 6 | 7 | plugins { 8 | `kotlin-dsl` 9 | } 10 | 11 | dependencies { 12 | implementation("gradle.plugin.org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.12.2") 13 | implementation("io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0") 14 | implementation(gradleKotlinDsl()) 15 | } 16 | 17 | kotlinDslPluginOptions { 18 | experimentalWarning.set(false) 19 | } 20 | 21 | tasks.withType().configureEach { 22 | kotlinOptions.jvmTarget = "17" 23 | } 24 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/jashmore/gradle/utils/VersionUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jashmore.gradle.utils 2 | 3 | fun isSnapshotVersion(version: String) = version.endsWith("-SNAPSHOT") -------------------------------------------------------------------------------- /buildSrc/src/main/resources/META-INF/gradle-plugins/com.jashmore.gradle.jacoco-coveralls.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.jashmore.gradle.JacocoCoverallsPlugin -------------------------------------------------------------------------------- /buildSrc/src/main/resources/META-INF/gradle-plugins/com.jashmore.gradle.release.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.jashmore.gradle.ReleasePlugin -------------------------------------------------------------------------------- /configuration/spotbugs/bugsExcludeFilter.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # Java Dynamic SQS Listener Core 2 | 3 | This is the core implementation of the Dynamic SQS Listener API. This contains basic implementations that should cover a significant amount of use cases 4 | but if it doesn't, the consumer can easily implement their own. 5 | 6 | ## Usage 7 | 8 | See the [core-example](../examples/core-example) for examples of using this core code to listen to a queue in a Java application. If it is a Kotlin application, 9 | the [core-kotlin-example](../examples/core-kotlin-example) uses a [Core Kotlin DSL](../extensions/core-kotlin-dsl) for constructing a core message listener. 10 | 11 | ## More Information 12 | 13 | For more information you can look at the root project [README.md](../README.md) which provides more information about the architecture 14 | of the application. The [API](../api) is also a good location to find more information about what each part of the framework is how 15 | they interact with each other. 16 | -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains the core functionality for the library" 3 | 4 | val immutablesVersion: String by project 5 | val jacksonVersion: String by project 6 | val slf4jVersion: String by project 7 | 8 | dependencies { 9 | // External dependencies 10 | implementation("org.slf4j:slf4j-api:$slf4jVersion") 11 | api("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") 12 | annotationProcessor("org.immutables:value:$immutablesVersion") 13 | api("org.immutables:value-annotations:$immutablesVersion") 14 | 15 | api(project(":java-dynamic-sqs-listener-api")) 16 | implementation(project(":annotation-utils")) 17 | compileOnly(project(":documentation-annotations")) 18 | implementation(project(":common-utils")) 19 | 20 | testImplementation(project(":elasticmq-sqs-client")) 21 | testImplementation(project(":proxy-method-interceptor")) 22 | testImplementation(project(":expected-test-exception")) 23 | testCompileOnly(project(":documentation-annotations")) 24 | } 25 | -------------------------------------------------------------------------------- /core/src/integrationTest/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/DelegatingArgumentResolverService.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | import lombok.AllArgsConstructor; 6 | 7 | /** 8 | * Delegates the resolving of an argument in the method using a list of {@link ArgumentResolver}s. 9 | * 10 | *

Note that a {@link Set} is used for the {@link ArgumentResolver}s because the ordering of them should not impact the result of the 11 | * resolution of the argument. 12 | */ 13 | @AllArgsConstructor 14 | public class DelegatingArgumentResolverService implements ArgumentResolverService { 15 | 16 | private final List> argumentResolvers; 17 | 18 | @Override 19 | public ArgumentResolver getArgumentResolver(final MethodParameter methodParameter) throws UnsupportedArgumentResolutionException { 20 | return argumentResolvers 21 | .stream() 22 | .filter(resolver -> resolver.canResolveParameter(methodParameter)) 23 | .findFirst() 24 | .orElseThrow(() -> new UnsupportedArgumentResolutionException(methodParameter)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/attribute/MessageAttribute.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.attribute; 2 | 3 | import static java.lang.annotation.ElementType.PARAMETER; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Populate on of the arguments in a message processing method by taking a value from the message attributes. 11 | */ 12 | @Retention(RUNTIME) 13 | @Target(PARAMETER) 14 | public @interface MessageAttribute { 15 | /** 16 | * The name of the attribute to use. 17 | * 18 | * @return the attribute name 19 | */ 20 | String value(); 21 | 22 | /** 23 | * Fail the processing of the message if the message attribute with the provided name does not exist. 24 | * 25 | * @return whether the message should fail to be processed if the message attribute is missing 26 | */ 27 | boolean required() default false; 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/attribute/MessageAttributeDataTypes.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.attribute; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum MessageAttributeDataTypes { 9 | STRING("String"), 10 | NUMBER("Number"), 11 | BINARY("Binary"); 12 | 13 | private final String value; 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/message/MessageArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.message; 2 | 3 | import com.jashmore.sqs.QueueProperties; 4 | import com.jashmore.sqs.argument.ArgumentResolutionException; 5 | import com.jashmore.sqs.argument.ArgumentResolver; 6 | import com.jashmore.sqs.argument.MethodParameter; 7 | import software.amazon.awssdk.services.sqs.model.Message; 8 | 9 | /** 10 | * {@link ArgumentResolver} that can be used to get the entire message that is being processed. 11 | * 12 | *

This can be useful if the current {@link ArgumentResolver}s do not provide the functionality required and a custom one does not want to be built. Another 13 | * use case could be when the entire message is needed to forward to another queue. 14 | */ 15 | public class MessageArgumentResolver implements ArgumentResolver { 16 | 17 | @Override 18 | public boolean canResolveParameter(final MethodParameter methodParameter) { 19 | return methodParameter.getParameter().getType() == Message.class; 20 | } 21 | 22 | @Override 23 | public Message resolveArgumentForParameter( 24 | final QueueProperties queueProperties, 25 | final MethodParameter methodParameter, 26 | final Message message 27 | ) throws ArgumentResolutionException { 28 | return message; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/messageid/MessageId.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.messageid; 2 | 3 | import static java.lang.annotation.ElementType.PARAMETER; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import com.jashmore.sqs.argument.ArgumentResolver; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Annotation for a message consumer's parameter that indicate that it should be resolved to contain the message ID of the message. 12 | * 13 | *

Parameters marked with this annotation must be of type {@link String} and should not be annotated with any other annotations or types 14 | * that would be resolved by an {@link ArgumentResolver}. 15 | */ 16 | @Retention(RUNTIME) 17 | @Target(PARAMETER) 18 | public @interface MessageId { 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/messageid/MessageIdArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.messageid; 2 | 3 | import com.jashmore.sqs.QueueProperties; 4 | import com.jashmore.sqs.argument.ArgumentResolutionException; 5 | import com.jashmore.sqs.argument.ArgumentResolver; 6 | import com.jashmore.sqs.argument.MethodParameter; 7 | import com.jashmore.sqs.util.annotation.AnnotationUtils; 8 | import software.amazon.awssdk.services.sqs.model.Message; 9 | 10 | /** 11 | * Argument resolver for parameters annotated with the {@link MessageId} annotation. 12 | */ 13 | public class MessageIdArgumentResolver implements ArgumentResolver { 14 | 15 | @Override 16 | public boolean canResolveParameter(final MethodParameter methodParameter) { 17 | return ( 18 | methodParameter.getParameter().getType().isAssignableFrom(String.class) && 19 | AnnotationUtils.findParameterAnnotation(methodParameter, MessageId.class).isPresent() 20 | ); 21 | } 22 | 23 | @Override 24 | public String resolveArgumentForParameter( 25 | final QueueProperties queueProperties, 26 | final MethodParameter methodParameter, 27 | final Message message 28 | ) throws ArgumentResolutionException { 29 | return message.messageId(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/payload/Payload.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.payload; 2 | 3 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Annotation for a message consumer's parameter that indicate that it should be resolved to the payload of the message. 11 | * 12 | *

Parameters marked with this annotation do not have a type restriction but the type of the parameter must be able to be map 13 | * from the message body. 14 | */ 15 | @Retention(value = RUNTIME) 16 | @Target(ElementType.PARAMETER) 17 | public @interface Payload { 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/payload/mapper/JacksonPayloadMapper.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.payload.mapper; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import java.io.IOException; 5 | import lombok.AllArgsConstructor; 6 | import software.amazon.awssdk.services.sqs.model.Message; 7 | 8 | /** 9 | * Cast the message body to a Java Bean using a Jackson {@link ObjectMapper}. 10 | */ 11 | @AllArgsConstructor 12 | public class JacksonPayloadMapper implements PayloadMapper { 13 | 14 | private final ObjectMapper objectMapper; 15 | 16 | @Override 17 | public Object map(Message message, Class clazz) throws PayloadMappingException { 18 | if (clazz.equals(String.class)) { 19 | return message.body(); 20 | } 21 | 22 | try { 23 | return objectMapper.readValue(message.body(), clazz); 24 | } catch (final IOException exception) { 25 | throw new PayloadMappingException("Error trying to resolve Payload for argument", exception); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/payload/mapper/PayloadMapper.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.payload.mapper; 2 | 3 | import software.amazon.awssdk.services.sqs.model.Message; 4 | 5 | /** 6 | * Mapper that is able to map the message body to an object of a certain type. 7 | * 8 | *

For example, if the message body is a JSON object this can be used to map this to a Java Bean with the fields built 9 | * from the JSON body. 10 | */ 11 | public interface PayloadMapper { 12 | /** 13 | * Cast the message body to the provided class type. 14 | * 15 | * @param message the message to map the body from 16 | * @param clazz the class to build the object from the message body 17 | * @return the message body as an object of the given class type 18 | * @throws PayloadMappingException exception thrown if there was a failure to map the message body to the defined type 19 | */ 20 | Object map(Message message, Class clazz) throws PayloadMappingException; 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/argument/payload/mapper/PayloadMappingException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.payload.mapper; 2 | 3 | /** 4 | * Exception thrown when there was a problem casting the message content to a Java Bean. 5 | */ 6 | public class PayloadMappingException extends RuntimeException { 7 | 8 | public PayloadMappingException(String message) { 9 | super(message); 10 | } 11 | 12 | public PayloadMappingException(final String message, final Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/broker/concurrent/ConcurrentMessageBrokerConstants.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.broker.concurrent; 2 | 3 | import java.time.Duration; 4 | import lombok.experimental.UtilityClass; 5 | 6 | @UtilityClass 7 | public class ConcurrentMessageBrokerConstants { 8 | 9 | /** 10 | * The default amount of time to sleep the thread when there was an error organising the message processing threads. 11 | */ 12 | public static final Duration DEFAULT_BACKOFF_TIME = Duration.ofSeconds(10); 13 | 14 | /** 15 | * The default amount of time the thread should wait for a thread to process a message before it tries again and checks the available concurrency. 16 | */ 17 | public static final Duration DEFAULT_CONCURRENCY_POLLING = Duration.ofMinutes(1); 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/container/CoreMessageListenerContainerConstants.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.container; 2 | 3 | import com.jashmore.sqs.retriever.MessageRetriever; 4 | import java.time.Duration; 5 | import lombok.experimental.UtilityClass; 6 | 7 | @UtilityClass 8 | class CoreMessageListenerContainerConstants { 9 | 10 | /** 11 | * The default time that the container will wait for a component to shutdown before giving up. 12 | */ 13 | static final Duration DEFAULT_SHUTDOWN_TIME = Duration.ofSeconds(60); 14 | 15 | /** 16 | * The default setting for whether the current messages being processed should be interrupted when the container is being shut down. 17 | */ 18 | static boolean DEFAULT_SHOULD_INTERRUPT_MESSAGE_PROCESSING_ON_SHUTDOWN = false; 19 | 20 | /** 21 | * The default setting for whether any extra messages downloaded by the {@link MessageRetriever} should be processed before the container is 22 | * completely shut down. 23 | */ 24 | static boolean DEFAULT_SHOULD_PROCESS_EXTRA_MESSAGES_ON_SHUTDOWN = true; 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/placeholder/PlaceholderResolver.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.placeholder; 2 | 3 | @FunctionalInterface 4 | public interface PlaceholderResolver { 5 | /** 6 | * Resolve placeholders in the provided text. E.g. ${something.url} may be resolved to https://localhost:9090. 7 | * 8 | * @param text the text to parse and replace 9 | * @return the new text with placeholders replaced 10 | */ 11 | String resolvePlaceholders(String text); 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/placeholder/StaticPlaceholderResolver.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.placeholder; 2 | 3 | import com.jashmore.documentation.annotations.Nonnull; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Simple implementation of a {@link PlaceholderResolver} that just does a simple string 9 | * replace from a static map of values. This is mostly only useful for testing. 10 | */ 11 | public class StaticPlaceholderResolver implements PlaceholderResolver { 12 | 13 | private final Map placeholderMap; 14 | 15 | public StaticPlaceholderResolver() { 16 | this.placeholderMap = new HashMap<>(); 17 | } 18 | 19 | public StaticPlaceholderResolver withMapping(final String from, final String to) { 20 | placeholderMap.put(from, to); 21 | return this; 22 | } 23 | 24 | public void reset() { 25 | this.placeholderMap.clear(); 26 | } 27 | 28 | @Override 29 | public String resolvePlaceholders(@Nonnull final String text) { 30 | String previousText = text; 31 | String mutatedText = null; 32 | while (!previousText.equals(mutatedText)) { 33 | mutatedText = mutatedText == null ? text : mutatedText; 34 | previousText = mutatedText; 35 | 36 | for (Map.Entry entry : placeholderMap.entrySet()) { 37 | mutatedText = mutatedText.replace(entry.getKey(), entry.getValue()); 38 | } 39 | } 40 | return mutatedText; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/resolver/batching/StaticBatchingMessageResolverProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.resolver.batching; 2 | 3 | import static com.jashmore.sqs.aws.AwsConstants.MAX_NUMBER_OF_MESSAGES_IN_BATCH; 4 | 5 | import com.jashmore.documentation.annotations.Max; 6 | import com.jashmore.documentation.annotations.Positive; 7 | import java.time.Duration; 8 | import lombok.Builder; 9 | import lombok.NonNull; 10 | import lombok.Value; 11 | 12 | /** 13 | * Static implementation that will contain constant size and time limit for the buffer. 14 | */ 15 | @Value 16 | @Builder(toBuilder = true) 17 | public class StaticBatchingMessageResolverProperties implements BatchingMessageResolverProperties { 18 | 19 | int bufferingSizeLimit; 20 | Duration bufferingTime; 21 | 22 | @Positive 23 | @Max(MAX_NUMBER_OF_MESSAGES_IN_BATCH) 24 | @Override 25 | public int getBufferingSizeLimit() { 26 | return bufferingSizeLimit; 27 | } 28 | 29 | @NonNull 30 | @Positive 31 | @Override 32 | public Duration getBufferingTime() { 33 | return bufferingTime; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/retriever/batching/BatchingMessageRetrieverConstants.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.retriever.batching; 2 | 3 | import java.time.Duration; 4 | import lombok.experimental.UtilityClass; 5 | 6 | @UtilityClass 7 | class BatchingMessageRetrieverConstants { 8 | 9 | /** 10 | * The default amount of time to sleep the thread when there was an error obtaining messages. 11 | */ 12 | static final Duration DEFAULT_BACKOFF_TIME = Duration.ofSeconds(10); 13 | 14 | /** 15 | * The default number of threads requesting messages for it trigger a request for messages. 16 | */ 17 | static final int DEFAULT_BATCHING_TRIGGER = 1; 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/retriever/batching/StaticBatchingMessageRetrieverProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.retriever.batching; 2 | 3 | import com.jashmore.documentation.annotations.Nullable; 4 | import com.jashmore.documentation.annotations.Positive; 5 | import com.jashmore.documentation.annotations.PositiveOrZero; 6 | import java.time.Duration; 7 | import lombok.Builder; 8 | import lombok.Value; 9 | 10 | /** 11 | * Static implementation of the properties that will never change during the processing of the messages. 12 | */ 13 | @Value 14 | @Builder(toBuilder = true) 15 | public class StaticBatchingMessageRetrieverProperties implements BatchingMessageRetrieverProperties { 16 | 17 | int batchSize; 18 | Duration batchingPeriod; 19 | Duration messageVisibilityTimeout; 20 | Duration errorBackoffTime; 21 | 22 | @Positive 23 | @Override 24 | public int getBatchSize() { 25 | return batchSize; 26 | } 27 | 28 | @Nullable 29 | @Positive 30 | @Override 31 | public Duration getBatchingPeriod() { 32 | return batchingPeriod; 33 | } 34 | 35 | @Nullable 36 | @Positive 37 | @Override 38 | public Duration getMessageVisibilityTimeout() { 39 | return messageVisibilityTimeout; 40 | } 41 | 42 | @Nullable 43 | @PositiveOrZero 44 | @Override 45 | public Duration getErrorBackoffTime() { 46 | return errorBackoffTime; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/retriever/prefetch/PrefetchingMessageRetrieverConstants.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.retriever.prefetch; 2 | 3 | import java.time.Duration; 4 | import lombok.experimental.UtilityClass; 5 | 6 | @UtilityClass 7 | class PrefetchingMessageRetrieverConstants { 8 | 9 | /** 10 | * The default backoff timeout for when there is an error retrieving messages. 11 | */ 12 | static final Duration DEFAULT_ERROR_BACKOFF_TIMEOUT = Duration.ofSeconds(10); 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/retriever/prefetch/QueueDrain.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.retriever.prefetch; 2 | 3 | import java.util.Queue; 4 | import java.util.concurrent.CompletableFuture; 5 | import lombok.Builder; 6 | import lombok.Value; 7 | import software.amazon.awssdk.services.sqs.model.Message; 8 | 9 | @Value 10 | @Builder 11 | public class QueueDrain { 12 | 13 | Queue> futuresWaitingForMessages; 14 | Queue messagesAvailableForProcessing; 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/java/com/jashmore/sqs/retriever/prefetch/StaticPrefetchingMessageRetrieverProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.retriever.prefetch; 2 | 3 | import com.jashmore.documentation.annotations.Min; 4 | import com.jashmore.documentation.annotations.Nullable; 5 | import com.jashmore.documentation.annotations.Positive; 6 | import com.jashmore.documentation.annotations.PositiveOrZero; 7 | import java.time.Duration; 8 | import lombok.Builder; 9 | import lombok.EqualsAndHashCode; 10 | import lombok.NonNull; 11 | import lombok.ToString; 12 | 13 | @Builder(toBuilder = true) 14 | @ToString 15 | @EqualsAndHashCode 16 | public class StaticPrefetchingMessageRetrieverProperties implements PrefetchingMessageRetrieverProperties { 17 | 18 | @NonNull 19 | private final Integer desiredMinPrefetchedMessages; 20 | 21 | @NonNull 22 | private final Integer maxPrefetchedMessages; 23 | 24 | private final Duration messageVisibilityTimeout; 25 | private final Duration errorBackoffTime; 26 | 27 | @Override 28 | @Positive 29 | public int getDesiredMinPrefetchedMessages() { 30 | return desiredMinPrefetchedMessages; 31 | } 32 | 33 | @Override 34 | @Min(1) 35 | public int getMaxPrefetchedMessages() { 36 | return maxPrefetchedMessages; 37 | } 38 | 39 | @Override 40 | @Nullable 41 | @Positive 42 | public Duration getMessageVisibilityTimeout() { 43 | return messageVisibilityTimeout; 44 | } 45 | 46 | @Override 47 | @Nullable 48 | @PositiveOrZero 49 | public Duration getErrorBackoffTime() { 50 | return errorBackoffTime; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/test/java/com/jashmore/sqs/argument/messageid/MessageIdArgumentResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.argument.messageid; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import software.amazon.awssdk.services.sqs.model.Message; 7 | 8 | class MessageIdArgumentResolverTest { 9 | 10 | private final MessageIdArgumentResolver messageIdArgumentResolver = new MessageIdArgumentResolver(); 11 | 12 | @Test 13 | void messageIdCanBeResolvedFromMessage() { 14 | // arrange 15 | final Message message = Message.builder().messageId("id").build(); 16 | 17 | // act 18 | final Object argument = messageIdArgumentResolver.resolveArgumentForParameter(null, null, message); 19 | 20 | // assert 21 | assertThat(argument).isEqualTo("id"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/test/java/com/jashmore/sqs/placeholder/StaticPlaceholderResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.placeholder; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class StaticPlaceholderResolverTest { 8 | 9 | @Test 10 | void canReplacePlaceholdersFromStaticList() { 11 | assertThat(new StaticPlaceholderResolver().withMapping("from", "to").resolvePlaceholders("from something")) 12 | .isEqualTo("to something"); 13 | } 14 | 15 | @Test 16 | void resettingPlaceholdersWillClearMapping() { 17 | final StaticPlaceholderResolver placeholderResolver = new StaticPlaceholderResolver().withMapping("from", "to"); 18 | 19 | placeholderResolver.reset(); 20 | 21 | assertThat(placeholderResolver.resolvePlaceholders("from something")).isEqualTo("from something"); 22 | } 23 | 24 | @Test 25 | void canChangePlaceholderReplacements() { 26 | final StaticPlaceholderResolver placeholderResolver = new StaticPlaceholderResolver() 27 | .withMapping("first", "second") 28 | .withMapping("second", "third word") 29 | .withMapping("third", "fourth"); 30 | 31 | assertThat(placeholderResolver.resolvePlaceholders("first for us")).isEqualTo("fourth word for us"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/test/java/com/jashmore/sqs/retriever/prefetch/StaticPrefetchingMessageRetrieverPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.retriever.prefetch; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertThrows; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class StaticPrefetchingMessageRetrieverPropertiesTest { 8 | 9 | @Test 10 | void maxPrefetchedMessagesIsRequired() { 11 | assertThrows( 12 | NullPointerException.class, 13 | () -> StaticPrefetchingMessageRetrieverProperties.builder().desiredMinPrefetchedMessages(10).build() 14 | ); 15 | } 16 | 17 | @Test 18 | void desiredMinMessagePrefetchedMessagesFailsWhenLessThanZero() { 19 | assertThrows( 20 | NullPointerException.class, 21 | () -> StaticPrefetchingMessageRetrieverProperties.builder().desiredMinPrefetchedMessages(-1).build() 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/test/java/com/jashmore/sqs/util/thread/ThreadTestUtils.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.util.thread; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import lombok.experimental.UtilityClass; 6 | 7 | @UtilityClass 8 | public class ThreadTestUtils { 9 | 10 | public static void startRunnableInThread(final Runnable runnable, final BlockingConsumer threadConsumer) { 11 | final Thread thread = new Thread(runnable); 12 | try { 13 | thread.start(); 14 | threadConsumer.accept(thread); 15 | } catch (final Exception exception) { 16 | throw new RuntimeException(exception); 17 | } finally { 18 | thread.interrupt(); 19 | } 20 | } 21 | 22 | @SuppressWarnings("BusyWait") 23 | public static void waitUntilThreadInState(Thread thread, Thread.State expectedState) throws InterruptedException { 24 | int numberOfTimesCompleted = 0; 25 | while (thread.getState() != expectedState && numberOfTimesCompleted < 60) { 26 | Thread.sleep(100); 27 | numberOfTimesCompleted++; 28 | } 29 | 30 | assertThat(thread.getState()).isEqualTo(expectedState); 31 | } 32 | 33 | @FunctionalInterface 34 | public interface BlockingConsumer { 35 | void accept(T object) throws Exception; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /doc/how-to-guides/core/core-how-to-implement-a-custom-message-retrieval.md: -------------------------------------------------------------------------------- 1 | # Core - How to implement custom message retrieval logic 2 | 3 | The [MessageRetriever](../../../api/src/main/java/com/jashmore/sqs/retriever/MessageRetriever.java) handles the logic for how to 4 | get messages from the SQS Queue. This is the most complicated part of the library as this is where the most performance improvements 5 | could be utilised. 6 | 7 | ## Examples 8 | 9 | To learn how to create your own [MessageRetriever](../../../api/src/main/java/com/jashmore/sqs/retriever/MessageRetriever.java) it 10 | is useful to take a look at how they are built into the core library. It isn't as straight forward to do as it involves dealing with handling concurrency and 11 | is written in a non-blocking programming style. The current core implementations can be found in the 12 | [retriever package](../../../core/src/main/java/com/jashmore/sqs/retriever). 13 | 14 | Once you have built your own retriever you can see how this can be integrated into the framework by looking in the [examples](../../../examples) directory 15 | and more specifically in the [core-example](../../../examples/core-example) module. 16 | 17 | ### Integrating the new message retriever into the spring app 18 | 19 | If you are using the Spring Starter for this, you can take a look at 20 | [Spring - How to add custom queue wrapper](../spring/spring-how-to-add-own-queue-listener.md) for details on integrating this into the spring application. 21 | -------------------------------------------------------------------------------- /doc/how-to-guides/how-to-connect-to-aws-sqs-queue.md: -------------------------------------------------------------------------------- 1 | # How to Connect to an AWS SQS Queue 2 | 3 | While most of the examples provided in this library use [ElasticMQ](https://github.com/adamw/elasticmq) as the SQS queue, the real application should be 4 | using an actual SQS queue. This provides a very high level, generic guide to creating a Queue in AWS which should help you get started. However, the 5 | online documentation provided by Amazon would be a better resources for learning about SQS queues, for example I used 6 | [AWS Documentation - SQS Setting Up](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-setting-up.html) myself to 7 | write this guide. 8 | 9 | In this example we are defining the SQS Properties needed via Environment variables in the application. See `SqsAsyncClient#create()` for more thorough 10 | documentation about different ways to define these properties. The [Spring AWS Example](../../examples/spring-aws-example) will be 11 | used for this guide. 12 | 13 | ## Usage 14 | 15 | See the [Spring AWS Example README.md](../../examples/spring-aws-example/README.md) for steps to use this yourself. 16 | -------------------------------------------------------------------------------- /doc/how-to-guides/spring/spring-how-to-add-brave-tracing.md: -------------------------------------------------------------------------------- 1 | # Spring - How to add Brave Tracing 2 | 3 | ## Steps 4 | 5 | 1. Make sure you have the brave tracing dependency configured, e.g. via Spring Sleuth. See their documentation on how to do this. 6 | 1. Add the [brave-extension-spring-boot](../../../extensions/brave-extension/spring-boot) module 7 | 8 | ```xml 9 | 10 | com.jashmore 11 | brave-extension-spring-boot 12 | ${library.version} 13 | 14 | ``` 15 | 16 | 1. Now any processing message should have tracing information 17 | 18 | ## Example 19 | 20 | See the [spring-sleuth-example](../../../examples/spring-sleuth-example/README.md) for a Spring application that has this setup. 21 | 22 | ## Including Brave information in your SQS messages 23 | 24 | If you want to include the Trace information into your SQS message so that the message listener continues the trace, 25 | you can use the [sqs-brave-tracing](../../../util/sqs-brave-tracing) utility module to add the tracing information to the 26 | SQS Message Attributes. 27 | 28 | An example of this being done is in the 29 | [spring-sleuth-example](../../../examples/spring-sleuth-example/src/main/java/com/jashmore/sqs/examples/sleuth/Application.java). 30 | -------------------------------------------------------------------------------- /doc/how-to-guides/spring/spring-how-to-change-message-visibility.md: -------------------------------------------------------------------------------- 1 | # Spring - How to change message visibility 2 | 3 | By default, the queue listener will use the message visibility defined in the SQS queue, however if you wish to override the visibility for all messages process in this listener you can set this in the listener annotation. 4 | 5 | ### Example 6 | 7 | ```java 8 | public class MyClass { 9 | 10 | private final SomeService someService; 11 | 12 | public MyClass(final SomeService someService) { 13 | this.someService = someService; 14 | } 15 | 16 | @QueueListener(value = "${queueUrl}", messageVisibilityTimeoutInSeconds = 5) 17 | public void myMethod(@Payload final String payload) { 18 | someService.methodThatTakesLong(payload); 19 | } 20 | } 21 | 22 | ``` 23 | 24 | If the message processing could take a longer period than this and you wish to dynamically extend the visibility while it is processing, take a look at the [Spring - How to extend message visibility during processing](spring-how-to-extend-message-visibility-during-processing.md) 25 | -------------------------------------------------------------------------------- /doc/how-to-guides/spring/spring-how-to-prevent-containers-starting-on-startup.md: -------------------------------------------------------------------------------- 1 | # Spring - How to prevent Message Listener Containers starting on startup 2 | 3 | By default, a [SpringMessageListenerContainerCoordinator.java](../../../spring/spring-core/src/main/java/com/jashmore/sqs/spring/container/SpringMessageListenerContainerCoordinator.java) 4 | is configured to discover containers as well as start and stop the containers in the Spring Lifecycle. This will auto startup the containers by default but, 5 | if this is not desirable, you can supply your own properties to disable this functionality. 6 | 7 | ## Steps 8 | 9 | 1. Define your own 10 | [SpringMessageListenerContainerCoordinatorProperties.java](../../../spring/spring-core/src/main/java/com/jashmore/sqs/spring/container/SpringMessageListenerContainerCoordinatorProperties.java) 11 | with the configuration you desire. 12 | 13 | ```java 14 | @Configuration 15 | class MyConfiguration { 16 | 17 | @Bean 18 | SpringMessageListenerContainerCoordinatorProperties springMessageListenerContainerCoordinatorProperties() { 19 | return StaticSpringMessageListenerContainerCoordinatorProperties.builder().isAutoStartContainersEnabled(false).build(); 20 | } 21 | } 22 | 23 | ``` 24 | 25 | This will not work if you have supplied your 26 | own [MessageListenerContainerCoordinator](../../../api/src/main/java/com/jashmore/sqs/container/MessageListenerContainerCoordinator.java) 27 | bean as the default coordinator will now not be configured for you anymore. 28 | -------------------------------------------------------------------------------- /doc/how-to-guides/spring/spring-how-to-set-queue-listener-props-from-the-environment.md: -------------------------------------------------------------------------------- 1 | # Spring - How to set Queue Listener props from the Environment 2 | 3 | The Queue Listener annotations have been written to allow for them to be set via the Spring Environment, e.g. properties from an `application.yml` file. 4 | 5 | ## Steps 6 | 7 | 1. Create some properties in the `application.yml` file, in this case there are two profiles with different concurrency rates. 8 | 9 | ```yaml 10 | --- 11 | spring.profiles: staging 12 | 13 | queues: 14 | my-queue: 15 | concurrency: 5 16 | 17 | --- 18 | spring.profiles: production 19 | queues: 20 | my-queue: 21 | concurrency: 10 22 | ``` 23 | 24 | 1. Use the annotations `{propertyName}String` fields in the annotations to pull data from the environment: 25 | 26 | ```java 27 | @QueueListener(value = "http://localhost:9432/q/myqueue", concurrencyLevelString="${queues.my-queue.concurrency}") 28 | public void myMethod(@Payload String payload) { 29 | 30 | } 31 | ``` 32 | 33 | **\*NOTE**: the `{propertyName}String` fields will override any of the other properties if they are set. E.g. if both `concurrencyLevel` and 34 | `concurrencyLevelString` are set the `concurrencyLevelString` one will be prioritised\* 35 | -------------------------------------------------------------------------------- /doc/local-development/setting-up-intellij.md: -------------------------------------------------------------------------------- 1 | # Local Development Guide - Setting up IntelliJ 2 | 3 | This guide contains some steps for setting up your local environment to work with this library. Note that this will 4 | assume that you are using IntelliJ for your IDE. 5 | 6 | ## Steps 7 | 8 | _Note these steps are for IntelliJ 2017.1, so the steps may be different for you._ 9 | 10 | 1. Open the project in IntelliJ. 11 | 1. Install the Lombok Plugin in IntelliJ. 12 | `IntelliJ IDEA -> Preferences -> Plugins -> Browse Repositories -> Search Lombok -> Install` 13 | 1. Install the Prettier Plugin in IntelliJ 14 | `IntelliJ IDEA -> Preferences -> Plugins -> Browse Repositories -> Search Prettier -> Install` 15 | 1. Configure the prettier plugin to run on all files `{**/*,*}.*` and to run on code format 16 | 1. Enable Annotation Processing 17 | `Build, Execution, Deployment -> Compiler -> Annotation Processors -> Enable Annotation Processing` 18 | 1. Update the Import layout for the Java code style: 19 | `IntelliJ IDEA -> Preferences -> Editor -> Code Style -> Java -> Imports` to be 20 | 21 | ```text 22 | import static all other imports 23 | 24 | import all other imports 25 | 26 | ``` 27 | -------------------------------------------------------------------------------- /doc/resources/README.md: -------------------------------------------------------------------------------- 1 | # Architectural diagrams 2 | 3 | [Draw IO](https://www.draw.io/) is being used to create the diagrams needed for the documentation. The raw XMLs of each diagram should be persisted with the 4 | exported diagram so that future edits can be made. 5 | -------------------------------------------------------------------------------- /doc/resources/architecture_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaidenAshmore/java-dynamic-sqs-listener/745fde95ca438b02270ede19450dfda877b7d7e1/doc/resources/architecture_diagram.png -------------------------------------------------------------------------------- /examples/auto-visibility-extender-example/README.md: -------------------------------------------------------------------------------- 1 | # FIFO Example 2 | 3 | An example of setting up the [Spring Starter](../../spring/spring-starter) module in a Spring Application that will automatically 4 | extend the visiblity of messages if it takes a while to process. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | gradle bootRun 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/auto-visibility-extender-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains example for a queue that takes a long take and will auto extend it" 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val springBootVersion: String by project 9 | 10 | dependencies { 11 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 12 | implementation("org.springframework.boot:spring-boot-starter") 13 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 14 | implementation(project(":elasticmq-sqs-client")) 15 | } 16 | 17 | tasks.withType { 18 | sourceCompatibility = "17" 19 | targetCompatibility = "17" 20 | 21 | options.encoding = "UTF-8" 22 | options.compilerArgs.addAll(setOf("-Xlint:all", "-Werror", "-Xlint:-processing", "-Xlint:-serial")) 23 | } 24 | -------------------------------------------------------------------------------- /examples/auto-visibility-extender-example/src/main/java/com/jashmore/sqs/examples/Application.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import com.jashmore.sqs.elasticmq.ElasticMqSqsAsyncClient; 4 | import com.jashmore.sqs.util.SqsQueuesConfig; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 10 | 11 | @EnableScheduling 12 | @SpringBootApplication 13 | public class Application { 14 | 15 | static final String QUEUE_NAME = "queue"; 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(Application.class); 19 | } 20 | 21 | @Bean 22 | public SqsAsyncClient sqsAsyncClient() { 23 | return new ElasticMqSqsAsyncClient( 24 | SqsQueuesConfig.QueueConfig 25 | .builder() 26 | .queueName(QUEUE_NAME) 27 | .deadLetterQueueName("queue-dlq") 28 | .maxReceiveCount(3) 29 | .visibilityTimeout(5) 30 | .build() 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/auto-visibility-extender-example/src/main/java/com/jashmore/sqs/examples/ScheduledMessageProducer.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import static com.jashmore.sqs.examples.Application.QUEUE_NAME; 4 | 5 | import java.util.concurrent.ExecutionException; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.stereotype.Component; 10 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 11 | 12 | @Slf4j 13 | @Component 14 | public class ScheduledMessageProducer { 15 | 16 | private final SqsAsyncClient sqsAsyncClient; 17 | private final String queueUrl; 18 | 19 | @Autowired 20 | private ScheduledMessageProducer(final SqsAsyncClient sqsAsyncClient) { 21 | this.sqsAsyncClient = sqsAsyncClient; 22 | 23 | try { 24 | this.queueUrl = sqsAsyncClient.getQueueUrl(request -> request.queueName(QUEUE_NAME)).get().queueUrl(); 25 | } catch (final InterruptedException interruptedException) { 26 | Thread.currentThread().interrupt(); 27 | throw new RuntimeException("Error determining queue URL"); 28 | } catch (final ExecutionException executionException) { 29 | throw new RuntimeException(executionException); 30 | } 31 | } 32 | 33 | @Scheduled(initialDelay = 1_000, fixedDelay = 100) 34 | public void addMessages() { 35 | sqsAsyncClient.sendMessage(builder -> builder.queueUrl(queueUrl).messageBody("body")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/auto-visibility-extender-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/assets/iam_access_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaidenAshmore/java-dynamic-sqs-listener/745fde95ca438b02270ede19450dfda877b7d7e1/examples/aws-xray-spring-example/assets/iam_access_key.png -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/assets/iam_permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaidenAshmore/java-dynamic-sqs-listener/745fde95ca438b02270ede19450dfda877b7d7e1/examples/aws-xray-spring-example/assets/iam_permissions.png -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/assets/sns_subscription.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaidenAshmore/java-dynamic-sqs-listener/745fde95ca438b02270ede19450dfda877b7d7e1/examples/aws-xray-spring-example/assets/sns_subscription.png -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains examples for using the AWS XRay tracing." 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val awsVersion: String by project 9 | val awsXrayVersion: String by project 10 | val springBootVersion: String by project 11 | 12 | dependencies { 13 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 14 | api(platform("software.amazon.awssdk:bom:$awsVersion")) 15 | implementation("software.amazon.awssdk:sns") 16 | implementation("com.amazonaws:aws-xray-recorder-sdk-core:$awsXrayVersion") 17 | implementation("com.amazonaws:aws-xray-recorder-sdk-aws-sdk-v2-instrumentor:$awsXrayVersion") 18 | implementation("org.springframework.boot:spring-boot-starter") 19 | implementation(project(":aws-xray-extension-spring-boot")) 20 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 21 | } 22 | -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/src/main/java/com/jashmore/sqs/examples/Application.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | import software.amazon.awssdk.services.sns.SnsAsyncClient; 8 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 9 | 10 | @EnableScheduling 11 | @SpringBootApplication 12 | public class Application { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class); 16 | } 17 | 18 | @Bean 19 | public SqsAsyncClient sqsAsyncClient() { 20 | return SqsAsyncClient.create(); 21 | } 22 | 23 | @Bean 24 | public SnsAsyncClient snsAsyncClient() { 25 | return SnsAsyncClient.create(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/src/main/java/com/jashmore/sqs/examples/SomeService.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import com.amazonaws.xray.AWSXRay; 4 | import com.amazonaws.xray.entities.Subsegment; 5 | import java.util.Random; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Slf4j 10 | @Component 11 | public class SomeService { 12 | 13 | private static final Random RANDOM = new Random(); 14 | 15 | public void someMethod() throws InterruptedException { 16 | final Subsegment subsegment = AWSXRay.beginSubsegment("someMethod"); 17 | try { 18 | if (RANDOM.nextBoolean()) { 19 | log.info("Service doing some processing"); 20 | Thread.sleep(500); 21 | } else { 22 | Thread.sleep(200); 23 | throw new RuntimeException("Expected random failure to process"); 24 | } 25 | } catch (RuntimeException exception) { 26 | subsegment.addException(exception); 27 | throw exception; 28 | } finally { 29 | AWSXRay.endSubsegment(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: my-service 4 | 5 | sqs.queue.url: replaceThis 6 | 7 | sns.arn: replaceThis 8 | -------------------------------------------------------------------------------- /examples/aws-xray-spring-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/core-example/README.md: -------------------------------------------------------------------------------- 1 | # Core Example 2 | 3 | This example shows the usage of the [core](../../core) library to create a message listener that dynamically changes the rate 4 | of message processing concurrency. As the application runs, in a fixed interval the concurrency rate will randomly change to a different level showing 5 | that the message listener can be changed based on some business logic that you define. 6 | 7 | ## Usage 8 | 9 | Run the main function from an IDE or in the terminal run: 10 | 11 | ```bash 12 | gradle runApp 13 | ``` 14 | 15 | ## Kotlin DSL alternative 16 | 17 | If you are using Kotlin, the [core-kotlin-example](../core-kotlin-example) shows an equivalent application that uses 18 | the [Kotlin Core DSL](../../extensions/core-kotlin-dsl) to build the message listener in a less verbose way. 19 | -------------------------------------------------------------------------------- /examples/core-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains examples for using the core implementation of the listener with plan Java objects." 3 | 4 | val braveVersion: String by project 5 | val logbackVersion: String by project 6 | 7 | dependencies { 8 | implementation("com.google.guava:guava:29.0-jre") 9 | implementation(project(":java-dynamic-sqs-listener-core")) 10 | implementation(project(":elasticmq-sqs-client")) 11 | implementation("io.zipkin.brave:brave:$braveVersion") 12 | implementation("io.zipkin.brave:brave-context-slf4j:$braveVersion") 13 | implementation(project(":brave-extension-core")) 14 | compileOnly(project(":documentation-annotations")) 15 | implementation("ch.qos.logback:logback-core:$logbackVersion") 16 | implementation("ch.qos.logback:logback-classic:$logbackVersion") 17 | } 18 | 19 | tasks.create("runApp") { 20 | classpath = sourceSets.main.get().runtimeClasspath 21 | 22 | main = "com.jashmore.sqs.examples.ConcurrentBrokerExample" 23 | } 24 | -------------------------------------------------------------------------------- /examples/core-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg %yellow(%mdc) %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/core-kotlin-example/README.md: -------------------------------------------------------------------------------- 1 | # Core Kotlin Example 2 | 3 | This example shows the usage of the [core](../../core) module using the [Core Kotlin DSL](../../extensions/core-kotlin-dsl). It creates a 4 | message listener that will change the rate of concurrency each time period to show that we can dynamically scale the message listeners. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | gradle runApp 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/core-kotlin-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | description = "Example of using the Core library with the Kotlin DSL, this should be equivalent to the core-examples." 4 | 5 | val braveVersion: String by project 6 | val jacksonVersion: String by project 7 | val logbackVersion: String by project 8 | 9 | plugins { 10 | kotlin("jvm") 11 | } 12 | 13 | dependencies { 14 | implementation(kotlin("stdlib-jdk8")) 15 | implementation(project(":java-dynamic-sqs-listener-core")) 16 | implementation(project(":elasticmq-sqs-client")) 17 | implementation(project(":core-kotlin-dsl")) 18 | implementation("io.zipkin.brave:brave:$braveVersion") 19 | implementation("io.zipkin.brave:brave-context-slf4j:$braveVersion") 20 | implementation(project(":brave-extension-core")) 21 | implementation("ch.qos.logback:logback-core:$logbackVersion") 22 | implementation("ch.qos.logback:logback-classic:$logbackVersion") 23 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") 24 | } 25 | 26 | tasks.withType().configureEach { 27 | kotlinOptions.jvmTarget = "17" 28 | } 29 | 30 | tasks.create("runApp") { 31 | classpath = sourceSets.main.get().runtimeClasspath 32 | 33 | main = "com.jashmore.sqs.examples.KotlinConcurrentBrokerExample" 34 | } 35 | -------------------------------------------------------------------------------- /examples/core-kotlin-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg %yellow(%mdc) %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/fifo-example/README.md: -------------------------------------------------------------------------------- 1 | # FIFO Example 2 | 3 | An example of setting up the [Spring Starter](../../spring/spring-starter) module in a Spring Application that consumes a FIFO queue. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | gradle bootRun 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/fifo-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains example for consuming a SQS FIFO queue." 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val springBootVersion: String by project 9 | 10 | dependencies { 11 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 12 | implementation("org.springframework.boot:spring-boot-starter") 13 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 14 | implementation(project(":elasticmq-sqs-client")) 15 | implementation(project(":expected-test-exception")) 16 | } 17 | -------------------------------------------------------------------------------- /examples/fifo-example/src/main/java/com/jashmore/sqs/examples/Application.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import com.jashmore.sqs.elasticmq.ElasticMqSqsAsyncClient; 4 | import com.jashmore.sqs.util.SqsQueuesConfig; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 10 | 11 | @EnableScheduling 12 | @SpringBootApplication 13 | public class Application { 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(Application.class); 17 | } 18 | 19 | @Bean 20 | public SqsAsyncClient sqsAsyncClient() { 21 | return new ElasticMqSqsAsyncClient( 22 | SqsQueuesConfig.QueueConfig.builder().queueName("test-queue.fifo").fifoQueue(true).visibilityTimeout(10).build() 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/fifo-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/ktor-example/README.md: -------------------------------------------------------------------------------- 1 | # Ktor Example 2 | 3 | This example shows the usage of the [Ktor Core](../../ktor/core) module which uses the [Core Kotlin DSL](../../extensions/core-kotlin-dsl) to build 4 | the message listeners. It creates multiple message listeners that use different methods to process messages, such as prefetching messages, etc. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | gradle runApp 10 | ``` 11 | 12 | Perform a GET request to [http://localhost:8080/message/{queueIdentifier}](http://localhost:8080/message/one) where the queueIdentifier is one 13 | of `one`, `two`, or `three`. Any unknown queue will return 404. You can also start and stop the message listeners by navigating to 14 | [http://localhost:8080/start/{queueIdentifier}](http://localhost:8080/start/one) and 15 | [http://localhost:8080/stop/{queueIdentifier}](http://localhost:8080/stop/one) respectively. 16 | -------------------------------------------------------------------------------- /examples/ktor-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | description = "Contains examples for connection to SQS in an Ktor Application" 4 | 5 | val logbackVersion: String by project 6 | val ktorVersion: String by project 7 | 8 | plugins { 9 | kotlin("jvm") 10 | } 11 | 12 | dependencies { 13 | implementation(project(":java-dynamic-sqs-listener-core")) 14 | implementation(project(":core-kotlin-dsl")) 15 | implementation(project(":elasticmq-sqs-client")) 16 | api(project(":java-dynamic-sqs-listener-ktor-core")) 17 | implementation("io.ktor:ktor-server-netty:$ktorVersion") 18 | implementation("ch.qos.logback:logback-core:$logbackVersion") 19 | implementation("ch.qos.logback:logback-classic:$logbackVersion") 20 | } 21 | 22 | tasks.withType().configureEach { 23 | kotlinOptions.jvmTarget = "17" 24 | } 25 | 26 | tasks.create("runApp") { 27 | classpath = sourceSets.main.get().runtimeClasspath 28 | 29 | main = "com.jashmore.sqs.examples.KtorApplicationExample" 30 | } 31 | -------------------------------------------------------------------------------- /examples/ktor-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg %yellow(%mdc) %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/micronaut-integration-test-example/README.md: -------------------------------------------------------------------------------- 1 | # Micronaut Integration Test Example 2 | 3 | There are many examples in this codebase to run integration tests for this application (look in the src/test/java/integrationTest folder of 4 | the module) but this shows a minimal use case to copy from. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | gradle integrationTest 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/micronaut-integration-test-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains an example for writing an integration test for the SQS Listener" 3 | 4 | plugins { 5 | id("io.micronaut.minimal.application") version "4.4.2" 6 | } 7 | 8 | val micronautVersion: String by project 9 | 10 | dependencies { 11 | implementation("io.micronaut:micronaut-http-server-netty") 12 | runtimeOnly("ch.qos.logback:logback-classic") 13 | runtimeOnly("org.yaml:snakeyaml") 14 | implementation(project(":java-dynamic-sqs-listener-micronaut-core")) 15 | annotationProcessor(project(":java-dynamic-sqs-listener-micronaut-inject-java")) 16 | 17 | testImplementation(project(":elasticmq-sqs-client")) 18 | testImplementation(project(":expected-test-exception")) 19 | testImplementation("io.micronaut.test:micronaut-test-junit5") 20 | testImplementation("org.mockito:mockito-core") 21 | testImplementation("org.mockito:mockito-junit-jupiter") 22 | } 23 | -------------------------------------------------------------------------------- /examples/micronaut-integration-test-example/src/main/java/com/jashmore/sqs/examples/integrationtests/TestApplication.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples.integrationtests; 2 | 3 | import com.jashmore.sqs.annotations.core.basic.QueueListener; 4 | import com.jashmore.sqs.argument.payload.Payload; 5 | import io.micronaut.runtime.Micronaut; 6 | import jakarta.inject.Singleton; 7 | import lombok.AllArgsConstructor; 8 | 9 | public class TestApplication { 10 | 11 | public static void main(String[] args) { 12 | Micronaut.build(args).mainClass(TestApplication.class).start(); 13 | } 14 | 15 | @Singleton 16 | public static class SomeService { 17 | 18 | /** 19 | * Process a message payload, no-op. 20 | * 21 | * @param payload the payload of the message 22 | */ 23 | public void run(final String payload) { 24 | // do nothing 25 | } 26 | } 27 | 28 | @Singleton 29 | @AllArgsConstructor 30 | public static class MessageListener { 31 | 32 | private final SomeService someService; 33 | 34 | /** 35 | * We specifically override the visibility timeout here from the default of 30 to decrease the time 36 | * for the tests to run. 37 | * 38 | * @param message the payload of the message 39 | */ 40 | 41 | @QueueListener(value = "${sqs.queues.integrationTestingQueue}", messageVisibilityTimeoutInSeconds = 2) 42 | public void messageListener(@Payload final String message) { 43 | someService.run(message); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/micronaut-integration-test-example/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | java-dynamic-sqs-listener-micronaut: 2 | auto-start-containers-enabled: true # default 3 | -------------------------------------------------------------------------------- /examples/micronaut-integration-test-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/spring-aws-example/README.md: -------------------------------------------------------------------------------- 1 | # Spring AWS Example 2 | 3 | An example that uses the [spring starter](../../spring/spring-starter) module to listen to an actual queue on AWS. 4 | 5 | ## Steps 6 | 7 | 1. Create a new AWS account. You can Google for links on where to do this. 8 | 1. Create a new SQS Queue in a region near you, e.g. us-east-2 (Ohio). You will need the region and Queue URL. 9 | 1. Create a new IAM user that has full permission (for simplicity, otherwise you can put a more restricted permissions) to this SQS queue. You will 10 | need the Access Key ID and Secret Access Key. 11 | 1. Change directory to the AWS Spring example. 12 | 13 | ```bash 14 | cd examples/java-dynamic-sqs-listener-spring-aws-example 15 | ``` 16 | 17 | 1. Run the Spring Boot application with the AWS details recorded above. For example: 18 | 19 | ```bash 20 | AWS_ACCESS_KEY_ID={KEY_RECORDED_ABOVE} \ 21 | AWS_REGION={REGION_QUEUE_CREATED_IN_ABOVE} \ 22 | AWS_SECRET_ACCESS_KEY={SECRET_KEY_RECORDED_ABOVE} \ 23 | SQS_QUEUE_URL={FULL_URL_OF_SQS_QUEUE} \ 24 | gradle bootRun 25 | ``` 26 | 27 | 1. Send a message to the Queue by right clicking the queue in the AWS Console and selecting Send Message 28 | -------------------------------------------------------------------------------- /examples/spring-aws-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains examples for connecting to an actual AWS SQS Queue" 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val springBootVersion: String by project 9 | 10 | dependencies { 11 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 12 | implementation("org.springframework.boot:spring-boot-starter") 13 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 14 | } 15 | -------------------------------------------------------------------------------- /examples/spring-aws-example/src/main/java/com/jashmore/examples/spring/aws/Application.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.examples.spring.aws; 2 | 3 | import com.jashmore.sqs.annotations.core.basic.QueueListener; 4 | import com.jashmore.sqs.argument.payload.Payload; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | @SpringBootApplication 10 | @Slf4j 11 | public class Application { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(Application.class); 15 | } 16 | 17 | /** 18 | * Process the message. 19 | * 20 | * @param payload the payload of the message 21 | */ 22 | @QueueListener("${sqs.queues.queueUrl}") 23 | public void messageListener(@Payload final String payload) { 24 | log.info("Payload: {}", payload); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/spring-aws-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | sqs: 2 | queues: 3 | queueUrl: ${SQS_QUEUE_URL} 4 | -------------------------------------------------------------------------------- /examples/spring-aws-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-consumer/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.davidmc24.gradle.plugin.avro.GenerateAvroJavaTask 2 | 3 | description = "Contains an example of a consumer deserializing a message payload that is in a schema registered in the Spring Cloud Schema Registry" 4 | 5 | plugins { 6 | id("org.springframework.boot") 7 | id("com.github.davidmc24.gradle.plugin.avro") 8 | } 9 | 10 | val springBootVersion: String by project 11 | 12 | dependencies { 13 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 14 | implementation("org.springframework.boot:spring-boot-starter") 15 | implementation("org.springframework.boot:spring-boot-autoconfigure-processor") 16 | 17 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 18 | implementation(project(":local-sqs-async-client")) 19 | 20 | implementation(project(":avro-spring-cloud-schema-registry-extension")) 21 | } 22 | 23 | val generateAvro = tasks.register("generateAvro") { 24 | setSource("src/main/resources/avro") 25 | setOutputDir(file("build/generated/sources/avro")) 26 | } 27 | 28 | tasks.named("compileJava").configure { 29 | source(generateAvro) 30 | } 31 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-consumer/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | schema-registry-client: 4 | endpoint: http://localhost:8990 5 | schema: 6 | avro: 7 | schema-locations: 8 | - classpath:avro/sensor.avsc 9 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-consumer/src/main/resources/avro/sensor.avsc: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "com.example", 3 | "type": "record", 4 | "name": "Sensor", 5 | "fields": [ 6 | { "name": "id", "type": "string" }, 7 | { "name": "internalTemperature", "type": "float", "default": 0.0, "aliases": ["temperature"] }, 8 | { "name": "externalTemperature", "type": "float", "default": 0.0 }, 9 | { "name": "acceleration", "type": "float", "default": 0.0 }, 10 | { "name": "velocity", "type": "float", "default": 0.0 } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-consumer/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-producer-two/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.davidmc24.gradle.plugin.avro.GenerateAvroJavaTask 2 | 3 | description = "Includes a second example of a service producing messages whose schema is registered in the Spring Cloud Schema Registry\n and is in a different format to the first." 4 | 5 | plugins { 6 | id("org.springframework.boot") 7 | id("com.github.davidmc24.gradle.plugin.avro") 8 | } 9 | 10 | val awsVersion: String by project 11 | val springBootVersion: String by project 12 | 13 | dependencies { 14 | api(platform("software.amazon.awssdk:bom:$awsVersion")) 15 | api("software.amazon.awssdk:sqs") 16 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 17 | implementation("org.springframework.boot:spring-boot-starter") 18 | implementation(project(":avro-spring-cloud-schema-registry-sqs-client")) 19 | } 20 | 21 | val generateAvro = tasks.register("generateAvro") { 22 | setSource("src/main/resources/avro") 23 | setOutputDir(file("build/generated/sources/avro")) 24 | } 25 | 26 | tasks.named("compileJava").configure { 27 | source(generateAvro) 28 | } 29 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-producer-two/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | schema-registry-client: 4 | endpoint: http://localhost:8990 5 | schema: 6 | avro: 7 | schema-locations: 8 | - classpath:avro/sensor.avsc 9 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-producer-two/src/main/resources/avro/sensor.avsc: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "com.example", 3 | "type": "record", 4 | "name": "Sensor", 5 | "fields": [ 6 | { "name": "id", "type": "string" }, 7 | { "name": "internalTemperature", "type": "float", "default": 0.0, "aliases": ["temperature"] }, 8 | { "name": "externalTemperature", "type": "float", "default": 0.0 }, 9 | { "name": "acceleration", "type": "float", "default": 0.0 }, 10 | { "name": "velocity", "type": "float", "default": 0.0 }, 11 | { 12 | "name": "accelerometer", 13 | "type": [ 14 | "null", 15 | { 16 | "type": "array", 17 | "items": "float" 18 | } 19 | ] 20 | }, 21 | { 22 | "name": "magneticField", 23 | "type": [ 24 | "null", 25 | { 26 | "type": "array", 27 | "items": "float" 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-producer/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.davidmc24.gradle.plugin.avro.GenerateAvroJavaTask 2 | 3 | description = "Includes an example of a service producing messages whose schema is registered in the Spring Cloud Schema Registry" 4 | 5 | plugins { 6 | id("org.springframework.boot") 7 | id("com.github.davidmc24.gradle.plugin.avro") 8 | } 9 | 10 | val awsVersion: String by project 11 | val springBootVersion: String by project 12 | 13 | dependencies { 14 | api(platform("software.amazon.awssdk:bom:$awsVersion")) 15 | api("software.amazon.awssdk:sqs") 16 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 17 | implementation("org.springframework.boot:spring-boot-starter") 18 | implementation(project(":avro-spring-cloud-schema-registry-sqs-client")) 19 | } 20 | 21 | val generateAvro = tasks.register("generateAvro") { 22 | setSource("src/main/resources/avro") 23 | setOutputDir(file("build/generated/sources/avro")) 24 | } 25 | 26 | tasks.named("compileJava").configure { 27 | source(generateAvro) 28 | } 29 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-producer/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | schema-registry-client: 4 | endpoint: http://localhost:8990 5 | schema: 6 | avro: 7 | schema-locations: 8 | - classpath:avro/sensor.avsc 9 | -------------------------------------------------------------------------------- /examples/spring-cloud-schema-registry-example/spring-cloud-schema-registry-producer/src/main/resources/avro/sensor.avsc: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "com.example", 3 | "type": "record", 4 | "name": "Sensor", 5 | "fields": [ 6 | { "name": "id", "type": "string" }, 7 | { "name": "temperature", "type": "float", "default": 0.0 }, 8 | { "name": "acceleration", "type": "float", "default": 0.0 }, 9 | { "name": "velocity", "type": "float", "default": 0.0 } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/spring-integration-test-example/README.md: -------------------------------------------------------------------------------- 1 | # Spring Integration Test Example 2 | 3 | There are many examples in this codebase to run integration tests for this application (look in the src/test/java/it folder of 4 | the module) but this shows a minimal use case to copy from. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | gradle integrationTest 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/spring-integration-test-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains an example for writing an integration test for the SQS Listener" 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val springBootVersion: String by project 9 | 10 | dependencies { 11 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 12 | implementation("org.springframework.boot:spring-boot-starter") 13 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 14 | 15 | testImplementation("org.springframework:spring-test") 16 | testImplementation("org.springframework.boot:spring-boot-test") 17 | testImplementation(project(":elasticmq-sqs-client")) 18 | testImplementation(project(":expected-test-exception")) 19 | } 20 | -------------------------------------------------------------------------------- /examples/spring-integration-test-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | sqs: 2 | queues: 3 | # We provide the queue URL via an environment variable and this would be used in a production instance where we may want different deployments having 4 | # different queues included here 5 | integrationTestingQueue: ${SQS_QUEUE_URL} 6 | -------------------------------------------------------------------------------- /examples/spring-integration-test-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/spring-multiple-aws-account-example/README.md: -------------------------------------------------------------------------------- 1 | # Spring Connect to Multiple AWS Account SQS queues 2 | 3 | There can be use cases to connect to SQS queues across multiple AWS accounts and this shows how to configure the 4 | [spring starter](../../spring/spring-starter) module to do this. Note that this uses multiple ElasticMQ servers but it 5 | would be equivalent for configuring multiple `SqsAsyncClients` with different credentials. 6 | 7 | ## Usage 8 | 9 | ```bash 10 | gradle bootRun 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/spring-multiple-aws-account-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Java Dynamic SQS Listener - Spring Starter - Multiple AWS Accounts Example" 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val springBootVersion: String by project 9 | 10 | dependencies { 11 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 12 | implementation("org.springframework.boot:spring-boot-starter") 13 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 14 | implementation(project(":elasticmq-sqs-client")) 15 | } 16 | -------------------------------------------------------------------------------- /examples/spring-multiple-aws-account-example/src/main/java/com/jashmore/sqs/examples/MessageListeners.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import com.jashmore.sqs.annotations.core.basic.QueueListener; 4 | import com.jashmore.sqs.argument.payload.Payload; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @SuppressWarnings("unused") 10 | @Slf4j 11 | public class MessageListeners { 12 | 13 | /** 14 | * Basic queue listener. 15 | * 16 | * @param payload the payload of the SQS Message 17 | */ 18 | @QueueListener(value = "firstClientQueue", sqsClient = "firstClient") 19 | public void firstClientMessageProcessing(@Payload final String payload) { 20 | log.info("Message Received from firstClient#firstClientQueue: {}", payload); 21 | } 22 | 23 | /** 24 | * Basic queue listener. 25 | * 26 | * @param payload the payload of the SQS Message 27 | */ 28 | @QueueListener(value = "secondClientQueue", sqsClient = "secondClient") 29 | public void secondClientMessageProcessing(@Payload final String payload) { 30 | log.info("Message Received from secondClient#secondClientQueue: {}", payload); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/spring-sleuth-example/README.md: -------------------------------------------------------------------------------- 1 | # Spring Sleuth Example 2 | 3 | An example of setting up Brave Tracing with this library. 4 | 5 | ## Steps 6 | 7 | 1. Start up a Zipkin server 8 | 9 | ```shell script 10 | docker run -d -p 9411:9411 openzipkin/zipkin 11 | ``` 12 | 13 | 1. Start the application 14 | 15 | ```shell script 16 | gradle bootRun 17 | ``` 18 | 19 | 1. Look in the logs for the server to see messages processed with Trace IDs 20 | 1. Navigate to and type one of the trace IDs into the search bar to see the full trace 21 | -------------------------------------------------------------------------------- /examples/spring-sleuth-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains examples for listening to SQS queues with tracing provided by Spring Sleuth" 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val springBootVersion: String by project 9 | val springCloudVersion: String by project 10 | 11 | dependencies { 12 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 13 | implementation("org.springframework.boot:spring-boot-starter-web") 14 | implementation("org.springframework.cloud:spring-cloud-starter-zipkin:$springCloudVersion") 15 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 16 | implementation(project(":elasticmq-sqs-client")) 17 | implementation(project(":brave-extension-spring-boot")) 18 | implementation(project(":sqs-brave-tracing")) 19 | } 20 | -------------------------------------------------------------------------------- /examples/spring-sleuth-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring.zipkin.baseUrl: http://localhost:9411/ 2 | spring.zipkin.service.name: service 3 | -------------------------------------------------------------------------------- /examples/spring-sleuth-example/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg %yellow(%mdc) %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/spring-starter-example/README.md: -------------------------------------------------------------------------------- 1 | # Spring Starter Example 2 | 3 | An example of setting up the [Spring Starter](../../spring/spring-starter) module in a Spring Application to listen to 4 | local SQS queues. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | gradle bootRun 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/spring-starter-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains examples for using the Spring Starter implementation of the framework." 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val elasticMqVersion: String by project 9 | val springBootVersion: String by project 10 | 11 | dependencies { 12 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 13 | implementation("org.springframework.boot:spring-boot-starter") 14 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 15 | implementation("org.elasticmq:elasticmq-rest-sqs_2.12:$elasticMqVersion") 16 | implementation(project(":local-sqs-async-client")) 17 | } 18 | -------------------------------------------------------------------------------- /examples/spring-starter-example/src/main/java/com/jashmore/sqs/examples/ScheduledQueueListenerEnabler.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import com.jashmore.sqs.container.MessageListenerContainerCoordinator; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.scheduling.annotation.Scheduled; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Slf4j 10 | @Component 11 | @RequiredArgsConstructor 12 | public class ScheduledQueueListenerEnabler { 13 | 14 | private final MessageListenerContainerCoordinator messageListenerContainerCoordinator; 15 | 16 | /** 17 | * Just a scheduled job that shows that there are methods of turning off the container when necessary. 18 | * 19 | * @throws InterruptedException if the thread was interrupted while sleeping 20 | */ 21 | @Scheduled(initialDelay = 10_000, fixedDelay = 30_000) 22 | public void turnOfSqsListener() throws InterruptedException { 23 | log.info("Turning off SQS Listener for a short period"); 24 | 25 | messageListenerContainerCoordinator.stopAllContainers(); 26 | Thread.sleep(5_000); 27 | log.info("Turning SQS Listener back on"); 28 | messageListenerContainerCoordinator.startAllContainers(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/spring-starter-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/spring-starter-minimal-example/README.md: -------------------------------------------------------------------------------- 1 | # Spring Starter Minimal Example 2 | 3 | An example of setting up the [Spring Starter](../../spring/spring-starter) module in a Spring Application to show the minimal use case of connecting to 4 | a local ElasticMQ server. It will periodically send messages to the queue and log these in the message listener. 5 | 6 | ## Usage 7 | 8 | ```bash 9 | gradle bootRun 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/spring-starter-minimal-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains examples for a minimal implementation of using the Spring Starter of the framework." 3 | 4 | plugins { 5 | id("org.springframework.boot") 6 | } 7 | 8 | val springBootVersion: String by project 9 | 10 | dependencies { 11 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 12 | implementation("org.springframework.boot:spring-boot-starter") 13 | implementation(project(":java-dynamic-sqs-listener-spring-starter")) 14 | implementation(project(":elasticmq-sqs-client")) 15 | } 16 | -------------------------------------------------------------------------------- /examples/spring-starter-minimal-example/src/main/java/com/jashmore/sqs/examples/Application.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import com.jashmore.sqs.elasticmq.ElasticMqSqsAsyncClient; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 8 | 9 | @SpringBootApplication 10 | public class Application { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(Application.class); 14 | } 15 | 16 | @Bean 17 | public SqsAsyncClient sqsAsyncClient() { 18 | return new ElasticMqSqsAsyncClient("myQueueName"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/spring-starter-minimal-example/src/main/java/com/jashmore/sqs/examples/MessageListeners.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import com.jashmore.sqs.annotations.core.basic.QueueListener; 4 | import com.jashmore.sqs.argument.payload.Payload; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @SuppressWarnings("unused") 11 | public class MessageListeners { 12 | 13 | private static final Logger log = LoggerFactory.getLogger(MessageListeners.class); 14 | 15 | @QueueListener(value = "myQueueName") 16 | public void listener(@Payload final String payload) { 17 | log.info("Message received: {}", payload); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/spring-starter-minimal-example/src/main/java/com/jashmore/sqs/examples/ScheduledMessageProducer.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.examples; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.scheduling.annotation.EnableScheduling; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | import software.amazon.awssdk.services.sqs.SqsAsyncClient; 8 | 9 | @Component 10 | @EnableScheduling 11 | public class ScheduledMessageProducer { 12 | 13 | private final SqsAsyncClient sqsAsyncClient; 14 | 15 | @Autowired 16 | public ScheduledMessageProducer(SqsAsyncClient sqsAsyncClient) { 17 | this.sqsAsyncClient = sqsAsyncClient; 18 | } 19 | 20 | @Scheduled(fixedRate = 1_000) 21 | public void sendMessageToQueue() throws Exception { 22 | String queueUrl = sqsAsyncClient.getQueueUrl(builder -> builder.queueName("myQueueName")).get().queueUrl(); 23 | sqsAsyncClient.sendMessage(builder -> builder.queueUrl(queueUrl).messageBody("hello world!")); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/spring-starter-minimal-example/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Extension for integration AWS Xray Tracing into the Message Listeners" 3 | 4 | val awsXrayVersion: String by project 5 | 6 | dependencies { 7 | api("com.amazonaws:aws-xray-recorder-sdk-core:$awsXrayVersion") 8 | api(project(":java-dynamic-sqs-listener-api")) 9 | implementation(project(":common-utils")) 10 | compileOnly(project(":documentation-annotations")) 11 | 12 | testImplementation("org.checkerframework:checker-qual:3.7.1") 13 | testImplementation(project(":java-dynamic-sqs-listener-core")) 14 | testImplementation(project(":expected-test-exception")) 15 | } 16 | 17 | configurations.all { 18 | resolutionStrategy.eachDependency { 19 | // xray requires an older version of jackson 20 | if (requested.group.startsWith("com.fasterxml.jackson")) { 21 | useVersion("2.15.2") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/client/ClientSegmentMutator.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.client; 2 | 3 | import com.amazonaws.xray.entities.Segment; 4 | import com.jashmore.documentation.annotations.ThreadSafe; 5 | 6 | /** 7 | * Mutator that can be used to apply your own custom changes to the segment created in the {@link XrayWrappedSqsAsyncClient}. 8 | * 9 | *

Implementations of this mutator must be thread safe as multiple threads can be using this concurrently. 10 | */ 11 | @ThreadSafe 12 | @FunctionalInterface 13 | public interface ClientSegmentMutator { 14 | /** 15 | * Apply changes to a segment built in the {@link XrayWrappedSqsAsyncClient}. 16 | * 17 | * @param segment the segment to configure 18 | */ 19 | void mutateSegment(Segment segment); 20 | } 21 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/client/ClientSegmentNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.client; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | 5 | /** 6 | * Strategy for naming the Segment that will be created if non exist when using the {@link XrayWrappedSqsAsyncClient}. 7 | * 8 | *

Implementations of this strategy must be thread safe as multiple threads can be using this concurrently. 9 | */ 10 | @ThreadSafe 11 | @FunctionalInterface 12 | public interface ClientSegmentNamingStrategy { 13 | /** 14 | * Get the name of the segment. 15 | * 16 | * @return the segment name 17 | */ 18 | String getSegmentName(); 19 | } 20 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/client/StaticClientSegmentNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.client; 2 | 3 | /** 4 | * A {@link ClientSegmentNamingStrategy} that just returns the same segment name whenever it is requested. 5 | */ 6 | public class StaticClientSegmentNamingStrategy implements ClientSegmentNamingStrategy { 7 | 8 | private final String segmentName; 9 | 10 | /** 11 | * Constructor. 12 | * 13 | * @param segmentName the name of the segment to be used each time 14 | */ 15 | public StaticClientSegmentNamingStrategy(final String segmentName) { 16 | this.segmentName = segmentName; 17 | } 18 | 19 | @Override 20 | public String getSegmentName() { 21 | return segmentName; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/client/UnsampledClientSegmentMutator.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.client; 2 | 3 | import com.amazonaws.xray.entities.Segment; 4 | 5 | /** 6 | * {@link ClientSegmentMutator} that will set the segment as being unsampled. 7 | * 8 | *

This is useful if you don't wish to track the SQS information in the library, such as the message retriever. 9 | */ 10 | public class UnsampledClientSegmentMutator implements ClientSegmentMutator { 11 | 12 | @Override 13 | public void mutateSegment(final Segment segment) { 14 | segment.setSampled(false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/decorator/DecoratorSegmentMutator.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.decorator; 2 | 3 | import com.amazonaws.xray.entities.Segment; 4 | import com.jashmore.documentation.annotations.ThreadSafe; 5 | import com.jashmore.sqs.decorator.MessageProcessingContext; 6 | import software.amazon.awssdk.services.sqs.model.Message; 7 | 8 | /** 9 | * Mutator that can be used to apply your own custom changes to the segment. 10 | * 11 | *

Implementations of this mutator must be thread safe as multiple threads can be using this concurrently. 12 | */ 13 | @ThreadSafe 14 | @FunctionalInterface 15 | public interface DecoratorSegmentMutator { 16 | /** 17 | * Given the {@link Segment} created for the {@link BasicXrayMessageProcessingDecorator}, apply any desired changes, e.g. setting as unsampled, etc. 18 | * 19 | * @param segment the segment to mutate 20 | * @param context the context for this message 21 | * @param message the message being processed 22 | */ 23 | void mutateSegment(Segment segment, MessageProcessingContext context, Message message); 24 | } 25 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/decorator/DecoratorSegmentNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.decorator; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | import com.jashmore.sqs.decorator.MessageProcessingContext; 5 | import software.amazon.awssdk.services.sqs.model.Message; 6 | 7 | /** 8 | * Strategy for naming the Segment that will be started when processing a message. 9 | * 10 | *

Implementations of this strategy must be thread safe as multiple threads can be using this concurrently. 11 | */ 12 | @ThreadSafe 13 | @FunctionalInterface 14 | public interface DecoratorSegmentNamingStrategy { 15 | /** 16 | * Get the name of the segment for this message being processed. 17 | * 18 | * @param context the context of the message listener 19 | * @param message the message being processed 20 | * @return the name of the segment 21 | */ 22 | String getSegmentName(MessageProcessingContext context, Message message); 23 | } 24 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/decorator/DecoratorSubsegmentMutator.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.decorator; 2 | 3 | import com.amazonaws.xray.entities.Subsegment; 4 | import com.jashmore.sqs.decorator.MessageProcessingContext; 5 | import software.amazon.awssdk.services.sqs.model.Message; 6 | 7 | /** 8 | * Mutator that can be used to apply your own custom changes to the subsegment. 9 | * 10 | *

Implementations of this mutator must be thread safe as multiple threads can be using this concurrently. 11 | */ 12 | @FunctionalInterface 13 | public interface DecoratorSubsegmentMutator { 14 | /** 15 | * Given the {@link Subsegment} created for the {@link BasicXrayMessageProcessingDecorator}, apply any desired changes, e.g. setting as unsampled, etc. 16 | * 17 | * @param subsegment the subsegment to mutate 18 | * @param context the context for this message 19 | * @param message the message being processed 20 | */ 21 | void mutateSubsegment(Subsegment subsegment, MessageProcessingContext context, Message message); 22 | } 23 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/decorator/DecoratorSubsegmentNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.decorator; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | import com.jashmore.sqs.decorator.MessageProcessingContext; 5 | import software.amazon.awssdk.services.sqs.model.Message; 6 | 7 | /** 8 | * Strategy for naming the Subsegment that will be started when processing a message. 9 | * 10 | *

Implementations of this strategy must be thread safe as multiple threads can be using this concurrently. 11 | */ 12 | @ThreadSafe 13 | @FunctionalInterface 14 | public interface DecoratorSubsegmentNamingStrategy { 15 | /** 16 | * Get the name of the subsegment for this message being processed. 17 | * 18 | * @param context the context of the message listener 19 | * @param message the message being processed 20 | * @return the name of the segment 21 | */ 22 | String getSubsegmentName(MessageProcessingContext context, Message message); 23 | } 24 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/main/java/com/jashmore/sqs/extensions/xray/decorator/StaticDecoratorSegmentNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.decorator; 2 | 3 | import com.jashmore.sqs.decorator.MessageProcessingContext; 4 | import software.amazon.awssdk.services.sqs.model.Message; 5 | 6 | /** 7 | * Static implementation that will return the same segment name regardless of the message listener or message being processed. 8 | */ 9 | public class StaticDecoratorSegmentNamingStrategy implements DecoratorSegmentNamingStrategy { 10 | 11 | private final String segmentName; 12 | 13 | /** 14 | * Constructor. 15 | * 16 | * @param segmentName the name of the segment to always use 17 | */ 18 | public StaticDecoratorSegmentNamingStrategy(final String segmentName) { 19 | this.segmentName = segmentName; 20 | } 21 | 22 | @Override 23 | public String getSegmentName(final MessageProcessingContext context, final Message message) { 24 | return segmentName; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/core/src/test/java/com/jashmore/sqs/extensions/xray/client/StaticClientSegmentNamingStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.xray.client; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | class StaticClientSegmentNamingStrategyTest { 8 | 9 | @Test 10 | void willReturnSameNameForEachCall() { 11 | final StaticClientSegmentNamingStrategy strategy = new StaticClientSegmentNamingStrategy("static-name"); 12 | assertThat(strategy.getSegmentName()).isEqualTo("static-name"); 13 | assertThat(strategy.getSegmentName()).isEqualTo("static-name"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/spring-boot/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Extension for integration AWS Xray Tracing into the Message Listeners in a Spring Boot Application" 3 | 4 | val springBootVersion: String by project 5 | 6 | dependencies { 7 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 8 | 9 | api(project(":aws-xray-extension-core")) 10 | implementation("org.springframework:spring-context") 11 | implementation("org.springframework.boot:spring-boot-autoconfigure") 12 | implementation(project(":java-dynamic-sqs-listener-core")) 13 | implementation(project(":java-dynamic-sqs-listener-spring-core")) 14 | 15 | testImplementation("org.checkerframework:checker-qual:3.7.1") 16 | testImplementation("org.springframework:spring-test") 17 | testImplementation("org.springframework.boot:spring-boot-test") 18 | testImplementation(project(":elasticmq-sqs-client")) 19 | } 20 | 21 | configurations.all { 22 | resolutionStrategy.eachDependency { 23 | // xray requires an older version of jackson 24 | if (requested.group.startsWith("com.fasterxml.jackson")) { 25 | useVersion("2.15.2") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /extensions/aws-xray-extension/spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | com.jashmore.sqs.extensions.xray.spring.SqsListenerXrayConfiguration 2 | -------------------------------------------------------------------------------- /extensions/brave-extension/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Message Processing Decorator that adds Brave Tracing to the message listeners" 3 | 4 | val braveVersion: String by project 5 | 6 | dependencies { 7 | api(project(":java-dynamic-sqs-listener-api")) 8 | api("io.zipkin.brave:brave:$braveVersion") 9 | implementation(project(":sqs-brave-tracing")) 10 | compileOnly(project(":documentation-annotations")) 11 | 12 | testImplementation("io.zipkin.brave:brave-tests:$braveVersion") 13 | testImplementation(project(":elasticmq-sqs-client")) 14 | testImplementation(project(":expected-test-exception")) 15 | } 16 | -------------------------------------------------------------------------------- /extensions/brave-extension/spring-boot/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Message Processing Decorator that adds Brave Tracing to the message listeners in a Spring Boot Application" 3 | 4 | val springBootVersion: String by project 5 | val braveVersion: String by project 6 | 7 | dependencies { 8 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 9 | api(project(":brave-extension-core")) 10 | compileOnly(project(":documentation-annotations")) 11 | implementation("org.springframework:spring-context") 12 | implementation("org.springframework.boot:spring-boot-autoconfigure") 13 | 14 | testImplementation(project(":sqs-brave-tracing")) 15 | testImplementation("io.zipkin.brave:brave-tests:$braveVersion") 16 | testImplementation("org.springframework:spring-test") 17 | testImplementation("org.springframework.boot:spring-boot-test") 18 | testImplementation(project(":elasticmq-sqs-client")) 19 | testImplementation(project(":java-dynamic-sqs-listener-spring-starter")) 20 | testImplementation(project(":expected-test-exception")) 21 | } 22 | -------------------------------------------------------------------------------- /extensions/brave-extension/spring-boot/src/main/java/com/jashmore/sqs/extensions/brave/spring/BraveMessageProcessingDecoratorConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.brave.spring; 2 | 3 | import brave.Tracing; 4 | import com.jashmore.sqs.extensions.brave.decorator.BraveMessageProcessingDecorator; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | @ConditionalOnClass(name = "brave.Tracing") 12 | public class BraveMessageProcessingDecoratorConfiguration { 13 | 14 | @Bean 15 | @ConditionalOnMissingBean 16 | public BraveMessageProcessingDecorator braveMessageProcessorDecorator(final Tracing tracing) { 17 | return new BraveMessageProcessingDecorator(tracing); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /extensions/brave-extension/spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | =com.jashmore.sqs.extensions.brave.spring.BraveMessageProcessingDecoratorConfiguration 2 | -------------------------------------------------------------------------------- /extensions/core-kotlin-dsl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | description = "Kotlin DSL for building the Core message listeners" 4 | 5 | val jacksonVersion: String by project 6 | val mockitoKotlinVersion: String by project 7 | 8 | plugins { 9 | kotlin("jvm") 10 | } 11 | 12 | dependencies { 13 | implementation(kotlin("stdlib-jdk8")) 14 | api(project(":java-dynamic-sqs-listener-core")) 15 | 16 | testImplementation(project(":elasticmq-sqs-client")) 17 | testImplementation(project(":expected-test-exception")) 18 | testImplementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion") 19 | testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") 20 | } 21 | 22 | tasks.withType().configureEach { 23 | kotlinOptions.jvmTarget = "17" 24 | } 25 | -------------------------------------------------------------------------------- /extensions/core-kotlin-dsl/src/main/kotlin/com/jashmore/sqs/core/kotlin/dsl/MessageListenerDsl.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MatchingDeclarationName") 2 | 3 | package com.jashmore.sqs.core.kotlin.dsl 4 | 5 | import com.jashmore.sqs.argument.ArgumentResolverService 6 | import com.jashmore.sqs.broker.MessageBroker 7 | import com.jashmore.sqs.container.MessageListenerContainer 8 | import com.jashmore.sqs.processor.MessageProcessor 9 | import com.jashmore.sqs.resolver.MessageResolver 10 | import com.jashmore.sqs.retriever.MessageRetriever 11 | 12 | @DslMarker 13 | annotation class MessageListenerComponentDslMarker 14 | 15 | /** 16 | * Used to build a single component of the [MessageListenerContainer]. 17 | * 18 | * @param the type of the component being built 19 | */ 20 | typealias MessageListenerComponentDslBuilder = () -> T 21 | 22 | typealias MessageRetrieverDslBuilder = MessageListenerComponentDslBuilder 23 | 24 | typealias MessageProcessorDslBuilder = MessageListenerComponentDslBuilder 25 | 26 | typealias ArgumentResolverServiceDslBuilder = MessageListenerComponentDslBuilder 27 | 28 | typealias MessageBrokerDslBuilder = MessageListenerComponentDslBuilder 29 | 30 | typealias MessageResolverDslBuilder = MessageListenerComponentDslBuilder 31 | 32 | fun initComponent(componentBuilder: T, init: T.() -> Unit): T { 33 | componentBuilder.init() 34 | return componentBuilder 35 | } 36 | -------------------------------------------------------------------------------- /extensions/core-kotlin-dsl/src/main/kotlin/com/jashmore/sqs/core/kotlin/dsl/processor/DecoratedMessageProcessorDslBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.core.kotlin.dsl.processor 2 | 3 | import com.jashmore.sqs.QueueProperties 4 | import com.jashmore.sqs.decorator.MessageProcessingDecorator 5 | import com.jashmore.sqs.processor.DecoratingMessageProcessor 6 | import com.jashmore.sqs.processor.MessageProcessor 7 | 8 | /** 9 | * Wrap the delegate [MessageProcessor] in a [DecoratingMessageProcessor] if the list of decorators is not empty. 10 | */ 11 | fun optionalDecoratedProcessor( 12 | identifier: String, 13 | queueProperties: QueueProperties, 14 | decorators: List, 15 | delegate: MessageProcessor 16 | ): MessageProcessor { 17 | if (decorators.isEmpty()) { 18 | return delegate 19 | } 20 | 21 | return DecoratingMessageProcessor( 22 | identifier, 23 | queueProperties, 24 | decorators, 25 | delegate 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /extensions/core-kotlin-dsl/src/main/kotlin/com/jashmore/sqs/core/kotlin/dsl/utils/CachingDslUtils.kt: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.core.kotlin.dsl.utils 2 | 3 | import java.time.Duration 4 | 5 | /** 6 | * Cached implementations that works when the usage of it is single threaded. 7 | * 8 | * If this cached function is used in multiple threads, it may result in multiple calls to get the current value at the same time. 9 | * 10 | * @param timeout the amount of time that a value should be cached for 11 | * @param delegate the delegate method to get the value when the cache has expired 12 | * @param the type of the value to cache 13 | */ 14 | fun cached(timeout: Duration, delegate: () -> T): () -> T { 15 | var timeOfLastCachedValueInMs: Long = System.currentTimeMillis() - timeout.toMillis() - 1 16 | var cachedValue: T? = null 17 | 18 | return { 19 | if (timeOfLastCachedValueInMs + timeout.toMillis() < System.currentTimeMillis()) { 20 | cachedValue = delegate() 21 | timeOfLastCachedValueInMs = System.currentTimeMillis() 22 | } 23 | cachedValue!! 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /extensions/core-kotlin-dsl/src/main/kotlin/com/jashmore/sqs/core/kotlin/dsl/utils/RequiredFieldException.kt: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.core.kotlin.dsl.utils 2 | 3 | /** 4 | * Exception thrown if a field is required for the specific component construction. 5 | * 6 | * This is required as the compile time checks for required fields in Kotlin DSL isn't quite there yet. 7 | */ 8 | class RequiredFieldException( 9 | fieldName: String, 10 | componentName: String 11 | ) : RuntimeException("$fieldName is required for $componentName") 12 | -------------------------------------------------------------------------------- /extensions/core-kotlin-dsl/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg %yellow(%mdc) %n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/README.md: -------------------------------------------------------------------------------- 1 | # Spring Cloud Schema Registry Extension 2 | 3 | This extension allows the SQS consumer to be able to parse messages that have been serialized using a schema 4 | like [Avro](https://avro.apache.org/docs/1.9.2/gettingstartedjava.html) and these definitions have been stored in the 5 | [Spring Cloud Schema Registry](https://docs.spring.io/spring-cloud-schema-registry/docs/current/reference/html/spring-cloud-schema-registry.html). 6 | 7 | ## Why would you want this 8 | 9 | You may want to more easily control how the schema of your messages change during the lifecycle of the application using a tool like the 10 | Spring Cloud Schema Registry. This will allow you to have your producers with different versions of your message schema, but the SQS 11 | consumer can still be able to use this. 12 | 13 | ## Examples 14 | 15 | To see this in action take a look at the [Spring Cloud Schema Registry Example](../../examples/spring-cloud-schema-registry-example). 16 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.davidmc24.gradle.plugin.avro.GenerateAvroJavaTask 2 | 3 | description = "Apache Avro implementation of the Spring Cloud Schema Registry Extension" 4 | 5 | plugins { 6 | id("com.github.davidmc24.gradle.plugin.avro") 7 | } 8 | 9 | val avroVersion: String by project 10 | val springBootVersion: String by project 11 | 12 | dependencies { 13 | api("org.apache.avro:avro:$avroVersion") 14 | api(project(":spring-cloud-schema-registry-extension-api")) 15 | 16 | compileOnly(project(":documentation-annotations")) 17 | 18 | testImplementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 19 | testImplementation("org.springframework.boot:spring-boot-starter-test") 20 | testImplementation(project(":elasticmq-sqs-client")) 21 | testImplementation(project(":avro-spring-cloud-schema-registry-sqs-client")) 22 | testImplementation(project(":java-dynamic-sqs-listener-spring-starter")) 23 | testImplementation(project(":in-memory-spring-cloud-schema-registry")) 24 | } 25 | 26 | val generateAvro = tasks.register("generateAvro") { 27 | setSource("src/test/resources/avro-test-schemas") 28 | setOutputDir(file("${buildDir}/generated-test-sources/avro")) 29 | } 30 | 31 | tasks.named("compileJava").configure { 32 | source(generateAvro) 33 | } 34 | 35 | sourceSets { 36 | test { 37 | java { 38 | srcDir("${buildDir}/generated-test-sources/avro") 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/main/java/com/jashmore/sqs/extensions/registry/avro/AvroSchemaProcessingException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry.avro; 2 | 3 | /** 4 | * Exception thrown when there was a problem processing the Avro schema. 5 | */ 6 | public class AvroSchemaProcessingException extends RuntimeException { 7 | 8 | public AvroSchemaProcessingException(final String message, final Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/main/java/com/jashmore/sqs/extensions/registry/avro/AvroSchemaRegistryProducerSchemaRetriever.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry.avro; 2 | 3 | import com.jashmore.sqs.extensions.registry.ProducerSchemaRetriever; 4 | import com.jashmore.sqs.extensions.registry.ProducerSchemaRetrieverException; 5 | import org.apache.avro.Schema; 6 | import org.springframework.cloud.schema.registry.SchemaReference; 7 | import org.springframework.cloud.schema.registry.client.SchemaRegistryClient; 8 | 9 | /** 10 | * Implementation that uses the Spring Cloud Schema Registry to retrieve the schemas that the producer has sent 11 | * messages using. 12 | */ 13 | public class AvroSchemaRegistryProducerSchemaRetriever implements ProducerSchemaRetriever { 14 | 15 | private final SchemaRegistryClient schemaRegistryClient; 16 | 17 | public AvroSchemaRegistryProducerSchemaRetriever(final SchemaRegistryClient schemaRegistryClient) { 18 | this.schemaRegistryClient = schemaRegistryClient; 19 | } 20 | 21 | @Override 22 | public Schema getSchema(final SchemaReference schemaReference) { 23 | try { 24 | final String schemaContent = schemaRegistryClient.fetch(schemaReference); 25 | return new Schema.Parser().parse(schemaContent); 26 | } catch (RuntimeException runtimeException) { 27 | throw new ProducerSchemaRetrieverException(runtimeException); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/main/java/com/jashmore/sqs/extensions/registry/avro/AvroSpringCloudSchemaProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry.avro; 2 | 3 | import com.jashmore.documentation.annotations.Nullable; 4 | import java.util.List; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.core.io.Resource; 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @ConfigurationProperties("spring.cloud.schema.avro") 17 | public class AvroSpringCloudSchemaProperties { 18 | 19 | /** 20 | * The list of schema resources that should be loaded before the {@link #schemaLocations} for the 21 | * scenario that these are reliant on other schemas. 22 | */ 23 | @Nullable 24 | private List schemaImports; 25 | 26 | /** 27 | * The locations of the schemas to use for this service. 28 | * 29 | *

These schemas can be dependent on other schema's defined in the {@link #schemaImports} property. 30 | */ 31 | @Nullable 32 | private List schemaLocations; 33 | } 34 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/main/java/com/jashmore/sqs/extensions/registry/avro/EnableSchemaRegistrySqsExtension.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry.avro; 2 | 3 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 4 | 5 | import com.jashmore.sqs.extensions.registry.SpringCloudSchemaSqsConfiguration; 6 | import com.jashmore.sqs.extensions.registry.avro.AvroSqsSpringCloudSchemaRegistryConfiguration; 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | import org.springframework.context.annotation.Import; 11 | 12 | @Retention(value = RUNTIME) 13 | @Target(ElementType.TYPE) 14 | @Import({ SpringCloudSchemaSqsConfiguration.class, AvroSqsSpringCloudSchemaRegistryConfiguration.class }) 15 | public @interface EnableSchemaRegistrySqsExtension { 16 | } 17 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | schema-registry-client: 4 | endpoint: http://localhost:8990 5 | schema: 6 | avro: 7 | schema-imports: 8 | - classpath:avro-test-schemas/import/author.avsc 9 | schema-locations: 10 | - classpath:avro-test-schemas/schema/book.avsc 11 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/test/resources/avro-non-generated-test-schemas/non-built-schema.avsc: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "com.jashmore.sqs.extensions.registry.model", 3 | "type": "record", 4 | "name": "NonBuiltSchema", 5 | "fields": [ 6 | { "name": "firstname", "type": "string" }, 7 | { "name": "lastname", "type": "string" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/test/resources/avro-test-schemas/import/author.avsc: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "com.jashmore.sqs.extensions.registry.model", 3 | "type": "record", 4 | "name": "Author", 5 | "fields": [ 6 | { "name": "firstname", "type": "string" }, 7 | { "name": "lastname", "type": "string" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/avro-spring-cloud-schema-registry-extension/src/test/resources/avro-test-schemas/schema/book.avsc: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "com.jashmore.sqs.extensions.registry.model", 3 | "type": "record", 4 | "name": "Book", 5 | "fields": [ 6 | { "name": "id", "type": "string" }, 7 | { "name": "name", "type": "string" }, 8 | { "name": "author", "type": "Author" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/in-memory-spring-cloud-schema-registry/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "In Memory implementation of the Spring Cloud Schema Registry that is used for testing purposes" 3 | 4 | val springCloudSchemaRegistryVersion: String by project 5 | 6 | dependencies { 7 | compileOnly(project(":documentation-annotations")) 8 | implementation("org.springframework.cloud:spring-cloud-schema-registry-client:$springCloudSchemaRegistryVersion") 9 | } 10 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/README.md: -------------------------------------------------------------------------------- 1 | # Spring Cloud Schema Registry Extension 2 | 3 | This extension parses the payload of a message that has been serialized by a tool such as an Apache Avro. This is the basic API and does not contain 4 | the implementation details on how to obtain the schemas or how to serialize the payload with these schemas. See the implementations for specific 5 | details, such as the [avro-spring-cloud-schema-registry-extension](../avro-spring-cloud-schema-registry-extension). 6 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "API for building a payload parser that has been serialized via the schema registered in the Spring Cloud Schema Registry" 3 | 4 | val springCloudSchemaRegistryVersion: String by project 5 | 6 | dependencies { 7 | api(project(":java-dynamic-sqs-listener-api")) 8 | api("org.springframework.cloud:spring-cloud-schema-registry-client:$springCloudSchemaRegistryVersion") { 9 | exclude(group = "org.apache.avro", module = "avro") 10 | } 11 | implementation(project(":annotation-utils")) 12 | compileOnly(project(":documentation-annotations")) 13 | } 14 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/ConsumerSchemaRetriever.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | 5 | /** 6 | * Retriever used to get the schema representation of this class that will deserialize the message payload. 7 | * 8 | * @param the type for the schema, for example an Avro schema 9 | */ 10 | @ThreadSafe 11 | @FunctionalInterface 12 | public interface ConsumerSchemaRetriever { 13 | /** 14 | * Get the schema representation for the provided class. 15 | * 16 | *

This will be used by the {@link MessagePayloadDeserializer} to determine how it can transform the message 17 | * that may be have been produced with a different version of the schema. 18 | * 19 | * @param clazz the class of the object to get the schema for 20 | * @return the schema definition for this class 21 | */ 22 | T getSchema(Class clazz); 23 | } 24 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/ConsumerSchemaRetrieverException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | /** 4 | * Exception thrown if there is a problem getting the consumer schema. 5 | */ 6 | public class ConsumerSchemaRetrieverException extends RuntimeException { 7 | 8 | public ConsumerSchemaRetrieverException(final String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/InMemoryCachingProducerSchemaRetriever.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import org.springframework.cloud.schema.registry.SchemaReference; 7 | 8 | /** 9 | * In memory cache implementation that can be used to reduce the number of times that the schema 10 | * is calculated (which would be a lot). 11 | */ 12 | @ThreadSafe 13 | public class InMemoryCachingProducerSchemaRetriever implements ProducerSchemaRetriever { 14 | 15 | private final Map cache = new ConcurrentHashMap<>(); 16 | private final ProducerSchemaRetriever delegate; 17 | 18 | public InMemoryCachingProducerSchemaRetriever(final ProducerSchemaRetriever delegate) { 19 | this.delegate = delegate; 20 | } 21 | 22 | @Override 23 | public T getSchema(final SchemaReference reference) { 24 | return cache.computeIfAbsent(reference, delegate::getSchema); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/MessagePayloadDeserializerException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | /** 4 | * Exception thrown when there was an error trying to deserialize the message payload to the required schema. 5 | */ 6 | public class MessagePayloadDeserializerException extends RuntimeException { 7 | 8 | public MessagePayloadDeserializerException(final Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/ProducerSchemaRetriever.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | import org.springframework.cloud.schema.registry.SchemaReference; 5 | 6 | /** 7 | * Used to obtain the schema for a message that was sent from a producer. 8 | * 9 | * @param the type of the schema, for example an Avro schema 10 | */ 11 | @ThreadSafe 12 | @FunctionalInterface 13 | public interface ProducerSchemaRetriever { 14 | /** 15 | * Given the schema reference, obtain the Schema that represents the payload that was sent from the producer. 16 | * 17 | *

For example, given that it is an object of type "Signal.v2", return the schema for this so the consumer 18 | * will know how to transform this to the version that they have defined, e.g. "Signal.v3". 19 | * 20 | * @param reference the reference to the schema that the producer was using 21 | * @return the schema definition 22 | * @throws ProducerSchemaRetrieverException when there was an error getting the schema 23 | */ 24 | T getSchema(SchemaReference reference) throws ProducerSchemaRetrieverException; 25 | } 26 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/ProducerSchemaRetrieverException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | /** 4 | * Exception thrown when there was an error trying to obtain the schema of the message that the producer 5 | * used to publish the message. 6 | */ 7 | public class ProducerSchemaRetrieverException extends RuntimeException { 8 | 9 | public ProducerSchemaRetrieverException(final Throwable cause) { 10 | super(cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/SchemaReferenceExtractor.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | import com.jashmore.documentation.annotations.ThreadSafe; 4 | import org.springframework.cloud.schema.registry.SchemaReference; 5 | import software.amazon.awssdk.services.sqs.model.Message; 6 | 7 | @ThreadSafe 8 | @FunctionalInterface 9 | public interface SchemaReferenceExtractor { 10 | /** 11 | * Obtain the {@link SchemaReference} from the message to use for determining what schema should be used to deserialize the message. 12 | * 13 | *

This could be obtained by looking at the content type of the {@link Message}, for example the {@link MessageAttributeSchemaReferenceExtractor} 14 | * which looks at the message attribute of the message to get the version, etc. 15 | * 16 | * @param message the message to process 17 | * @return the schema reference for this message 18 | */ 19 | SchemaReference extract(Message message); 20 | } 21 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/SchemaReferenceExtractorException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | /** 4 | * Exception for when there was a problem determining the version of the schema for the received message. 5 | */ 6 | public class SchemaReferenceExtractorException extends RuntimeException { 7 | 8 | public SchemaReferenceExtractorException(final String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/SpringCloudSchemaRegistryPayload.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Default annotation for a message consumer's parameter that indicate that it should be resolved to the payload of the message using schemas defined 11 | * in the Spring Cloud Schema Registry. 12 | * 13 | *

Parameters marked with this annotation do not have a type restriction but the type of the parameter must be able to be map 14 | * from the message body via the Schema, such as an Avro schema. 15 | */ 16 | @Retention(value = RUNTIME) 17 | @Target(ElementType.PARAMETER) 18 | public @interface SpringCloudSchemaRegistryPayload { 19 | } 20 | -------------------------------------------------------------------------------- /extensions/spring-cloud-schema-registry-extension/spring-cloud-schema-registry-extension-api/src/main/java/com/jashmore/sqs/extensions/registry/SpringCloudSchemaSqsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.extensions.registry; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 4 | import org.springframework.cloud.schema.registry.client.EnableSchemaRegistryClient; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | @EnableSchemaRegistryClient 10 | @SuppressWarnings("checkstyle:javadocmethod") 11 | public class SpringCloudSchemaSqsConfiguration { 12 | 13 | @Bean 14 | @ConditionalOnMissingBean(SchemaReferenceExtractor.class) 15 | public SchemaReferenceExtractor schemaReferenceExtractor() { 16 | return new MessageAttributeSchemaReferenceExtractor(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Versions 2 | assertJVersion=3.26.3 3 | avroVersion=1.12.0 4 | awsVersion=2.28.15 5 | awsXrayVersion=2.7.1 6 | braveVersion=6.0.3 7 | cglibVersion=3.3.0 8 | elasticMqVersion=1.6.8 9 | immutablesVersion=2.10.1 10 | jacksonVersion=2.18.0 11 | junitJupiterVersion=5.11.1 12 | ktorVersion=2.3.12 13 | logbackVersion=1.5.8 14 | lombokVersion=1.18.34 15 | micronautVersion=4.6.1 16 | mockitoVersion=5.14.1 17 | mockitoKotlinVersion=5.4.0 18 | slf4jVersion=2.0.16 19 | spotbugsVersion=4.2.3 20 | springBootVersion=3.3.4 21 | springCloudVersion=2.2.8.RELEASE 22 | springCloudSchemaRegistryVersion=1.1.5 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaidenAshmore/java-dynamic-sqs-listener/745fde95ca438b02270ede19450dfda877b7d7e1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /ktor/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | description = "Library for integrating the Java Dynamic Sqs Listener in a Ktor application" 4 | 5 | val ktorVersion: String by project 6 | val mockitoKotlinVersion: String by project 7 | 8 | plugins { 9 | kotlin("jvm") 10 | } 11 | 12 | dependencies { 13 | implementation(kotlin("stdlib-jdk8")) 14 | api(project(":java-dynamic-sqs-listener-core")) 15 | api(project(":core-kotlin-dsl")) 16 | implementation("io.ktor:ktor-server-core:$ktorVersion") 17 | 18 | testImplementation(project(":elasticmq-sqs-client")) 19 | testImplementation(project(":expected-test-exception")) 20 | testImplementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion") 21 | testImplementation("io.ktor:ktor-server-test-host:$ktorVersion") 22 | testImplementation("io.ktor:ktor-server-netty:$ktorVersion") 23 | } 24 | 25 | tasks.withType().configureEach { 26 | kotlinOptions.jvmTarget = "17" 27 | } 28 | -------------------------------------------------------------------------------- /ktor/core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.addLombokGeneratedAnnotation = true 2 | # Adds @ConstructorProperties to constructors so that Jackson Deserialization is able to deserialize the fields 3 | lombok.anyConstructor.addConstructorProperties=true -------------------------------------------------------------------------------- /micronaut/micronaut-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "Library for integrating the Java Dynamic Sqs Listener in a Micronaut application" 2 | 3 | val micronautVersion: String by project 4 | 5 | dependencies { 6 | implementation(platform("io.micronaut.platform:micronaut-platform:${micronautVersion}")) 7 | annotationProcessor(platform("io.micronaut.platform:micronaut-platform:${micronautVersion}")) 8 | 9 | api(project(":java-dynamic-sqs-listener-core")) 10 | api(project(":java-dynamic-sqs-listener-annotations")) 11 | annotationProcessor("io.micronaut:micronaut-inject-java") 12 | implementation(project(":common-utils")) 13 | implementation(project(":annotation-utils")) 14 | compileOnly(project(":documentation-annotations")) 15 | implementation("io.micronaut:micronaut-inject") 16 | implementation("io.micronaut:micronaut-messaging") 17 | implementation("io.micronaut:micronaut-context") 18 | implementation("io.micronaut:micronaut-jackson-databind") 19 | 20 | testAnnotationProcessor(platform("io.micronaut.platform:micronaut-platform:${micronautVersion}")) 21 | testAnnotationProcessor("io.micronaut:micronaut-inject-java") 22 | testImplementation("io.micronaut.test:micronaut-test-junit5") 23 | testImplementation(project(":elasticmq-sqs-client")) 24 | testImplementation(project(":expected-test-exception")) 25 | 26 | integrationTestAnnotationProcessor(project(":java-dynamic-sqs-listener-micronaut-inject-java")) 27 | integrationTestAnnotationProcessor("io.micronaut:micronaut-inject-java") 28 | integrationTestCompileOnly("io.micronaut:micronaut-aop:$micronautVersion") 29 | } 30 | -------------------------------------------------------------------------------- /micronaut/micronaut-core/src/main/java/com/jashmore/sqs/micronaut/container/MicronautMessageListenerContainerCoordinatorProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.micronaut.container; 2 | 3 | import com.jashmore.sqs.container.MessageListenerContainer; 4 | import io.micronaut.context.annotation.ConfigurationProperties; 5 | import io.micronaut.core.bind.annotation.Bindable; 6 | 7 | @ConfigurationProperties(MicronautMessageListenerContainerCoordinatorProperties.PREFIX) 8 | public interface MicronautMessageListenerContainerCoordinatorProperties { 9 | String PREFIX = "java-dynamic-sqs-listener-micronaut"; 10 | 11 | /** 12 | * Determine if the {@link MessageListenerContainer}s should be started up by default 13 | * when the Micronaut application starts up. 14 | * 15 | * @return whether the containers will be started on startup 16 | */ 17 | @Bindable(defaultValue = "true") 18 | boolean isAutoStartContainersEnabled(); 19 | } 20 | -------------------------------------------------------------------------------- /micronaut/micronaut-core/src/main/java/com/jashmore/sqs/micronaut/container/MicronautMessageListenerContainerRegistry.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.micronaut.container; 2 | 3 | import com.jashmore.sqs.container.MessageListenerContainer; 4 | import jakarta.inject.Singleton; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.concurrent.ConcurrentMap; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | @Singleton 11 | @Slf4j 12 | public class MicronautMessageListenerContainerRegistry { 13 | 14 | private final ConcurrentMap messageListenerContainers = new ConcurrentHashMap<>(); 15 | 16 | synchronized void put(MessageListenerContainer container) { 17 | if (messageListenerContainers.containsKey(container.getIdentifier())) { 18 | throw new IllegalStateException("Created two MessageListenerContainers with the same identifier: " + container.getIdentifier()); 19 | } 20 | log.debug("Created MessageListenerContainer with id: {}", container.getIdentifier()); 21 | messageListenerContainers.put(container.getIdentifier(), container); 22 | } 23 | 24 | Map getContainerMap() { 25 | return Map.copyOf(messageListenerContainers); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /micronaut/micronaut-core/src/main/java/com/jashmore/sqs/micronaut/jackson/SqsListenerObjectMapperSupplier.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.micronaut.jackson; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import java.util.function.Supplier; 5 | 6 | /** 7 | * Specific {@link ObjectMapper} supplier for the use in this Spring Library to provide easier customisation of the {@link ObjectMapper} used in 8 | * de-serialisation. 9 | * 10 | *

This wrapper is needed as there is complications with how a custom {@link ObjectMapper} can be supplied without impacting the default Spring Web 11 | * {@link ObjectMapper}. We don't want this library's {@link ObjectMapper} to override the default Spring Boot one unintentionally. 12 | */ 13 | @FunctionalInterface 14 | public interface SqsListenerObjectMapperSupplier extends Supplier {} 15 | -------------------------------------------------------------------------------- /micronaut/micronaut-core/src/main/java/com/jashmore/sqs/micronaut/placeholder/MicronautPlaceholderResolver.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.micronaut.placeholder; 2 | 3 | import com.jashmore.sqs.placeholder.PlaceholderResolver; 4 | import io.micronaut.context.env.Environment; 5 | import lombok.AllArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | public class MicronautPlaceholderResolver implements PlaceholderResolver { 9 | 10 | private final Environment environment; 11 | 12 | @Override 13 | public String resolvePlaceholders(String text) { 14 | return environment.getPlaceholderResolver().resolveRequiredPlaceholders(text); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /micronaut/micronaut-inject-java/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "Micronaut annotation processing for core annotations" 2 | 3 | val micronautVersion: String by project 4 | 5 | configurations.annotationProcessor { 6 | extendsFrom(configurations.implementation.get()) 7 | } 8 | 9 | dependencies { 10 | implementation(platform("io.micronaut.platform:micronaut-platform:${micronautVersion}")) 11 | implementation("io.micronaut:micronaut-inject-java") 12 | } -------------------------------------------------------------------------------- /micronaut/micronaut-inject-java/src/main/java/com/jashmore/sqs/micronaut/service/FifoQueueListenerAnnotationTransformer.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.micronaut.service; 2 | 3 | import io.micronaut.context.annotation.Executable; 4 | import io.micronaut.core.annotation.AnnotationValue; 5 | import io.micronaut.core.annotation.NonNull; 6 | import io.micronaut.inject.annotation.NamedAnnotationTransformer; 7 | import io.micronaut.inject.visitor.VisitorContext; 8 | import java.lang.annotation.Annotation; 9 | import java.util.List; 10 | 11 | public class FifoQueueListenerAnnotationTransformer implements NamedAnnotationTransformer { 12 | 13 | @Override 14 | public @NonNull String getName() { 15 | return "com.jashmore.sqs.annotations.core.fifo.FifoQueueListener"; 16 | } 17 | 18 | @Override 19 | public List> transform(AnnotationValue annotation, VisitorContext visitorContext) { 20 | return List.of( 21 | annotation.mutate().stereotype(AnnotationValue.builder(Executable.class).member("processOnStartup", true).build()).build() 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /micronaut/micronaut-inject-java/src/main/java/com/jashmore/sqs/micronaut/service/PrefetchingQueueListenerAnnotationTransformer.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.micronaut.service; 2 | 3 | import io.micronaut.context.annotation.Executable; 4 | import io.micronaut.core.annotation.AnnotationValue; 5 | import io.micronaut.core.annotation.NonNull; 6 | import io.micronaut.inject.annotation.NamedAnnotationTransformer; 7 | import io.micronaut.inject.visitor.VisitorContext; 8 | import java.lang.annotation.Annotation; 9 | import java.util.List; 10 | 11 | public class PrefetchingQueueListenerAnnotationTransformer implements NamedAnnotationTransformer { 12 | 13 | @Override 14 | public @NonNull String getName() { 15 | return "com.jashmore.sqs.annotations.core.prefetch.PrefetchingQueueListener"; 16 | } 17 | 18 | @Override 19 | public List> transform(AnnotationValue annotation, VisitorContext visitorContext) { 20 | return List.of( 21 | annotation.mutate().stereotype(AnnotationValue.builder(Executable.class).member("processOnStartup", true).build()).build() 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /micronaut/micronaut-inject-java/src/main/java/com/jashmore/sqs/micronaut/service/QueueListenerAnnotationTransformer.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.micronaut.service; 2 | 3 | import io.micronaut.context.annotation.Executable; 4 | import io.micronaut.core.annotation.AnnotationValue; 5 | import io.micronaut.core.annotation.NonNull; 6 | import io.micronaut.inject.annotation.NamedAnnotationTransformer; 7 | import io.micronaut.inject.visitor.VisitorContext; 8 | import java.lang.annotation.Annotation; 9 | import java.util.List; 10 | 11 | public class QueueListenerAnnotationTransformer implements NamedAnnotationTransformer { 12 | 13 | @Override 14 | public @NonNull String getName() { 15 | return "com.jashmore.sqs.annotations.core.basic.QueueListener"; 16 | } 17 | 18 | @Override 19 | public List> transform(AnnotationValue annotation, VisitorContext visitorContext) { 20 | return List.of( 21 | annotation.mutate().stereotype(AnnotationValue.builder(Executable.class).member("processOnStartup", true).build()).build() 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /micronaut/micronaut-inject-java/src/main/resources/META-INF/services/io.micronaut.inject.annotation.AnnotationTransformer: -------------------------------------------------------------------------------- 1 | com.jashmore.sqs.micronaut.service.QueueListenerAnnotationTransformer 2 | com.jashmore.sqs.micronaut.service.FifoQueueListenerAnnotationTransformer 3 | com.jashmore.sqs.micronaut.service.PrefetchingQueueListenerAnnotationTransformer 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@prettier/plugin-xml": "^3.0.0", 4 | "husky": "^8.0.0", 5 | "lint-staged": "^13.2.3", 6 | "markdown-link-check": "^3.11.2", 7 | "prettier": "^3.0.0", 8 | "prettier-plugin-java": "^2.2.0" 9 | }, 10 | "scripts": { 11 | "md-links-check": "find . -name \\*.md -not -path \"*/target/*\" -not -path \"*/build/*\" -not -path \"*/node_modules/*\" | tr '\\n' '\\0' | xargs -0 -n1 sh -c 'npx markdown-link-check -c \".markdownlinkcheck.json\" $0 || exit 255'", 12 | "format": "npx prettier --write \"**/*\"", 13 | "test": "npm run md-links-check && npx prettier --check \"**/*\"", 14 | "prepare": "husky install" 15 | }, 16 | "lint-staged": { 17 | "*": [ 18 | "prettier --write", 19 | "git add" 20 | ] 21 | }, 22 | "packageManager": "yarn@3.6.1" 23 | } 24 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "separateMinorPatch": true, 4 | "patch": { 5 | "automerge": true, 6 | "automergeType": "branch" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spring/spring-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Core Spring implementation for the Java Dynamic SQS Listener library" 3 | 4 | val springBootVersion: String by project 5 | 6 | dependencies { 7 | implementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 8 | 9 | api(project(":java-dynamic-sqs-listener-core")) 10 | api(project(":java-dynamic-sqs-listener-annotations")) 11 | implementation(project(":common-utils")) 12 | implementation(project(":annotation-utils")) 13 | compileOnly(project(":documentation-annotations")) 14 | implementation("org.springframework.boot:spring-boot-autoconfigure") 15 | 16 | testImplementation("org.springframework.boot:spring-boot-starter") 17 | testImplementation("org.springframework:spring-tx") 18 | testImplementation("org.springframework:spring-test") 19 | testImplementation("org.springframework.boot:spring-boot-test") 20 | testImplementation("org.springframework.boot:spring-boot-starter-aop") 21 | testImplementation(project(":elasticmq-sqs-client")) 22 | testImplementation(project(":expected-test-exception")) 23 | } 24 | -------------------------------------------------------------------------------- /spring/spring-core/src/main/java/com/jashmore/sqs/spring/container/SpringMessageListenerContainerCoordinatorProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.spring.container; 2 | 3 | import com.jashmore.sqs.container.MessageListenerContainer; 4 | 5 | public interface SpringMessageListenerContainerCoordinatorProperties { 6 | /** 7 | * Determine if the {@link MessageListenerContainer}s should be started up by default when the Spring Container starts up. 8 | * 9 | * @return whether the containers will be started on startup 10 | */ 11 | boolean isAutoStartContainersEnabled(); 12 | } 13 | -------------------------------------------------------------------------------- /spring/spring-core/src/main/java/com/jashmore/sqs/spring/container/StaticSpringMessageListenerContainerCoordinatorProperties.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.spring.container; 2 | 3 | import lombok.Builder; 4 | import lombok.Value; 5 | 6 | /** 7 | * Provides the properties as static values that never change. 8 | */ 9 | @Value 10 | @Builder(toBuilder = true) 11 | public class StaticSpringMessageListenerContainerCoordinatorProperties implements SpringMessageListenerContainerCoordinatorProperties { 12 | 13 | boolean isAutoStartContainersEnabled; 14 | 15 | @Override 16 | public boolean isAutoStartContainersEnabled() { 17 | return isAutoStartContainersEnabled; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /spring/spring-core/src/main/java/com/jashmore/sqs/spring/jackson/SqsListenerObjectMapperSupplier.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.spring.jackson; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import java.util.function.Supplier; 5 | 6 | /** 7 | * Specific {@link ObjectMapper} supplier for the use in this Spring Library to provide easier customisation of the {@link ObjectMapper} used in 8 | * de-serialisation. 9 | * 10 | *

This wrapper is needed as there is complications with how a custom {@link ObjectMapper} can be supplied without impacting the default Spring Web 11 | * {@link ObjectMapper}. We don't want this library's {@link ObjectMapper} to override the default Spring Boot one unintentionally. 12 | */ 13 | @FunctionalInterface 14 | public interface SqsListenerObjectMapperSupplier extends Supplier {} 15 | -------------------------------------------------------------------------------- /spring/spring-core/src/main/java/com/jashmore/sqs/spring/placeholder/SpringPlaceholderResolver.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.spring.placeholder; 2 | 3 | import com.jashmore.sqs.placeholder.PlaceholderResolver; 4 | import org.springframework.core.env.Environment; 5 | 6 | /** 7 | * Implementation that replaces placeholders via the Spring Environment, e.g. application.properties files. 8 | */ 9 | public class SpringPlaceholderResolver implements PlaceholderResolver { 10 | 11 | private final Environment environment; 12 | 13 | public SpringPlaceholderResolver(final Environment environment) { 14 | this.environment = environment; 15 | } 16 | 17 | @Override 18 | public String resolvePlaceholders(final String text) { 19 | return environment.resolveRequiredPlaceholders(text); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring/spring-core/src/test/java/com/jashmore/sqs/spring/placeholder/SpringPlaceholderResolverTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.spring.placeholder; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.junit.jupiter.api.Assertions.*; 5 | 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.mock.env.MockEnvironment; 9 | 10 | class SpringPlaceholderResolverTest { 11 | 12 | MockEnvironment environment; 13 | 14 | SpringPlaceholderResolver resolver; 15 | 16 | @BeforeEach 17 | void setUp() { 18 | environment = new MockEnvironment(); 19 | resolver = new SpringPlaceholderResolver(environment); 20 | } 21 | 22 | @Test 23 | void willResolvePlaceholdersFromMapping() { 24 | environment.withProperty("key", "value"); 25 | assertThat(resolver.resolvePlaceholders("something ${key}")).isEqualTo("something value"); 26 | } 27 | 28 | @Test 29 | void willThrowExceptionsIfNoMappingFound() { 30 | assertThrows(IllegalArgumentException.class, () -> resolver.resolvePlaceholders("something ${key}")); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spring/spring-core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /spring/spring-core/src/test/resources/slf4jtest.properties: -------------------------------------------------------------------------------- 1 | print.level=INFO -------------------------------------------------------------------------------- /spring/spring-starter/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Spring Starter for automatically setting up the Spring Core implementation in a Spring Boot Application" 3 | 4 | val springBootVersion: String by project 5 | 6 | dependencies { 7 | api(project(":java-dynamic-sqs-listener-spring-core")) 8 | 9 | testImplementation(project(":elasticmq-sqs-client")) 10 | testImplementation(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion")) 11 | testImplementation("org.springframework.boot:spring-boot-starter") 12 | testImplementation("org.springframework.boot:spring-boot-starter-web") 13 | testImplementation("org.springframework:spring-tx") 14 | testImplementation("org.springframework:spring-test") 15 | testImplementation("org.springframework.boot:spring-boot-test") 16 | testImplementation("org.springframework.boot:spring-boot-starter-aop") 17 | } 18 | -------------------------------------------------------------------------------- /spring/spring-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | com.jashmore.sqs.spring.config.QueueListenerConfiguration 2 | -------------------------------------------------------------------------------- /util/annotation-utils/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Utility methods for dealing with annotations in a library that may be using proxying tools like cglib" 3 | 4 | dependencies { 5 | implementation(project(":java-dynamic-sqs-listener-api")) 6 | implementation(project(":common-utils")) 7 | compileOnly(project(":documentation-annotations")) 8 | 9 | testImplementation(project(":proxy-method-interceptor")) 10 | } 11 | -------------------------------------------------------------------------------- /util/avro-spring-cloud-schema-registry-sqs-client/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Wrapper around the AWS SQS Async Client to help serialize messages using Avro and the Spring Cloud Schema Registry" 3 | 4 | val avroVersion: String by project 5 | val awsVersion: String by project 6 | val springCloudSchemaRegistryVersion: String by project 7 | 8 | dependencies { 9 | api(platform("software.amazon.awssdk:bom:$awsVersion")) 10 | api("software.amazon.awssdk:sqs") 11 | api("org.springframework.cloud:spring-cloud-schema-registry-client:$springCloudSchemaRegistryVersion") 12 | api("org.apache.avro:avro:$avroVersion") 13 | } 14 | -------------------------------------------------------------------------------- /util/common-utils/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Utility methods for dealing with basic functionality for this library, split out so so it can be consumed by other extensions" 3 | 4 | val slf4jVersion: String by project 5 | 6 | dependencies { 7 | implementation("org.slf4j:slf4j-api:$slf4jVersion") 8 | compileOnly(project(":documentation-annotations")) 9 | 10 | testImplementation(project(":proxy-method-interceptor")) 11 | testImplementation(project(":expected-test-exception")) 12 | } 13 | -------------------------------------------------------------------------------- /util/common-utils/src/main/java/com/jashmore/sqs/util/ResizableSemaphore.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.util; 2 | 3 | import java.util.concurrent.Semaphore; 4 | 5 | /** 6 | * Semaphore that is able to dynamically update the number of available permits. 7 | */ 8 | public class ResizableSemaphore extends Semaphore { 9 | 10 | private int maximumPermits; 11 | 12 | public ResizableSemaphore(final int permits) { 13 | super(permits); 14 | this.maximumPermits = permits; 15 | } 16 | 17 | /** 18 | * Change the maximum number of permits available. 19 | * 20 | *

The changing of permit size is not thread safe and therefore this method should only be used by a single thread. E.g. only one thread has the 21 | * responsibility of changing the permit size. 22 | * 23 | * @param permits new max size for permits 24 | */ 25 | public void changePermitSize(final int permits) { 26 | if (permits > this.maximumPermits) { 27 | this.release(permits - this.maximumPermits); 28 | } else if (permits < this.maximumPermits) { 29 | this.reducePermits(this.maximumPermits - permits); 30 | } 31 | this.maximumPermits = permits; 32 | } 33 | 34 | public int getMaximumPermits() { 35 | return maximumPermits; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /util/common-utils/src/main/java/com/jashmore/sqs/util/identifier/IdentifierUtils.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.util.identifier; 2 | 3 | import com.jashmore.documentation.annotations.Nullable; 4 | import com.jashmore.sqs.util.string.StringUtils; 5 | import java.lang.reflect.Method; 6 | import lombok.experimental.UtilityClass; 7 | 8 | @UtilityClass 9 | public class IdentifierUtils { 10 | 11 | /** 12 | * Builds an identifier from a provided identifier if it is not empty, otherwise build an identifier from the class and method. 13 | * 14 | * @param identifier the identifier to use if it is not an empty string 15 | * @param clazz the class that the method is on, used if no identifier supplied 16 | * @param method the method used for the message listener, used if no identifier supplied 17 | * @return an identifier for this class' method 18 | */ 19 | public String buildIdentifierForMethod(@Nullable final String identifier, final Class clazz, final Method method) { 20 | if (!StringUtils.hasText(identifier.trim())) { 21 | return StringUtils.toLowerHyphenCase(clazz.getSimpleName() + "-" + method.getName()); 22 | } else { 23 | return identifier.trim(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /util/common-utils/src/test/java/com/jashmore/sqs/util/identifier/IdentifierUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.util.identifier; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.lang.reflect.Method; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class IdentifierUtilsTest { 9 | 10 | @Test 11 | void whenIdentifierEmptyCanBeBuiltFromClassAndMethod() throws Exception { 12 | // arrange 13 | final Method method = IdentifierUtilsTest.class.getMethod("method"); 14 | 15 | // act 16 | final String identifier = IdentifierUtils.buildIdentifierForMethod("", IdentifierUtilsTest.class, method); 17 | 18 | // assert 19 | assertThat(identifier).isEqualTo("identifier-utils-test-method"); 20 | } 21 | 22 | @Test 23 | void whenIdentifierNotEmptyStringItIsReturnedAfterBeingTrimmed() throws Exception { 24 | // arrange 25 | final Method method = IdentifierUtilsTest.class.getMethod("method"); 26 | 27 | // act 28 | final String identifier = IdentifierUtils.buildIdentifierForMethod(" test ", IdentifierUtilsTest.class, method); 29 | 30 | // assert 31 | assertThat(identifier).isEqualTo("test"); 32 | } 33 | 34 | @SuppressWarnings("WeakerAccess") 35 | public void method() {} 36 | } 37 | -------------------------------------------------------------------------------- /util/documentation-annotations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Java Dynamic SQS Listener - Utilities - Documentation Annotations" 3 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/GuardedBy.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Indicates that the given field is protected from concurrency problems by the given object. 12 | */ 13 | @Documented 14 | @Target({ FIELD }) 15 | @Retention(SOURCE) 16 | public @interface GuardedBy { 17 | String value(); 18 | } 19 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/Max.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * Indicates that the field, return value, etc should be less than the provided value. 17 | */ 18 | @Documented 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 20 | @Retention(SOURCE) 21 | public @interface Max { 22 | int value(); 23 | } 24 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/Min.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * Indicates that the field, return value, etc should be larger than the provided value. 17 | */ 18 | @Documented 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 20 | @Retention(SOURCE) 21 | public @interface Min { 22 | int value(); 23 | } 24 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/Nonnull.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * Indicates that the field, return value, etc can return a null value. 17 | */ 18 | @Documented 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 20 | @Retention(SOURCE) 21 | public @interface Nonnull { 22 | } 23 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/NotThreadSafe.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.SOURCE; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * Indicates that the given class or method does not need to be thread safe as is should not be used concurrently. 13 | */ 14 | @Documented 15 | @Target({ TYPE, METHOD }) 16 | @Retention(SOURCE) 17 | public @interface NotThreadSafe { 18 | } 19 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/Nullable.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * Indicates that the field, return value, etc can return a null value. 17 | */ 18 | @Documented 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 20 | @Retention(SOURCE) 21 | public @interface Nullable { 22 | } 23 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/Positive.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * Indicates that the field, return value, etc should return a number that is positive. 17 | */ 18 | @Documented 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 20 | @Retention(SOURCE) 21 | public @interface Positive { 22 | } 23 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/PositiveOrZero.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * Indicates that the field, return value, etc should return a number that is positive or zero. 17 | */ 18 | @Documented 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 20 | @Retention(SOURCE) 21 | public @interface PositiveOrZero { 22 | } 23 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/ThreadSafe.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.SOURCE; 6 | 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * Indicates that the given class or method must be Thread Safe as it will be used concurrently. 13 | */ 14 | @Documented 15 | @Target({ TYPE, METHOD }) 16 | @Retention(SOURCE) 17 | public @interface ThreadSafe { 18 | } 19 | -------------------------------------------------------------------------------- /util/documentation-annotations/src/main/java/com/jashmore/documentation/annotations/VisibleForTesting.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.documentation.annotations; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 5 | import static java.lang.annotation.ElementType.FIELD; 6 | import static java.lang.annotation.ElementType.METHOD; 7 | import static java.lang.annotation.ElementType.PARAMETER; 8 | import static java.lang.annotation.ElementType.TYPE_USE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | import java.lang.annotation.Documented; 12 | import java.lang.annotation.Retention; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * Indicates that the component should be a lower restriction, e.g. private instead of package protected, but is more open for testing purposes. 17 | */ 18 | @Documented 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 20 | @Retention(SOURCE) 21 | public @interface VisibleForTesting { 22 | } 23 | -------------------------------------------------------------------------------- /util/elasticmq-sqs-client/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Provides the ability to create a SqsAsyncClient backed by an in-memory ElasticMQ SQS Server" 3 | 4 | val elasticMqVersion: String by project 5 | 6 | dependencies { 7 | api(project(":local-sqs-async-client")) 8 | implementation("org.elasticmq:elasticmq-rest-sqs_2.12:$elasticMqVersion") 9 | } 10 | -------------------------------------------------------------------------------- /util/expected-test-exception/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Contains an Expected Test Exception to make it clear that this is expected and don't include the stack trace" 3 | -------------------------------------------------------------------------------- /util/expected-test-exception/src/main/java/com/jashmore/sqs/util/ExpectedTestException.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.util; 2 | 3 | /** 4 | * Exception that is expected and therefore would be nice to litter the logs with stack traces that make it seem like something is broken. 5 | */ 6 | public class ExpectedTestException extends RuntimeException { 7 | 8 | public ExpectedTestException() { 9 | super("This was expected"); 10 | } 11 | 12 | public synchronized Throwable fillInStackTrace() { 13 | return this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /util/local-sqs-async-client/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Provides a local Amazon SQS implementation that can talk to a locally running SQS queue like localstack" 3 | 4 | val awsVersion: String by project 5 | val slf4jVersion: String by project 6 | val elasticMqVersion: String by project 7 | 8 | dependencies { 9 | api(platform("software.amazon.awssdk:bom:$awsVersion")) 10 | api("software.amazon.awssdk:sqs") 11 | implementation("org.slf4j:slf4j-api:$slf4jVersion") 12 | implementation(project(":common-utils")) 13 | 14 | testImplementation("org.elasticmq:elasticmq-rest-sqs_2.12:$elasticMqVersion") 15 | } 16 | -------------------------------------------------------------------------------- /util/local-sqs-async-client/src/main/java/com/jashmore/sqs/util/CreateRandomQueueResponse.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.util; 2 | 3 | import lombok.Builder; 4 | import lombok.Value; 5 | import software.amazon.awssdk.services.sqs.model.CreateQueueResponse; 6 | 7 | @Value 8 | @Builder 9 | public class CreateRandomQueueResponse { 10 | 11 | CreateQueueResponse response; 12 | String queueName; 13 | 14 | public String queueUrl() { 15 | return response.queueUrl(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /util/local-sqs-async-client/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /util/proxy-method-interceptor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Utility methods for testing a method being proxied via cglib" 3 | 4 | val cglibVersion: String by project 5 | 6 | dependencies { 7 | api("cglib:cglib:$cglibVersion") 8 | } 9 | -------------------------------------------------------------------------------- /util/proxy-method-interceptor/src/main/java/com/jashmore/sqs/util/ProxyMethodInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.util; 2 | 3 | import java.lang.reflect.Method; 4 | import net.sf.cglib.proxy.Enhancer; 5 | import net.sf.cglib.proxy.MethodInterceptor; 6 | import net.sf.cglib.proxy.MethodProxy; 7 | 8 | /** 9 | * Interceptor used to test a method being proxied via cglib. 10 | * 11 | * @param the type of the original object to proxy 12 | */ 13 | public class ProxyMethodInterceptor implements MethodInterceptor { 14 | 15 | private final T original; 16 | 17 | private ProxyMethodInterceptor(final T original) { 18 | this.original = original; 19 | } 20 | 21 | @Override 22 | public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 23 | System.out.println("BEFORE"); 24 | method.invoke(original, args); 25 | System.out.println("AFTER"); 26 | return null; 27 | } 28 | 29 | /** 30 | * Wrap the provided object so that it has before and after log messages. 31 | * 32 | * @param original the original object to wrap 33 | * @param clazz the class of the object to wrap 34 | * @param the type parameter for the class 35 | * @return the wrapped object 36 | */ 37 | @SuppressWarnings("unchecked") 38 | public static S wrapObject(final S original, final Class clazz) { 39 | return (S) Enhancer.create(clazz, new ProxyMethodInterceptor<>(original)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /util/sqs-brave-tracing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "Helper Util for adding Tracing information to the message attributes of outbound SQS messages" 3 | 4 | val awsVersion: String by project 5 | val braveVersion: String by project 6 | 7 | dependencies { 8 | api(platform("software.amazon.awssdk:bom:$awsVersion")) 9 | implementation("software.amazon.awssdk:sqs") 10 | api("io.zipkin.brave:brave:$braveVersion") 11 | 12 | testImplementation("io.zipkin.brave:brave-tests:$braveVersion") 13 | testImplementation(project(":elasticmq-sqs-client")) 14 | } 15 | -------------------------------------------------------------------------------- /util/sqs-brave-tracing/src/main/java/com/jashmore/sqs/brave/propogation/SendMessageRemoteGetter.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.brave.propogation; 2 | 3 | import static brave.Span.Kind.PRODUCER; 4 | 5 | import brave.Span; 6 | import brave.Tracing; 7 | import brave.propagation.Propagation; 8 | import brave.propagation.TraceContext; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; 12 | 13 | /** 14 | * Used to consume the tracing information from the message attributes of the SQS message. 15 | * 16 | * @see SendMessageRemoteSetter for placing this information into the message attributes 17 | */ 18 | public class SendMessageRemoteGetter implements Propagation.RemoteGetter> { 19 | 20 | @Override 21 | public Span.Kind spanKind() { 22 | return PRODUCER; 23 | } 24 | 25 | @Override 26 | public String get(final Map request, final String fieldName) { 27 | return Optional.ofNullable(request.get(fieldName)).map(MessageAttributeValue::stringValue).orElse(null); 28 | } 29 | 30 | /** 31 | * Helper static function to create an extractor for this {@link Propagation.RemoteGetter}. 32 | * 33 | * @param tracing trace instrumentation utilities 34 | * @return the extractor 35 | */ 36 | public static TraceContext.Extractor> create(final Tracing tracing) { 37 | return tracing.propagation().extractor(new SendMessageRemoteGetter()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /util/sqs-brave-tracing/src/main/java/com/jashmore/sqs/brave/propogation/SendMessageRemoteSetter.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.brave.propogation; 2 | 3 | import static brave.Span.Kind.PRODUCER; 4 | 5 | import brave.Span; 6 | import brave.Tracing; 7 | import brave.propagation.Propagation; 8 | import brave.propagation.TraceContext; 9 | import java.util.Map; 10 | import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; 11 | 12 | /** 13 | * Used to set the tracing information into the message attributes of a SQS message. 14 | * 15 | * @see SendMessageRemoteGetter for extraction this information from the message attributes 16 | */ 17 | public class SendMessageRemoteSetter implements Propagation.RemoteSetter> { 18 | 19 | @Override 20 | public Span.Kind spanKind() { 21 | return PRODUCER; 22 | } 23 | 24 | @Override 25 | public void put(final Map carrier, final String fieldName, final String value) { 26 | carrier.put(fieldName, MessageAttributeValue.builder().dataType("String").stringValue(value).build()); 27 | } 28 | 29 | /** 30 | * Helper static function to create an injector for this {@link Propagation.RemoteGetter}. 31 | * 32 | * @param tracing trace instrumentation utilities 33 | * @return the injector 34 | */ 35 | public static TraceContext.Injector> create(final Tracing tracing) { 36 | return tracing.propagation().injector(new SendMessageRemoteSetter()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /util/sqs-brave-tracing/src/test/java/com/jashmore/sqs/brave/propogation/SendMessageRemoteGetterTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.brave.propogation; 2 | 3 | import static brave.Span.Kind.PRODUCER; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.util.Map; 7 | import org.junit.jupiter.api.Test; 8 | import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; 9 | import software.amazon.awssdk.utils.ImmutableMap; 10 | 11 | class SendMessageRemoteGetterTest { 12 | 13 | @Test 14 | void spanKindIsProducer() { 15 | assertThat(new SendMessageRemoteGetter().spanKind()).isEqualTo(PRODUCER); 16 | } 17 | 18 | @Test 19 | void willObtainStringValueWhenFound() { 20 | final Map attributes = ImmutableMap.of( 21 | "key", 22 | MessageAttributeValue.builder().dataType("String").stringValue("value").build() 23 | ); 24 | 25 | assertThat(new SendMessageRemoteGetter().get(attributes, "key")).isEqualTo("value"); 26 | } 27 | 28 | @Test 29 | void willReturnNullWhenNotFound() { 30 | final Map attributes = ImmutableMap.of( 31 | "another", 32 | MessageAttributeValue.builder().dataType("String").stringValue("value").build() 33 | ); 34 | 35 | assertThat(new SendMessageRemoteGetter().get(attributes, "key")).isNull(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /util/sqs-brave-tracing/src/test/java/com/jashmore/sqs/brave/propogation/SendMessageRemoteSetterTest.java: -------------------------------------------------------------------------------- 1 | package com.jashmore.sqs.brave.propogation; 2 | 3 | import static brave.Span.Kind.PRODUCER; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import org.junit.jupiter.api.Test; 9 | import software.amazon.awssdk.services.sqs.model.MessageAttributeValue; 10 | 11 | class SendMessageRemoteSetterTest { 12 | 13 | @Test 14 | void spanKindIsProducer() { 15 | assertThat(new SendMessageRemoteSetter().spanKind()).isEqualTo(PRODUCER); 16 | } 17 | 18 | @Test 19 | void willSetStringValueWhenFound() { 20 | final Map attributes = new HashMap<>(); 21 | 22 | new SendMessageRemoteSetter().put(attributes, "other", "value"); 23 | assertThat(attributes).containsEntry("other", MessageAttributeValue.builder().dataType("String").stringValue("value").build()); 24 | } 25 | } 26 | --------------------------------------------------------------------------------