From 3e9908a797aada598599ecbf441758f36ab79716 Mon Sep 17 00:00:00 2001 From: Martin BENDA Date: Thu, 30 Mar 2023 08:38:36 +0200 Subject: [PATCH] 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 08a5bc58d6..89b07037de 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 53490552eb..ee7b693861 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 0d26498090..5d1303089b 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 0000000000..769a98616f --- /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 0000000000..901b4c033a --- /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 0000000000..1354b505a2 --- /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 0000000000..6efb74837f --- /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 0000000000..47364cbf6e --- /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 0000000000..23fa393781 --- /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 0000000000..1c1d7dc1dd --- /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 3baa2f99d5..8f29c1ce14 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 c3ebd99315..f688b1590d 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 0000000000..934d861eea --- /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 0000000000..74d3502758 --- /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 34f56fb921..a9a143dbd0 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 0d3bfb7c74..2dcd7cfaef 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 0000000000..622bef10b4 --- /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") +}