diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java index 1087934873b..a4732f0dd58 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -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; /** @@ -48,4 +49,43 @@ import org.springframework.context.annotation.Import; ArtemisConnectionFactoryConfiguration.class }) public class ArtemisAutoConfiguration { + @Bean + @ConditionalOnMissingBean(ArtemisConnectionDetails.class) + ArtemisConnectionDetails artemisConnectionDetails(ArtemisProperties properties) { + return new PropertiesArtemisConnectionDetails(properties); + } + + /** + * Adapts {@link ArtemisProperties} to {@link ArtemisConnectionDetails}. + */ + static class PropertiesArtemisConnectionDetails implements ArtemisConnectionDetails { + + private final ArtemisProperties properties; + + PropertiesArtemisConnectionDetails(ArtemisProperties properties) { + this.properties = properties; + } + + @Override + public ArtemisMode getMode() { + return this.properties.getMode(); + } + + @Override + public String getBrokerUrl() { + return this.properties.getBrokerUrl(); + } + + @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/artemis/ArtemisConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionDetails.java new file mode 100644 index 00000000000..94db3541440 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionDetails.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2024 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.artemis; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a connection to an Artemis service. + * + * @author Eddú Meléndez + * @since 3.3.0 + */ +public interface ArtemisConnectionDetails extends ConnectionDetails { + + ArtemisMode getMode(); + + String getBrokerUrl(); + + String getUser(); + + String getPassword(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java index 98a305be8f3..5f4f879431d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -49,13 +49,14 @@ class ArtemisConnectionFactoryConfiguration { @Bean(name = "jmsConnectionFactory") @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") - ActiveMQConnectionFactory jmsConnectionFactory(ArtemisProperties properties, ListableBeanFactory beanFactory) { - return createJmsConnectionFactory(properties, beanFactory); + ActiveMQConnectionFactory jmsConnectionFactory(ArtemisProperties properties, ListableBeanFactory beanFactory, + ArtemisConnectionDetails connectionDetails) { + return createJmsConnectionFactory(properties, connectionDetails, beanFactory); } private static ActiveMQConnectionFactory createJmsConnectionFactory(ArtemisProperties properties, - ListableBeanFactory beanFactory) { - return new ArtemisConnectionFactoryFactory(beanFactory, properties) + ArtemisConnectionDetails connectionDetails, ListableBeanFactory beanFactory) { + return new ArtemisConnectionFactoryFactory(beanFactory, properties, connectionDetails) .createConnectionFactory(ActiveMQConnectionFactory.class); } @@ -67,10 +68,11 @@ class ArtemisConnectionFactoryConfiguration { @Bean(name = "jmsConnectionFactory") CachingConnectionFactory cachingJmsConnectionFactory(JmsProperties jmsProperties, - ArtemisProperties properties, ListableBeanFactory beanFactory) { + ArtemisProperties properties, ArtemisConnectionDetails connectionDetails, + ListableBeanFactory beanFactory) { JmsProperties.Cache cacheProperties = jmsProperties.getCache(); CachingConnectionFactory connectionFactory = new CachingConnectionFactory( - createJmsConnectionFactory(properties, beanFactory)); + createJmsConnectionFactory(properties, connectionDetails, beanFactory)); connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); connectionFactory.setCacheProducers(cacheProperties.isProducers()); connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); @@ -87,8 +89,10 @@ class ArtemisConnectionFactoryConfiguration { static class PooledConnectionFactoryConfiguration { @Bean(destroyMethod = "stop") - JmsPoolConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties) { - ActiveMQConnectionFactory connectionFactory = new ArtemisConnectionFactoryFactory(beanFactory, properties) + JmsPoolConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, + ArtemisConnectionDetails connectionDetails) { + ActiveMQConnectionFactory connectionFactory = new ArtemisConnectionFactoryFactory(beanFactory, properties, + 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/artemis/ArtemisConnectionFactoryFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java index 344f8ace981..70e74e4b3e4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -47,13 +47,18 @@ class ArtemisConnectionFactoryFactory { private final ArtemisProperties properties; + private final ArtemisConnectionDetails connectionDetails; + private final ListableBeanFactory beanFactory; - ArtemisConnectionFactoryFactory(ListableBeanFactory beanFactory, ArtemisProperties properties) { + ArtemisConnectionFactoryFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, + ArtemisConnectionDetails connectionDetails) { Assert.notNull(beanFactory, "BeanFactory must not be null"); Assert.notNull(properties, "Properties must not be null"); + Assert.notNull(connectionDetails, "ConnectionDetails must not be null"); this.beanFactory = beanFactory; this.properties = properties; + this.connectionDetails = connectionDetails; } T createConnectionFactory(Class factoryClass) { @@ -80,7 +85,7 @@ class ArtemisConnectionFactoryFactory { } private T doCreateConnectionFactory(Class factoryClass) throws Exception { - ArtemisMode mode = this.properties.getMode(); + ArtemisMode mode = this.connectionDetails.getMode(); if (mode == null) { mode = deduceMode(); } @@ -127,17 +132,17 @@ class ArtemisConnectionFactoryFactory { private T createNativeConnectionFactory(Class factoryClass) throws Exception { T connectionFactory = newNativeConnectionFactory(factoryClass); - String user = this.properties.getUser(); + String user = this.connectionDetails.getUser(); if (StringUtils.hasText(user)) { connectionFactory.setUser(user); - connectionFactory.setPassword(this.properties.getPassword()); + connectionFactory.setPassword(this.connectionDetails.getPassword()); } return connectionFactory; } private T newNativeConnectionFactory(Class factoryClass) throws Exception { - String brokerUrl = StringUtils.hasText(this.properties.getBrokerUrl()) ? this.properties.getBrokerUrl() - : DEFAULT_BROKER_URL; + String brokerUrl = StringUtils.hasText(this.connectionDetails.getBrokerUrl()) + ? this.connectionDetails.getBrokerUrl() : DEFAULT_BROKER_URL; Constructor constructor = factoryClass.getConstructor(String.class); return constructor.newInstance(brokerUrl); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java index b296ae14b89..89e7823ca22 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -44,15 +44,16 @@ class ArtemisXAConnectionFactoryConfiguration { @Primary @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) ConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, - XAConnectionFactoryWrapper wrapper) throws Exception { - return wrapper.wrapConnectionFactory(new ArtemisConnectionFactoryFactory(beanFactory, properties) - .createConnectionFactory(ActiveMQXAConnectionFactory.class)); + ArtemisConnectionDetails connectionDetails, XAConnectionFactoryWrapper wrapper) throws Exception { + return wrapper + .wrapConnectionFactory(new ArtemisConnectionFactoryFactory(beanFactory, properties, connectionDetails) + .createConnectionFactory(ActiveMQXAConnectionFactory.class)); } @Bean - ActiveMQXAConnectionFactory nonXaJmsConnectionFactory(ListableBeanFactory beanFactory, - ArtemisProperties properties) { - return new ArtemisConnectionFactoryFactory(beanFactory, properties) + ActiveMQXAConnectionFactory nonXaJmsConnectionFactory(ListableBeanFactory beanFactory, ArtemisProperties properties, + ArtemisConnectionDetails connectionDetails) { + return new ArtemisConnectionFactoryFactory(beanFactory, properties, connectionDetails) .createConnectionFactory(ActiveMQXAConnectionFactory.class); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java index ec0c32031e7..0d1ed76b217 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -46,6 +46,7 @@ import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration.PropertiesArtemisConnectionDetails; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -371,6 +372,27 @@ class ArtemisAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(ActiveMQConnectionFactory.class)); } + @Test + void definesPropertiesBasedConnectionDetailsByDefault() { + this.contextRunner + .run((context) -> assertThat(context).hasSingleBean(PropertiesArtemisConnectionDetails.class)); + } + + @Test + void testConnectionFactoryWithOverridesWhenUsingCustomConnectionDetails() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CachingConnectionFactory.class)) + .withPropertyValues("spring.artemis.pool.enabled=false", "spring.jms.cache.enabled=false") + .withUserConfiguration(TestConnectionDetailsConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ArtemisConnectionDetails.class) + .doesNotHaveBean(PropertiesArtemisConnectionDetails.class); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(connectionFactory.toURI().toString()).startsWith("tcp://localhost:12345"); + assertThat(connectionFactory.getUser()).isEqualTo("springuser"); + assertThat(connectionFactory.getPassword()).isEqualTo("spring"); + }); + } + private ConnectionFactory getConnectionFactory(AssertableApplicationContext context) { assertThat(context).hasSingleBean(ConnectionFactory.class).hasBean("jmsConnectionFactory"); ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); @@ -496,4 +518,36 @@ class ArtemisAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class TestConnectionDetailsConfiguration { + + @Bean + ArtemisConnectionDetails activemqConnectionDetails() { + return new ArtemisConnectionDetails() { + + @Override + public ArtemisMode getMode() { + return ArtemisMode.NATIVE; + } + + @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-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactory.java new file mode 100644 index 00000000000..c2ceeb0c643 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2024 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.jms.artemis.ArtemisConnectionDetails; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisMode; +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 ArtemisConnectionDetails} for an {@code artemis} service. + * + * @author Eddú Meléndez + */ +class ArtemisDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int ACTIVEMQ_PORT = 61616; + + protected ArtemisDockerComposeConnectionDetailsFactory() { + super("apache/activemq-classic"); + } + + @Override + protected ArtemisConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new ArtemisDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link ArtemisConnectionDetails} backed by a {@code artemis} + * {@link RunningService}. + */ + static class ArtemisDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements ArtemisConnectionDetails { + + private final ArtemisEnvironment environment; + + private final String brokerUrl; + + protected ArtemisDockerComposeConnectionDetails(RunningService service) { + super(service); + this.environment = new ArtemisEnvironment(service.env()); + this.brokerUrl = "tcp://" + service.host() + ":" + service.ports().get(ACTIVEMQ_PORT); + } + + @Override + public ArtemisMode getMode() { + return ArtemisMode.NATIVE; + } + + @Override + public String getBrokerUrl() { + return this.brokerUrl; + } + + @Override + public String getUser() { + return this.environment.getUser(); + } + + @Override + public String getPassword() { + return this.environment.getPassword(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironment.java new file mode 100644 index 00000000000..078f216c258 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironment.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2024 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; + +/** + * Artemis environment details. + * + * @author Eddú Meléndez + */ +class ArtemisEnvironment { + + private final String user; + + private final String password; + + ArtemisEnvironment(Map env) { + this.user = env.get("ACTIVEMQ_CONNECTION_USER"); + this.password = env.get("ACTIVEMQ_CONNECTION_PASSWORD"); + } + + String getUser() { + return this.user; + } + + String getPassword() { + return this.password; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index 3ffd311e363..fffc5722e0c 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -6,6 +6,7 @@ org.springframework.boot.docker.compose.service.connection.DockerComposeServiceC # Connection Details Factories org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ org.springframework.boot.docker.compose.service.connection.activemq.ActiveMQDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.activemq.ArtemisDockerComposeConnectionDetailsFactory,\ 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.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 00000000000..2dd9413b7dc --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2024 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.artemis.ArtemisConnectionDetails; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisMode; +import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; +import org.springframework.boot.testsupport.testcontainers.DockerImageNames; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ArtemisDockerComposeConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +class ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { + + ArtemisDockerComposeConnectionDetailsFactoryIntegrationTests() { + super("artemis-compose.yaml", DockerImageNames.artemis()); + } + + @Test + void runCreatesConnectionDetails() { + ArtemisConnectionDetails connectionDetails = run(ArtemisConnectionDetails.class); + assertThat(connectionDetails.getMode()).isEqualTo(ArtemisMode.NATIVE); + assertThat(connectionDetails.getBrokerUrl()).isNotNull().startsWith("tcp://"); + assertThat(connectionDetails.getUser()).isEqualTo("root"); + assertThat(connectionDetails.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironmentTests.java new file mode 100644 index 00000000000..d3781014b0f --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/activemq/ArtemisEnvironmentTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2024 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 ArtemisEnvironment}. + * + * @author Eddú Meléndez + */ +class ArtemisEnvironmentTests { + + @Test + void getUserWhenHasNoActiveMqUser() { + ArtemisEnvironment environment = new ArtemisEnvironment(Collections.emptyMap()); + assertThat(environment.getUser()).isNull(); + } + + @Test + void getUserWhenHasActiveMqUser() { + ArtemisEnvironment environment = new ArtemisEnvironment(Map.of("ACTIVEMQ_CONNECTION_USER", "me")); + assertThat(environment.getUser()).isEqualTo("me"); + } + + @Test + void getPasswordWhenHasNoActiveMqPassword() { + ArtemisEnvironment environment = new ArtemisEnvironment(Collections.emptyMap()); + assertThat(environment.getPassword()).isNull(); + } + + @Test + void getPasswordWhenHasActiveMqPassword() { + ArtemisEnvironment environment = new ArtemisEnvironment(Map.of("ACTIVEMQ_CONNECTION_PASSWORD", "secret")); + assertThat(environment.getPassword()).isEqualTo("secret"); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/activemq/artemis-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/activemq/artemis-compose.yaml new file mode 100644 index 00000000000..ffdd8c6b78a --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/activemq/artemis-compose.yaml @@ -0,0 +1,8 @@ +services: + artemis: + image: '{imageName}' + ports: + - '61616' + environment: + ACTIVEMQ_CONNECTION_USER: 'root' + ACTIVEMQ_CONNECTION_PASSWORD: 'secret' 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 24bcf0d1ee4..c86faa72b03 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 @@ -983,6 +983,9 @@ The following service connection factories are provided in the `spring-boot-test | `ActiveMQConnectionDetails` | Containers named "symptoma/activemq" +| `ArtemisConnectionDetails` +| Containers of type `ArtemisContainer` + | `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 28b62213219..f719fd8b5c2 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -17,6 +17,7 @@ dependencies { optional("org.springframework:spring-test") optional("org.springframework.data:spring-data-mongodb") optional("org.springframework.data:spring-data-neo4j") + optional("org.testcontainers:activemq") optional("org.testcontainers:cassandra") optional("org.testcontainers:couchbase") optional("org.testcontainers:elasticsearch") @@ -44,6 +45,9 @@ dependencies { exclude group: "commons-logging", module: "commons-logging" } testImplementation("org.apache.activemq:activemq-client-jakarta") + testImplementation("org.apache.activemq:artemis-jakarta-client") { + exclude group: "commons-logging", module: "commons-logging" + } testImplementation("org.assertj:assertj-core") testImplementation("org.awaitility:awaitility") testImplementation("org.influxdb:influxdb-java") diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactory.java new file mode 100644 index 00000000000..798ff7d97f8 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactory.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 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.activemq.ArtemisContainer; + +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConnectionDetails; +import org.springframework.boot.autoconfigure.jms.artemis.ArtemisMode; +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 ArtemisConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link ArtemisContainer}. + * + * @author Eddú Meléndez + */ +class ArtemisContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory { + + @Override + protected ArtemisConnectionDetails getContainerConnectionDetails( + ContainerConnectionSource source) { + return new ArtemisContainerConnectionDetails(source); + } + + private static final class ArtemisContainerConnectionDetails extends ContainerConnectionDetails + implements ArtemisConnectionDetails { + + private ArtemisContainerConnectionDetails(ContainerConnectionSource source) { + super(source); + } + + @Override + public ArtemisMode getMode() { + return ArtemisMode.NATIVE; + } + + @Override + public String getBrokerUrl() { + return getContainer().getBrokerUrl(); + } + + @Override + public String getUser() { + return getContainer().getUser(); + } + + @Override + public String getPassword() { + return getContainer().getPassword(); + } + + } + +} 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 5b8bda347fc..71721a3f32d 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 @@ -9,6 +9,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.activemq.ArtemisContainerConnectionDetailsFactory,\ 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/ArtemisContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 00000000000..4c695936bbd --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/activemq/ArtemisContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2024 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.activemq.ArtemisContainer; +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.artemis.ArtemisAutoConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.testcontainers.DockerImageNames; +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 ArtemisContainerConnectionDetailsFactory}. + * + * @author Eddú Meléndez + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class ArtemisContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final ArtemisContainer artemis = new ArtemisContainer(DockerImageNames.artemis()); + + @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({ ArtemisAutoConfiguration.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-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java index 81a5673d8fe..7f9d598095c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/DockerImageNames.java @@ -30,6 +30,8 @@ public final class DockerImageNames { private static final String ACTIVE_MQ_VERSION = "5.18.0"; + private static final String ARTEMIS_VERSION = "2.31.2"; + private static final String CASSANDRA_VERSION = "3.11.10"; private static final String COUCHBASE_VERSION = "7.1.4"; @@ -81,6 +83,14 @@ public final class DockerImageNames { return DockerImageName.parse("symptoma/activemq").withTag(ACTIVE_MQ_VERSION); } + /** + * Return a {@link DockerImageName} suitable for running Artemis. + * @return a docker image name for running artemis + */ + public static DockerImageName artemis() { + return DockerImageName.parse("apache/activemq-artemis").withTag(ARTEMIS_VERSION); + } + /** * Return a {@link DockerImageName} suitable for running Cassandra. * @return a docker image name for running cassandra