From 63121dd08a7af4203b8c563b1d0433d8eb2f8e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 19 Apr 2023 10:34:19 -0600 Subject: [PATCH] Add service connection for Testcontainers ActiveMQ See gh-35080 --- .../activemq/ActiveMQAutoConfiguration.java | 36 ++++++++ .../activemq/ActiveMQConnectionDetails.java | 35 ++++++++ ...ctiveMQConnectionFactoryConfiguration.java | 22 +++-- .../ActiveMQConnectionFactoryFactory.java | 21 ++--- .../jms/activemq/ActiveMQProperties.java | 10 +++ ...iveMQXAConnectionFactoryConfiguration.java | 13 +-- .../ActiveMQAutoConfigurationTests.java | 47 ++++++++++ .../jms/activemq/ActiveMQPropertiesTests.java | 8 +- .../src/docs/asciidoc/features/testing.adoc | 3 + .../spring-boot-testcontainers/build.gradle | 2 + ...veMQContainerConnectionDetailsFactory.java | 79 ++++++++++++++++ .../connection/activemq/package-info.java | 20 +++++ .../main/resources/META-INF/spring.factories | 1 + ...nectionDetailsFactoryIntegrationTests.java | 90 +++++++++++++++++++ .../build.gradle | 2 + .../activemq/SampleActiveMqTests.java | 17 ++-- 16 files changed, 367 insertions(+), 39 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java index da642a7f86..44c3cebe30 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; /** @@ -35,6 +36,7 @@ import org.springframework.context.annotation.Import; * * @author Stephane Nicoll * @author Phillip Webb + * @author Eddú Meléndez * @since 3.1.0 */ @AutoConfiguration(before = JmsAutoConfiguration.class, after = JndiConnectionFactoryAutoConfiguration.class) @@ -44,4 +46,38 @@ import org.springframework.context.annotation.Import; @Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class }) public class ActiveMQAutoConfiguration { + @Bean + @ConditionalOnMissingBean(ActiveMQConnectionDetails.class) + ActiveMQConnectionDetails activemqConnectionDetails(ActiveMQProperties properties) { + return new PropertiesActiveMQConnectionDetails(properties); + } + + /** + * Adapts {@link ActiveMQProperties} to {@link ActiveMQConnectionDetails}. + */ + static class PropertiesActiveMQConnectionDetails implements ActiveMQConnectionDetails { + + private final ActiveMQProperties properties; + + PropertiesActiveMQConnectionDetails(ActiveMQProperties properties) { + this.properties = properties; + } + + @Override + public String getBrokerUrl() { + return this.properties.determineBrokerUrl(); + } + + @Override + public String getUser() { + return this.properties.getUser(); + } + + @Override + public String getPassword() { + return this.properties.getPassword(); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java new file mode 100644 index 0000000000..b139215095 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionDetails.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2023 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.autoconfigure.jms.activemq; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an ActiveMQ service. + * + * @author Eddú Meléndez + * @since 3.1.0 + */ +public interface ActiveMQConnectionDetails extends ConnectionDetails { + + String getBrokerUrl(); + + String getUser(); + + String getPassword(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java index a55337e58a..a4d242600e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java @@ -39,6 +39,7 @@ import org.springframework.jms.connection.CachingConnectionFactory; * @author Phillip Webb * @author Andy Wilkinson * @author Aurélien Leboulanger + * @author Eddú Meléndez */ @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(ConnectionFactory.class) @@ -52,13 +53,16 @@ class ActiveMQConnectionFactoryConfiguration { @Bean @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return createJmsConnectionFactory(properties, factoryCustomizers); + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + return createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails); } private static ActiveMQConnectionFactory createJmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList(), + connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); } @@ -70,10 +74,11 @@ class ActiveMQConnectionFactoryConfiguration { @Bean CachingConnectionFactory jmsConnectionFactory(JmsProperties jmsProperties, ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { JmsProperties.Cache cacheProperties = jmsProperties.getCache(); CachingConnectionFactory connectionFactory = new CachingConnectionFactory( - createJmsConnectionFactory(properties, factoryCustomizers)); + createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails)); connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); connectionFactory.setCacheProducers(cacheProperties.isProducers()); connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); @@ -91,9 +96,10 @@ class ActiveMQConnectionFactoryConfiguration { @Bean(destroyMethod = "stop") @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true") JmsPoolConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, - factoryCustomizers.orderedStream().toList()) + factoryCustomizers.orderedStream().toList(), connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); return new JmsPoolConnectionFactoryFactory(properties.getPool()) .createPooledConnectionFactory(connectionFactory); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java index b571860491..67768c0363 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java @@ -32,20 +32,22 @@ import org.springframework.util.StringUtils; * * @author Phillip Webb * @author Venil Noronha + * @author Eddú Meléndez */ class ActiveMQConnectionFactoryFactory { - private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; - private final ActiveMQProperties properties; private final List factoryCustomizers; + private final ActiveMQConnectionDetails connectionDetails; + ActiveMQConnectionFactoryFactory(ActiveMQProperties properties, - List factoryCustomizers) { + List factoryCustomizers, ActiveMQConnectionDetails connectionDetails) { Assert.notNull(properties, "Properties must not be null"); this.properties = properties; this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList(); + this.connectionDetails = connectionDetails; } T createConnectionFactory(Class factoryClass) { @@ -79,9 +81,9 @@ class ActiveMQConnectionFactoryFactory { private T createConnectionFactoryInstance(Class factoryClass) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { - String brokerUrl = determineBrokerUrl(); - String user = this.properties.getUser(); - String password = this.properties.getPassword(); + String brokerUrl = this.connectionDetails.getBrokerUrl(); + String user = this.connectionDetails.getUser(); + String password = this.connectionDetails.getPassword(); if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) { return factoryClass.getConstructor(String.class, String.class, String.class) .newInstance(user, password, brokerUrl); @@ -95,11 +97,4 @@ class ActiveMQConnectionFactoryFactory { } } - String determineBrokerUrl() { - if (this.properties.getBrokerUrl() != null) { - return this.properties.getBrokerUrl(); - } - return DEFAULT_NETWORK_BROKER_URL; - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java index 48b72e8893..2877479a08 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java @@ -31,11 +31,14 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty; * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Venil Noronha + * @author Eddú Meléndez * @since 3.1.0 */ @ConfigurationProperties(prefix = "spring.activemq") public class ActiveMQProperties { + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; + /** * URL of the ActiveMQ broker. Auto-generated by default. */ @@ -128,6 +131,13 @@ public class ActiveMQProperties { return this.packages; } + String determineBrokerUrl() { + if (this.brokerUrl != null) { + return this.brokerUrl; + } + return DEFAULT_NETWORK_BROKER_URL; + } + public static class Packages { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java index 4a7cbd214c..6458c5824a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.context.annotation.Primary; * * @author Phillip Webb * @author Aurélien Leboulanger + * @author Eddú Meléndez */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(TransactionManager.class) @@ -46,10 +47,10 @@ class ActiveMQXAConnectionFactoryConfiguration { @Primary @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers, XAConnectionFactoryWrapper wrapper) - throws Exception { + ObjectProvider factoryCustomizers, XAConnectionFactoryWrapper wrapper, + ActiveMQConnectionDetails connectionDetails) throws Exception { ActiveMQXAConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, - factoryCustomizers.orderedStream().toList()) + factoryCustomizers.orderedStream().toList(), connectionDetails) .createConnectionFactory(ActiveMQXAConnectionFactory.class); return wrapper.wrapConnectionFactory(connectionFactory); } @@ -58,8 +59,10 @@ class ActiveMQXAConnectionFactoryConfiguration { @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", matchIfMissing = true) ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties, - ObjectProvider factoryCustomizers) { - return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) + ObjectProvider factoryCustomizers, + ActiveMQConnectionDetails connectionDetails) { + return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList(), + connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java index 0edd4270c7..3e1b0980d8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.mockingDetails; * @author Andy Wilkinson * @author Aurélien Leboulanger * @author Stephane Nicoll + * @author Eddú Meléndez */ class ActiveMQAutoConfigurationTests { @@ -233,6 +234,27 @@ class ActiveMQAutoConfigurationTests { .doesNotHaveBean("jmsConnectionFactory")); } + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner.run((context) -> assertThat(context) + .hasSingleBean(ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails.class)); + } + + @Test + void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CachingConnectionFactory.class)) + .withPropertyValues("spring.activemq.pool.enabled=false", "spring.jms.cache.enabled=false") + .withUserConfiguration(TestConnectionDetailsConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ActiveMQConnectionDetails.class) + .doesNotHaveBean(ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails.class); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(connectionFactory.getBrokerURL()).isEqualTo("tcp://localhost:12345"); + assertThat(connectionFactory.getUserName()).isEqualTo("springuser"); + assertThat(connectionFactory.getPassword()).isEqualTo("spring"); + }); + } + @Configuration(proxyBeanMethods = false) static class EmptyConfiguration { @@ -261,4 +283,29 @@ class ActiveMQAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class TestConnectionDetailsConfiguration { + + @Bean + ActiveMQConnectionDetails activemqConnectionDetails() { + return new ActiveMQConnectionDetails() { + @Override + public String getBrokerUrl() { + return "tcp://localhost:12345"; + } + + @Override + public String getUser() { + return "springuser"; + } + + @Override + public String getPassword() { + return "spring"; + } + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java index d6b66bc123..07d84e900e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java @@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Venil Noronha + * @author Eddú Meléndez */ class ActiveMQPropertiesTests { @@ -38,13 +39,13 @@ class ActiveMQPropertiesTests { @Test void getBrokerUrlIsLocalhostByDefault() { - assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); } @Test void getBrokerUrlUseExplicitBrokerUrl() { this.properties.setBrokerUrl("tcp://activemq.example.com:71717"); - assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); + assertThat(this.properties.determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); } @Test @@ -66,7 +67,8 @@ class ActiveMQPropertiesTests { } private ActiveMQConnectionFactoryFactory createFactory(ActiveMQProperties properties) { - return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList()); + return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList(), + new ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails(properties)); } } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc index 436deb91f7..57ded55126 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/testing.adoc @@ -946,6 +946,9 @@ The following service connection factories are provided in the `spring-boot-test |=== | Connection Details | Matched on +| `ActiveMQConnectionDetails` +| Containers named "symptoma/activemq" + | `CassandraConnectionDetails` | Containers of type `CassandraContainer` diff --git a/spring-boot-project/spring-boot-testcontainers/build.gradle b/spring-boot-project/spring-boot-testcontainers/build.gradle index 9cfb970958..2d20062409 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation("ch.qos.logback:logback-classic") + testImplementation("org.apache.activemq:activemq-client-jakarta") testImplementation("org.assertj:assertj-core") testImplementation("org.awaitility:awaitility") testImplementation("org.influxdb:influxdb-java") @@ -45,6 +46,7 @@ dependencies { testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.springframework:spring-core-test") + testImplementation("org.springframework:spring-jms") testImplementation("org.springframework:spring-r2dbc") testImplementation("org.springframework.amqp:spring-rabbit") testImplementation("org.springframework.kafka:spring-kafka") diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java new file mode 100644 index 0000000000..a81469645a --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 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.testcontainers.service.connection.activemq; + +import java.util.Map; + +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails; +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 ActiveMQConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} + * using the {@code "symptoma/activemq"} image. + * + * @author Eddú Meléndez + */ +class ActiveMQContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory> { + + ActiveMQContainerConnectionDetailsFactory() { + super("symptoma/activemq"); + } + + @Override + protected ActiveMQConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + return new ActiveMQContainerConnectionDetails(source); + } + + private static final class ActiveMQContainerConnectionDetails extends ContainerConnectionDetails + implements ActiveMQConnectionDetails { + + private final String brokerUrl; + + private final Map envVars; + + private ActiveMQContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + this.brokerUrl = "tcp://" + source.getContainer().getHost() + ":" + + source.getContainer().getFirstMappedPort(); + this.envVars = source.getContainer().getEnvMap(); + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getUser() { + return this.envVars.get("ACTIVEMQ_USERNAME"); + } + + @Override + public String getPassword() { + return this.envVars.get("ACTIVEMQ_PASSWORD"); + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java new file mode 100644 index 0000000000..0981f1bf9b --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 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. + */ + +/** + * Support for testcontainers ActiveMQ service connections. + */ +package org.springframework.boot.testcontainers.service.connection.activemq; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index 2cfe37359c..f26cc7230f 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -8,6 +8,7 @@ org.springframework.boot.testcontainers.service.connection.ServiceConnectionCont # Connection Details Factories org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ +org.springframework.boot.testcontainers.service.connection.activemq.ActiveMQContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.amqp.RabbitContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.cassandra.CassandraContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseContainerConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 0000000000..647b4861d0 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ActiveMQContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2023 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.testcontainers.service.connection.activemq; + +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.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.testcontainers.ActiveMQContainer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.annotation.JmsListener; +import org.springframework.jms.core.JmsMessagingTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class ActiveMQContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final ActiveMQContainer activemq = new ActiveMQContainer(); + + @Autowired + private JmsMessagingTemplate jmsTemplate; + + @Autowired + private TestListener listener; + + @Test + void connectionCanBeMadeToActiveMQContainer() { + this.jmsTemplate.convertAndSend("sample.queue", "message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)) + .untilAsserted(() -> assertThat(this.listener.messages).containsExactly("message")); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration({ ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class }) + static class TestConfiguration { + + @Bean + TestListener testListener() { + return new TestListener(); + } + + } + + static class TestListener { + + private final List messages = new ArrayList<>(); + + @JmsListener(destination = "sample.queue") + void processMessage(String message) { + this.messages.add(message); + } + + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle index 5ad092f62c..f250b84c07 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle @@ -8,6 +8,8 @@ description = "Spring Boot Actuator ActiveMQ smoke test" dependencies { implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-activemq")) + testImplementation("org.awaitility:awaitility") + testImplementation("org.testcontainers:testcontainers") testImplementation("org.testcontainers:junit-jupiter") testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) testImplementation(project(":spring-boot-project:spring-boot-testcontainers")) diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java index 7637a28f88..9d68eda78d 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java @@ -16,6 +16,9 @@ package smoketest.activemq; +import java.time.Duration; + +import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.junit.jupiter.Container; @@ -25,9 +28,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.boot.testsupport.testcontainers.ActiveMQContainer; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; import static org.assertj.core.api.Assertions.assertThat; @@ -43,21 +45,16 @@ import static org.assertj.core.api.Assertions.assertThat; class SampleActiveMqTests { @Container + @ServiceConnection private static final ActiveMQContainer container = new ActiveMQContainer(); - @DynamicPropertySource - static void activeMqProperties(DynamicPropertyRegistry registry) { - registry.add("spring.activemq.broker-url", container::getBrokerUrl); - } - @Autowired private Producer producer; @Test - void sendSimpleMessage(CapturedOutput output) throws InterruptedException { + void sendSimpleMessage(CapturedOutput output) { this.producer.send("Test message"); - Thread.sleep(1000L); - assertThat(output).contains("Test message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(output).contains("Test message")); } }