From 3e9908a797aada598599ecbf441758f36ab79716 Mon Sep 17 00:00:00 2001 From: Martin BENDA Date: Thu, 30 Mar 2023 08:38:36 +0200 Subject: [PATCH 1/2] Reintroduce support for ActiveMQ See gh-35048 --- .../build.gradle | 1 + ...JmsHealthContributorAutoConfiguration.java | 3 +- .../spring-boot-autoconfigure/build.gradle | 1 + .../activemq/ActiveMQAutoConfiguration.java | 52 ++++ ...ctiveMQConnectionFactoryConfiguration.java | 107 +++++++ .../ActiveMQConnectionFactoryCustomizer.java | 37 +++ .../ActiveMQConnectionFactoryFactory.java | 105 +++++++ .../jms/activemq/ActiveMQProperties.java | 162 +++++++++++ ...iveMQXAConnectionFactoryConfiguration.java | 69 +++++ .../jms/activemq/package-info.java | 20 ++ .../transaction/jta/JtaAutoConfiguration.java | 5 +- ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../ActiveMQAutoConfigurationTests.java | 264 ++++++++++++++++++ .../jms/activemq/ActiveMQPropertiesTests.java | 72 +++++ .../spring-boot-dependencies/build.gradle | 9 + .../src/docs/asciidoc/messaging/jms.adoc | 49 ++++ .../spring-boot-starter-activemq/build.gradle | 11 + 17 files changed, 965 insertions(+), 3 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryCustomizer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java create mode 100644 spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle index 08a5bc58d6a..89b07037dea 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle @@ -77,6 +77,7 @@ dependencies { optional("jakarta.persistence:jakarta.persistence-api") optional("jakarta.servlet:jakarta.servlet-api") optional("javax.cache:cache-api") + optional("org.apache.activemq:activemq-client-jakarta") optional("org.apache.commons:commons-dbcp2") { exclude group: "commons-logging", module: "commons-logging" } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java index 53490552eb1..ee7b693861d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration; import org.springframework.context.annotation.Bean; @@ -38,7 +39,7 @@ import org.springframework.context.annotation.Bean; * @author Stephane Nicoll * @since 2.0.0 */ -@AutoConfiguration(after = ArtemisAutoConfiguration.class) +@AutoConfiguration(after = { ActiveMQAutoConfiguration.class, ArtemisAutoConfiguration.class }) @ConditionalOnClass(ConnectionFactory.class) @ConditionalOnBean(ConnectionFactory.class) @ConditionalOnEnabledHealthIndicator("jms") diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 0d264980904..5d1303089bb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -48,6 +48,7 @@ dependencies { optional("jakarta.ws.rs:jakarta.ws.rs-api") optional("javax.cache:cache-api") optional("javax.money:money-api") + optional("org.apache.activemq:activemq-client-jakarta") optional("org.apache.activemq:artemis-jakarta-client") { exclude group: "commons-logging", module: "commons-logging" } 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 new file mode 100644 index 00000000000..769a98616f9 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2019 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 jakarta.jms.ConnectionFactory; +import org.apache.activemq.ActiveMQConnectionFactory; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.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.Configuration; +import org.springframework.context.annotation.Import; + +/** + * {@link EnableAutoConfiguration Auto-configuration} to integrate with an ActiveMQ + * broker. Validates that the classpath contain the necessary classes before starting an + * embedded broker. + * + * @author Stephane Nicoll + * @author Phillip Webb + * @since 1.1.0 + */ +@Configuration(proxyBeanMethods = false) +@AutoConfigureBefore(JmsAutoConfiguration.class) +@AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class }) +@ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class }) +@ConditionalOnMissingBean(ConnectionFactory.class) +@EnableConfigurationProperties({ ActiveMQProperties.class, JmsProperties.class }) +@Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class }) +public class ActiveMQAutoConfiguration { + +} 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 new file mode 100644 index 00000000000..901b4c033ac --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java @@ -0,0 +1,107 @@ +/* + * Copyright 2012-2020 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 java.util.stream.Collectors; + +import jakarta.jms.ConnectionFactory; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.commons.pool2.PooledObject; +import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.jms.JmsPoolConnectionFactoryFactory; +import org.springframework.boot.autoconfigure.jms.JmsProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.connection.CachingConnectionFactory; + +/** + * Configuration for ActiveMQ {@link ConnectionFactory}. + * + * @author Greg Turnquist + * @author Stephane Nicoll + * @author Phillip Webb + * @author Andy Wilkinson + * @author Aurélien Leboulanger + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnMissingBean(ConnectionFactory.class) +class ActiveMQConnectionFactoryConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", + matchIfMissing = true) + static class SimpleConnectionFactoryConfiguration { + + @Bean + @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "false") + ActiveMQConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, + ObjectProvider factoryCustomizers) { + return createJmsConnectionFactory(properties, factoryCustomizers); + } + + private static ActiveMQConnectionFactory createJmsConnectionFactory(ActiveMQProperties properties, + ObjectProvider factoryCustomizers) { + return new ActiveMQConnectionFactoryFactory(properties, + factoryCustomizers.orderedStream().collect(Collectors.toList())) + .createConnectionFactory(ActiveMQConnectionFactory.class); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(CachingConnectionFactory.class) + @ConditionalOnProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = "true", + matchIfMissing = true) + static class CachingConnectionFactoryConfiguration { + + @Bean + CachingConnectionFactory jmsConnectionFactory(JmsProperties jmsProperties, ActiveMQProperties properties, + ObjectProvider factoryCustomizers) { + JmsProperties.Cache cacheProperties = jmsProperties.getCache(); + CachingConnectionFactory connectionFactory = new CachingConnectionFactory( + createJmsConnectionFactory(properties, factoryCustomizers)); + connectionFactory.setCacheConsumers(cacheProperties.isConsumers()); + connectionFactory.setCacheProducers(cacheProperties.isProducers()); + connectionFactory.setSessionCacheSize(cacheProperties.getSessionCacheSize()); + return connectionFactory; + } + + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass({ JmsPoolConnectionFactory.class, PooledObject.class }) + static class PooledConnectionFactoryConfiguration { + + @Bean(destroyMethod = "stop") + @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "true") + JmsPoolConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, + ObjectProvider factoryCustomizers) { + ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, + factoryCustomizers.orderedStream().collect(Collectors.toList())) + .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/ActiveMQConnectionFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryCustomizer.java new file mode 100644 index 00000000000..1354b505a2c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryCustomizer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2019 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.apache.activemq.ActiveMQConnectionFactory; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link ActiveMQConnectionFactory} whilst retaining default auto-configuration. + * + * @author Stephane Nicoll + * @since 1.5.5 + */ +@FunctionalInterface +public interface ActiveMQConnectionFactoryCustomizer { + + /** + * Customize the {@link ActiveMQConnectionFactory}. + * @param factory the factory to customize + */ + void customize(ActiveMQConnectionFactory factory); + +} 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 new file mode 100644 index 00000000000..6efb74837f4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2019 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 java.lang.reflect.InvocationTargetException; +import java.util.Collections; +import java.util.List; + +import org.apache.activemq.ActiveMQConnectionFactory; + +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties.Packages; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Factory to create a {@link ActiveMQConnectionFactory} instance from properties defined + * in {@link ActiveMQProperties}. + * + * @author Phillip Webb + * @author Venil Noronha + */ +class ActiveMQConnectionFactoryFactory { + + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; + + private final ActiveMQProperties properties; + + private final List factoryCustomizers; + + ActiveMQConnectionFactoryFactory(ActiveMQProperties properties, + List factoryCustomizers) { + Assert.notNull(properties, "Properties must not be null"); + this.properties = properties; + this.factoryCustomizers = (factoryCustomizers != null) ? factoryCustomizers : Collections.emptyList(); + } + + T createConnectionFactory(Class factoryClass) { + try { + return doCreateConnectionFactory(factoryClass); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to create ActiveMQConnectionFactory", ex); + } + } + + private T doCreateConnectionFactory(Class factoryClass) throws Exception { + T factory = createConnectionFactoryInstance(factoryClass); + if (this.properties.getCloseTimeout() != null) { + factory.setCloseTimeout((int) this.properties.getCloseTimeout().toMillis()); + } + factory.setNonBlockingRedelivery(this.properties.isNonBlockingRedelivery()); + if (this.properties.getSendTimeout() != null) { + factory.setSendTimeout((int) this.properties.getSendTimeout().toMillis()); + } + Packages packages = this.properties.getPackages(); + if (packages.getTrustAll() != null) { + factory.setTrustAllPackages(packages.getTrustAll()); + } + if (!packages.getTrusted().isEmpty()) { + factory.setTrustedPackages(packages.getTrusted()); + } + customize(factory); + return factory; + } + + private T createConnectionFactoryInstance(Class factoryClass) + throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + String brokerUrl = determineBrokerUrl(); + String user = this.properties.getUser(); + String password = this.properties.getPassword(); + if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) { + return factoryClass.getConstructor(String.class, String.class, String.class) + .newInstance(user, password, brokerUrl); + } + return factoryClass.getConstructor(String.class).newInstance(brokerUrl); + } + + private void customize(ActiveMQConnectionFactory connectionFactory) { + for (ActiveMQConnectionFactoryCustomizer factoryCustomizer : this.factoryCustomizers) { + factoryCustomizer.customize(connectionFactory); + } + } + + 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 new file mode 100644 index 00000000000..47364cbf6eb --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java @@ -0,0 +1,162 @@ +/* + * Copyright 2012-2019 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 java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.autoconfigure.jms.JmsPoolConnectionFactoryProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Configuration properties for ActiveMQ. + * + * @author Greg Turnquist + * @author Stephane Nicoll + * @author Aurélien Leboulanger + * @author Venil Noronha + * @since 1.0.0 + */ +@ConfigurationProperties(prefix = "spring.activemq") +public class ActiveMQProperties { + + /** + * URL of the ActiveMQ broker. Auto-generated by default. + */ + private String brokerUrl; + + /** + * Login user of the broker. + */ + private String user; + + /** + * Login password of the broker. + */ + private String password; + + /** + * Time to wait before considering a close complete. + */ + private Duration closeTimeout = Duration.ofSeconds(15); + + /** + * Whether to stop message delivery before re-delivering messages from a rolled back + * transaction. This implies that message order is not preserved when this is enabled. + */ + private boolean nonBlockingRedelivery = false; + + /** + * Time to wait on message sends for a response. Set it to 0 to wait forever. + */ + private Duration sendTimeout = Duration.ofMillis(0); + + @NestedConfigurationProperty + private final JmsPoolConnectionFactoryProperties pool = new JmsPoolConnectionFactoryProperties(); + + private final Packages packages = new Packages(); + + public String getBrokerUrl() { + return this.brokerUrl; + } + + public void setBrokerUrl(String brokerUrl) { + this.brokerUrl = brokerUrl; + } + + public String getUser() { + return this.user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Duration getCloseTimeout() { + return this.closeTimeout; + } + + public void setCloseTimeout(Duration closeTimeout) { + this.closeTimeout = closeTimeout; + } + + public boolean isNonBlockingRedelivery() { + return this.nonBlockingRedelivery; + } + + public void setNonBlockingRedelivery(boolean nonBlockingRedelivery) { + this.nonBlockingRedelivery = nonBlockingRedelivery; + } + + public Duration getSendTimeout() { + return this.sendTimeout; + } + + public void setSendTimeout(Duration sendTimeout) { + this.sendTimeout = sendTimeout; + } + + public JmsPoolConnectionFactoryProperties getPool() { + return this.pool; + } + + public Packages getPackages() { + return this.packages; + } + + public static class Packages { + + /** + * Whether to trust all packages. + */ + private Boolean trustAll; + + /** + * Comma-separated list of specific packages to trust (when not trusting all + * packages). + */ + private List trusted = new ArrayList<>(); + + public Boolean getTrustAll() { + return this.trustAll; + } + + public void setTrustAll(Boolean trustAll) { + this.trustAll = trustAll; + } + + public List getTrusted() { + return this.trusted; + } + + public void setTrusted(List trusted) { + this.trusted = trusted; + } + + } + +} 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 new file mode 100644 index 00000000000..23fa3937818 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2019 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 java.util.stream.Collectors; + +import jakarta.jms.ConnectionFactory; +import jakarta.transaction.TransactionManager; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.ActiveMQXAConnectionFactory; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.jms.XAConnectionFactoryWrapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * Configuration for ActiveMQ XA {@link ConnectionFactory}. + * + * @author Phillip Webb + * @author Aurélien Leboulanger + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(TransactionManager.class) +@ConditionalOnBean(XAConnectionFactoryWrapper.class) +@ConditionalOnMissingBean(ConnectionFactory.class) +class ActiveMQXAConnectionFactoryConfiguration { + + @Primary + @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) + ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties, + ObjectProvider factoryCustomizers, XAConnectionFactoryWrapper wrapper) + throws Exception { + ActiveMQXAConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(properties, + factoryCustomizers.orderedStream().collect(Collectors.toList())) + .createConnectionFactory(ActiveMQXAConnectionFactory.class); + return wrapper.wrapConnectionFactory(connectionFactory); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.activemq.pool", name = "enabled", havingValue = "false", + matchIfMissing = true) + ActiveMQConnectionFactory nonXaJmsConnectionFactory(ActiveMQProperties properties, + ObjectProvider factoryCustomizers) { + return new ActiveMQConnectionFactoryFactory(properties, + factoryCustomizers.orderedStream().collect(Collectors.toList())) + .createConnectionFactory(ActiveMQConnectionFactory.class); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java new file mode 100644 index 00000000000..1c1d7dc1dd6 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2019 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 ActiveMQ. + */ +package org.springframework.boot.autoconfigure.jms.activemq; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java index 3baa2f99d58..8f29c1ce14c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; @@ -34,8 +35,8 @@ import org.springframework.context.annotation.Import; * @author Nishant Raut * @since 1.2.0 */ -@AutoConfiguration(before = { XADataSourceAutoConfiguration.class, ArtemisAutoConfiguration.class, - HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class }) +@AutoConfiguration(before = { XADataSourceAutoConfiguration.class, ActiveMQAutoConfiguration.class, + ArtemisAutoConfiguration.class, HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class }) @ConditionalOnClass(jakarta.transaction.Transaction.class) @ConditionalOnProperty(prefix = "spring.jta", value = "enabled", matchIfMissing = true) @Import(JndiJtaConfiguration.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c3ebd99315e..f688b1590dc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -76,6 +76,7 @@ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration +org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration 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 new file mode 100644 index 00000000000..934d861eeae --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfigurationTests.java @@ -0,0 +1,264 @@ +/* + * Copyright 2012-2020 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 jakarta.jms.ConnectionFactory; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.junit.jupiter.api.Test; +import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jms.connection.CachingConnectionFactory; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockingDetails; + +/** + * Tests for {@link ActiveMQAutoConfiguration}. + * + * @author Andy Wilkinson + * @author Aurélien Leboulanger + * @author Stephane Nicoll + */ +class ActiveMQAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class)); + + @Test + void brokerIsLocalhostByDefault() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(CachingConnectionFactory.class).hasBean("jmsConnectionFactory"); + CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + assertThat(connectionFactory.getTargetConnectionFactory()).isInstanceOf(ActiveMQConnectionFactory.class); + assertThat(((ActiveMQConnectionFactory) connectionFactory.getTargetConnectionFactory()).getBrokerURL()) + .isEqualTo("tcp://localhost:61616"); + }); + } + + @Test + void configurationBacksOffWhenCustomConnectionFactoryExists() { + this.contextRunner.withUserConfiguration(CustomConnectionFactoryConfiguration.class) + .run((context) -> assertThat(mockingDetails(context.getBean(ConnectionFactory.class)).isMock()).isTrue()); + } + + @Test + void connectionFactoryIsCachedByDefault() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(CachingConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + assertThat(connectionFactory.getTargetConnectionFactory()).isInstanceOf(ActiveMQConnectionFactory.class); + assertThat(connectionFactory.isCacheConsumers()).isFalse(); + assertThat(connectionFactory.isCacheProducers()).isTrue(); + assertThat(connectionFactory.getSessionCacheSize()).isEqualTo(1); + }); + } + + @Test + void connectionFactoryCachingCanBeCustomized() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.jms.cache.consumers=true", "spring.jms.cache.producers=false", + "spring.jms.cache.session-cache-size=10") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(CachingConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + CachingConnectionFactory connectionFactory = context.getBean(CachingConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + assertThat(connectionFactory.isCacheConsumers()).isTrue(); + assertThat(connectionFactory.isCacheProducers()).isFalse(); + assertThat(connectionFactory.getSessionCacheSize()).isEqualTo(10); + }); + } + + @Test + void connectionFactoryCachingCanBeDisabled() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.jms.cache.enabled=false") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(ActiveMQConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + ActiveMQConnectionFactory defaultFactory = new ActiveMQConnectionFactory( + "vm://localhost?broker.persistent=false"); + assertThat(connectionFactory.getUserName()).isEqualTo(defaultFactory.getUserName()); + assertThat(connectionFactory.getPassword()).isEqualTo(defaultFactory.getPassword()); + assertThat(connectionFactory.getCloseTimeout()).isEqualTo(defaultFactory.getCloseTimeout()); + assertThat(connectionFactory.isNonBlockingRedelivery()) + .isEqualTo(defaultFactory.isNonBlockingRedelivery()); + assertThat(connectionFactory.getSendTimeout()).isEqualTo(defaultFactory.getSendTimeout()); + assertThat(connectionFactory.isTrustAllPackages()).isEqualTo(defaultFactory.isTrustAllPackages()); + assertThat(connectionFactory.getTrustedPackages()) + .containsExactly(StringUtils.toStringArray(defaultFactory.getTrustedPackages())); + }); + } + + @Test + void customConnectionFactoryIsApplied() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.jms.cache.enabled=false", + "spring.activemq.brokerUrl=vm://localhost?useJmx=false&broker.persistent=false", + "spring.activemq.user=foo", "spring.activemq.password=bar", "spring.activemq.closeTimeout=500", + "spring.activemq.nonBlockingRedelivery=true", "spring.activemq.sendTimeout=1000", + "spring.activemq.packages.trust-all=false", "spring.activemq.packages.trusted=com.example.acme") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(ActiveMQConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + assertThat(connectionFactory.getUserName()).isEqualTo("foo"); + assertThat(connectionFactory.getPassword()).isEqualTo("bar"); + assertThat(connectionFactory.getCloseTimeout()).isEqualTo(500); + assertThat(connectionFactory.isNonBlockingRedelivery()).isTrue(); + assertThat(connectionFactory.getSendTimeout()).isEqualTo(1000); + assertThat(connectionFactory.isTrustAllPackages()).isFalse(); + assertThat(connectionFactory.getTrustedPackages()).containsExactly("com.example.acme"); + }); + } + + @Test + void defaultPoolConnectionFactoryIsApplied() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.activemq.pool.enabled=true") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(JmsPoolConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + JmsPoolConnectionFactory connectionFactory = context.getBean(JmsPoolConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + JmsPoolConnectionFactory defaultFactory = new JmsPoolConnectionFactory(); + assertThat(connectionFactory.isBlockIfSessionPoolIsFull()) + .isEqualTo(defaultFactory.isBlockIfSessionPoolIsFull()); + assertThat(connectionFactory.getBlockIfSessionPoolIsFullTimeout()) + .isEqualTo(defaultFactory.getBlockIfSessionPoolIsFullTimeout()); + assertThat(connectionFactory.getConnectionIdleTimeout()) + .isEqualTo(defaultFactory.getConnectionIdleTimeout()); + assertThat(connectionFactory.getMaxConnections()).isEqualTo(defaultFactory.getMaxConnections()); + assertThat(connectionFactory.getMaxSessionsPerConnection()) + .isEqualTo(defaultFactory.getMaxSessionsPerConnection()); + assertThat(connectionFactory.getConnectionCheckInterval()) + .isEqualTo(defaultFactory.getConnectionCheckInterval()); + assertThat(connectionFactory.isUseAnonymousProducers()) + .isEqualTo(defaultFactory.isUseAnonymousProducers()); + }); + } + + @Test + void customPoolConnectionFactoryIsApplied() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.activemq.pool.enabled=true", "spring.activemq.pool.blockIfFull=false", + "spring.activemq.pool.blockIfFullTimeout=64", "spring.activemq.pool.idleTimeout=512", + "spring.activemq.pool.maxConnections=256", "spring.activemq.pool.maxSessionsPerConnection=1024", + "spring.activemq.pool.timeBetweenExpirationCheck=2048", + "spring.activemq.pool.useAnonymousProducers=false") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(JmsPoolConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + JmsPoolConnectionFactory connectionFactory = context.getBean(JmsPoolConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + assertThat(connectionFactory.isBlockIfSessionPoolIsFull()).isFalse(); + assertThat(connectionFactory.getBlockIfSessionPoolIsFullTimeout()).isEqualTo(64); + assertThat(connectionFactory.getConnectionIdleTimeout()).isEqualTo(512); + assertThat(connectionFactory.getMaxConnections()).isEqualTo(256); + assertThat(connectionFactory.getMaxSessionsPerConnection()).isEqualTo(1024); + assertThat(connectionFactory.getConnectionCheckInterval()).isEqualTo(2048); + assertThat(connectionFactory.isUseAnonymousProducers()).isFalse(); + }); + } + + @Test + void poolConnectionFactoryConfiguration() { + this.contextRunner.withUserConfiguration(EmptyConfiguration.class) + .withPropertyValues("spring.activemq.pool.enabled:true") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(JmsPoolConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + ConnectionFactory factory = context.getBean(ConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(factory); + assertThat(factory).isInstanceOf(JmsPoolConnectionFactory.class); + context.getSourceApplicationContext().close(); + assertThat(factory.createConnection()).isNull(); + }); + } + + @Test + void cachingConnectionFactoryNotOnTheClasspathThenSimpleConnectionFactoryAutoConfigured() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CachingConnectionFactory.class)) + .withPropertyValues("spring.activemq.pool.enabled=false", "spring.jms.cache.enabled=false") + .run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(ActiveMQConnectionFactory.class) + .hasBean("jmsConnectionFactory"); + ActiveMQConnectionFactory connectionFactory = context.getBean(ActiveMQConnectionFactory.class); + assertThat(context.getBean("jmsConnectionFactory")).isSameAs(connectionFactory); + }); + } + + @Test + void cachingConnectionFactoryNotOnTheClasspathAndCacheEnabledThenSimpleConnectionFactoryNotConfigured() { + this.contextRunner.withClassLoader(new FilteredClassLoader(CachingConnectionFactory.class)) + .withPropertyValues("spring.activemq.pool.enabled=false", "spring.jms.cache.enabled=true") + .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactory.class) + .doesNotHaveBean(ActiveMQConnectionFactory.class) + .doesNotHaveBean("jmsConnectionFactory")); + } + + @Configuration(proxyBeanMethods = false) + static class EmptyConfiguration { + + } + + @Configuration(proxyBeanMethods = false) + static class CustomConnectionFactoryConfiguration { + + @Bean + ConnectionFactory connectionFactory() { + return mock(ConnectionFactory.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfiguration { + + @Bean + ActiveMQConnectionFactoryCustomizer activeMQConnectionFactoryCustomizer() { + return (factory) -> { + factory.setBrokerURL("vm://localhost?useJmx=false&broker.persistent=false"); + factory.setUserName("foobar"); + }; + } + + } + +} 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 new file mode 100644 index 00000000000..74d3502758c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2019 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 java.util.Collections; + +import org.apache.activemq.ActiveMQConnectionFactory; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ActiveMQProperties} and {@link ActiveMQConnectionFactoryFactory}. + * + * @author Stephane Nicoll + * @author Aurélien Leboulanger + * @author Venil Noronha + */ +class ActiveMQPropertiesTests { + + private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616"; + + private final ActiveMQProperties properties = new ActiveMQProperties(); + + @Test + void getBrokerUrlIsLocalhostByDefault() { + assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo(DEFAULT_NETWORK_BROKER_URL); + } + + @Test + void getBrokerUrlUseExplicitBrokerUrl() { + this.properties.setBrokerUrl("vm://foo-bar"); + assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo("vm://foo-bar"); + } + + @Test + void setTrustAllPackages() { + this.properties.getPackages().setTrustAll(true); + assertThat(createFactory(this.properties).createConnectionFactory(ActiveMQConnectionFactory.class) + .isTrustAllPackages()).isTrue(); + } + + @Test + void setTrustedPackages() { + this.properties.getPackages().setTrustAll(false); + this.properties.getPackages().getTrusted().add("trusted.package"); + ActiveMQConnectionFactory factory = createFactory(this.properties) + .createConnectionFactory(ActiveMQConnectionFactory.class); + assertThat(factory.isTrustAllPackages()).isFalse(); + assertThat(factory.getTrustedPackages().size()).isEqualTo(1); + assertThat(factory.getTrustedPackages().get(0)).isEqualTo("trusted.package"); + } + + private ActiveMQConnectionFactoryFactory createFactory(ActiveMQProperties properties) { + return new ActiveMQConnectionFactoryFactory(properties, Collections.emptyList()); + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 34f56fb9213..a9a143dbd05 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -14,6 +14,15 @@ bom { issueLabels = ["type: dependency-upgrade"] } } + library("ActiveMQ", "5.18.1") { + group("org.apache.activemq") { + modules = [ + "activemq-client", + "activemq-client-jakarta", + "activemq-openwire-legacy", + ] + } + } library("Angus Mail", "1.1.0") { group("org.eclipse.angus") { modules = [ diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc index 0d3bfb7c747..2dcd7cfaefc 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc @@ -7,6 +7,55 @@ Spring Boot also auto-configures the necessary infrastructure to send and receiv +[[messaging.jms.activemq]] +=== ActiveMQ Support +When https://activemq.apache.org/[ActiveMQ] is available on the classpath, Spring Boot can also configure a `ConnectionFactory`. + +NOTE: If you use `spring-boot-starter-activemq`, the necessary dependencies to connect to an ActiveMQ instance are provided, as is the Spring infrastructure to integrate with JMS. + +ActiveMQ configuration is controlled by external configuration properties in `+spring.activemq.*+`. + +By default, ActiveMQ is auto-configured to use the https://activemq.apache.org/tcp-transport-reference[TCP transport] eith the default broker URL `tcp://localhost:61616`. + +The following example shows how to change the default broker URL: + +[source,yaml,indent=0,configprops,configblocks] +---- + spring: + activemq: + broker-url: "tcp://192.168.1.210:9876" + user: "admin" + password: "secret" +---- + +By default, a `CachingConnectionFactory` wraps the native `ConnectionFactory` with sensible settings that you can control by external configuration properties in `+spring.jms.*+`: + +[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +---- + spring: + jms: + cache: + session-cache-size: 5 +---- + +If you'd rather use native pooling, you can do so by adding a dependency to `org.messaginghub:pooled-jms` and configuring the `JmsPoolConnectionFactory` accordingly, as shown in the following example: + +[source,yaml,indent=0,subs="verbatim",configprops,configblocks] +---- + spring: + activemq: + pool: + enabled: true + max-connections: 50 +---- + +TIP: See {spring-boot-autoconfigure-module-code}/jms/activemq/ActiveMQProperties.java[`ActiveMQProperties`] for more of the supported options. +You can also register an arbitrary number of beans that implement `ActiveMQConnectionFactoryCustomizer` for more advanced customizations. + +By default, ActiveMQ creates a destination if it does not yet exist so that destinations are resolved against their provided names. + + + [[messaging.jms.artemis]] === ActiveMQ Artemis Support Spring Boot can auto-configure a `ConnectionFactory` when it detects that https://activemq.apache.org/components/artemis/[ActiveMQ Artemis] is available on the classpath. diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle new file mode 100644 index 00000000000..622bef10b47 --- /dev/null +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-activemq/build.gradle @@ -0,0 +1,11 @@ +plugins { + id "org.springframework.boot.starter" +} + +description = "Starter for JMS messaging using Apache ActiveMQ" + +dependencies { + api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) + api("org.springframework:spring-jms") + api("org.apache.activemq:activemq-client-jakarta") +} From a323bd90a8ec14f8d60e1ea6600f6fd8380ef241 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 18 Apr 2023 13:55:37 +0200 Subject: [PATCH 2/2] Polish "Reintroduce support for ActiveMQ" See gh-35048 --- ...JmsHealthContributorAutoConfiguration.java | 2 +- .../activemq/ActiveMQAutoConfiguration.java | 15 ++--- ...ctiveMQConnectionFactoryConfiguration.java | 2 +- .../ActiveMQConnectionFactoryCustomizer.java | 4 +- .../ActiveMQConnectionFactoryFactory.java | 2 +- .../jms/activemq/ActiveMQProperties.java | 4 +- ...iveMQXAConnectionFactoryConfiguration.java | 2 +- .../jms/activemq/package-info.java | 2 +- .../transaction/jta/JtaAutoConfiguration.java | 2 +- .../ActiveMQAutoConfigurationTests.java | 2 +- .../jms/activemq/ActiveMQPropertiesTests.java | 6 +- .../spring-boot-dependencies/build.gradle | 28 +++++++++ .../src/docs/asciidoc/messaging/jms.adoc | 7 +-- .../testcontainers/ActiveMQContainer.java | 44 +++++++++++++ .../testcontainers/DockerImageNames.java | 11 ++++ .../build.gradle | 16 +++++ .../java/smoketest/activemq/Consumer.java | 30 +++++++++ .../java/smoketest/activemq/Producer.java | 45 +++++++++++++ .../activemq/SampleActiveMQApplication.java | 40 ++++++++++++ .../src/main/resources/application.properties | 1 + .../activemq/SampleActiveMqTests.java | 63 +++++++++++++++++++ 21 files changed, 299 insertions(+), 29 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/ActiveMQContainer.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Consumer.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Producer.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/SampleActiveMQApplication.java create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/resources/application.properties create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java index ee7b693861d..aa7d72ab27a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jms/JmsHealthContributorAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. 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 769a98616f9..da642a7f868 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -19,8 +19,7 @@ package org.springframework.boot.autoconfigure.jms.activemq; import jakarta.jms.ConnectionFactory; import org.apache.activemq.ActiveMQConnectionFactory; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -28,21 +27,17 @@ 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.Configuration; import org.springframework.context.annotation.Import; /** * {@link EnableAutoConfiguration Auto-configuration} to integrate with an ActiveMQ - * broker. Validates that the classpath contain the necessary classes before starting an - * embedded broker. + * broker. * * @author Stephane Nicoll * @author Phillip Webb - * @since 1.1.0 + * @since 3.1.0 */ -@Configuration(proxyBeanMethods = false) -@AutoConfigureBefore(JmsAutoConfiguration.class) -@AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class }) +@AutoConfiguration(before = JmsAutoConfiguration.class, after = JndiConnectionFactoryAutoConfiguration.class) @ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class }) @ConditionalOnMissingBean(ConnectionFactory.class) @EnableConfigurationProperties({ ActiveMQProperties.class, JmsProperties.class }) 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 901b4c033ac..458493c982b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryCustomizer.java index 1354b505a2c..c53e10ed2f2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryCustomizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -23,7 +23,7 @@ import org.apache.activemq.ActiveMQConnectionFactory; * {@link ActiveMQConnectionFactory} whilst retaining default auto-configuration. * * @author Stephane Nicoll - * @since 1.5.5 + * @since 3.1.0 */ @FunctionalInterface public interface ActiveMQConnectionFactoryCustomizer { 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 6efb74837f4..b571860491f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. 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 47364cbf6eb..48b72e88935 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -31,7 +31,7 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty; * @author Stephane Nicoll * @author Aurélien Leboulanger * @author Venil Noronha - * @since 1.0.0 + * @since 3.1.0 */ @ConfigurationProperties(prefix = "spring.activemq") public class ActiveMQProperties { 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 23fa3937818..cf28767de18 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java index 1c1d7dc1dd6..0b95b83875c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java index 8f29c1ce14c..a9040ac4ba1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * 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. 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 934d861eeae..0edd4270c7e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * 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. 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 74d3502758c..d6b66bc1239 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -43,8 +43,8 @@ class ActiveMQPropertiesTests { @Test void getBrokerUrlUseExplicitBrokerUrl() { - this.properties.setBrokerUrl("vm://foo-bar"); - assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo("vm://foo-bar"); + this.properties.setBrokerUrl("tcp://activemq.example.com:71717"); + assertThat(createFactory(this.properties).determineBrokerUrl()).isEqualTo("tcp://activemq.example.com:71717"); } @Test diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index a9a143dbd05..ac33bf52233 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -17,9 +17,36 @@ bom { library("ActiveMQ", "5.18.1") { group("org.apache.activemq") { modules = [ + "activemq-amqp", + "activemq-blueprint", + "activemq-broker", "activemq-client", "activemq-client-jakarta", + "activemq-console" { + exclude group: "commons-logging", module: "commons-logging" + }, + "activemq-http", + "activemq-jaas", + "activemq-jdbc-store", + "activemq-jms-pool", + "activemq-kahadb-store", + "activemq-karaf", + "activemq-log4j-appender", + "activemq-mqtt", + "activemq-openwire-generator", "activemq-openwire-legacy", + "activemq-osgi", + "activemq-partition", + "activemq-pool", + "activemq-ra", + "activemq-run", + "activemq-runtime-config", + "activemq-shiro", + "activemq-spring" { + exclude group: "commons-logging", module: "commons-logging" + }, + "activemq-stomp", + "activemq-web" ] } } @@ -1217,6 +1244,7 @@ bom { "spring-boot-loader-tools", "spring-boot-properties-migrator", "spring-boot-starter", + "spring-boot-starter-activemq", "spring-boot-starter-actuator", "spring-boot-starter-amqp", "spring-boot-starter-aop", diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc index 2dcd7cfaefc..137bd648849 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/messaging/jms.adoc @@ -9,15 +9,12 @@ Spring Boot also auto-configures the necessary infrastructure to send and receiv [[messaging.jms.activemq]] === ActiveMQ Support -When https://activemq.apache.org/[ActiveMQ] is available on the classpath, Spring Boot can also configure a `ConnectionFactory`. +When https://activemq.apache.org/[ActiveMQ] is available on the classpath, Spring Boot can configure a `ConnectionFactory`. NOTE: If you use `spring-boot-starter-activemq`, the necessary dependencies to connect to an ActiveMQ instance are provided, as is the Spring infrastructure to integrate with JMS. ActiveMQ configuration is controlled by external configuration properties in `+spring.activemq.*+`. - -By default, ActiveMQ is auto-configured to use the https://activemq.apache.org/tcp-transport-reference[TCP transport] eith the default broker URL `tcp://localhost:61616`. - -The following example shows how to change the default broker URL: +By default, ActiveMQ is auto-configured to use the https://activemq.apache.org/tcp-transport-reference[TCP transport], connecting by default to `tcp://localhost:61616`. The following example shows how to change the default broker URL: [source,yaml,indent=0,configprops,configblocks] ---- diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/ActiveMQContainer.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/ActiveMQContainer.java new file mode 100644 index 00000000000..6fc7dc5c157 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/testcontainers/ActiveMQContainer.java @@ -0,0 +1,44 @@ +/* + * 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.testsupport.testcontainers; + +import org.testcontainers.containers.GenericContainer; + +/** + * A {@link GenericContainer} for ActiveMQ. + * + * @author Stephane Nicoll + * @since 3.1.0 + */ +public class ActiveMQContainer extends GenericContainer { + + private static final int DEFAULT_PORT = 61616; + + public ActiveMQContainer() { + super(DockerImageNames.activeMq()); + addExposedPorts(DEFAULT_PORT); + } + + /** + * Return the broker URL to use. + * @return the broker url of the ActiveMQ instance + */ + public String getBrokerUrl() { + return String.format("tcp://" + getHost() + ":" + getMappedPort(DEFAULT_PORT)); + } + +} 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 f07904acd71..965092d64ef 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 @@ -27,6 +27,8 @@ import org.testcontainers.utility.DockerImageName; */ public final class DockerImageNames { + private static final String ACTIVE_MQ_VERSION = "5.18.0"; + private static final String CASSANDRA_VERSION = "3.11.10"; private static final String COUCHBASE_VERSION = "6.5.1"; @@ -56,6 +58,15 @@ public final class DockerImageNames { private DockerImageNames() { } + /** + * Return a {@link DockerImageName} suitable for running ActiveMQ. + * @return a docker image name for running activeMq + */ + public static DockerImageName activeMq() { + return DockerImageName.parse("symptoma/activemq").withTag(ACTIVE_MQ_VERSION); + } + + /** * Return a {@link DockerImageName} suitable for running Cassandra. * @return a docker image name for running cassandra 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 new file mode 100644 index 00000000000..fd587ebd0cf --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/build.gradle @@ -0,0 +1,16 @@ +plugins { + id "java" + id "org.springframework.boot.conventions" +} + +description = "Spring Boot Actuator ActiveMQ smoke test" + +dependencies { + implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-activemq")) + + 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")) + testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) +} \ No newline at end of file diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Consumer.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Consumer.java new file mode 100644 index 00000000000..8af2450b57f --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Consumer.java @@ -0,0 +1,30 @@ +/* + * 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 smoketest.activemq; + +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +@Component +public class Consumer { + + @JmsListener(destination = "sample.queue") + public void receiveQueue(String text) { + System.out.println(text); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Producer.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Producer.java new file mode 100644 index 00000000000..4689909c6ab --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/Producer.java @@ -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 smoketest.activemq; + +import jakarta.jms.Queue; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.jms.core.JmsMessagingTemplate; +import org.springframework.stereotype.Component; + +@Component +public class Producer implements CommandLineRunner { + + @Autowired + private JmsMessagingTemplate jmsMessagingTemplate; + + @Autowired + private Queue queue; + + @Override + public void run(String... args) throws Exception { + send("Sample message"); + System.out.println("Message was sent to the Queue"); + } + + public void send(String msg) { + this.jmsMessagingTemplate.convertAndSend(this.queue, msg); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/SampleActiveMQApplication.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/SampleActiveMQApplication.java new file mode 100644 index 00000000000..a485714e08c --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/java/smoketest/activemq/SampleActiveMQApplication.java @@ -0,0 +1,40 @@ +/* + * 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 smoketest.activemq; + +import jakarta.jms.Queue; +import org.apache.activemq.command.ActiveMQQueue; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.jms.annotation.EnableJms; + +@SpringBootApplication +@EnableJms +public class SampleActiveMQApplication { + + @Bean + public Queue queue() { + return new ActiveMQQueue("sample.queue"); + } + + public static void main(String[] args) { + SpringApplication.run(SampleActiveMQApplication.class, args); + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/resources/application.properties new file mode 100644 index 00000000000..5527c1d8852 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.activemq.pool.enabled=false 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 new file mode 100644 index 00000000000..7637a28f886 --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-activemq/src/test/java/smoketest/activemq/SampleActiveMqTests.java @@ -0,0 +1,63 @@ +/* + * 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 smoketest.activemq; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +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.testsupport.testcontainers.ActiveMQContainer; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for demo application. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + */ +@SpringBootTest +@Testcontainers(disabledWithoutDocker = true) +@ExtendWith(OutputCaptureExtension.class) +class SampleActiveMqTests { + + @Container + 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 { + this.producer.send("Test message"); + Thread.sleep(1000L); + assertThat(output).contains("Test message"); + } + +}