Merge pull request #35080 from eddumelendez

* pr/35080:
  Polish "Add service connection for Testcontainers ActiveMQ"
  Add service connection for Testcontainers ActiveMQ

Closes gh-35080
pull/36503/head
Stephane Nicoll 1 year ago
commit 83abaf3b49

@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.JmsProperties; import org.springframework.boot.autoconfigure.jms.JmsProperties;
import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
/** /**
@ -35,6 +36,7 @@ import org.springframework.context.annotation.Import;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Eddú Meléndez
* @since 3.1.0 * @since 3.1.0
*/ */
@AutoConfiguration(before = JmsAutoConfiguration.class, after = JndiConnectionFactoryAutoConfiguration.class) @AutoConfiguration(before = JmsAutoConfiguration.class, after = JndiConnectionFactoryAutoConfiguration.class)
@ -44,4 +46,38 @@ import org.springframework.context.annotation.Import;
@Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class }) @Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class })
public class ActiveMQAutoConfiguration { 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();
}
}
} }

@ -0,0 +1,48 @@
/*
* 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
* @author Stephane Nicoll
* @since 3.2.0
*/
public interface ActiveMQConnectionDetails extends ConnectionDetails {
/**
* Broker URL to use.
* @return the url of the broker
*/
String getBrokerUrl();
/**
* Login user to authenticate to the broker.
* @return the login user to authenticate to the broker or {@code null}
*/
String getUser();
/**
* Login to authenticate against the broker.
* @return the login to authenticate against the broker or {@code null}
*/
String getPassword();
}

@ -39,6 +39,7 @@ import org.springframework.jms.connection.CachingConnectionFactory;
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Aurélien Leboulanger * @author Aurélien Leboulanger
* @author Eddú Meléndez
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ConnectionFactory.class) @ConditionalOnMissingBean(ConnectionFactory.class)
@ -52,13 +53,16 @@ class ActiveMQConnectionFactoryConfiguration {
@Bean @Bean
@ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false")
ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) { ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers,
return createJmsConnectionFactory(properties, factoryCustomizers); ActiveMQConnectionDetails connectionDetails) {
return createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails);
} }
private static ActiveMQConnectionFactory createJmsConnectionFactory(ActiveMQProperties properties, private static ActiveMQConnectionFactory createJmsConnectionFactory(ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) { ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers,
return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) ActiveMQConnectionDetails connectionDetails) {
return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList(),
connectionDetails)
.createConnectionFactory(ActiveMQConnectionFactory.class); .createConnectionFactory(ActiveMQConnectionFactory.class);
} }
@ -70,10 +74,11 @@ class ActiveMQConnectionFactoryConfiguration {
@Bean @Bean
CachingConnectionFactory jmsConnectionFactory(JmsProperties jmsProperties, ActiveMQProperties properties, CachingConnectionFactory jmsConnectionFactory(JmsProperties jmsProperties, ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) { ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers,
ActiveMQConnectionDetails connectionDetails) {
JmsProperties.Cache cacheProperties = jmsProperties.getCache(); JmsProperties.Cache cacheProperties = jmsProperties.getCache();
CachingConnectionFactory connectionFactory = new CachingConnectionFactory( CachingConnectionFactory connectionFactory = new CachingConnectionFactory(
createJmsConnectionFactory(properties, factoryCustomizers)); createJmsConnectionFactory(properties, factoryCustomizers, connectionDetails));
connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); connectionFactory.setCacheConsumers(cacheProperties.isConsumers());
connectionFactory.setCacheProducers(cacheProperties.isProducers()); connectionFactory.setCacheProducers(cacheProperties.isProducers());
connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize());
@ -91,9 +96,10 @@ class ActiveMQConnectionFactoryConfiguration {
@Bean(destroyMethod = "stop") @Bean(destroyMethod = "stop")
@ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true")
JmsPoolConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, JmsPoolConnectionFactory jmsConnectionFactory(ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) { ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers,
ActiveMQConnectionDetails connectionDetails) {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties,
factoryCustomizers.orderedStream().toList()) factoryCustomizers.orderedStream().toList(), connectionDetails)
.createConnectionFactory(ActiveMQConnectionFactory.class); .createConnectionFactory(ActiveMQConnectionFactory.class);
return new JmsPoolConnectionFactoryFactory(properties.getPool()) return new JmsPoolConnectionFactoryFactory(properties.getPool())
.createPooledConnectionFactory(connectionFactory); .createPooledConnectionFactory(connectionFactory);

@ -32,20 +32,22 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Venil Noronha * @author Venil Noronha
* @author Eddú Meléndez
*/ */
class ActiveMQConnectionFactoryFactory { class ActiveMQConnectionFactoryFactory {
private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
private final ActiveMQProperties properties; private final ActiveMQProperties properties;
private final List<ActiveMQConnectionFactoryCustomizer> factoryCustomizers; private final List<ActiveMQConnectionFactoryCustomizer> factoryCustomizers;
private final ActiveMQConnectionDetails connectionDetails;
ActiveMQConnectionFactoryFactory(ActiveMQProperties properties, ActiveMQConnectionFactoryFactory(ActiveMQProperties properties,
List<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) { List<ActiveMQConnectionFactoryCustomizer> factoryCustomizers, ActiveMQConnectionDetails connectionDetails) {
Assert.notNull(properties, "Properties must not be null"); Assert.notNull(properties, "Properties must not be null");
this.properties = properties; this.properties = properties;
this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList(); this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList();
this.connectionDetails = connectionDetails;
} }
<T extends ActiveMQConnectionFactory> T createConnectionFactory(Class<T> factoryClass) { <T extends ActiveMQConnectionFactory> T createConnectionFactory(Class<T> factoryClass) {
@ -79,9 +81,9 @@ class ActiveMQConnectionFactoryFactory {
private <T extends ActiveMQConnectionFactory> T createConnectionFactoryInstance(Class<T> factoryClass) private <T extends ActiveMQConnectionFactory> T createConnectionFactoryInstance(Class<T> factoryClass)
throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
String brokerUrl = determineBrokerUrl(); String brokerUrl = this.connectionDetails.getBrokerUrl();
String user = this.properties.getUser(); String user = this.connectionDetails.getUser();
String password = this.properties.getPassword(); String password = this.connectionDetails.getPassword();
if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) { if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) {
return factoryClass.getConstructor(String.class, String.class, String.class) return factoryClass.getConstructor(String.class, String.class, String.class)
.newInstance(user, password, brokerUrl); .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;
}
} }

@ -31,11 +31,14 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Aurélien Leboulanger * @author Aurélien Leboulanger
* @author Venil Noronha * @author Venil Noronha
* @author Eddú Meléndez
* @since 3.1.0 * @since 3.1.0
*/ */
@ConfigurationProperties(prefix = "spring.activemq") @ConfigurationProperties(prefix = "spring.activemq")
public class ActiveMQProperties { public class ActiveMQProperties {
private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
/** /**
* URL of the ActiveMQ broker. Auto-generated by default. * URL of the ActiveMQ broker. Auto-generated by default.
*/ */
@ -128,6 +131,13 @@ public class ActiveMQProperties {
return this.packages; return this.packages;
} }
String determineBrokerUrl() {
if (this.brokerUrl != null) {
return this.brokerUrl;
}
return DEFAULT_NETWORK_BROKER_URL;
}
public static class Packages { public static class Packages {
/** /**

@ -36,6 +36,7 @@ import org.springframework.context.annotation.Primary;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Aurélien Leboulanger * @author Aurélien Leboulanger
* @author Eddú Meléndez
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(TransactionManager.class) @ConditionalOnClass(TransactionManager.class)
@ -46,10 +47,10 @@ class ActiveMQXAConnectionFactoryConfiguration {
@Primary @Primary
@Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" })
ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers, XAConnectionFactoryWrapper wrapper) ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers, XAConnectionFactoryWrapper wrapper,
throws Exception { ActiveMQConnectionDetails connectionDetails) throws Exception {
ActiveMQXAConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, ActiveMQXAConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties,
factoryCustomizers.orderedStream().toList()) factoryCustomizers.orderedStream().toList(), connectionDetails)
.createConnectionFactory(ActiveMQXAConnectionFactory.class); .createConnectionFactory(ActiveMQXAConnectionFactory.class);
return wrapper.wrapConnectionFactory(connectionFactory); return wrapper.wrapConnectionFactory(connectionFactory);
} }
@ -58,8 +59,10 @@ class ActiveMQXAConnectionFactoryConfiguration {
@ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false",
matchIfMissing = true) matchIfMissing = true)
ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties, ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties,
ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers) { ObjectProvider<ActiveMQConnectionFactoryCustomizer> factoryCustomizers,
return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList()) ActiveMQConnectionDetails connectionDetails) {
return new ActiveMQConnectionFactoryFactory(properties, factoryCustomizers.orderedStream().toList(),
connectionDetails)
.createConnectionFactory(ActiveMQConnectionFactory.class); .createConnectionFactory(ActiveMQConnectionFactory.class);
} }

@ -40,6 +40,7 @@ import static org.mockito.Mockito.mockingDetails;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Aurélien Leboulanger * @author Aurélien Leboulanger
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Eddú Meléndez
*/ */
class ActiveMQAutoConfigurationTests { class ActiveMQAutoConfigurationTests {
@ -233,6 +234,27 @@ class ActiveMQAutoConfigurationTests {
.doesNotHaveBean("jmsConnectionFactory")); .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) @Configuration(proxyBeanMethods = false)
static class EmptyConfiguration { 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";
}
};
}
}
} }

@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Aurélien Leboulanger * @author Aurélien Leboulanger
* @author Venil Noronha * @author Venil Noronha
* @author Eddú Meléndez
*/ */
class ActiveMQPropertiesTests { class ActiveMQPropertiesTests {
@ -38,13 +39,13 @@ class ActiveMQPropertiesTests {
@Test @Test
void getBrokerUrlIsLocalhostByDefault() { void getBrokerUrlIsLocalhostByDefault() {
assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); assertThat(this.properties.determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL);
} }
@Test @Test
void getBrokerUrlUseExplicitBrokerUrl() { void getBrokerUrlUseExplicitBrokerUrl() {
this.properties.setBrokerUrl("tcp://activemq.example.com:71717"); 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 @Test
@ -66,7 +67,8 @@ class ActiveMQPropertiesTests {
} }
private ActiveMQConnectionFactoryFactory createFactory(ActiveMQProperties properties) { private ActiveMQConnectionFactoryFactory createFactory(ActiveMQProperties properties) {
return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList()); return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList(),
new ActiveMQAutoConfiguration.PropertiesActiveMQConnectionDetails(properties));
} }
} }

@ -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.docker.compose.service.connection.activemq;
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails;
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;
/**
* {@link DockerComposeConnectionDetailsFactory} to create
* {@link ActiveMQConnectionDetails} for an {@code activemq} service.
*
* @author Stephane Nicoll
*/
class ActiveMQDockerComposeConnectionDetailsFactory
extends DockerComposeConnectionDetailsFactory<ActiveMQConnectionDetails> {
private static final int ACTIVEMQ_PORT = 61616;
protected ActiveMQDockerComposeConnectionDetailsFactory() {
super("symptoma/activemq");
}
@Override
protected ActiveMQConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) {
return new ActiveMQDockerComposeConnectionDetails(source.getRunningService());
}
/**
* {@link RabbitConnectionDetails} backed by a {@code rabbitmq}
* {@link RunningService}.
*/
static class ActiveMQDockerComposeConnectionDetails extends DockerComposeConnectionDetails
implements ActiveMQConnectionDetails {
private final ActiveMQEnvironment environment;
private final String brokerUrl;
protected ActiveMQDockerComposeConnectionDetails(RunningService service) {
super(service);
this.environment = new ActiveMQEnvironment(service.env());
this.brokerUrl = "tcp://" + service.host() + ":" + service.ports().get(ACTIVEMQ_PORT);
}
@Override
public String getBrokerUrl() {
return this.brokerUrl;
}
@Override
public String getUser() {
return this.environment.getUser();
}
@Override
public String getPassword() {
return this.environment.getPassword();
}
}
}

@ -0,0 +1,45 @@
/*
* 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.docker.compose.service.connection.activemq;
import java.util.Map;
/**
* ActiveMQ environment details.
*
* @author Stephane Nicoll
*/
class ActiveMQEnvironment {
private final String user;
private final String password;
ActiveMQEnvironment(Map<String, String> env) {
this.user = env.get("ACTIVEMQ_USERNAME");
this.password = env.get("ACTIVEMQ_PASSWORD");
}
String getUser() {
return this.user;
}
String getPassword() {
return this.password;
}
}

@ -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.
*/
/**
* Auto-configuration for docker compose ActiveMQ service connections.
*/
package org.springframework.boot.docker.compose.service.connection.activemq;

@ -5,6 +5,7 @@ org.springframework.boot.docker.compose.service.connection.DockerComposeServiceC
# Connection Details Factories # Connection Details Factories
org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\
org.springframework.boot.docker.compose.service.connection.activemq.ActiveMQDockerComposeConnectionDetailsFactory,\
org.springframework.boot.docker.compose.service.connection.cassandra.CassandraDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.cassandra.CassandraDockerComposeConnectionDetailsFactory,\
org.springframework.boot.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\
org.springframework.boot.docker.compose.service.connection.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\

@ -0,0 +1,45 @@
/*
* 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.docker.compose.service.connection.activemq;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionDetails;
import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ActiveMQDockerComposeConnectionDetailsFactory}.
*
* @author Stephane Nicoll
*/
class ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests {
ActiveMQDockerComposeConnectionDetailsFactoryIntegrationTests() {
super("activemq-compose.yaml");
}
@Test
void runCreatesConnectionDetails() {
ActiveMQConnectionDetails connectionDetails = run(ActiveMQConnectionDetails.class);
assertThat(connectionDetails.getBrokerUrl()).isNotNull().startsWith("tcp://");
assertThat(connectionDetails.getUser()).isEqualTo("root");
assertThat(connectionDetails.getPassword()).isEqualTo("secret");
}
}

@ -0,0 +1,57 @@
/*
* 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.docker.compose.service.connection.activemq;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ActiveMQEnvironment}.
*
* @author Stephane Nicoll
*/
class ActiveMQEnvironmentTests {
@Test
void getUserWhenHasNoActiveMqUser() {
ActiveMQEnvironment environment = new ActiveMQEnvironment(Collections.emptyMap());
assertThat(environment.getUser()).isNull();
}
@Test
void getUserWhenHasActiveMqUser() {
ActiveMQEnvironment environment = new ActiveMQEnvironment(Map.of("ACTIVEMQ_USERNAME", "me"));
assertThat(environment.getUser()).isEqualTo("me");
}
@Test
void getPasswordWhenHasNoActiveMqPassword() {
ActiveMQEnvironment environment = new ActiveMQEnvironment(Collections.emptyMap());
assertThat(environment.getPassword()).isNull();
}
@Test
void getPasswordWhenHasActiveMqPassword() {
ActiveMQEnvironment environment = new ActiveMQEnvironment(Map.of("ACTIVEMQ_PASSWORD", "secret"));
assertThat(environment.getPassword()).isEqualTo("secret");
}
}

@ -0,0 +1,8 @@
services:
activemq:
image: 'symptoma/activemq:5.18.0'
ports:
- '61616'
environment:
ACTIVEMQ_USERNAME: 'root'
ACTIVEMQ_PASSWORD: 'secret'

@ -58,6 +58,9 @@ The following service connections are currently supported:
|=== |===
| Connection Details | Matched on | Connection Details | Matched on
| `ActiveMQConnectionDetails`
| Containers named "symptoma/activemq"
| `CassandraConnectionDetails` | `CassandraConnectionDetails`
| Containers named "cassandra" | Containers named "cassandra"

@ -946,6 +946,9 @@ The following service connection factories are provided in the `spring-boot-test
|=== |===
| Connection Details | Matched on | Connection Details | Matched on
| `ActiveMQConnectionDetails`
| Containers named "symptoma/activemq"
| `CassandraConnectionDetails` | `CassandraConnectionDetails`
| Containers of type `CassandraContainer` | Containers of type `CassandraContainer`

@ -36,6 +36,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation("ch.qos.logback:logback-classic") testImplementation("ch.qos.logback:logback-classic")
testImplementation("org.apache.activemq:activemq-client-jakarta")
testImplementation("org.assertj:assertj-core") testImplementation("org.assertj:assertj-core")
testImplementation("org.awaitility:awaitility") testImplementation("org.awaitility:awaitility")
testImplementation("org.influxdb:influxdb-java") testImplementation("org.influxdb:influxdb-java")
@ -45,6 +46,7 @@ dependencies {
testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("org.springframework:spring-core-test") testImplementation("org.springframework:spring-core-test")
testImplementation("org.springframework:spring-jms")
testImplementation("org.springframework:spring-r2dbc") testImplementation("org.springframework:spring-r2dbc")
testImplementation("org.springframework.amqp:spring-rabbit") testImplementation("org.springframework.amqp:spring-rabbit")
testImplementation("org.springframework.kafka:spring-kafka") testImplementation("org.springframework.kafka:spring-kafka")

@ -0,0 +1,70 @@
/*
* 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 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<Container<?>, ActiveMQConnectionDetails> {
ActiveMQContainerConnectionDetailsFactory() {
super("symptoma/activemq");
}
@Override
protected ActiveMQConnectionDetails getContainerConnectionDetails(ContainerConnectionSource<Container<?>> source) {
return new ActiveMQContainerConnectionDetails(source);
}
private static final class ActiveMQContainerConnectionDetails extends ContainerConnectionDetails<Container<?>>
implements ActiveMQConnectionDetails {
private ActiveMQContainerConnectionDetails(ContainerConnectionSource<Container<?>> source) {
super(source);
}
@Override
public String getBrokerUrl() {
return "tcp://" + getContainer().getHost() + ":" + getContainer().getFirstMappedPort();
}
@Override
public String getUser() {
return getContainer().getEnvMap().get("ACTIVEMQ_USERNAME");
}
@Override
public String getPassword() {
return getContainer().getEnvMap().get("ACTIVEMQ_PASSWORD");
}
}
}

@ -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;

@ -8,6 +8,7 @@ org.springframework.boot.testcontainers.service.connection.ServiceConnectionCont
# Connection Details Factories # Connection Details Factories
org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ 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.amqp.RabbitContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.cassandra.CassandraContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.cassandra.CassandraContainerConnectionDetailsFactory,\
org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseContainerConnectionDetailsFactory,\

@ -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<String> messages = new ArrayList<>();
@JmsListener(destination = "sample.queue")
void processMessage(String message) {
this.messages.add(message);
}
}
}

@ -8,6 +8,7 @@ description = "Spring Boot Actuator ActiveMQ smoke test"
dependencies { dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-activemq")) implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-activemq"))
testImplementation("org.awaitility:awaitility")
testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.testcontainers:junit-jupiter")
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test")) testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
testImplementation(project(":spring-boot-project:spring-boot-testcontainers")) testImplementation(project(":spring-boot-project:spring-boot-testcontainers"))

@ -16,6 +16,9 @@
package smoketest.activemq; package smoketest.activemq;
import java.time.Duration;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.junit.jupiter.Container; 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.context.SpringBootTest;
import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension; 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.boot.testsupport.testcontainers.ActiveMQContainer;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -43,21 +45,16 @@ import static org.assertj.core.api.Assertions.assertThat;
class SampleActiveMqTests { class SampleActiveMqTests {
@Container @Container
@ServiceConnection
private static final ActiveMQContainer container = new ActiveMQContainer(); private static final ActiveMQContainer container = new ActiveMQContainer();
@DynamicPropertySource
static void activeMqProperties(DynamicPropertyRegistry registry) {
registry.add("spring.activemq.broker-url", container::getBrokerUrl);
}
@Autowired @Autowired
private Producer producer; private Producer producer;
@Test @Test
void sendSimpleMessage(CapturedOutput output) throws InterruptedException { void sendSimpleMessage(CapturedOutput output) {
this.producer.send("Test message"); this.producer.send("Test message");
Thread.sleep(1000L); Awaitility.waitAtMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(output).contains("Test message"));
assertThat(output).contains("Test message");
} }
} }

Loading…
Cancel
Save