diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc index 0194ea8b3d1..f5de88d29eb 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc @@ -1712,17 +1712,17 @@ * xref:reference:io/webservices.adoc#io.webservices[#boot-features-webservices] * xref:reference:io/webservices.adoc#io.webservices[#features.webservices] * xref:reference:io/webservices.adoc#io.webservices[#io.webservices] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#boot-features-rabbitmq] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#features.messaging.amqp.rabbit] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#messaging.amqp.rabbit] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq[#messaging.amqp.rabbitmq] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.receiving[#boot-features-using-amqp-receiving] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.receiving[#features.messaging.amqp.receiving] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.receiving[#messaging.amqp.receiving] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending-stream[#messaging.amqp.sending-stream] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending[#boot-features-using-amqp-sending] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending[#features.messaging.amqp.sending] -* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq.sending[#messaging.amqp.sending] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09[#boot-features-rabbitmq] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09[#features.messaging.amqp.rabbit] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09[#messaging.amqp.rabbit] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09[#messaging.amqp.rabbitmq09] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09.receiving[#boot-features-using-amqp-receiving] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09.receiving[#features.messaging.amqp.receiving] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09.receiving[#messaging.amqp.receiving] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09.sending-stream[#messaging.amqp.sending-stream] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09.sending[#boot-features-using-amqp-sending] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09.sending[#features.messaging.amqp.sending] +* xref:reference:messaging/amqp.adoc#messaging.amqp.rabbitmq09.sending[#messaging.amqp.sending] * xref:reference:messaging/amqp.adoc#messaging.amqp[#boot-features-amqp] * xref:reference:messaging/amqp.adoc#messaging.amqp[#features.messaging.amqp] * xref:reference:messaging/amqp.adoc#messaging.amqp[#messaging.amqp] diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc index 235a7f0170b..1718f0abaa7 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/messaging/amqp.adoc @@ -3,7 +3,14 @@ The Advanced Message Queuing Protocol (AMQP) is a platform-neutral, wire-level protocol for message-oriented middleware. The Spring AMQP project applies core Spring concepts to the development of AMQP-based messaging solutions. -Spring Boot offers several conveniences for working with AMQP: generic AMQP 1.0 support is provided by the `spring-boot-starter-amqp` starter while specific RabbitMQ support is available via the `spring-boot-starter-rabbitmq` starter. +Spring Boot provides auto-configuration for three different AMQP styles: + +* xref:messaging/amqp.adoc#messaging.amqp.generic[Generic AMQP 1.0] (`spring-boot-starter-amqp`) — broker-agnostic AMQP 1.0 support based on the Qpid ProtonJ2 client library. + Use this when portability across AMQP 1.0-compatible brokers such as ActiveMQ, Azure Service Bus, or RabbitMQ is important. +* xref:messaging/amqp.adoc#messaging.amqp.rabbitmq[RabbitMQ AMQP 1.0] (`spring-boot-starter-amqp-rabbitmq`) — RabbitMQ-specific support via the native https://github.com/rabbitmq/rabbitmq-amqp-java-client[RabbitMQ AMQP 1.0 Java client]. + Use this when your application targets RabbitMQ exclusively and you want to use the AMQP 1.0 protocol. +* xref:messaging/amqp.adoc#messaging.amqp.rabbitmq09[RabbitMQ AMQP 0.9] (`spring-boot-starter-rabbitmq`) — RabbitMQ support via the classic AMQP 0.9.1 protocol. + Use this for the most established and feature-rich RabbitMQ integration, including Streams support. @@ -77,9 +84,107 @@ Then you can use the factory in any javadoc:org.springframework.amqp.client.anno include-code::custom/MyBean[] - [[messaging.amqp.rabbitmq]] -== RabbitMQ Support +== RabbitMQ AMQP 1.0 Support + +Spring AMQP provides {url-spring-amqp-docs}/rabbitmq-amqp-client.html[RabbitMQ dedicated support for AMQP 1.0] via `org.springframework.amqp:spring-rabbitmq-client`. + +RabbitMQ AMQP 1.0 configuration is controlled by external configuration properties in `+spring.amqp.rabbitmq.*+`. +For example, you might declare the following section in `application.properties`: + +[configprops,yaml] +---- +spring: + amqp: + rabbitmq: + host: "localhost" + port: 5672 + username: "admin" + password: "secret" +---- + +Alternatively, you could configure the same connection using the `address` attribute: + +[configprops,yaml] +---- +spring: + amqp: + rabbitmq: + address: "amqp://admin:secret@localhost" +---- + +NOTE: When specifying an address that way, the `host` and `port` properties are ignored. +If the address uses the `amqps` scheme, an xref:features/ssl.adoc#features.ssl.bundles[SSL bundle] must be configured. + +See javadoc:org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitProperties[] for more of the supported property-based configuration options. +To configure lower-level details of the auto-configured javadoc:com.rabbitmq.client.amqp.Environment[], define a javadoc:org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpEnvironmentCustomizer[] bean. + +If a javadoc:com.rabbitmq.client.amqp.CredentialsProvider[] bean exists in the context, it will be automatically used by the auto-configured javadoc:com.rabbitmq.client.amqp.Environment[]. + + + +[[messaging.amqp.rabbitmq.ssl]] +=== SSL + +To use SSL with RabbitMQ AMQP 1.0, set configprop:spring.amqp.rabbitmq.ssl.bundle[] to configure the xref:features/ssl.adoc#features.ssl.bundles[SSL bundle] to use. + + + +[[messaging.amqp.rabbitmq.sending]] +=== Sending a Message + +Spring's javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate[] and javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin[] are auto-configured, and you can autowire them directly into your own beans, as shown in the following example: + +include-code::MyBean[] + +If a javadoc:org.springframework.amqp.support.converter.MessageConverter[] bean is defined, it is associated automatically to the auto-configured javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate[]. + +You can set properties for the template as follows: + +[configprops,yaml] +---- +spring: + amqp: + rabbitmq: + template: + exchange: "my-exchange" + routing-key: "my-key" + default-receive-queue: "my-queue" +---- + +To further configure the auto-configured javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate[], declare a javadoc:org.springframework.boot.amqp.rabbitmq.autoconfigure.RabbitAmqpTemplateCustomizer[] bean. + + + +[[messaging.amqp.rabbitmq.receiving]] +=== Receiving a Message + +When the RabbitMQ AMQP 1.0 infrastructure is present, any bean can be annotated with javadoc:org.springframework.amqp.rabbit.annotation.RabbitListener[format=annotation] to create a listener endpoint. +If no javadoc:org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory[] has been defined, a default one is automatically configured. +If a javadoc:org.springframework.amqp.support.converter.MessageConverter[] bean is defined, it is associated automatically with the default factory. + +The following sample component creates a listener endpoint on the `someQueue` queue: + +include-code::MyBean[] + +TIP: See javadoc:org.springframework.amqp.rabbit.annotation.EnableRabbit[format=annotation] for more details. + +If you need to create more javadoc:org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory[] instances or if you want to override the default, Spring Boot provides a javadoc:org.springframework.boot.amqp.rabbitmq.autoconfigure.RabbitAmqpListenerContainerFactoryConfigurer[] that you can use to initialize a javadoc:org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory[] with the same settings as the factory used by the auto-configuration. + +For instance, the following configuration class exposes another factory that uses a specific javadoc:org.springframework.amqp.support.converter.MessageConverter[]: + +include-code::custom/MyRabbitAmqpConfiguration[] + +Then you can use the factory in any javadoc:org.springframework.amqp.rabbit.annotation.RabbitListener[format=annotation]-annotated method, as follows: + +include-code::custom/MyBean[] + +To customize the listener container, define a javadoc:org.springframework.amqp.rabbit.config.ContainerCustomizer[] bean parameterized with javadoc:org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer[]. + + + +[[messaging.amqp.rabbitmq09]] +== RabbitMQ AMQP 0.9 Support https://www.rabbitmq.com/[RabbitMQ] is a lightweight, reliable, scalable, and portable message broker based on the AMQP protocol. Spring uses RabbitMQ to communicate through the AMQP protocol. @@ -120,7 +225,7 @@ TIP: See https://spring.io/blog/2010/06/14/understanding-amqp-the-protocol-used- -[[messaging.amqp.rabbitmq.sending]] +[[messaging.amqp.rabbitmq09.sending]] === Sending a Message Spring's javadoc:org.springframework.amqp.core.AmqpTemplate[] and javadoc:org.springframework.amqp.core.AmqpAdmin[] are auto-configured, and you can autowire them directly into your own beans, as shown in the following example: @@ -153,7 +258,7 @@ If there's a bean of type javadoc:org.springframework.amqp.rabbit.support.microm -[[messaging.amqp.rabbitmq.sending-stream]] +[[messaging.amqp.rabbitmq09.sending-stream]] === Sending a Message To A Stream To send a message to a particular stream, specify the name of the stream, as shown in the following example: @@ -172,13 +277,13 @@ If you need to create more javadoc:org.springframework.rabbit.stream.producer.Ra -[[messaging.amqp.rabbitmq.sending-stream.ssl]] +[[messaging.amqp.rabbitmq09.sending-stream.ssl]] ==== SSL To use SSL with RabbitMQ Streams, set configprop:spring.rabbitmq.stream.ssl.enabled[] to `true` or set configprop:spring.rabbitmq.stream.ssl.bundle[] to configure the xref:features/ssl.adoc#features.ssl.bundles[SSL bundle] to use. -[[messaging.amqp.rabbitmq.receiving]] +[[messaging.amqp.rabbitmq09.receiving]] === Receiving a Message When the Rabbit infrastructure is present, any bean can be annotated with javadoc:org.springframework.amqp.rabbit.annotation.RabbitListener[format=annotation] to create a listener endpoint. @@ -213,85 +318,3 @@ You can also customize the javadoc:org.springframework.core.retry.RetryPolicy[] IMPORTANT: By default, if retries are disabled and the listener throws an exception, the delivery is retried indefinitely. You can modify this behavior in two ways: Set the `defaultRequeueRejected` property to `false` so that zero re-deliveries are attempted or throw an javadoc:org.springframework.amqp.AmqpRejectAndDontRequeueException[] to signal the message should be rejected. The latter is the mechanism used when retries are enabled and the maximum number of delivery attempts is reached. - - - -[[messaging.amqp.rabbitmq-amqp]] -== RabbitMQ AMQP 1.0 Support - -https://www.rabbitmq.com/[RabbitMQ] supports the AMQP 1.0 protocol via the https://github.com/rabbitmq/rabbitmq-amqp-java-client[RabbitMQ AMQP 1.0 Java client]. -Spring AMQP provides support for this client through `spring-rabbitmq-client`. -Spring Boot offers auto-configuration for this support via the `spring-boot-starter-amqp-rabbitmq` starter. - -RabbitMQ AMQP 1.0 configuration is controlled by external configuration properties in `+spring.amqp.rabbitmq.*+`. -For example, you might declare the following section in `application.properties`: - -[configprops,yaml] ----- -spring: - amqp: - rabbitmq: - host: "localhost" - port: 5672 - username: "admin" - password: "secret" ----- - -Alternatively, you could configure the same connection using the `address` attribute: - -[configprops,yaml] ----- -spring: - amqp: - rabbitmq: - address: "amqp://admin:secret@localhost" ----- - -NOTE: When specifying an address that way, the `host` and `port` properties are ignored. - -See javadoc:org.springframework.boot.amqp.autoconfigure.RabbitAmqpProperties[] for more of the supported property-based configuration options. -To configure lower-level details of the auto-configured javadoc:com.rabbitmq.client.amqp.Environment[], define a javadoc:org.springframework.boot.amqp.autoconfigure.RabbitAmqpEnvironmentBuilderCustomizer[] bean. - -If a javadoc:com.rabbitmq.client.amqp.CredentialsProvider[] bean exists in the context, it will be automatically used by the auto-configured javadoc:com.rabbitmq.client.amqp.Environment[]. - - - -[[messaging.amqp.rabbitmq-amqp.sending]] -=== Sending a Message - -Spring's javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate[] and javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin[] are auto-configured, and you can autowire them directly into your own beans, as shown in the following example: - -include-code::MyBean[] - -If a javadoc:org.springframework.amqp.support.converter.MessageConverter[] bean is defined, it is associated automatically to the auto-configured javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate[]. - -You can set properties for the template as follows: - -[configprops,yaml] ----- -spring: - amqp: - rabbitmq: - template: - exchange: "my-exchange" - routing-key: "my-key" - default-receive-queue: "my-queue" ----- - -You can also customize the javadoc:org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate[] programmatically by declaring a javadoc:org.springframework.boot.amqp.autoconfigure.RabbitAmqpTemplateCustomizer[] bean. - - - -[[messaging.amqp.rabbitmq-amqp.receiving]] -=== Receiving a Message - -When the RabbitMQ AMQP 1.0 infrastructure is present, any bean can be annotated with javadoc:org.springframework.amqp.rabbit.annotation.RabbitListener[format=annotation] to create a listener endpoint. -If no javadoc:org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory[] has been defined, a default one is automatically configured. - -The following sample component creates a listener endpoint on the `someQueue` queue: - -include-code::MyBean[] - -TIP: See javadoc:org.springframework.amqp.rabbit.annotation.EnableRabbit[format=annotation] for more details. - -To customize the listener container, define a javadoc:org.springframework.amqp.rabbit.config.ContainerCustomizer[] bean parameterized with javadoc:org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer[]. diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.java index 56064bfb94c..1d224b15829 100644 --- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.java +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.java @@ -16,13 +16,15 @@ package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component +@RabbitListener(queues = "someQueue") public class MyBean { - @RabbitListener(queues = "someQueue") + @RabbitHandler public void processMessage(String content) { // ... } diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitAmqpConfiguration.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitAmqpConfiguration.java new file mode 100644 index 00000000000..ce801674115 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitAmqpConfiguration.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom; + +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.RabbitAmqpListenerContainerFactoryConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class MyRabbitAmqpConfiguration { + + @Bean + public RabbitAmqpListenerContainerFactory myFactory(RabbitAmqpListenerContainerFactoryConfigurer configurer, + AmqpConnectionFactory connectionFactory) { + RabbitAmqpListenerContainerFactory factory = new RabbitAmqpListenerContainerFactory(connectionFactory); + configurer.configure(factory); + factory.setMessageConverter(new MyMessageConverter()); + return factory; + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.java index 51b91d35769..90811489713 100644 --- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.java +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.java @@ -16,27 +16,23 @@ package org.springframework.boot.docs.messaging.amqp.rabbitmq.sending; -import org.springframework.amqp.core.AmqpAdmin; -import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; import org.springframework.stereotype.Component; @Component public class MyBean { - private final AmqpAdmin amqpAdmin; + private final RabbitAmqpAdmin amqpAdmin; - private final AmqpTemplate amqpTemplate; + private final RabbitAmqpTemplate amqpTemplate; - public MyBean(AmqpAdmin amqpAdmin, AmqpTemplate amqpTemplate) { + public MyBean(RabbitAmqpAdmin amqpAdmin, RabbitAmqpTemplate amqpTemplate) { this.amqpAdmin = amqpAdmin; this.amqpTemplate = amqpTemplate; } // @fold:on // ... - public void someMethod() { - this.amqpAdmin.getQueueInfo("someQueue"); - } - public void someOtherMethod() { this.amqpTemplate.convertAndSend("hello"); } diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmqamqp/receiving/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/MyBean.java similarity index 81% rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmqamqp/receiving/MyBean.java rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/MyBean.java index 83ac5b882b0..6d44d39ec2e 100644 --- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmqamqp/receiving/MyBean.java +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/MyBean.java @@ -14,17 +14,15 @@ * limitations under the License. */ -package org.springframework.boot.docs.messaging.amqp.rabbitmqamqp.receiving; +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving; -import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component -@RabbitListener(queues = "someQueue") public class MyBean { - @RabbitHandler + @RabbitListener(queues = "someQueue") public void processMessage(String content) { // ... } diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyBean.java new file mode 100644 index 00000000000..35031b330cc --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyBean.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving.custom; + +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Component +public class MyBean { + + @RabbitListener(queues = "someQueue", containerFactory = "myFactory") + public void processMessage(String content) { + // ... + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyMessageConverter.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyMessageConverter.java new file mode 100644 index 00000000000..e63133f58d0 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyMessageConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving.custom; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.support.converter.MessageConversionException; +import org.springframework.amqp.support.converter.MessageConverter; + +class MyMessageConverter implements MessageConverter { + + @Override + public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException { + return null; + } + + @Override + public Object fromMessage(Message message) throws MessageConversionException { + return null; + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyRabbitConfiguration.java similarity index 95% rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.java rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyRabbitConfiguration.java index 318428b08be..f47e0d4d1f9 100644 --- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.java +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyRabbitConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom; +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving.custom; import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmqamqp/sending/MyBean.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/sending/MyBean.java similarity index 69% rename from documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmqamqp/sending/MyBean.java rename to documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/sending/MyBean.java index a05a238bf68..a4247919fc6 100644 --- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmqamqp/sending/MyBean.java +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/messaging/amqp/rabbitmq09/sending/MyBean.java @@ -14,25 +14,29 @@ * limitations under the License. */ -package org.springframework.boot.docs.messaging.amqp.rabbitmqamqp.sending; +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.sending; -import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; -import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.core.AmqpTemplate; import org.springframework.stereotype.Component; @Component public class MyBean { - private final RabbitAmqpAdmin amqpAdmin; + private final AmqpAdmin amqpAdmin; - private final RabbitAmqpTemplate amqpTemplate; + private final AmqpTemplate amqpTemplate; - public MyBean(RabbitAmqpAdmin amqpAdmin, RabbitAmqpTemplate amqpTemplate) { + public MyBean(AmqpAdmin amqpAdmin, AmqpTemplate amqpTemplate) { this.amqpAdmin = amqpAdmin; this.amqpTemplate = amqpTemplate; } // @fold:on // ... + public void someMethod() { + this.amqpAdmin.getQueueInfo("someQueue"); + } + public void someOtherMethod() { this.amqpTemplate.convertAndSend("hello"); } diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.kt index 9bac053496d..a477cb2d84c 100644 --- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.kt +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/MyBean.kt @@ -16,17 +16,18 @@ package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving +import org.springframework.amqp.rabbit.annotation.RabbitHandler import org.springframework.amqp.rabbit.annotation.RabbitListener import org.springframework.stereotype.Component @Suppress("UNUSED_PARAMETER") @Component +@RabbitListener(queues = ["someQueue"]) class MyBean { - @RabbitListener(queues = ["someQueue"]) + @RabbitHandler fun processMessage(content: String?) { // ... } } - diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.kt index 3b90bf3dc42..41e3ab585b0 100644 --- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.kt +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyBean.kt @@ -29,4 +29,3 @@ class MyBean { } } - diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.kt index 03b165448e0..971452683e7 100644 --- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.kt +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyMessageConverter.kt @@ -31,4 +31,3 @@ internal class MyMessageConverter : MessageConverter { } } - diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitAmqpConfiguration.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitAmqpConfiguration.kt new file mode 100644 index 00000000000..95249b2409c --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitAmqpConfiguration.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom + +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory +import org.springframework.boot.amqp.rabbitmq.autoconfigure.RabbitAmqpListenerContainerFactoryConfigurer +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration(proxyBeanMethods = false) +class MyRabbitAmqpConfiguration { + + @Bean + fun myFactory( + configurer: RabbitAmqpListenerContainerFactoryConfigurer, + connectionFactory: AmqpConnectionFactory + ): RabbitAmqpListenerContainerFactory { + val factory = RabbitAmqpListenerContainerFactory(connectionFactory) + configurer.configure(factory) + factory.setMessageConverter(MyMessageConverter()) + return factory + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.kt index 6d21de9bce0..cf1223d7105 100644 --- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.kt +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/sending/MyBean.kt @@ -16,22 +16,17 @@ package org.springframework.boot.docs.messaging.amqp.rabbitmq.sending -import org.springframework.amqp.core.AmqpAdmin -import org.springframework.amqp.core.AmqpTemplate +import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate import org.springframework.stereotype.Component @Component -class MyBean(private val amqpAdmin: AmqpAdmin, private val amqpTemplate: AmqpTemplate) { +class MyBean(private val amqpAdmin: RabbitAmqpAdmin, private val amqpTemplate: RabbitAmqpTemplate) { // @fold:on // ... - fun someMethod() { - amqpAdmin.getQueueInfo("someQueue") - } - fun someOtherMethod() { amqpTemplate.convertAndSend("hello") } // @fold:off } - diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/MyBean.kt new file mode 100644 index 00000000000..99244d15b01 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/MyBean.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving + +import org.springframework.amqp.rabbit.annotation.RabbitListener +import org.springframework.stereotype.Component + +@Suppress("UNUSED_PARAMETER") +@Component +class MyBean { + + @RabbitListener(queues = ["someQueue"]) + fun processMessage(content: String?) { + // ... + } + +} + diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyBean.kt new file mode 100644 index 00000000000..b119e83ccdf --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyBean.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving.custom + +import org.springframework.amqp.rabbit.annotation.RabbitListener +import org.springframework.stereotype.Component + +@Suppress("UNUSED_PARAMETER") +@Component +class MyBean { + + @RabbitListener(queues = ["someQueue"], containerFactory = "myFactory") + fun processMessage(content: String?) { + // ... + } + +} + diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyMessageConverter.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyMessageConverter.kt new file mode 100644 index 00000000000..0005bbb328e --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyMessageConverter.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving.custom + +import org.springframework.amqp.core.Message +import org.springframework.amqp.core.MessageProperties +import org.springframework.amqp.support.converter.MessageConverter + +internal class MyMessageConverter : MessageConverter { + + override fun toMessage(`object`: Any, messageProperties: MessageProperties): Message { + return Message(byteArrayOf()) + } + + override fun fromMessage(message: Message): Any { + return Any() + } + +} + diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyRabbitConfiguration.kt similarity index 95% rename from documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.kt rename to documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyRabbitConfiguration.kt index 900866bf246..17d5ced6dd0 100644 --- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq/receiving/custom/MyRabbitConfiguration.kt +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/receiving/custom/MyRabbitConfiguration.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.docs.messaging.amqp.rabbitmq.receiving.custom +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.receiving.custom import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory import org.springframework.amqp.rabbit.connection.CachingConnectionFactory diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/sending/MyBean.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/sending/MyBean.kt new file mode 100644 index 00000000000..07dbdff26ad --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/messaging/amqp/rabbitmq09/sending/MyBean.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docs.messaging.amqp.rabbitmq09.sending + +import org.springframework.amqp.core.AmqpAdmin +import org.springframework.amqp.core.AmqpTemplate +import org.springframework.stereotype.Component + +@Component +class MyBean(private val amqpAdmin: AmqpAdmin, private val amqpTemplate: AmqpTemplate) { + + // @fold:on // ... + fun someMethod() { + amqpAdmin.getQueueInfo("someQueue") + } + + fun someOtherMethod() { + amqpTemplate.convertAndSend("hello") + } + // @fold:off + +} + diff --git a/module/spring-boot-amqp-rabbitmq/build.gradle b/module/spring-boot-amqp-rabbitmq/build.gradle index edd172e77c6..827a5f4a755 100644 --- a/module/spring-boot-amqp-rabbitmq/build.gradle +++ b/module/spring-boot-amqp-rabbitmq/build.gradle @@ -23,7 +23,7 @@ plugins { id "org.springframework.boot.optional-dependencies" } -description = "Spring Boot AMQP" +description = "Spring Boot AMQP 1.0 support for RabbitMQ" dependencies { api(project(":core:spring-boot")) @@ -38,9 +38,6 @@ dependencies { optional(project(":core:spring-boot-docker-compose")) optional(project(":core:spring-boot-testcontainers")) optional(project(":module:spring-boot-health")) - optional(project(":module:spring-boot-micrometer-metrics")) - optional("io.micrometer:micrometer-core") - optional("org.springframework.amqp:spring-rabbit-stream") optional("org.testcontainers:testcontainers-rabbitmq") dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/docker/compose/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java b/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/docker/compose/AmqpRabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests.java similarity index 51% rename from module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/docker/compose/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java rename to module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/docker/compose/AmqpRabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests.java index 77baad2a4bf..3a4cfbd8b2d 100644 --- a/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/docker/compose/RabbitDockerComposeConnectionDetailsFactoryIntegrationTests.java +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/docker/compose/AmqpRabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -14,17 +14,18 @@ * limitations under the License. */ -package org.springframework.boot.amqp.docker.compose; +package org.springframework.boot.amqp.rabbitmq.docker.compose; -import org.springframework.boot.amqp.autoconfigure.RabbitAmqpConnectionDetails; -import org.springframework.boot.amqp.autoconfigure.RabbitAmqpConnectionDetails.Address; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails.Address; import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.testsupport.container.TestImage; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link RabbitDockerComposeConnectionDetailsFactory}. + * Integration tests for {@link AmqpRabbitMqDockerComposeConnectionDetailsFactory}. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -32,14 +33,27 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Scott Frederick * @author Eddú Meléndez */ -class RabbitDockerComposeConnectionDetailsFactoryIntegrationTests { +class AmqpRabbitMqDockerComposeConnectionDetailsFactoryIntegrationTests { - @DockerComposeTest(composeFile = "rabbit-compose.yaml", image = TestImage.RABBITMQ) - void runCreatesConnectionDetails(RabbitAmqpConnectionDetails connectionDetails) { + @DockerComposeTest(composeFile = "rabbitmq-compose.yaml", image = TestImage.RABBITMQ) + void runCreatesConnectionDetails(AmqpRabbitConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + assertThat(connectionDetails.getSslBundle()).isNull(); + } + + @DockerComposeTest(composeFile = "rabbitmq-ssl-compose.yaml", image = TestImage.RABBITMQ, + additionalResources = { "../../ca.crt", "../../server.crt", "../../server.key", "../../client.crt", + "../../client.key", "rabbitmq-ssl.conf" }) + void runWithSslCreatesConnectionDetails(AmqpRabbitConnectionDetails connectionDetails) { + assertConnectionDetails(connectionDetails); + SslBundle sslBundle = connectionDetails.getSslBundle(); + assertThat(sslBundle).isNotNull(); + } + + private void assertConnectionDetails(AmqpRabbitConnectionDetails connectionDetails) { assertThat(connectionDetails.getUsername()).isEqualTo("myuser"); assertThat(connectionDetails.getPassword()).isEqualTo("secret"); assertThat(connectionDetails.getVirtualHost()).isEqualTo("/"); - assertThat(connectionDetails.getAddress()).isNotNull(); Address address = connectionDetails.getAddress(); assertThat(address.host()).isNotNull(); assertThat(address.port()).isGreaterThan(0); diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/testcontainers/RabbitContainerConnectionDetailsFactoryIntegrationTests.java b/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerConnectionDetailsFactoryIntegrationTests.java similarity index 84% rename from module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/testcontainers/RabbitContainerConnectionDetailsFactoryIntegrationTests.java rename to module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerConnectionDetailsFactoryIntegrationTests.java index 34487fddcf6..cbab101ebca 100644 --- a/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/testcontainers/RabbitContainerConnectionDetailsFactoryIntegrationTests.java +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerConnectionDetailsFactoryIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.amqp.testcontainers; +package org.springframework.boot.amqp.rabbitmq.testcontainers; import java.time.Duration; import java.util.ArrayList; @@ -30,8 +30,8 @@ import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.amqp.autoconfigure.RabbitAmqpAutoConfiguration; -import org.springframework.boot.amqp.autoconfigure.RabbitAmqpConnectionDetails; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitAutoConfiguration; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.container.TestImage; @@ -42,7 +42,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link RabbitContainerConnectionDetailsFactory}. + * Tests for {@link AmqpRabbitMqContainerConnectionDetailsFactory}. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -51,14 +51,14 @@ import static org.assertj.core.api.Assertions.assertThat; */ @SpringJUnitConfig @Testcontainers(disabledWithoutDocker = true) -class RabbitContainerConnectionDetailsFactoryIntegrationTests { +class AmqpRabbitMqContainerConnectionDetailsFactoryIntegrationTests { @Container @ServiceConnection static final RabbitMQContainer rabbit = TestImage.container(RabbitMQContainer.class); @Autowired(required = false) - private RabbitAmqpConnectionDetails connectionDetails; + private AmqpRabbitConnectionDetails connectionDetails; @Autowired private RabbitAmqpTemplate rabbitAmqpTemplate; @@ -75,7 +75,7 @@ class RabbitContainerConnectionDetailsFactoryIntegrationTests { } @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(RabbitAmqpAutoConfiguration.class) + @ImportAutoConfiguration(AmqpRabbitAutoConfiguration.class) static class TestConfiguration { @Bean diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerWithSslConnectionDetailsFactoryIntegrationTests.java b/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerWithSslConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 00000000000..9517219297e --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerWithSslConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.testcontainers; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.rabbitmq.RabbitMQContainer; +import org.testcontainers.utility.MountableFile; + +import org.springframework.amqp.rabbit.annotation.Queue; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitAutoConfiguration; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.PemKeyStore; +import org.springframework.boot.testcontainers.service.connection.PemTrustStore; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AmqpRabbitMqContainerConnectionDetailsFactory} with SSL. + * + * @author Stephane Nicoll + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class AmqpRabbitMqContainerWithSslConnectionDetailsFactoryIntegrationTests { + + private static final int RABBITMQ_TLS_PORT = 5671; + + @Container + @ServiceConnection + @PemTrustStore(certificate = "classpath:org/springframework/boot/amqp/rabbitmq/ca.crt") + @PemKeyStore(certificate = "classpath:org/springframework/boot/amqp/rabbitmq/client.crt", + privateKey = "classpath:org/springframework/boot/amqp/rabbitmq/client.key") + static final RabbitMQContainer rabbit = getRabbitMqContainer(); + + private static RabbitMQContainer getRabbitMqContainer() { + RabbitMQContainer container = TestImage.container(RabbitMQContainer.class); + container.addExposedPorts(RABBITMQ_TLS_PORT); + container.withCopyFileToContainer( + MountableFile + .forClasspathResource("org/springframework/boot/amqp/rabbitmq/testcontainers/rabbitmq-ssl.conf"), + "/etc/rabbitmq/rabbitmq.conf"); + container.withCopyFileToContainer( + MountableFile.forClasspathResource("org/springframework/boot/amqp/rabbitmq/ca.crt"), + "/etc/rabbitmq/ca.crt"); + container.withCopyFileToContainer( + MountableFile.forClasspathResource("org/springframework/boot/amqp/rabbitmq/server.key"), + "/etc/rabbitmq/server.key"); + container.withCopyFileToContainer( + MountableFile.forClasspathResource("org/springframework/boot/amqp/rabbitmq/server.crt"), + "/etc/rabbitmq/server.crt"); + return container; + } + + @Autowired(required = false) + private AmqpRabbitConnectionDetails connectionDetails; + + @Autowired + private RabbitAmqpTemplate rabbitAmqpTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToRabbitContainerWithSsl() { + assertThat(this.connectionDetails).isNotNull(); + assertThat(this.connectionDetails.getSslBundle()).isNotNull(); + this.rabbitAmqpTemplate.convertAndSend("test", "message"); + Awaitility.waitAtMost(Duration.ofMinutes(4)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("message")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(AmqpRabbitAutoConfiguration.class) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @RabbitListener(queuesToDeclare = @Queue("test")) + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/ca.crt b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/ca.crt new file mode 100644 index 00000000000..beed250b132 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/ca.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFhjCCA26gAwIBAgIUfIkk29IT9OpbgfjL8oRIPSLjUcAwDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +OzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNh +dGUgQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAusN2 +KzQQUUxZSiI3ZZuZohFwq2KXSUNPdJ6rgD3/YKNTDSZXKZPO53kYPP0DXf0sm3CH +cyWSWVabyimZYuPWena1MElSL4ZpJ9WwkZoOQ3bPFK1utz6kMOwrgAUcky8H/rIK +j2JEBhkSHUIGr57NjUEwG1ygaSerM8RzWw1PtMq+C8LOu3v94qzE3NDg1QRpyvV9 +OmsLsjISd0ZmAJNi9vmiEH923KnPyiqnQmWKpYicdgQmX1GXylS22jZqAwaOkYGj +X8UdeyvrohkZkM0hn9uaSufQGEW4yKACn3PkjJtzi8drBIyjIi9YcAzBxZB9oVKq +XZMlltgO2fDMmIJi0Ngt0Ci7fCoEMqSocKyDKML6YLr9UWtx4bfsrk+rVO9Q/D/v +8RKgstv7dCf2KWRX3ZJEC0IBHS5gLNq0qqqVcGx3LcSyhdiKJOtSwAnNkHMh+jSQ +xLSlBjcSqTPiGTRK/Rddl+xnU/mBgk7ZBGNrUFaD5McMFjddS7Ih82aHnpQ1gekW +nUGv+Tm/G68h2BvZ5U2q+RfeOCgRW9i/AYW2jgT7IFnfjyUXgBQveauMAchomqFE +VLe95ZgViF6vmH34EKo3w9L5TQiwk/r53YlM7TSOTyDqx66t4zGYDsVMicpKmzi4 +2Rp8EpErARRyREUIKSvWs9O9+uT3+7arNLgHe5ECAwEAAaOBgTB/MB0GA1UdDgQW +BBRVMLDVqPECWaH6GruL9E52VcTrPjAfBgNVHSMEGDAWgBRVMLDVqPECWaH6GruL +9E52VcTrPjAPBgNVHRMBAf8EBTADAQH/MCwGA1UdEQQlMCOCC2V4YW1wbGUuY29t +gglsb2NhbGhvc3SCCTEyNy4wLjAuMTANBgkqhkiG9w0BAQsFAAOCAgEAeSpjCL3j +2GIFBNKr/5amLOYa0kZ6r1dJs+K6xvMsUvsBJ/QQsV5nYDMIoV/NYUd8SyYV4lEj +7LHX5ZbmJrvPk30LGEBG/5Vy2MIATrQrQ14S4nXtEdSnBvTQwPOOaHc+2dTp3YpM +f4ffELKWyispTifx1eqdiUJhURKeQBh+3W7zpyaiN4vJaqEDKGgFQtHA/OyZL2hZ +BpxHB0zpb2iDHV8MeyfOT7HQWUk6p13vdYm6EnyJT8fzWvE+TqYNbqFmB+CLRSXy +R3p1yaeTd4LnVknJ0UBKqEyul3ziHZDhKhBpwdglYOQz4eWjSFhikX9XZ8NaI38Q +QqLZVn0DsH2ztkjrQrUVgK2xn4aUuqoLDk4Hu6h5baUn+f2GLuzx+EXc/i3ikYvw +Y3JyufOgw6nGGFG+/QXEj85XtLPhN7Wm42z2e/BGzi0MLl65sfpEDXvFTA72Yzws +OYaeg/HxeYwUHQgs2fKl/LgV4chntSCvTqfNl6OnQafD/ISJNpx3xWR3HwF+ypFG +UaLE+e1soqEJbzL31U/6pypHLsj8Y8r9hJbZXo2ibnhjFV6fypUAP0rbIzaoWcrJ +T0Sbliz+KQTMzCcubiAi4bI/kZ5FJ4kkaHqUpIWzlx1h2WVJ65ASFDjBWb8eVmB6 +Dyno/RVFR/rUL5091gjGRXhLsi1oUHKdEzU= +-----END CERTIFICATE----- diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/client.crt b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/client.crt new file mode 100644 index 00000000000..811d880fcbd --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/client.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCAkKgAwIBAgIURBZvq442tp+/K9TZII5Vy/LzVx0wDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +LzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDESMBAGA1UEAwwJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvGb7tu0odSuOjeY1lHlh +sRR4PayAvlryjfrrp49hjoVTiL3d/Jo6Po5HlqwJcYuclm0EWQR5Vur/zYJpfUE7 +b8+E9Qwe50+YzfQ2tVFEdq/VfqemrYRGee+pMelOCI90enOKCxfpo6EHbz+WnUP0 +mnD8OAF9QpolSdWAMOGJoPdWX65KQvyMXvQbj9VIHmsx7NCaIOYxjHXB/dI2FmXV ++m4VT6mb8he9dXmgK/ozMq6XIPOAXe0n3dlfMTSEddeNeVwnBpr/n5e0cpwGFhdf +NNu5CI4ecipBhXljJi/4/47M/6hd69HwE05C4zyH4ZDZ2JTfaSKOLV+jYdBUqJP5 +dwIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEBBAQDAgeAMB0GA1Ud +DgQWBBRWiWOo9cm2IF/ZlhWLVjifLzYa/DAfBgNVHSMEGDAWgBRVMLDVqPECWaH6 +GruL9E52VcTrPjANBgkqhkiG9w0BAQsFAAOCAgEAA5Wphtu2nBhY+QNOBOwXq4zF +N5qt2IYTLfR7xqpKhhXx9VkIjdPWpcsGuCuMmfPVNvQWE6iK0/jMMqToTj4H6K7e +MN74j0GwwcknT1P42tUzEpg8LKR8VMdhWhyqdniCDNWWuaz1iVSoF0S2i4jFSzH5 +1q3KMKMZ4niK5aJI0fAGa4fCjyuun1Mfg/qGBGwLnqDkIXjeAopZf4Jb64TtzjAs +j9NT6mYbe3E0tw3fHT9ihYdbZDZgSjeCsuq9OiRMVb0DWWmRoLmmOrlN8IJlHV/3 +WyI/ta4Cw5EZ0oaOg0lIyOxXyvElth1xIvh+kdqZSBsU0gNBri6ZIzYbbTh2KTTO +BJHQt9L5naWG27pDrIxBicWXS/MIYonktm3YgCLfuW3kWcVk8bIlNhfcoAYBBgfM +IEYSYEq+bH2IQ+YoWQz3AxjJ8gEuuSUP6R6mYY65FfpjkKgcpGBvw4EIAmqKDtPS +hlLY/F0XVj9KZzrMyH4/vonu+DAb/P7Zmt2fyk/dQO6bAc3ltRmJbJm4VJ2v/T8I +LVu2FtcUYgtLNtkWUPfdb3GSUUgkKlUpWSty31TKSUszJjW1oRykQhEko6o5U3S8 +ptQzXdApsb1lGOqewkubE25tIu2RLiNkKcjFOjJ/lu0vP9k76wWwRVnFLFvfo4lW +pgywiOifs5JbcCt0ZQ0= +-----END CERTIFICATE----- diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/client.key b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/client.key new file mode 100644 index 00000000000..2ae0f49bf4a --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8Zvu27Sh1K46N +5jWUeWGxFHg9rIC+WvKN+uunj2GOhVOIvd38mjo+jkeWrAlxi5yWbQRZBHlW6v/N +gml9QTtvz4T1DB7nT5jN9Da1UUR2r9V+p6athEZ576kx6U4Ij3R6c4oLF+mjoQdv +P5adQ/SacPw4AX1CmiVJ1YAw4Ymg91ZfrkpC/Ixe9BuP1UgeazHs0Jog5jGMdcH9 +0jYWZdX6bhVPqZvyF711eaAr+jMyrpcg84Bd7Sfd2V8xNIR11415XCcGmv+fl7Ry +nAYWF18027kIjh5yKkGFeWMmL/j/jsz/qF3r0fATTkLjPIfhkNnYlN9pIo4tX6Nh +0FSok/l3AgMBAAECggEABXnBe3MwXAMQENzNypOiXK4VE3XMYkePfdsSK163byOD +w3ZeTgQNfU4g8LJK8/homzO0SQIJAdz2+ZFbpsp4A2W2zJ+1jvN5RuX/8/UcVhmk +tb1IL/LWCvx5/aoYBWkgIA70UfQJa2jDbdM0v5j/Gu9yE7GI14jh6DFC3xGMGV3b +fOwManxf7sDibCI1nGjnFYNGxninRr+tpb+a1KNbVzhett68LrgPmtph6B3HCPAJ +zBigk1Phgb8WHozTXxnLyw9/RdKJ0Ro4PFmtQv0EvCSlytptnF+0nXkqr3f851XS +bUWwYFchIFWPMhPfD5B3niNWCV42/sU/bQlk+BMQAQKBgQD6NvMq8EdYy2Y7fXT5 +FgB4s+7EkLgI2d5LUaCXCFgc6iZtCTQKUXj1rIWeRfGrFVCCe8qV+XIMKt/G5eEi +tn5ifHhktA2A8GK1scj026qHP3bVn0hMaUnkCF1UpDRKPiEO5G/apPtav8PbCNaX +GAimLGw+WZNZuv7+T33bEBeUdwKBgQDAwiidayLXkRkz2deefdDKcXQsB7RHFGGy +vfZPBCGqizxml+6ojJkkDsVUKL1IXFfyK9KpQAI6tezn4oktgu4jAQqkYY7QZobs +RpQx1dR+KxEm7ISDBTq/B1Q9cFKUKVvQQy8N2pnIbCdzb6MTOKLmJqFGTjr+5T8q +F32B5vkDAQKBgDCKfH42AwFc5EZiPlEcTZcdARMtKCa/bXqbKVZjjgR+AFpi0K+3 +womWoI1l8E5KYkYOEe0qaU+m+aaybgy37qjYkNqoe34qJFwvU1b9ToXScBFdRz9b +pbQRU1naSTKl/u/OrUxzeTfPwAU8H7VMOlFSiOVHp2he+J0JetcGtixdAoGBAIJQ +QMj7rxhxHcqyEVUy1b6nKNTDeJs9Kjd+uU/+CQyVCQaK3GvScY2w9rLIv/51f3dX +LRoDDf7HExxJSFgeVgQQJjOvSK+XQMvngzSVzQxm7TeVWpiBJpAS0l6e2xUTSODp +KpyBFsoqZBlkdaj+9xIFN66iILxGG4fHTbBOiDYBAoGBAOZMKjM5N/hGcCmik/6t +p/zBA2pN9O6zwPndITTsdyVWSlVqCZhXlRX47CerAN+/WVCidlh7Vp5Tuy75Wa77 +v16IDLO01txgWNobcLaM4VgFsyLi5JuxK73S18Vb1cKWdHFRF0LH3cUIq20fjpv6 +Odl4vjNOncXMZCLPHQ+bKWaf +-----END PRIVATE KEY----- diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/docker/compose/rabbit-compose.yaml b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-compose.yaml similarity index 100% rename from module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/docker/compose/rabbit-compose.yaml rename to module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-compose.yaml diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-ssl-compose.yaml b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-ssl-compose.yaml new file mode 100644 index 00000000000..69179aa1efe --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-ssl-compose.yaml @@ -0,0 +1,26 @@ +services: + rabbitmq: + image: '{imageName}' + environment: + - 'RABBITMQ_DEFAULT_USER=myuser' + - 'RABBITMQ_DEFAULT_PASS=secret' + ports: + - '5672' + - '5671' + secrets: + - ssl-ca + - ssl-key + - ssl-cert + volumes: + - ./rabbitmq-ssl.conf:/etc/rabbitmq/rabbitmq.conf:ro + labels: + - 'org.springframework.boot.sslbundle.pem.keystore.certificate=client.crt' + - 'org.springframework.boot.sslbundle.pem.keystore.private-key=client.key' + - 'org.springframework.boot.sslbundle.pem.truststore.certificate=ca.crt' +secrets: + ssl-ca: + file: 'ca.crt' + ssl-key: + file: 'server.key' + ssl-cert: + file: 'server.crt' diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-ssl.conf b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-ssl.conf new file mode 100644 index 00000000000..55b842131ca --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/docker/compose/rabbitmq-ssl.conf @@ -0,0 +1,8 @@ +listeners.ssl.default=5671 + +ssl_options.cacertfile=/run/secrets/ssl-ca +ssl_options.certfile=/run/secrets/ssl-cert +ssl_options.keyfile=/run/secrets/ssl-key + +ssl_options.verify=verify_peer +ssl_options.fail_if_no_peer_cert=true diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/server.crt b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/server.crt new file mode 100644 index 00000000000..57c66cc78a3 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/server.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCAkKgAwIBAgIURBZvq442tp+/K9TZII5Vy/LzVxwwDQYJKoZIhvcNAQEL +BQAwOzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDEeMBwGA1UEAwwVQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MB4XDTI0MDUwMTE2NTMyNVoXDTM0MDQyOTE2NTMyNVow +LzEZMBcGA1UECgwQU3ByaW5nIEJvb3QgVGVzdDESMBAGA1UEAwwJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsllxsSQzTTJlNHMfXC2b +CIXCPsfCgCBl7FbPz828jwJk+EYcXh0+WTFGks0WxSwb8NQza5UtyCUDEueZj9fV +j5mWBY97WCu01Sl/3xClHmYisXfyyv27GKec7PaSOurCm2JDkyHRNumiJROa4jte +N0GOHzw7FYsM3779TuNw14/gtW+eBrGnvgrpU7fbUvx42Di6ftGYQUwIi+3uIaqT +//i7ktDMaAQJtkL6haTzZ5JN2qKO5a34/WRz/ApvPw3lpDV8c4qoTk3C0Bg9MP+a +DnZtjtLBSN9CJWwr+n11QaMgHTotEKsOahGdi3J2zYxCvJP0LT+hjN2O9aRzSMIs +MwIDAQABo2IwYDALBgNVHQ8EBAMCBaAwEQYJYIZIAYb4QgEBBAQDAgZAMB0GA1Ud +DgQWBBS9XQHGwJZhG0olAGM1UMNuwZ65DzAfBgNVHSMEGDAWgBRVMLDVqPECWaH6 +GruL9E52VcTrPjANBgkqhkiG9w0BAQsFAAOCAgEAhBcqm5UQahn8iFMETXvfLMR6 +OOPijsHQ5lVfhig08s46a9O5eaJ9EYSYyiDnxYvZ4gYVH03f/kPwNLamvGR5KIBQ +R0DltkPPX4a11/vjwlSq1cXAt9r59nY+sNcVXWgIWH7zNodL8lyTpYhqvB2wEQkx +t2/JKZ8A0sGjed4S6I5HofYd7bnBxQZgfZShQ2SdDbzbcyg4SCEb8ghwnsH0KNZo +jJF+20RpK2VMViE6lylLTEMd/PyAdST/NPoqVxyva3QjTrKt+tkkFTsmNVMXcmYC +f1xo1/YFp73FFE63VYFI+Yw+Ajau8sYSo4+YvgFCy+Efhf3h3GFDtaiNod56uX9G +9M/cu8XsFzFP2e/0YWY3XL+v7ESOdc3g7yS4FQZ7Z6YvfAed9hCB25cDECvZXqJG +HSYDR38NHyAPROuCwlEwDyVmWRl9bpwZt+hr9kaTQScIDx+rV/EF3o0GKIwtR7AK +jaPAta0f4/Uu+EuWAcccSRUMtfx5/Jse/6iliBvy7JXmA+Y0PrT7K4uHO7iktdI+ +x8WbfZKfnLVuqw5fneTjC1n48Ltjis/f8DgO7BuWTmLdZXddjqqxzBSukFTBn4Hg +/oSg3XiMywOAVrRCNJehcdTG0u/BqZsrRjcYAJaf5qG/0tMLNsuF9Y53XQQAeezE +etL+7y0mkeQhVF+Kmy4= +-----END CERTIFICATE----- diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/server.key b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/server.key new file mode 100644 index 00000000000..95e2ef3e8b3 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCyWXGxJDNNMmU0 +cx9cLZsIhcI+x8KAIGXsVs/PzbyPAmT4RhxeHT5ZMUaSzRbFLBvw1DNrlS3IJQMS +55mP19WPmZYFj3tYK7TVKX/fEKUeZiKxd/LK/bsYp5zs9pI66sKbYkOTIdE26aIl +E5riO143QY4fPDsViwzfvv1O43DXj+C1b54Gsae+CulTt9tS/HjYOLp+0ZhBTAiL +7e4hqpP/+LuS0MxoBAm2QvqFpPNnkk3aoo7lrfj9ZHP8Cm8/DeWkNXxziqhOTcLQ +GD0w/5oOdm2O0sFI30IlbCv6fXVBoyAdOi0Qqw5qEZ2LcnbNjEK8k/QtP6GM3Y71 +pHNIwiwzAgMBAAECgf9REZuCvy2Bi8SoTnjqQuHG5FuA6cPuisuFZr1k88IO+zJQ +uY3WKNs29BV+LcxnoK29W8jQnjqPHXcMfrF5dVWmkrrJdu8JLaGWVHF+uBq8nRb0 +2LvREh5XhZTGzIESNdc/7GIxdouag/8FlzCUYQGuT3v9+wUCiim+4CuIuPvv7ncD +8vANe3Ua5G0mHjVshOiMNpegg45zYlzYpMtUFPs+asLilW6A7UlgC+pLZ1cHUUlU +ZB7KOGT9JdrZpilTidl6LLvDDQK30TSWz8A26SuEAE71DR2VEjLVpjTNS76vlx+c +CrYr/WwpMb0xul+e/uHiNgo+51FiTiJ/IfuGeskCgYEA804CXQM6i5m4/Upps2yG +aTae5xBaYUquZREp5Zb054U6lUAHI41iTMTIwTTvWn5ogNojgi+YjljkzRj2RQ5k +NccBkjBBwwUNVWpBoGeZ73KAdejNB4C4ucGc2kkqEDo4MU5x3IE4JK1Yi1jl9mKb +IR6m3pqb2PCQHjO8sqKNHYkCgYEAu6fH/qUd/XGmCZJWY5K6jg3dISXH16MTO5M+ +jetprkGMMybWKZQa1GedXurPexE48oRlRhkjdQkW6Wcj1Qh6OKp6N2Zx8sY4dLeQ +yVChnMPFE2LK+UlRCKJUZi+rzX415ML6pZg+yW7O2cHpMKv7PlXISw2YDqtboCAi +Y+doqNsCgYBE1yqmBJbZDuqfiCF2KduyA0lcmWzpIEdNw1h2ZIrwwup7dj1O2t8Y +V4lx2TdsBF4vLwli+XKRvCcovMpZaaQC70bLhSnmMxS9uS3OY+HTNTORqQfx+oLJ +1DU8Mf1b0A08LjTbLhijkASAkOuoFehMq66NR3OXIyGz2fGnHYUN+QKBgCC47SL2 +X/hl7PIWVoIef/FtcXXqRKLRiPUGhA3zUwZT38K7rvSpItSPDN4UTAHFywxfEdnb +YFd0Mk6Y8aKgS8+9ynoGnzAaaJXRvKmeKdBQQvlSbNpzcnHy/IylG2xF6dfuOA7Q +MYKmk+Nc8PDPzIveIYMU58MHFn8hm12YaKOpAoGAV1CE8hFkEK9sbRGoKNJkx9nm +CZTv7PybaG/RN4ZrBSwVmnER0FEagA/Tzrlp1pi3sC8ZsC9onSOf6Btq8ZE0zbO1 +vsAm3gTBXcrCJxzw0Wjt8pzEbk3yELm4WE6VDEx4da2jWocdspslpIwdjHnPwsbH +r5O3ZAgigZs/ZtKW/U4= +-----END PRIVATE KEY----- diff --git a/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/testcontainers/rabbitmq-ssl.conf b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/testcontainers/rabbitmq-ssl.conf new file mode 100644 index 00000000000..b954a8b729e --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/dockerTest/resources/org/springframework/boot/amqp/rabbitmq/testcontainers/rabbitmq-ssl.conf @@ -0,0 +1,8 @@ +listeners.ssl.default=5671 + +ssl_options.cacertfile=/etc/rabbitmq/ca.crt +ssl_options.certfile=/etc/rabbitmq/server.crt +ssl_options.keyfile=/etc/rabbitmq/server.key + +ssl_options.verify=verify_peer +ssl_options.fail_if_no_peer_cert=true diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAnnotationDrivenConfiguration.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAnnotationDrivenConfiguration.java deleted file mode 100644 index c2c766c096e..00000000000 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAnnotationDrivenConfiguration.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.amqp.autoconfigure; - -import org.springframework.amqp.rabbit.annotation.EnableRabbit; -import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration for Spring AMQP annotation driven endpoints. - * - * @author Eddú Meléndez - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass(EnableRabbit.class) -class RabbitAnnotationDrivenConfiguration { - - @Configuration(proxyBeanMethods = false) - @EnableRabbit - @ConditionalOnMissingBean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME) - static class EnableRabbitConfiguration { - - } - -} diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpEnvironmentBuilderCustomizer.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpEnvironmentCustomizer.java similarity index 70% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpEnvironmentBuilderCustomizer.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpEnvironmentCustomizer.java index 807ead65def..3bc6fdac993 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpEnvironmentBuilderCustomizer.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpEnvironmentCustomizer.java @@ -14,26 +14,27 @@ * limitations under the License. */ -package org.springframework.boot.amqp.autoconfigure; +package org.springframework.boot.amqp.rabbitmq.autoconfigure; import com.rabbitmq.client.amqp.Environment; import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder; /** * Callback interface that can be implemented by beans wishing to customize the - * auto-configured {@link Environment} that is created by an + * auto-configured AMQP {@link Environment} that is created by an * {@link AmqpEnvironmentBuilder}. * * @author Eddú Meléndez - * @since 4.1.0 + * @author Stephane Nicoll + * @since 4.2.0 */ @FunctionalInterface -public interface RabbitAmqpEnvironmentBuilderCustomizer { +public interface AmqpEnvironmentCustomizer { /** - * Customize the {@code AmqpEnvironmentBuilder}. - * @param builder the builder to customize + * Customize the {@link AmqpEnvironmentBuilder}. + * @param environmentBuilder the environment builder to customize */ - void customize(AmqpEnvironmentBuilder builder); + void customize(AmqpEnvironmentBuilder environmentBuilder); } diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAnnotationDrivenConfiguration.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAnnotationDrivenConfiguration.java new file mode 100644 index 00000000000..244c4aa849a --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAnnotationDrivenConfiguration.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import org.springframework.amqp.rabbit.annotation.EnableRabbit; +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbit.config.RabbitListenerConfigUtils; +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; +import org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; + +/** + * Configuration for Spring AMQP annotation-driven endpoints using RabbitMQ. + * + * @author Eddú Meléndez + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(EnableRabbit.class) +class AmqpRabbitAnnotationDrivenConfiguration { + + @Bean + @ConditionalOnMissingBean + RabbitAmqpListenerContainerFactoryConfigurer rabbitAmqpListenerContainerFactoryConfigurer( + AmqpRabbitProperties properties, ObjectProvider messageConverter, + ObjectProvider taskSchedulers) { + RabbitAmqpListenerContainerFactoryConfigurer configurer = new RabbitAmqpListenerContainerFactoryConfigurer( + properties); + messageConverter.ifAvailable(configurer::setMessageConverter); + taskSchedulers.ifAvailable(configurer::setTaskScheduler); + return configurer; + } + + @Bean(name = "rabbitListenerContainerFactory") + @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory") + RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory( + RabbitAmqpListenerContainerFactoryConfigurer configurer, AmqpConnectionFactory connectionFactory, + ObjectProvider> amqpContainerCustomizer) { + RabbitAmqpListenerContainerFactory factory = new RabbitAmqpListenerContainerFactory(connectionFactory); + configurer.configure(factory); + amqpContainerCustomizer.ifUnique(factory::setContainerCustomizer); + return factory; + } + + @Configuration(proxyBeanMethods = false) + @EnableRabbit + @ConditionalOnMissingBean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME) + static class EnableRabbitConfiguration { + + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAutoConfiguration.java similarity index 66% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAutoConfiguration.java index a433060acc3..a5ad684f756 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAutoConfiguration.java @@ -14,73 +14,58 @@ * limitations under the License. */ -package org.springframework.boot.amqp.autoconfigure; +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import javax.net.ssl.SSLContext; import com.rabbitmq.client.amqp.Connection; import com.rabbitmq.client.amqp.CredentialsProvider; import com.rabbitmq.client.amqp.Environment; import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder; import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder.EnvironmentConnectionSettings; +import org.jspecify.annotations.Nullable; -import org.springframework.amqp.rabbit.config.ContainerCustomizer; import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; import org.springframework.amqp.rabbitmq.client.SingleAmqpConnectionFactory; -import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; -import org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.amqp.autoconfigure.RabbitAmqpConnectionDetails.Address; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails.Address; import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; /** - * {@link EnableAutoConfiguration Auto-configuration} for {@link RabbitAmqpTemplate}. + * Auto-configuration for AMQP 1.0 support with RabbitMQ. * * @author Eddú Meléndez - * @since 4.1.0 + * @since 4.2.0 */ -@AutoConfiguration +@AutoConfiguration(after = TaskSchedulingAutoConfiguration.class) @ConditionalOnClass({ RabbitAmqpTemplate.class, Connection.class }) -@EnableConfigurationProperties(RabbitAmqpProperties.class) -@Import(RabbitAnnotationDrivenConfiguration.class) -public final class RabbitAmqpAutoConfiguration { +@EnableConfigurationProperties(AmqpRabbitProperties.class) +@Import(AmqpRabbitAnnotationDrivenConfiguration.class) +public final class AmqpRabbitAutoConfiguration { - private final RabbitAmqpProperties properties; - - RabbitAmqpAutoConfiguration(RabbitAmqpProperties properties) { - this.properties = properties; + @Bean + @ConditionalOnMissingBean + AmqpRabbitConnectionDetails amqpRabbitConnectionDetails(AmqpRabbitProperties properties, + @Nullable SslBundles sslBundles) { + return new PropertiesAmqpRabbitConnectionDetails(properties, sslBundles); } @Bean @ConditionalOnMissingBean - RabbitAmqpConnectionDetails rabbitConnectionDetails() { - return new PropertiesRabbitAmqpConnectionDetails(this.properties); - } - - @Bean(name = "rabbitListenerContainerFactory") - @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory") - RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory(AmqpConnectionFactory connectionFactory, - ObjectProvider> amqpContainerCustomizer) { - RabbitAmqpListenerContainerFactory factory = new RabbitAmqpListenerContainerFactory(connectionFactory); - amqpContainerCustomizer.ifUnique(factory::setContainerCustomizer); - - RabbitAmqpProperties.AmqpContainer configuration = this.properties.getListener().getAmqp(); - factory.setObservationEnabled(configuration.isObservationEnabled()); - return factory; - } - - @Bean - @ConditionalOnMissingBean - Environment rabbitAmqpEnvironment(RabbitAmqpConnectionDetails connectionDetails, - ObjectProvider customizers, + Environment amqpRabbitEnvironment(AmqpRabbitConnectionDetails connectionDetails, + ObjectProvider customizers, ObjectProvider credentialsProvider) { PropertyMapper map = PropertyMapper.get(); EnvironmentConnectionSettings environmentConnectionSettings = new AmqpEnvironmentBuilder().connectionSettings(); @@ -91,7 +76,11 @@ public final class RabbitAmqpAutoConfiguration { map.from(connectionDetails::getPassword).to(environmentConnectionSettings::password); map.from(connectionDetails::getVirtualHost).to(environmentConnectionSettings::virtualHost); map.from(credentialsProvider::getIfAvailable).to(environmentConnectionSettings::credentialsProvider); - + SslBundle sslBundle = connectionDetails.getSslBundle(); + if (sslBundle != null) { + SSLContext sslContext = sslBundle.createSslContext(); + environmentConnectionSettings.tls().sslContext(sslContext); + } AmqpEnvironmentBuilder builder = environmentConnectionSettings.environmentBuilder(); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return builder.build(); @@ -99,20 +88,18 @@ public final class RabbitAmqpAutoConfiguration { @Bean @ConditionalOnMissingBean - AmqpConnectionFactory amqpConnectionFactory(Environment environment) { + AmqpConnectionFactory amqpRabbitConnectionFactory(Environment environment) { return new SingleAmqpConnectionFactory(environment); } @Bean @ConditionalOnMissingBean - RabbitAmqpTemplate rabbitAmqpTemplate(AmqpConnectionFactory connectionFactory, + RabbitAmqpTemplate rabbitAmqpTemplate(AmqpConnectionFactory connectionFactory, AmqpRabbitProperties properties, ObjectProvider customizers, ObjectProvider messageConverter) { RabbitAmqpTemplate rabbitAmqpTemplate = new RabbitAmqpTemplate(connectionFactory); - if (messageConverter.getIfAvailable() != null) { - rabbitAmqpTemplate.setMessageConverter(messageConverter.getIfAvailable()); - } - RabbitAmqpProperties.Template templateProperties = this.properties.getTemplate(); + messageConverter.ifAvailable(rabbitAmqpTemplate::setMessageConverter); + AmqpRabbitProperties.Template templateProperties = properties.getTemplate(); PropertyMapper map = PropertyMapper.get(); map.from(templateProperties::getDefaultReceiveQueue).to(rabbitAmqpTemplate::setReceiveQueue); diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpConnectionDetails.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitConnectionDetails.java similarity index 75% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpConnectionDetails.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitConnectionDetails.java index 344909fffb0..364918a6c05 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpConnectionDetails.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitConnectionDetails.java @@ -14,19 +14,26 @@ * limitations under the License. */ -package org.springframework.boot.amqp.autoconfigure; +package org.springframework.boot.amqp.rabbitmq.autoconfigure; import org.jspecify.annotations.Nullable; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; +import org.springframework.boot.ssl.SslBundle; /** * Details required to establish a connection to a RabbitMQ AMQP service. * * @author Eddú Meléndez - * @since 4.1.0 + * @since 4.2.0 */ -public interface RabbitAmqpConnectionDetails extends ConnectionDetails { +public interface AmqpRabbitConnectionDetails extends ConnectionDetails { + + /** + * Return the address of the broker. + * @return the address + */ + Address getAddress(); /** * Login user to authenticate to the broker. @@ -37,8 +44,8 @@ public interface RabbitAmqpConnectionDetails extends ConnectionDetails { } /** - * Login to authenticate against the broker. - * @return the login to authenticate against the broker or {@code null} + * Password used to authenticate to the broker. + * @return the password to authenticate to the broker or {@code null} */ default @Nullable String getPassword() { return null; @@ -53,11 +60,12 @@ public interface RabbitAmqpConnectionDetails extends ConnectionDetails { } /** - * Returns the address. - * @return the address - * @throws IllegalStateException if the address list is empty + * SSL bundle to use. + * @return the SSL bundle to use or {@code null} */ - Address getAddress(); + default @Nullable SslBundle getSslBundle() { + return null; + } /** * A RabbitMQ address. diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpProperties.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitProperties.java similarity index 86% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpProperties.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitProperties.java index 87ddff5492b..92aa9c12f14 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpProperties.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitProperties.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package org.springframework.boot.amqp.autoconfigure; +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import java.time.Duration; import org.jspecify.annotations.Nullable; @@ -26,12 +28,12 @@ import org.springframework.util.StringUtils; * Configuration properties for Rabbit AMQP. * * @author Eddú Meléndez - * @since 4.1.0 + * @since 4.2.0 */ @ConfigurationProperties("spring.amqp.rabbitmq") -public class RabbitAmqpProperties { +public class AmqpRabbitProperties { - private static final int DEFAULT_PORT = 5672; + static final int DEFAULT_PORT = 5672; /** * RabbitMQ host. Ignored if an address is set. @@ -65,11 +67,19 @@ public class RabbitAmqpProperties { */ private @Nullable String address; + /** + * SSL configuration. + */ + private final Ssl ssl = new Ssl(); + /** * Listener container configuration. */ private final Listener listener = new Listener(); + /** + * Template configuration. + */ private final Template template = new Template(); private @Nullable Address parsedAddress; @@ -120,7 +130,7 @@ public class RabbitAmqpProperties { public String determineAddress() { if (this.parsedAddress == null) { if (this.host.contains(",")) { - throw new InvalidConfigurationPropertyValueException("spring.amqp.host", this.host, + throw new InvalidConfigurationPropertyValueException("spring.amqp.rabbitmq.host", this.host, "Invalid character ','. Value must be a single host. For multiple hosts, use property 'spring.amqp.address' instead."); } return this.host + ":" + determinePort(); @@ -206,6 +216,10 @@ public class RabbitAmqpProperties { this.virtualHost = StringUtils.hasText(virtualHost) ? virtualHost : "/"; } + public Ssl getSsl() { + return this.ssl; + } + public Listener getListener() { return this.listener; } @@ -214,31 +228,34 @@ public class RabbitAmqpProperties { return this.template; } - public static class Listener { + /** + * SSL configuration. + */ + public static class Ssl { - private final AmqpContainer amqp = new AmqpContainer(); + /** + * SSL bundle name. + */ + private @Nullable String bundle; - public AmqpContainer getAmqp() { - return this.amqp; + public @Nullable String getBundle() { + return this.bundle; + } + + public void setBundle(@Nullable String bundle) { + this.bundle = bundle; } } - /** - * Configuration properties for {@code RabbitAmqpListenerContainer}. - */ - public static class AmqpContainer { + public static class Listener { /** * Whether to enable observation. */ private boolean observationEnabled; - /** - * Batch size, expressed as the number of physical messages, to be used by the - * container. - */ - private @Nullable Integer batchSize; + private final Batch batch = new Batch(); public boolean isObservationEnabled() { return this.observationEnabled; @@ -248,12 +265,39 @@ public class RabbitAmqpProperties { this.observationEnabled = observationEnabled; } - public @Nullable Integer getBatchSize() { - return this.batchSize; + public Batch getBatch() { + return this.batch; } - public void setBatchSize(@Nullable Integer batchSize) { - this.batchSize = batchSize; + public static class Batch { + + /** + * Batch size, expressed as the number of physical messages, to be used by the + * container. + */ + private @Nullable Integer size; + + /** + * Timeout for gathering batch messages. + */ + private Duration receiveTimeout = Duration.ofSeconds(30); + + public @Nullable Integer getSize() { + return this.size; + } + + public void setSize(@Nullable Integer size) { + this.size = size; + } + + public Duration getReceiveTimeout() { + return this.receiveTimeout; + } + + public void setReceiveTimeout(Duration receiveTimeout) { + this.receiveTimeout = receiveTimeout; + } + } } diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/PropertiesRabbitAmqpConnectionDetails.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/PropertiesAmqpRabbitConnectionDetails.java similarity index 59% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/PropertiesRabbitAmqpConnectionDetails.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/PropertiesAmqpRabbitConnectionDetails.java index 2eb06294eb9..9f1d9b71ed3 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/PropertiesRabbitAmqpConnectionDetails.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/PropertiesAmqpRabbitConnectionDetails.java @@ -14,21 +14,38 @@ * limitations under the License. */ -package org.springframework.boot.amqp.autoconfigure; +package org.springframework.boot.amqp.rabbitmq.autoconfigure; import org.jspecify.annotations.Nullable; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + /** - * Adapts {@link RabbitAmqpProperties} to {@link RabbitAmqpConnectionDetails}. + * Adapts {@link AmqpRabbitProperties} to {@link AmqpRabbitConnectionDetails}. * * @author Eddú Meléndez */ -class PropertiesRabbitAmqpConnectionDetails implements RabbitAmqpConnectionDetails { +class PropertiesAmqpRabbitConnectionDetails implements AmqpRabbitConnectionDetails { - private final RabbitAmqpProperties properties; + private final AmqpRabbitProperties properties; - PropertiesRabbitAmqpConnectionDetails(RabbitAmqpProperties properties) { + private final @Nullable SslBundles sslBundles; + + PropertiesAmqpRabbitConnectionDetails(AmqpRabbitProperties properties, @Nullable SslBundles sslBundles) { this.properties = properties; + this.sslBundles = sslBundles; + } + + @Override + public Address getAddress() { + String address = this.properties.determineAddress(); + int portSeparatorIndex = address.lastIndexOf(':'); + String host = address.substring(0, portSeparatorIndex); + String port = address.substring(portSeparatorIndex + 1); + return new Address(host, Integer.parseInt(port)); } @Override @@ -47,12 +64,13 @@ class PropertiesRabbitAmqpConnectionDetails implements RabbitAmqpConnectionDetai } @Override - public Address getAddress() { - String address = this.properties.determineAddress(); - int portSeparatorIndex = address.lastIndexOf(':'); - String host = address.substring(0, portSeparatorIndex); - String port = address.substring(portSeparatorIndex + 1); - return new Address(host, Integer.parseInt(port)); + public @Nullable SslBundle getSslBundle() { + String bundle = this.properties.getSsl().getBundle(); + if (StringUtils.hasLength(bundle)) { + Assert.notNull(this.sslBundles, "SSL bundle name has been set but no SSL bundles found in context"); + return this.sslBundles.getBundle(bundle); + } + return null; } } diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpListenerContainerFactoryConfigurer.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpListenerContainerFactoryConfigurer.java new file mode 100644 index 00000000000..deae4330a18 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpListenerContainerFactoryConfigurer.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import java.time.Duration; + +import org.jspecify.annotations.Nullable; + +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitProperties.Listener; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitProperties.Listener.Batch; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; + +/** + * Configure {@link RabbitAmqpListenerContainerFactory} with sensible defaults tuned using + * configuration properties. + *

+ * Can be injected into application code and used to define a custom + * {@code RabbitAmqpListenerContainerFactory} whose configuration is based upon that + * produced by auto-configuration. + * + * @author Stephane Nicoll + * @since 4.2.0 + */ +public final class RabbitAmqpListenerContainerFactoryConfigurer { + + private final AmqpRabbitProperties properties; + + private @Nullable MessageConverter messageConverter; + + private @Nullable TaskScheduler taskScheduler; + + /** + * Creates a new configurer that will use the given {@code properties}. + * @param properties the properties to use + */ + public RabbitAmqpListenerContainerFactoryConfigurer(AmqpRabbitProperties properties) { + this.properties = properties; + } + + /** + * Set the {@link MessageConverter} to use or {@code null} if the out-of-the-box + * converter should be used. + * @param messageConverter the {@link MessageConverter} + */ + protected void setMessageConverter(@Nullable MessageConverter messageConverter) { + this.messageConverter = messageConverter; + } + + /** + * Set the {@link TaskScheduler} to use or {@code null} if the default should be used. + * @param taskScheduler the {@link TaskScheduler} + */ + protected void setTaskScheduler(@Nullable TaskScheduler taskScheduler) { + this.taskScheduler = taskScheduler; + } + + /** + * Configure the specified {@link RabbitAmqpListenerContainerFactory}. The factory can + * be further tuned and default settings can be overridden. + * @param factory the {@link RabbitAmqpListenerContainerFactory} instance to configure + */ + public void configure(RabbitAmqpListenerContainerFactory factory) { + Assert.notNull(factory, "'factory' must not be null"); + PropertyMapper map = PropertyMapper.get(); + map.from(this.messageConverter).to(factory::setMessageConverter); + map.from(this.taskScheduler).to(factory::setTaskScheduler); + Listener listenerProperties = this.properties.getListener(); + map.from(listenerProperties.isObservationEnabled()).to(factory::setObservationEnabled); + Batch batchProperties = listenerProperties.getBatch(); + map.from(batchProperties.getSize()).to(factory::setBatchSize); + map.from(batchProperties.getReceiveTimeout()).as(Duration::toMillis).to(factory::setBatchReceiveTimeout); + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpTemplateCustomizer.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpTemplateCustomizer.java similarity index 93% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpTemplateCustomizer.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpTemplateCustomizer.java index e1eb2404cbb..f47c3a02843 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpTemplateCustomizer.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpTemplateCustomizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.amqp.autoconfigure; +package org.springframework.boot.amqp.rabbitmq.autoconfigure; import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; @@ -22,7 +22,7 @@ import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; * Callback interface that can be used to customize a {@link RabbitAmqpTemplate}. * * @author Eddú Meléndez - * @since 4.0.0 + * @since 4.2.0 */ @FunctionalInterface public interface RabbitAmqpTemplateCustomizer { diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/AmqpRabbitHealthContributorAutoConfiguration.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/AmqpRabbitHealthContributorAutoConfiguration.java new file mode 100644 index 00000000000..d4d9b367768 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/AmqpRabbitHealthContributorAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure.health; + +import com.rabbitmq.client.amqp.Connection; + +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitAutoConfiguration; +import org.springframework.boot.amqp.rabbitmq.health.AmqpRabbitHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.health.autoconfigure.contributor.CompositeHealthContributorConfiguration; +import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator; +import org.springframework.boot.health.contributor.HealthContributor; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for + * {@link AmqpRabbitHealthIndicator}. + * + * @author Stephane Nicoll + * @since 4.2.0 + */ +@AutoConfiguration(after = AmqpRabbitAutoConfiguration.class) +@ConditionalOnClass({ AmqpRabbitHealthIndicator.class, RabbitAmqpTemplate.class, Connection.class, + ConditionalOnEnabledHealthIndicator.class }) +@ConditionalOnBean(AmqpConnectionFactory.class) +@ConditionalOnEnabledHealthIndicator("rabbit") +public final class AmqpRabbitHealthContributorAutoConfiguration + extends CompositeHealthContributorConfiguration { + + AmqpRabbitHealthContributorAutoConfiguration() { + super(AmqpRabbitHealthIndicator::new); + } + + @Bean + @ConditionalOnMissingBean(name = { "rabbitHealthIndicator", "rabbitHealthContributor" }) + HealthContributor rabbitHealthContributor(ConfigurableListableBeanFactory beanFactory) { + return createContributor(beanFactory, AmqpConnectionFactory.class); + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/package-info.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/package-info.java new file mode 100644 index 00000000000..dfd3495d0d2 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for AMQP 1.0 support with RabbitMQ. + */ +@NullMarked +package org.springframework.boot.amqp.rabbitmq.autoconfigure.health; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/package-info.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/package-info.java new file mode 100644 index 00000000000..e2b848d419a --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for AMQP 1.0 support with RabbitMQ. + */ +@NullMarked +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitDockerComposeConnectionDetailsFactory.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/AmqpRabbitMqDockerComposeConnectionDetailsFactory.java similarity index 60% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitDockerComposeConnectionDetailsFactory.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/AmqpRabbitMqDockerComposeConnectionDetailsFactory.java index 289fbe1cd70..4c2a4d16e83 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitDockerComposeConnectionDetailsFactory.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/AmqpRabbitMqDockerComposeConnectionDetailsFactory.java @@ -14,35 +14,38 @@ * limitations under the License. */ -package org.springframework.boot.amqp.docker.compose; +package org.springframework.boot.amqp.rabbitmq.docker.compose; import org.jspecify.annotations.Nullable; -import org.springframework.boot.amqp.autoconfigure.RabbitAmqpConnectionDetails; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails; import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; +import org.springframework.boot.ssl.SslBundle; /** * {@link DockerComposeConnectionDetailsFactory} to create - * {@link RabbitAmqpConnectionDetails} for a {@code rabbitmq} service. + * {@link AmqpRabbitConnectionDetails} for a {@code rabbitmq} service. * * @author Eddú Meléndez */ -class RabbitDockerComposeConnectionDetailsFactory - extends DockerComposeConnectionDetailsFactory { +class AmqpRabbitMqDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { private static final int RABBITMQ_PORT = 5672; - protected RabbitDockerComposeConnectionDetailsFactory() { + private static final int RABBITMQ_TLS_PORT = 5671; + + protected AmqpRabbitMqDockerComposeConnectionDetailsFactory() { super("rabbitmq"); } @Override - protected @Nullable RabbitAmqpConnectionDetails getDockerComposeConnectionDetails( + protected @Nullable AmqpRabbitConnectionDetails getDockerComposeConnectionDetails( DockerComposeConnectionSource source) { try { - return new RabbitAmqpDockerComposeConnectionDetails(source.getRunningService()); + return new AmqpRabbitMqDockerComposeRabbitConnectionDetails(source.getRunningService()); } catch (IllegalStateException ex) { return null; @@ -50,20 +53,29 @@ class RabbitDockerComposeConnectionDetailsFactory } /** - * {@link RabbitAmqpConnectionDetails} backed by a {@code rabbitmq} + * {@link AmqpRabbitConnectionDetails} backed by a {@code rabbitmq} * {@link RunningService}. */ - static class RabbitAmqpDockerComposeConnectionDetails extends DockerComposeConnectionDetails - implements RabbitAmqpConnectionDetails { - - private final RabbitEnvironment environment; + static class AmqpRabbitMqDockerComposeRabbitConnectionDetails extends DockerComposeConnectionDetails + implements AmqpRabbitConnectionDetails { private final Address address; - protected RabbitAmqpDockerComposeConnectionDetails(RunningService service) { + private final RabbitEnvironment environment; + + private final @Nullable SslBundle sslBundle; + + protected AmqpRabbitMqDockerComposeRabbitConnectionDetails(RunningService service) { super(service); + this.sslBundle = getSslBundle(service); + int containerPort = (this.sslBundle != null) ? RABBITMQ_TLS_PORT : RABBITMQ_PORT; + this.address = new Address(service.host(), service.ports().get(containerPort)); this.environment = new RabbitEnvironment(service.env()); - this.address = new Address(service.host(), service.ports().get(RABBITMQ_PORT)); + } + + @Override + public Address getAddress() { + return this.address; } @Override @@ -82,8 +94,8 @@ class RabbitDockerComposeConnectionDetailsFactory } @Override - public Address getAddress() { - return this.address; + public @Nullable SslBundle getSslBundle() { + return this.sslBundle; } } diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitEnvironment.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/RabbitEnvironment.java similarity index 88% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitEnvironment.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/RabbitEnvironment.java index bc776b015db..709ccb58991 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/RabbitEnvironment.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/RabbitEnvironment.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.amqp.docker.compose; +package org.springframework.boot.amqp.rabbitmq.docker.compose; import java.util.Map; @@ -23,10 +23,7 @@ import org.jspecify.annotations.Nullable; /** * RabbitMQ environment details. * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @author Scott Frederick + * @author Eddú Meléndez */ class RabbitEnvironment { diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/package-info.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/package-info.java similarity index 92% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/package-info.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/package-info.java index c2d355c0797..94e20efb537 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/docker/compose/package-info.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/docker/compose/package-info.java @@ -18,6 +18,6 @@ * Support for Docker Compose RabbitMQ service connections. */ @NullMarked -package org.springframework.boot.amqp.docker.compose; +package org.springframework.boot.amqp.rabbitmq.docker.compose; import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/health/AmqpRabbitHealthIndicator.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/health/AmqpRabbitHealthIndicator.java new file mode 100644 index 00000000000..5e8d7b0b8a0 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/health/AmqpRabbitHealthIndicator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.health; + +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.boot.health.contributor.AbstractHealthIndicator; +import org.springframework.boot.health.contributor.Health; +import org.springframework.boot.health.contributor.HealthIndicator; +import org.springframework.util.Assert; + +/** + * {@link HealthIndicator} for AMQP 1.0 support with RabbitMQ. + * + * @author Stephane Nicoll + * @since 4.2.0 + */ +public class AmqpRabbitHealthIndicator extends AbstractHealthIndicator { + + private final AmqpConnectionFactory amqpConnectionFactory; + + public AmqpRabbitHealthIndicator(AmqpConnectionFactory amqpConnectionFactory) { + super("Rabbit health check failed"); + Assert.notNull(amqpConnectionFactory, "'amqpConnectionFactory' must not be null"); + this.amqpConnectionFactory = amqpConnectionFactory; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + builder.up(); + String version = this.amqpConnectionFactory.getConnection().connectionInfo().brokerVersion(); + if (version != null) { + builder.withDetail("version", version); + } + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/package-info.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/health/package-info.java similarity index 85% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/package-info.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/health/package-info.java index b519c35a2ca..4883951f350 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/autoconfigure/package-info.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/health/package-info.java @@ -15,9 +15,9 @@ */ /** - * Auto-configuration for RabbitMQ. + * Health integration for AMQP 1.0 support with RabbitMQ. */ @NullMarked -package org.springframework.boot.amqp.autoconfigure; +package org.springframework.boot.amqp.rabbitmq.health; import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/testcontainers/RabbitContainerConnectionDetailsFactory.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerConnectionDetailsFactory.java similarity index 68% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/testcontainers/RabbitContainerConnectionDetailsFactory.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerConnectionDetailsFactory.java index 58d6560e6b8..717049be83e 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/testcontainers/RabbitContainerConnectionDetailsFactory.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/testcontainers/AmqpRabbitMqContainerConnectionDetailsFactory.java @@ -14,45 +14,51 @@ * limitations under the License. */ -package org.springframework.boot.amqp.testcontainers; +package org.springframework.boot.amqp.rabbitmq.testcontainers; import java.net.URI; import org.jspecify.annotations.Nullable; import org.testcontainers.rabbitmq.RabbitMQContainer; -import org.springframework.boot.amqp.autoconfigure.RabbitAmqpConnectionDetails; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; /** - * {@link ContainerConnectionDetailsFactory} to create {@link RabbitAmqpConnectionDetails} + * {@link ContainerConnectionDetailsFactory} to create {@link AmqpRabbitConnectionDetails} * from a {@link ServiceConnection @ServiceConnection}-annotated * {@link RabbitMQContainer}. * * @author Eddú Meléndez */ -class RabbitContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory { +class AmqpRabbitMqContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { @Override - protected RabbitAmqpConnectionDetails getContainerConnectionDetails( + protected AmqpRabbitConnectionDetails getContainerConnectionDetails( ContainerConnectionSource source) { - return new RabbitAmqpMqContainerConnectionDetails(source); + return new AmqpRabbitMqContainerRabbitConnectionDetails(source); } /** - * {@link RabbitAmqpConnectionDetails} backed by a {@link ContainerConnectionSource}. + * {@link AmqpRabbitConnectionDetails} backed by a {@link ContainerConnectionSource}. */ - static final class RabbitAmqpMqContainerConnectionDetails extends ContainerConnectionDetails - implements RabbitAmqpConnectionDetails { + static final class AmqpRabbitMqContainerRabbitConnectionDetails + extends ContainerConnectionDetails implements AmqpRabbitConnectionDetails { - private RabbitAmqpMqContainerConnectionDetails(ContainerConnectionSource source) { + private AmqpRabbitMqContainerRabbitConnectionDetails(ContainerConnectionSource source) { super(source); } + @Override + public Address getAddress() { + URI uri = URI.create((getSslBundle() != null) ? getContainer().getAmqpsUrl() : getContainer().getAmqpUrl()); + return new Address(uri.getHost(), uri.getPort()); + } + @Override public String getUsername() { return getContainer().getAdminUsername(); @@ -63,12 +69,6 @@ class RabbitContainerConnectionDetailsFactory return getContainer().getAdminPassword(); } - @Override - public Address getAddress() { - URI uri = URI.create((getSslBundle() != null) ? getContainer().getAmqpsUrl() : getContainer().getAmqpUrl()); - return new Address(uri.getHost(), uri.getPort()); - } - @Override public @Nullable SslBundle getSslBundle() { return super.getSslBundle(); diff --git a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/testcontainers/package-info.java b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/testcontainers/package-info.java similarity index 92% rename from module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/testcontainers/package-info.java rename to module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/testcontainers/package-info.java index 69c6364ef32..dc19f3dd342 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/testcontainers/package-info.java +++ b/module/spring-boot-amqp-rabbitmq/src/main/java/org/springframework/boot/amqp/rabbitmq/testcontainers/package-info.java @@ -18,6 +18,6 @@ * Support for testcontainers RabbitMQ service connections. */ @NullMarked -package org.springframework.boot.amqp.testcontainers; +package org.springframework.boot.amqp.rabbitmq.testcontainers; import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring.factories b/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring.factories index 412b6813fab..b6129e3103c 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring.factories +++ b/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring.factories @@ -1,4 +1,4 @@ # Connection Details Factories org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ -org.springframework.boot.amqp.docker.compose.RabbitDockerComposeConnectionDetailsFactory,\ -org.springframework.boot.amqp.testcontainers.RabbitContainerConnectionDetailsFactory +org.springframework.boot.amqp.rabbitmq.docker.compose.AmqpRabbitMqDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.amqp.rabbitmq.testcontainers.AmqpRabbitMqContainerConnectionDetailsFactory diff --git a/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 050f5729e28..aa0a1c854fe 100644 --- a/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/module/spring-boot-amqp-rabbitmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ -org.springframework.boot.amqp.autoconfigure.RabbitAmqpAutoConfiguration \ No newline at end of file +org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitAutoConfiguration +org.springframework.boot.amqp.rabbitmq.autoconfigure.health.AmqpRabbitHealthContributorAutoConfiguration diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java deleted file mode 100644 index e1c5835ff6c..00000000000 --- a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2012-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.amqp.autoconfigure; - -import org.junit.jupiter.api.Test; -import org.mockito.InOrder; - -import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageProperties; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.amqp.rabbit.config.ContainerCustomizer; -import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; -import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; -import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; -import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; -import org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer; -import org.springframework.amqp.support.converter.MessageConversionException; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link RabbitAmqpAutoConfiguration}. - * - * @author Eddú Meléndez - */ -class RabbitAmqpAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RabbitAmqpAutoConfiguration.class)); - - @Test - void testDefaultRabbitAmqpConfiguration() { - this.contextRunner.run((context) -> { - RabbitAmqpListenerContainerFactory listenerContainerFactory = context - .getBean(RabbitAmqpListenerContainerFactory.class); - AmqpConnectionFactory connectionFactory = context.getBean(AmqpConnectionFactory.class); - RabbitAmqpTemplate rabbitAmqpTemplate = context.getBean(RabbitAmqpTemplate.class); - RabbitAmqpAdmin rabbitAmqpAdmin = context.getBean(RabbitAmqpAdmin.class); - - assertThat(listenerContainerFactory).isNotNull(); - assertThat(listenerContainerFactory).extracting("containerCustomizer").isNull(); - assertThat(connectionFactory).isNotNull(); - assertThat(rabbitAmqpTemplate).isNotNull(); - assertThat(rabbitAmqpAdmin).isNotNull(); - }); - } - - @Test - void whenMultipleRabbitAmqpTemplateCustomizersAreDefinedThenTheyAreCalledInOrder() { - this.contextRunner.withUserConfiguration(MultipleRabbitAmqpTemplateCustomizersConfiguration.class) - .run((context) -> { - RabbitAmqpTemplateCustomizer firstCustomizer = context.getBean("firstCustomizer", - RabbitAmqpTemplateCustomizer.class); - RabbitAmqpTemplateCustomizer secondCustomizer = context.getBean("secondCustomizer", - RabbitAmqpTemplateCustomizer.class); - InOrder inOrder = inOrder(firstCustomizer, secondCustomizer); - RabbitAmqpTemplate template = context.getBean(RabbitAmqpTemplate.class); - then(firstCustomizer).should(inOrder).customize(template); - then(secondCustomizer).should(inOrder).customize(template); - inOrder.verifyNoMoreInteractions(); - }); - } - - @Test - void testListenerContainerFactoryWithContainerCustomizer() { - this.contextRunner.withUserConfiguration(AmqpContainerCustomizerConfiguration.class).run((context) -> { - RabbitAmqpListenerContainerFactory listenerContainerFactory = context - .getBean(RabbitAmqpListenerContainerFactory.class); - - assertThat(listenerContainerFactory).isNotNull(); - assertThat(listenerContainerFactory).extracting("containerCustomizer").isNotNull(); - }); - } - - @Configuration(proxyBeanMethods = false) - static class MultipleRabbitAmqpTemplateCustomizersConfiguration { - - @Bean - @Order(Ordered.LOWEST_PRECEDENCE) - RabbitAmqpTemplateCustomizer secondCustomizer() { - return mock(RabbitAmqpTemplateCustomizer.class); - } - - @Bean - @Order(0) - RabbitAmqpTemplateCustomizer firstCustomizer() { - return mock(RabbitAmqpTemplateCustomizer.class); - } - - } - - @Import(TestListener.class) - @Configuration(proxyBeanMethods = false) - static class AmqpContainerCustomizerConfiguration { - - @Bean - @SuppressWarnings("unchecked") - ContainerCustomizer customizer() { - return mock(ContainerCustomizer.class); - } - - } - - @Configuration - static class CustomMessageConverterConfiguration { - - @Bean - MessageConverter messageConverter() { - return new MessageConverter() { - - @Override - public Message toMessage(Object object, MessageProperties messageProperties) - throws MessageConversionException { - return new Message(object.toString().getBytes()); - } - - @Override - public Object fromMessage(Message message) throws MessageConversionException { - return new String(message.getBody()); - } - - }; - } - - } - - static class TestListener { - - @RabbitListener(queues = "test", autoStartup = "false") - void listen(String in) { - } - - } - -} diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAutoConfigurationTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAutoConfigurationTests.java new file mode 100644 index 00000000000..f485de17b05 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitAutoConfigurationTests.java @@ -0,0 +1,294 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; +import org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer; +import org.springframework.amqp.support.converter.MessageConversionException; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails.Address; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AmqpRabbitAutoConfiguration}. + * + * @author Eddú Meléndez + */ +class AmqpRabbitAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(AmqpRabbitAutoConfiguration.class)); + + @Test + void testDefaultRabbitAmqpConfiguration() { + this.contextRunner.run((context) -> { + RabbitAmqpListenerContainerFactory listenerContainerFactory = context + .getBean(RabbitAmqpListenerContainerFactory.class); + AmqpConnectionFactory connectionFactory = context.getBean(AmqpConnectionFactory.class); + RabbitAmqpTemplate rabbitAmqpTemplate = context.getBean(RabbitAmqpTemplate.class); + RabbitAmqpAdmin rabbitAmqpAdmin = context.getBean(RabbitAmqpAdmin.class); + + assertThat(listenerContainerFactory).isNotNull(); + assertThat(listenerContainerFactory).extracting("containerCustomizer").isNull(); + assertThat(connectionFactory).isNotNull(); + assertThat(rabbitAmqpTemplate).isNotNull(); + assertThat(rabbitAmqpAdmin).isNotNull(); + }); + } + + @Test + void whenMultipleRabbitAmqpTemplateCustomizersAreDefinedThenTheyAreCalledInOrder() { + this.contextRunner.withUserConfiguration(MultipleRabbitAmqpTemplateCustomizersConfiguration.class) + .run((context) -> { + RabbitAmqpTemplateCustomizer firstCustomizer = context.getBean("firstCustomizer", + RabbitAmqpTemplateCustomizer.class); + RabbitAmqpTemplateCustomizer secondCustomizer = context.getBean("secondCustomizer", + RabbitAmqpTemplateCustomizer.class); + InOrder inOrder = inOrder(firstCustomizer, secondCustomizer); + RabbitAmqpTemplate template = context.getBean(RabbitAmqpTemplate.class); + then(firstCustomizer).should(inOrder).customize(template); + then(secondCustomizer).should(inOrder).customize(template); + inOrder.verifyNoMoreInteractions(); + }); + } + + @Test + void testListenerContainerFactoryWithContainerCustomizer() { + this.contextRunner.withUserConfiguration(AmqpContainerCustomizerConfiguration.class).run((context) -> { + RabbitAmqpListenerContainerFactory listenerContainerFactory = context + .getBean(RabbitAmqpListenerContainerFactory.class); + + assertThat(listenerContainerFactory).isNotNull(); + assertThat(listenerContainerFactory).extracting("containerCustomizer").isNotNull(); + }); + } + + @Test + void autoConfigurationConfiguresConnectionDetailsWithDefaultValues() { + this.contextRunner.run((context) -> { + AmqpRabbitConnectionDetails connectionDetails = context.getBean(AmqpRabbitConnectionDetails.class); + AmqpRabbitProperties properties = new AmqpRabbitProperties(); + assertThat(connectionDetails.getAddress().host()).isEqualTo(properties.getHost()); + assertThat(connectionDetails.getAddress().port()).isEqualTo(AmqpRabbitProperties.DEFAULT_PORT); + assertThat(connectionDetails.getUsername()).isEqualTo(properties.getUsername()); + assertThat(connectionDetails.getPassword()).isEqualTo(properties.getPassword()); + assertThat(connectionDetails.getVirtualHost()).isNull(); + }); + } + + @Test + void autoConfigurationConfiguresConnectionDetailsWithPropertyOverrides() { + this.contextRunner + .withPropertyValues("spring.amqp.rabbitmq.host=custom.host", "spring.amqp.rabbitmq.port=1234", + "spring.amqp.rabbitmq.username=user", "spring.amqp.rabbitmq.password=secret") + .run((context) -> { + AmqpRabbitConnectionDetails connectionDetails = context.getBean(AmqpRabbitConnectionDetails.class); + assertThat(connectionDetails.getAddress().host()).isEqualTo("custom.host"); + assertThat(connectionDetails.getAddress().port()).isEqualTo(1234); + assertThat(connectionDetails.getUsername()).isEqualTo("user"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + }); + } + + @Test + void autoConfigurationBacksOffWhenUserProvidesConnectionDetails() { + AmqpRabbitConnectionDetails connectionDetails = mock(AmqpRabbitConnectionDetails.class); + given(connectionDetails.getAddress()).willReturn(new Address("localhost", 5672)); + this.contextRunner.withBean(AmqpRabbitConnectionDetails.class, () -> connectionDetails).run((context) -> { + assertThat(context).hasSingleBean(AmqpRabbitConnectionDetails.class); + assertThat(context.getBean(AmqpRabbitConnectionDetails.class)).isSameAs(connectionDetails); + }); + } + + @Test + void autoConfigurationBacksOffWhenUserProvidesEnvironment() { + Environment environment = mock(Environment.class); + this.contextRunner.withBean(Environment.class, () -> environment).run((context) -> { + assertThat(context).hasSingleBean(Environment.class); + assertThat(context.getBean(Environment.class)).isSameAs(environment); + }); + } + + @Test + void autoConfigurationBacksOffWhenUserProvidesConnectionFactory() { + AmqpConnectionFactory connectionFactory = mock(AmqpConnectionFactory.class); + this.contextRunner.withBean(AmqpConnectionFactory.class, () -> connectionFactory).run((context) -> { + assertThat(context).hasSingleBean(AmqpConnectionFactory.class); + assertThat(context.getBean(AmqpConnectionFactory.class)).isSameAs(connectionFactory); + }); + } + + @Test + void autoConfigurationBacksOffWhenUserProvidesRabbitAmqpTemplate() { + RabbitAmqpTemplate template = mock(RabbitAmqpTemplate.class); + this.contextRunner.withBean(RabbitAmqpTemplate.class, () -> template).run((context) -> { + assertThat(context).hasSingleBean(RabbitAmqpTemplate.class); + assertThat(context.getBean(RabbitAmqpTemplate.class)).isSameAs(template); + }); + } + + @Test + void autoConfigurationBacksOffWhenUserProvidesRabbitAmqpAdmin() { + RabbitAmqpAdmin admin = mock(RabbitAmqpAdmin.class); + this.contextRunner.withBean(RabbitAmqpAdmin.class, () -> admin).run((context) -> { + assertThat(context).hasSingleBean(RabbitAmqpAdmin.class); + assertThat(context.getBean(RabbitAmqpAdmin.class)).isSameAs(admin); + }); + } + + @Test + void autoConfigurationBacksOffWhenUserProvidesRabbitListenerContainerFactory() { + RabbitAmqpListenerContainerFactory factory = mock(RabbitAmqpListenerContainerFactory.class); + this.contextRunner + .withBean("rabbitListenerContainerFactory", RabbitAmqpListenerContainerFactory.class, () -> factory) + .run((context) -> { + assertThat(context).hasSingleBean(RabbitAmqpListenerContainerFactory.class); + assertThat(context.getBean(RabbitAmqpListenerContainerFactory.class)).isSameAs(factory); + }); + } + + @Test + void autoConfigurationAppliesEnvironmentCustomizer() { + AmqpEnvironmentCustomizer customizer = mock(AmqpEnvironmentCustomizer.class); + this.contextRunner.withBean(AmqpEnvironmentCustomizer.class, () -> customizer) + .run((context) -> then(customizer).should().customize(any(AmqpEnvironmentBuilder.class))); + } + + @Test + void autoConfigurationConfiguresConnectionDetailsWithSslBundle() { + SslBundle sslBundle = mock(SslBundle.class); + SslBundles sslBundles = mock(SslBundles.class); + given(sslBundles.getBundle("test-bundle")).willReturn(sslBundle); + this.contextRunner.withPropertyValues("spring.amqp.rabbitmq.ssl.bundle=test-bundle") + .withBean(SslBundles.class, () -> sslBundles) + .run((context) -> { + AmqpRabbitConnectionDetails connectionDetails = context.getBean(AmqpRabbitConnectionDetails.class); + assertThat(connectionDetails.getSslBundle()).isSameAs(sslBundle); + }); + } + + @Test + void autoConfigurationConfiguresRabbitAmqpTemplateWithMessageConverter() { + this.contextRunner.withUserConfiguration(CustomMessageConverterConfiguration.class).run((context) -> { + MessageConverter expected = context.getBean(MessageConverter.class); + RabbitAmqpTemplate template = context.getBean(RabbitAmqpTemplate.class); + assertThat(template).hasFieldOrPropertyWithValue("messageConverter", expected); + }); + } + + @Test + void autoConfigurationConfiguresRabbitAmqpTemplateWithTemplateProperties() { + this.contextRunner + .withPropertyValues("spring.amqp.rabbitmq.template.exchange=my-exchange", + "spring.amqp.rabbitmq.template.routing-key=my-routing-key", + "spring.amqp.rabbitmq.template.default-receive-queue=my-queue") + .run((context) -> { + RabbitAmqpTemplate template = context.getBean(RabbitAmqpTemplate.class); + assertThat(template).hasFieldOrPropertyWithValue("defaultExchange", "my-exchange"); + assertThat(template).hasFieldOrPropertyWithValue("defaultRoutingKey", "my-routing-key"); + assertThat(template).hasFieldOrPropertyWithValue("defaultReceiveQueue", "my-queue"); + }); + } + + @Configuration(proxyBeanMethods = false) + static class MultipleRabbitAmqpTemplateCustomizersConfiguration { + + @Bean + @Order(Ordered.LOWEST_PRECEDENCE) + RabbitAmqpTemplateCustomizer secondCustomizer() { + return mock(RabbitAmqpTemplateCustomizer.class); + } + + @Bean + @Order(0) + RabbitAmqpTemplateCustomizer firstCustomizer() { + return mock(RabbitAmqpTemplateCustomizer.class); + } + + } + + @Import(TestListener.class) + @Configuration(proxyBeanMethods = false) + static class AmqpContainerCustomizerConfiguration { + + @Bean + @SuppressWarnings("unchecked") + ContainerCustomizer customizer() { + return mock(ContainerCustomizer.class); + } + + } + + @Configuration + static class CustomMessageConverterConfiguration { + + @Bean + MessageConverter messageConverter() { + return new MessageConverter() { + + @Override + public Message toMessage(Object object, MessageProperties messageProperties) + throws MessageConversionException { + return new Message(object.toString().getBytes()); + } + + @Override + public Object fromMessage(Message message) throws MessageConversionException { + return new String(message.getBody()); + } + + }; + } + + } + + static class TestListener { + + @RabbitListener(queues = "test", autoStartup = "false") + void listen(String in) { + } + + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitPropertiesTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitPropertiesTests.java new file mode 100644 index 00000000000..1ca06584808 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/AmqpRabbitPropertiesTests.java @@ -0,0 +1,213 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link AmqpRabbitProperties}. + * + * @author Stephane Nicoll + */ +class AmqpRabbitPropertiesTests { + + private final AmqpRabbitProperties properties = new AmqpRabbitProperties(); + + @Test + void customHost() { + this.properties.setHost("rabbit.example.com"); + assertThat(this.properties.getHost()).isEqualTo("rabbit.example.com"); + } + + @Test + void customPort() { + this.properties.setPort(1234); + assertThat(this.properties.getPort()).isEqualTo(1234); + } + + @Test + void determinePortDefaultsTo5672() { + assertThat(this.properties.determinePort()).isEqualTo(5672); + } + + @Test + void determinePortReturnsPortFromAddress() { + this.properties.setAddress("rabbit.example.com:1234"); + assertThat(this.properties.determinePort()).isEqualTo(1234); + } + + @Test + void determinePortDefaultsWhenAddressHasNoPort() { + this.properties.setAddress("rabbit.example.com"); + assertThat(this.properties.determinePort()).isEqualTo(5672); + } + + @Test + void customVirtualHost() { + this.properties.setVirtualHost("alpha"); + assertThat(this.properties.getVirtualHost()).isEqualTo("alpha"); + } + + @Test + void virtualHostRetainsALeadingSlash() { + this.properties.setVirtualHost("/alpha"); + assertThat(this.properties.getVirtualHost()).isEqualTo("/alpha"); + } + + @Test + void emptyVirtualHostIsCoercedToASlash() { + this.properties.setVirtualHost(""); + assertThat(this.properties.getVirtualHost()).isEqualTo("/"); + } + + @Test + void customUsername() { + this.properties.setUsername("user"); + assertThat(this.properties.getUsername()).isEqualTo("user"); + } + + @Test + void customPassword() { + this.properties.setPassword("secret"); + assertThat(this.properties.getPassword()).isEqualTo("secret"); + } + + @Test + void customAddress() { + this.properties.setAddress("user:secret@rabbit.example.com:1234/alpha"); + assertThat(this.properties.getAddress()).isEqualTo("user:secret@rabbit.example.com:1234/alpha"); + } + + @Test + void determineAddressUsesHostAndPortDefaults() { + assertThat(this.properties.determineAddress()).isEqualTo("localhost:5672"); + } + + @Test + void determineAddressUsesHostAndPortProperties() { + this.properties.setHost("rabbit.example.com"); + this.properties.setPort(1234); + assertThat(this.properties.determineAddress()).isEqualTo("rabbit.example.com:1234"); + } + + @Test + void determineAddressUsesAddressProperty() { + this.properties.setAddress("rabbit.example.com:1234"); + assertThat(this.properties.determineAddress()).isEqualTo("rabbit.example.com:1234"); + } + + @Test + void determineAddressFromAmqpUrl() { + this.properties.setAddress("amqp://user:secret@rabbit.example.com:1234/alpha"); + assertThat(this.properties.determineAddress()).isEqualTo("rabbit.example.com:1234"); + assertThat(this.properties.determineUsername()).isEqualTo("user"); + assertThat(this.properties.determinePassword()).isEqualTo("secret"); + assertThat(this.properties.determineVirtualHost()).isEqualTo("alpha"); + } + + @Test + void determineAddressFromAmqpsUrl() { + this.properties.setAddress("amqps://user:secret@rabbit.example.com:1234"); + assertThat(this.properties.determineAddress()).isEqualTo("rabbit.example.com:1234"); + } + + @Test + void determineUsernameFromAddress() { + this.properties.setAddress("user:secret@rabbit.example.com:1234"); + assertThat(this.properties.determineUsername()).isEqualTo("user"); + } + + @Test + void determineUsernameReturnsPropertyWhenAddressHasNoUsername() { + this.properties.setUsername("alice"); + this.properties.setAddress("rabbit.example.com:1234"); + assertThat(this.properties.determineUsername()).isEqualTo("alice"); + } + + @Test + void determineUsernameWithoutPassword() { + this.properties.setAddress("user@rabbit.example.com:1234"); + assertThat(this.properties.determineUsername()).isEqualTo("user"); + assertThat(this.properties.determinePassword()).isEqualTo("guest"); + } + + @Test + void determinePasswordFromAddress() { + this.properties.setAddress("user:secret@rabbit.example.com:1234"); + assertThat(this.properties.determinePassword()).isEqualTo("secret"); + } + + @Test + void determinePasswordReturnsPropertyWhenAddressHasNoPassword() { + this.properties.setPassword("12345678"); + this.properties.setAddress("rabbit.example.com:1234"); + assertThat(this.properties.determinePassword()).isEqualTo("12345678"); + } + + @Test + void determineVirtualHostFromAddress() { + this.properties.setAddress("rabbit.example.com:1234/alpha"); + assertThat(this.properties.determineVirtualHost()).isEqualTo("alpha"); + } + + @Test + void determineVirtualHostReturnsPropertyWhenAddressHasNoVirtualHost() { + this.properties.setVirtualHost("alpha"); + this.properties.setAddress("rabbit.example.com:1234"); + assertThat(this.properties.determineVirtualHost()).isEqualTo("alpha"); + } + + @Test + void determineVirtualHostIsSlashWhenAddressHasTrailingSlash() { + this.properties.setAddress("amqp://root:password@otherhost:1111/"); + assertThat(this.properties.determineVirtualHost()).isEqualTo("/"); + } + + @Test + void ipv6Address() { + this.properties.setAddress("amqp://foo:bar@[aaaa:bbbb:cccc::d]:1234"); + assertThat(this.properties.determineAddress()).isEqualTo("[aaaa:bbbb:cccc::d]:1234"); + assertThat(this.properties.determinePort()).isEqualTo(1234); + } + + @Test + void ipv6AddressDefaultPort() { + this.properties.setAddress("amqp://foo:bar@[aaaa:bbbb:cccc::d]"); + assertThat(this.properties.determineAddress()).isEqualTo("[aaaa:bbbb:cccc::d]:5672"); + assertThat(this.properties.determinePort()).isEqualTo(5672); + } + + @Test + void hostPropertyMustBeSingleHost() { + this.properties.setHost("my-rmq-host.net,my-rmq-host-2.net"); + assertThatExceptionOfType(InvalidConfigurationPropertyValueException.class) + .isThrownBy(this.properties::determineAddress) + .withMessageContaining("spring.amqp.rabbitmq.host"); + } + + @Test + void customSslBundle() { + this.properties.getSsl().setBundle("test-bundle"); + assertThat(this.properties.getSsl().getBundle()).isEqualTo("test-bundle"); + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/PropertiesAmqpRabbitConnectionDetailsTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/PropertiesAmqpRabbitConnectionDetailsTests.java new file mode 100644 index 00000000000..88d21b6cd27 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/PropertiesAmqpRabbitConnectionDetailsTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitConnectionDetails.Address; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundle; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link PropertiesAmqpRabbitConnectionDetails}. + * + * @author Stephane Nicoll + */ +class PropertiesAmqpRabbitConnectionDetailsTests { + + private AmqpRabbitProperties properties; + + private DefaultSslBundleRegistry sslBundleRegistry; + + private PropertiesAmqpRabbitConnectionDetails connectionDetails; + + @BeforeEach + void setUp() { + this.properties = new AmqpRabbitProperties(); + this.sslBundleRegistry = new DefaultSslBundleRegistry(); + this.connectionDetails = new PropertiesAmqpRabbitConnectionDetails(this.properties, this.sslBundleRegistry); + } + + @Test + void getAddressUsesDefaultHostAndPort() { + Address address = this.connectionDetails.getAddress(); + assertThat(address.host()).isEqualTo("localhost"); + assertThat(address.port()).isEqualTo(5672); + } + + @Test + void getAddressUsesHostAndPortProperties() { + this.properties.setHost("rabbit.example.com"); + this.properties.setPort(1234); + Address address = this.connectionDetails.getAddress(); + assertThat(address.host()).isEqualTo("rabbit.example.com"); + assertThat(address.port()).isEqualTo(1234); + } + + @Test + void getAddressUsesAddressProperty() { + this.properties.setAddress("rabbit.example.com:1234"); + Address address = this.connectionDetails.getAddress(); + assertThat(address.host()).isEqualTo("rabbit.example.com"); + assertThat(address.port()).isEqualTo(1234); + } + + @Test + void getAddressFromAmqpUrl() { + this.properties.setAddress("amqp://user:secret@rabbit.example.com:1234/alpha"); + Address address = this.connectionDetails.getAddress(); + assertThat(address.host()).isEqualTo("rabbit.example.com"); + assertThat(address.port()).isEqualTo(1234); + } + + @Test + void getAddressWithIpv6() { + this.properties.setAddress("amqp://foo:bar@[aaaa:bbbb:cccc::d]:1234"); + Address address = this.connectionDetails.getAddress(); + assertThat(address.host()).isEqualTo("[aaaa:bbbb:cccc::d]"); + assertThat(address.port()).isEqualTo(1234); + } + + @Test + void getUsernameReturnsDefaultGuest() { + assertThat(this.connectionDetails.getUsername()).isEqualTo("guest"); + } + + @Test + void getUsernameUsesProperty() { + this.properties.setUsername("alice"); + assertThat(this.connectionDetails.getUsername()).isEqualTo("alice"); + } + + @Test + void getUsernameFromAddress() { + this.properties.setAddress("user:secret@rabbit.example.com:1234"); + assertThat(this.connectionDetails.getUsername()).isEqualTo("user"); + } + + @Test + void getPasswordReturnsDefaultGuest() { + assertThat(this.connectionDetails.getPassword()).isEqualTo("guest"); + } + + @Test + void getPasswordUsesProperty() { + this.properties.setPassword("secret"); + assertThat(this.connectionDetails.getPassword()).isEqualTo("secret"); + } + + @Test + void getPasswordFromAddress() { + this.properties.setAddress("user:secret@rabbit.example.com:1234"); + assertThat(this.connectionDetails.getPassword()).isEqualTo("secret"); + } + + @Test + void getVirtualHostDefaultsToNull() { + assertThat(this.connectionDetails.getVirtualHost()).isNull(); + } + + @Test + void getVirtualHostUsesProperty() { + this.properties.setVirtualHost("alpha"); + assertThat(this.connectionDetails.getVirtualHost()).isEqualTo("alpha"); + } + + @Test + void getVirtualHostFromAddress() { + this.properties.setAddress("rabbit.example.com:1234/alpha"); + assertThat(this.connectionDetails.getVirtualHost()).isEqualTo("alpha"); + } + + @Test + void getSslBundleDefaultsToNull() { + assertThat(this.connectionDetails.getSslBundle()).isNull(); + } + + @Test + void getSslBundleUsesBundle() { + SslBundle bundle = mock(SslBundle.class); + this.sslBundleRegistry.registerBundle("test-bundle", bundle); + this.properties.getSsl().setBundle("test-bundle"); + assertThat(this.connectionDetails.getSslBundle()).isSameAs(bundle); + } + + @Test + void getSslBundleWithNoSslBundlesThrowsException() { + PropertiesAmqpRabbitConnectionDetails details = new PropertiesAmqpRabbitConnectionDetails(this.properties, + null); + this.properties.getSsl().setBundle("test-bundle"); + assertThatIllegalArgumentException().isThrownBy(details::getSslBundle) + .withMessageContaining("SSL bundle name has been set but no SSL bundles found in context"); + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpListenerContainerFactoryConfigurerTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpListenerContainerFactoryConfigurerTests.java new file mode 100644 index 00000000000..16a1aad5153 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/RabbitAmqpListenerContainerFactoryConfigurerTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; + +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitProperties.Listener.Batch; +import org.springframework.scheduling.TaskScheduler; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +/** + * Tests for {@link RabbitAmqpListenerContainerFactoryConfigurer}. + * + * @author Stephane Nicoll + */ +class RabbitAmqpListenerContainerFactoryConfigurerTests { + + @Test + void configureAppliesDefaultListenerProperties() { + AmqpRabbitProperties properties = new AmqpRabbitProperties(); + RabbitAmqpListenerContainerFactoryConfigurer configurer = new RabbitAmqpListenerContainerFactoryConfigurer( + properties); + RabbitAmqpListenerContainerFactory factory = mock(RabbitAmqpListenerContainerFactory.class); + configurer.configure(factory); + then(factory).should().setObservationEnabled(false); + then(factory).should().setBatchReceiveTimeout(Duration.ofSeconds(30).toMillis()); + then(factory).should(never()).setBatchSize(any()); + then(factory).should(never()).setMessageConverter(any()); + then(factory).should(never()).setTaskScheduler(any()); + } + + @Test + void configureAppliesCustomListenerProperties() { + AmqpRabbitProperties properties = new AmqpRabbitProperties(); + properties.getListener().setObservationEnabled(true); + Batch batch = properties.getListener().getBatch(); + batch.setSize(10); + batch.setReceiveTimeout(Duration.ofSeconds(5)); + RabbitAmqpListenerContainerFactoryConfigurer configurer = new RabbitAmqpListenerContainerFactoryConfigurer( + properties); + RabbitAmqpListenerContainerFactory factory = mock(RabbitAmqpListenerContainerFactory.class); + configurer.configure(factory); + then(factory).should().setObservationEnabled(true); + then(factory).should().setBatchSize(10); + then(factory).should().setBatchReceiveTimeout(Duration.ofSeconds(5).toMillis()); + } + + @Test + void configureAppliesMessageConverter() { + AmqpRabbitProperties properties = new AmqpRabbitProperties(); + RabbitAmqpListenerContainerFactoryConfigurer configurer = new RabbitAmqpListenerContainerFactoryConfigurer( + properties); + MessageConverter messageConverter = mock(MessageConverter.class); + configurer.setMessageConverter(messageConverter); + RabbitAmqpListenerContainerFactory factory = mock(RabbitAmqpListenerContainerFactory.class); + configurer.configure(factory); + then(factory).should().setMessageConverter(messageConverter); + } + + @Test + void configureAppliesTaskScheduler() { + AmqpRabbitProperties properties = new AmqpRabbitProperties(); + RabbitAmqpListenerContainerFactoryConfigurer configurer = new RabbitAmqpListenerContainerFactoryConfigurer( + properties); + TaskScheduler taskScheduler = mock(TaskScheduler.class); + configurer.setTaskScheduler(taskScheduler); + RabbitAmqpListenerContainerFactory factory = mock(RabbitAmqpListenerContainerFactory.class); + configurer.configure(factory); + then(factory).should().setTaskScheduler(taskScheduler); + } + + @Test + void configureSkipsOptionalDependenciesWhenNotSet() { + AmqpRabbitProperties properties = new AmqpRabbitProperties(); + RabbitAmqpListenerContainerFactoryConfigurer configurer = new RabbitAmqpListenerContainerFactoryConfigurer( + properties); + RabbitAmqpListenerContainerFactory factory = mock(RabbitAmqpListenerContainerFactory.class); + configurer.configure(factory); + then(factory).should(never()).setMessageConverter(any()); + then(factory).should(never()).setTaskScheduler(any()); + then(factory).should(never()).setBatchSize(any()); + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/AmqpRabbitHealthContributorAutoConfigurationTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/AmqpRabbitHealthContributorAutoConfigurationTests.java new file mode 100644 index 00000000000..3c0ee8b23ec --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/autoconfigure/health/AmqpRabbitHealthContributorAutoConfigurationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.autoconfigure.health; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.amqp.rabbitmq.autoconfigure.AmqpRabbitAutoConfiguration; +import org.springframework.boot.amqp.rabbitmq.health.AmqpRabbitHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AmqpRabbitHealthContributorAutoConfiguration}. + * + * @author Stephane Nicoll + */ +class AmqpRabbitHealthContributorAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(AmqpRabbitAutoConfiguration.class, + AmqpRabbitHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class)); + + @Test + void runShouldCreateIndicator() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(AmqpRabbitHealthIndicator.class)); + } + + @Test + void runWhenDisabledShouldNotCreateIndicator() { + this.contextRunner.withPropertyValues("management.health.rabbit.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(AmqpRabbitHealthIndicator.class)); + } + +} diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/docker/compose/RabbitEnvironmentTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/docker/compose/RabbitEnvironmentTests.java similarity index 91% rename from module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/docker/compose/RabbitEnvironmentTests.java rename to module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/docker/compose/RabbitEnvironmentTests.java index 1ecb0b4d6ff..f732c725183 100644 --- a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/docker/compose/RabbitEnvironmentTests.java +++ b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/docker/compose/RabbitEnvironmentTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.amqp.docker.compose; +package org.springframework.boot.amqp.rabbitmq.docker.compose; import java.util.Collections; import java.util.Map; @@ -52,19 +52,19 @@ class RabbitEnvironmentTests { } @Test - void getUsernameWhenNoRabbitmqDefaultPass() { + void getPasswordWhenNoRabbitmqDefaultPass() { RabbitEnvironment environment = new RabbitEnvironment(Collections.emptyMap()); assertThat(environment.getPassword()).isEqualTo("guest"); } @Test - void getUsernameWhenHasRabbitmqDefaultPass() { + void getPasswordWhenHasRabbitmqDefaultPass() { RabbitEnvironment environment = new RabbitEnvironment(Map.of("RABBITMQ_DEFAULT_PASS", "secret")); assertThat(environment.getPassword()).isEqualTo("secret"); } @Test - void getUsernameWhenHasRabbitmqPassword() { + void getPasswordWhenHasRabbitmqPassword() { RabbitEnvironment environment = new RabbitEnvironment(Map.of("RABBITMQ_PASSWORD", "secret")); assertThat(environment.getPassword()).isEqualTo("secret"); } diff --git a/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/health/AmqpRabbitHealthIndicatorTests.java b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/health/AmqpRabbitHealthIndicatorTests.java new file mode 100644 index 00000000000..97de985c768 --- /dev/null +++ b/module/spring-boot-amqp-rabbitmq/src/test/java/org/springframework/boot/amqp/rabbitmq/health/AmqpRabbitHealthIndicatorTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.amqp.rabbitmq.health; + +import com.rabbitmq.client.amqp.Connection; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.boot.health.contributor.Health; +import org.springframework.boot.health.contributor.Status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AmqpRabbitHealthIndicator}. + * + * @author Stephane Nicoll + */ +@ExtendWith(MockitoExtension.class) +class AmqpRabbitHealthIndicatorTests { + + @Mock + @SuppressWarnings("NullAway.Init") + private AmqpConnectionFactory amqpConnectionFactory; + + @Mock + @SuppressWarnings("NullAway.Init") + private Connection connection; + + @Test + @SuppressWarnings("NullAway") // Test null check + void createWhenAmqpConnectionFactoryIsNullShouldThrowException() { + assertThatIllegalArgumentException().isThrownBy(() -> new AmqpRabbitHealthIndicator(null)) + .withMessageContaining("'amqpConnectionFactory' must not be null"); + } + + @Test + void healthWhenConnectionSucceedsShouldReturnUpWithVersion() { + given(this.amqpConnectionFactory.getConnection()).willReturn(this.connection); + Connection.ConnectionInfo connectionInfo = mock(Connection.ConnectionInfo.class); + given(connectionInfo.brokerVersion()).willReturn("123"); + given(this.connection.connectionInfo()).willReturn(connectionInfo); + Health health = new AmqpRabbitHealthIndicator(this.amqpConnectionFactory).health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsEntry("version", "123"); + } + + @Test + void healthWhenVersionIsMissingShouldReturnUpWithoutVersion() { + given(this.amqpConnectionFactory.getConnection()).willReturn(this.connection); + Connection.ConnectionInfo connectionInfo = mock(Connection.ConnectionInfo.class); + given(connectionInfo.brokerVersion()).willReturn(null); + given(this.connection.connectionInfo()).willReturn(connectionInfo); + Health health = new AmqpRabbitHealthIndicator(this.amqpConnectionFactory).health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).doesNotContainKey("version"); + } + + @Test + void healthWhenConnectionFailsShouldReturnDown() { + given(this.amqpConnectionFactory.getConnection()).willThrow(new RuntimeException()); + Health health = new AmqpRabbitHealthIndicator(this.amqpConnectionFactory).health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + } + +} diff --git a/module/spring-boot-autoconfigure-classic-modules/build.gradle b/module/spring-boot-autoconfigure-classic-modules/build.gradle index 355de273613..cb82401cae7 100644 --- a/module/spring-boot-autoconfigure-classic-modules/build.gradle +++ b/module/spring-boot-autoconfigure-classic-modules/build.gradle @@ -31,6 +31,9 @@ dependencies { api(project(":module:spring-boot-amqp")) { transitive = false } + api(project(":module:spring-boot-amqp-rabbitmq")) { + transitive = false + } api(project(":module:spring-boot-artemis")) { transitive = false } diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index a89833cb5a0..18a37bcd250 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -2063,10 +2063,22 @@ bom { } links { site("https://github.com/rabbitmq/rabbitmq-java-client") - javadoc("https://rabbitmq.github.io/rabbitmq-java-client/api/current", "com.rabbitmq") + javadoc("https://rabbitmq.github.io/rabbitmq-java-client/api/current", "com.rabbitmq.client") releaseNotes("https://github.com/rabbitmq/rabbitmq-java-client/releases/tag/v{version}") } } + library("RabbitMQ AMQP Client", "1.2.0") { + group("com.rabbitmq.client") { + modules = [ + "amqp-client" + ] + } + links { + site("https://github.com/rabbitmq/rabbitmq-amqp-java-client") + javadoc("https://rabbitmq.github.io/rabbitmq-amqp-java-client/stable/api", "com.rabbitmq.amqp.client") + releaseNotes("https://github.com/rabbitmq/rabbitmq-amqp-java-client/releases/tag/v{version}") + } + } library("RabbitMQ Stream Client", "1.6.0") { group("com.rabbitmq") { modules = [ @@ -2248,6 +2260,7 @@ bom { "spring-boot-starter-actuator-test", "spring-boot-starter-amqp", "spring-boot-starter-amqp-rabbitmq", + "spring-boot-starter-amqp-rabbitmq-test", "spring-boot-starter-amqp-test", "spring-boot-starter-artemis", "spring-boot-starter-artemis-test", diff --git a/settings.gradle b/settings.gradle index ce1e29e4c9b..4a5e437de32 100644 --- a/settings.gradle +++ b/settings.gradle @@ -215,6 +215,7 @@ include "starter:spring-boot-starter-actuator" include "starter:spring-boot-starter-actuator-test" include "starter:spring-boot-starter-amqp" include "starter:spring-boot-starter-amqp-rabbitmq" +include "starter:spring-boot-starter-amqp-rabbitmq-test" include "starter:spring-boot-starter-amqp-test" include "starter:spring-boot-starter-artemis" include "starter:spring-boot-starter-artemis-test" diff --git a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/dockerTest/java/smoketest/amqp/SampleRabbitAmqpSimpleApplicationTests.java b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/dockerTest/java/smoketest/amqp/rabbitmq/SampleRabbitAmqpSimpleApplicationTests.java similarity index 91% rename from smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/dockerTest/java/smoketest/amqp/SampleRabbitAmqpSimpleApplicationTests.java rename to smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/dockerTest/java/smoketest/amqp/rabbitmq/SampleRabbitAmqpSimpleApplicationTests.java index bd9f51c562a..e0c7f8e6853 100644 --- a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/dockerTest/java/smoketest/amqp/SampleRabbitAmqpSimpleApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/dockerTest/java/smoketest/amqp/rabbitmq/SampleRabbitAmqpSimpleApplicationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package smoketest.amqp; +package smoketest.amqp.rabbitmq; import java.time.Duration; @@ -49,7 +49,8 @@ class SampleRabbitAmqpSimpleApplicationTests { @Test void sendSimpleMessage(CapturedOutput output) { this.sender.send("Test message"); - Awaitility.waitAtMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(output).contains("Test message")); + Awaitility.waitAtMost(Duration.ofMinutes(1)) + .untilAsserted(() -> assertThat(output).contains("'Test message'").contains("'smoke-test'")); } } diff --git a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/SampleRabbitAmqpSimpleApplication.java similarity index 82% rename from smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java rename to smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/SampleRabbitAmqpSimpleApplication.java index aff9bc342e3..83c73d1ed34 100644 --- a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java +++ b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/SampleRabbitAmqpSimpleApplication.java @@ -14,22 +14,20 @@ * limitations under the License. */ -package smoketest.amqp; +package smoketest.amqp.rabbitmq; +import com.rabbitmq.client.amqp.Message; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.messaging.handler.annotation.Payload; @SpringBootApplication -@RabbitListener(queues = "foo") public class SampleRabbitAmqpSimpleApplication { private static final Log logger = LogFactory.getLog(SampleRabbitAmqpSimpleApplication.class); @@ -44,9 +42,11 @@ public class SampleRabbitAmqpSimpleApplication { return new Queue("foo"); } - @RabbitHandler - public void process(@Payload String foo) { - logger.info(foo); + @RabbitListener(queues = "foo") + public void process(Message amqpMessage) { + String body = new String(amqpMessage.body()); + String sourceApp = (String) amqpMessage.property("source-app"); + logger.info("Received '%s' from '%s'".formatted(body, sourceApp)); } @Bean diff --git a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/Sender.java b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/Sender.java similarity index 78% rename from smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/Sender.java rename to smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/Sender.java index 6639bc0316c..d8b873d8972 100644 --- a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/Sender.java +++ b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/Sender.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package smoketest.amqp; +package smoketest.amqp.rabbitmq; import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; @@ -24,8 +24,11 @@ public class Sender { @Autowired private RabbitAmqpTemplate rabbitAmqpTemplate; - public void send(String message) { - this.rabbitAmqpTemplate.convertAndSend("foo", message); + public void send(String payload) { + this.rabbitAmqpTemplate.convertAndSend("foo", payload, (message) -> { + message.getMessageProperties().setHeader("source-app", "smoke-test"); + return message; + }); } } diff --git a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/package-info.java b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/package-info.java similarity index 95% rename from smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/package-info.java rename to smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/package-info.java index bc9e41a079d..62bf09a6b3d 100644 --- a/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/package-info.java +++ b/smoke-test/spring-boot-smoke-test-amqp-rabbitmq/src/main/java/smoketest/amqp/rabbitmq/package-info.java @@ -15,6 +15,6 @@ */ @NullMarked -package smoketest.amqp; +package smoketest.amqp.rabbitmq; import org.jspecify.annotations.NullMarked; diff --git a/starter/spring-boot-starter-amqp-rabbitmq-test/build.gradle b/starter/spring-boot-starter-amqp-rabbitmq-test/build.gradle new file mode 100644 index 00000000000..c4e673e9c22 --- /dev/null +++ b/starter/spring-boot-starter-amqp-rabbitmq-test/build.gradle @@ -0,0 +1,26 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id "org.springframework.boot.starter" +} + +description = "Starter for testing Spring AMQP with Rabbit MQ over AMQP 1.0 protocol" + +dependencies { + api(project(":starter:spring-boot-starter-amqp-rabbitmq")) + api(project(":starter:spring-boot-starter-test")) +}