From 8859824efc73bcee02cbe7c9d474912b208819ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Sun, 14 Jun 2015 11:12:32 -0500 Subject: [PATCH 1/3] Add Apache Artemis support Add auto-configuration support for Apache Artemis which was formed when HornetQ was donated to the Apache Foundation. The majority of this code is based on the HornetQ auto-configuration. Fixes gh-3154 Closes gh-3246 --- spring-boot-autoconfigure/pom.xml | 10 + .../jms/artemis/ArtemisAutoConfiguration.java | 53 +++ .../ArtemisConfigurationCustomizer.java | 40 ++ ...ArtemisConnectionFactoryConfiguration.java | 44 ++ .../ArtemisConnectionFactoryFactory.java | 135 ++++++ .../ArtemisEmbeddedConfigurationFactory.java | 78 ++++ .../ArtemisEmbeddedServerConfiguration.java | 128 ++++++ .../jms/artemis/ArtemisMode.java | 38 ++ .../artemis/ArtemisNoOpBindingRegistry.java | 48 ++ .../jms/artemis/ArtemisProperties.java | 202 +++++++++ ...temisXAConnectionFactoryConfiguration.java | 62 +++ .../jms/artemis/package-info.java | 22 + .../main/resources/META-INF/spring.factories | 1 + .../ArtemisAutoConfigurationTests.java | 421 ++++++++++++++++++ ...emisEmbeddedConfigurationFactoryTests.java | 75 ++++ spring-boot-dependencies/pom.xml | 11 + 16 files changed, 1368 insertions(+) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConfigurationCustomizer.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisMode.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisNoOpBindingRegistry.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/package-info.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index ce883887b9..7de84e78c8 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -130,6 +130,16 @@ activemq-pool true + + org.apache.activemq + artemis-jms-client + true + + + org.apache.activemq + artemis-jms-server + true + org.apache.commons commons-dbcp2 diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java new file mode 100644 index 0000000000..41d0022c64 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfiguration.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import javax.jms.ConnectionFactory; + +import org.apache.activemq.artemis.jms.client.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.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 Artemis broker. + * If the necessary classes are present, embed the broker in the application by default. + * Otherwise, connect to a broker available on the local machine with the default + * settings. + * + * @author Eddú Meléndez + * @since 1.3.0 + * @see ArtemisProperties + */ +@Configuration +@AutoConfigureBefore(JmsAutoConfiguration.class) +@AutoConfigureAfter({ JndiConnectionFactoryAutoConfiguration.class }) +@ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class }) +@ConditionalOnMissingBean(ConnectionFactory.class) +@EnableConfigurationProperties(ArtemisProperties.class) +@Import({ ArtemisXAConnectionFactoryConfiguration.class, + ArtemisConnectionFactoryConfiguration.class }) +public class ArtemisAutoConfiguration { + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConfigurationCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConfigurationCustomizer.java new file mode 100644 index 0000000000..1716a46d87 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConfigurationCustomizer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.jms.server.embedded.EmbeddedJMS; + +/** + * Callback interface that can be implemented by beans wishing to customize the Artemis + * JMS server {@link Configuration} before it is used by an auto-configured + * {@link EmbeddedJMS} instance. + * + * @author Eddú Meléndez + * @author Phillip Webb + * @since 1.3.0 + * @see ArtemisAutoConfiguration + */ +public interface ArtemisConfigurationCustomizer { + + /** + * Customize the configuration. + * @param configuration the configuration to customize + */ + void customize(Configuration configuration); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java new file mode 100644 index 0000000000..760f8c42ae --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import javax.jms.ConnectionFactory; + +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration for Artemis {@link ConnectionFactory}. + * + * @author Eddú Meléndez + * @author Phillip Webb + */ +@Configuration +@ConditionalOnMissingBean(ConnectionFactory.class) +class ArtemisConnectionFactoryConfiguration { + + @Bean + public ConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, + ArtemisProperties properties) { + return new ArtemisConnectionFactoryFactory(beanFactory, properties) + .createConnectionFactory(ActiveMQConnectionFactory.class); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java new file mode 100644 index 0000000000..322f963da6 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisConnectionFactoryFactory.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.api.core.client.ActiveMQClient; +import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory; +import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Factory to create a Artemis {@link ActiveMQConnectionFactory} instance from properties + * defined in {@link ArtemisProperties}. + * + * @author Eddú Meléndez + * @author Phillip Webb + * @author Stephane Nicoll + */ +class ArtemisConnectionFactoryFactory { + + static final String EMBEDDED_JMS_CLASS = "org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ"; + + private final ArtemisProperties properties; + + private final ListableBeanFactory beanFactory; + + public ArtemisConnectionFactoryFactory(ListableBeanFactory beanFactory, + ArtemisProperties properties) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + Assert.notNull(properties, "Properties must not be null"); + this.beanFactory = beanFactory; + this.properties = properties; + } + + public T createConnectionFactory( + Class factoryClass) { + try { + startEmbeddedJms(); + return doCreateConnectionFactory(factoryClass); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to create " + + "ActiveMQConnectionFactory", ex); + } + } + + private void startEmbeddedJms() { + if (ClassUtils.isPresent(EMBEDDED_JMS_CLASS, null)) { + try { + this.beanFactory.getBeansOfType(Class.forName(EMBEDDED_JMS_CLASS)); + } + catch (Exception ex) { + // Ignore + } + } + } + + private T doCreateConnectionFactory( + Class factoryClass) throws Exception { + ArtemisMode mode = this.properties.getMode(); + if (mode == null) { + mode = deduceMode(); + } + if (mode == ArtemisMode.EMBEDDED) { + return createEmbeddedConnectionFactory(factoryClass); + } + return createNativeConnectionFactory(factoryClass); + } + + /** + * Deduce the {@link ArtemisMode} to use if none has been set. + */ + private ArtemisMode deduceMode() { + if (this.properties.getEmbedded().isEnabled() + && ClassUtils.isPresent(EMBEDDED_JMS_CLASS, null)) { + return ArtemisMode.EMBEDDED; + } + return ArtemisMode.NATIVE; + } + + private T createEmbeddedConnectionFactory( + Class factoryClass) throws Exception { + try { + TransportConfiguration transportConfiguration = new TransportConfiguration( + InVMConnectorFactory.class.getName(), this.properties.getEmbedded() + .generateTransportParameters()); + ServerLocator serviceLocator = ActiveMQClient + .createServerLocatorWithoutHA(transportConfiguration); + return factoryClass.getConstructor(ServerLocator.class).newInstance( + serviceLocator); + } + catch (NoClassDefFoundError ex) { + throw new IllegalStateException("Unable to create InVM " + + "Artemis connection, ensure that artemis-jms-server.jar " + + "is in the classpath", ex); + } + } + + private T createNativeConnectionFactory( + Class factoryClass) throws Exception { + Map params = new HashMap(); + params.put(TransportConstants.HOST_PROP_NAME, this.properties.getHost()); + params.put(TransportConstants.PORT_PROP_NAME, this.properties.getPort()); + TransportConfiguration transportConfiguration = new TransportConfiguration( + NettyConnectorFactory.class.getName(), params); + Constructor constructor = factoryClass.getConstructor(boolean.class, + TransportConfiguration[].class); + return constructor.newInstance(false, + new TransportConfiguration[] { transportConfiguration }); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java new file mode 100644 index 0000000000..701254efc1 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactory.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import java.io.File; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl; +import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory; +import org.apache.activemq.artemis.core.server.JournalType; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Configuration used to create the embedded Artemis server. + * + * @author Eddú Meléndez + * @author Stephane Nicol + * @author Phillip Webb + */ +class ArtemisEmbeddedConfigurationFactory { + + private Log logger = LogFactory.getLog(ArtemisEmbeddedConfigurationFactory.class); + + private final ArtemisProperties.Embedded properties; + + public ArtemisEmbeddedConfigurationFactory(ArtemisProperties properties) { + this.properties = properties.getEmbedded(); + } + + public Configuration createConfiguration() { + ConfigurationImpl configuration = new ConfigurationImpl(); + configuration.setSecurityEnabled(false); + configuration.setPersistenceEnabled(this.properties.isPersistent()); + String dataDir = getDataDir(); + configuration.setJournalDirectory(dataDir + "/journal"); + if (this.properties.isPersistent()) { + configuration.setJournalType(JournalType.NIO); + configuration.setLargeMessagesDirectory(dataDir + "/largemessages"); + configuration.setBindingsDirectory(dataDir + "/bindings"); + configuration.setPagingDirectory(dataDir + "/paging"); + } + TransportConfiguration transportConfiguration = new TransportConfiguration( + InVMAcceptorFactory.class.getName(), + this.properties.generateTransportParameters()); + configuration.getAcceptorConfigurations().add(transportConfiguration); + if (this.properties.isDefaultClusterPassword()) { + this.logger.debug("Using default Artemis cluster password: " + + this.properties.getClusterPassword()); + } + configuration.setClusterPassword(this.properties.getClusterPassword()); + return configuration; + } + + private String getDataDir() { + if (this.properties.getDataDirectory() != null) { + return this.properties.getDataDirectory(); + } + String tempDirectory = System.getProperty("java.io.tmpdir"); + return new File(tempDirectory, "artemis-data").getAbsolutePath(); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java new file mode 100644 index 0000000000..686b4f91ef --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedServerConfiguration.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import java.util.Collection; +import java.util.List; + +import org.apache.activemq.artemis.jms.server.config.JMSConfiguration; +import org.apache.activemq.artemis.jms.server.config.JMSQueueConfiguration; +import org.apache.activemq.artemis.jms.server.config.TopicConfiguration; +import org.apache.activemq.artemis.jms.server.config.impl.JMSConfigurationImpl; +import org.apache.activemq.artemis.jms.server.config.impl.JMSQueueConfigurationImpl; +import org.apache.activemq.artemis.jms.server.config.impl.TopicConfigurationImpl; +import org.apache.activemq.artemis.jms.server.embedded.EmbeddedJMS; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; + +/** + * Configuration used to create the embedded Artemis server. + * + * @author Eddú Meléndez + * @author Phillip Webb + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnClass(name = ArtemisConnectionFactoryFactory.EMBEDDED_JMS_CLASS) +@ConditionalOnProperty(prefix = "spring.artemis.embedded", name = "enabled", havingValue = "true", matchIfMissing = true) +class ArtemisEmbeddedServerConfiguration { + + @Autowired + private ArtemisProperties properties; + + @Autowired(required = false) + private List configurationCustomizers; + + @Autowired(required = false) + private List queuesConfiguration; + + @Autowired(required = false) + private List topicsConfiguration; + + @Bean + @ConditionalOnMissingBean + public org.apache.activemq.artemis.core.config.Configuration artemisConfiguration() { + return new ArtemisEmbeddedConfigurationFactory(this.properties) + .createConfiguration(); + } + + @Bean(initMethod = "start", destroyMethod = "stop") + @ConditionalOnMissingBean + public EmbeddedJMS artemisServer( + org.apache.activemq.artemis.core.config.Configuration configuration, + JMSConfiguration jmsConfiguration) { + EmbeddedJMS server = new EmbeddedJMS(); + customize(configuration); + server.setConfiguration(configuration); + server.setJmsConfiguration(jmsConfiguration); + server.setRegistry(new ArtemisNoOpBindingRegistry()); + return server; + } + + private void customize( + org.apache.activemq.artemis.core.config.Configuration configuration) { + if (this.configurationCustomizers != null) { + AnnotationAwareOrderComparator.sort(this.configurationCustomizers); + for (ArtemisConfigurationCustomizer customizer : this.configurationCustomizers) { + customizer.customize(configuration); + } + } + } + + @Bean + @ConditionalOnMissingBean + public JMSConfiguration artemisJmsConfiguration() { + JMSConfiguration configuration = new JMSConfigurationImpl(); + addAll(configuration.getQueueConfigurations(), this.queuesConfiguration); + addAll(configuration.getTopicConfigurations(), this.topicsConfiguration); + addQueues(configuration, this.properties.getEmbedded().getQueues()); + addTopics(configuration, this.properties.getEmbedded().getTopics()); + return configuration; + } + + private void addAll(List list, Collection items) { + if (items != null) { + list.addAll(items); + } + } + + private void addQueues(JMSConfiguration configuration, String[] queues) { + boolean persistent = this.properties.getEmbedded().isPersistent(); + for (String queue : queues) { + JMSQueueConfigurationImpl jmsQueueConfiguration = new JMSQueueConfigurationImpl(); + jmsQueueConfiguration.setName(queue); + jmsQueueConfiguration.setDurable(persistent); + jmsQueueConfiguration.setBindings("/queue/" + queue); + configuration.getQueueConfigurations().add(jmsQueueConfiguration); + } + } + + private void addTopics(JMSConfiguration configuration, String[] topics) { + for (String topic : topics) { + TopicConfigurationImpl topicConfiguration = new TopicConfigurationImpl(); + topicConfiguration.setName(topic); + topicConfiguration.setBindings("/topic/" + topic); + configuration.getTopicConfigurations().add(topicConfiguration); + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisMode.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisMode.java new file mode 100644 index 0000000000..c5ed813875 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisMode.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +/** + * Define the mode in which Artemis can operate. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + * @since 1.3.0 + */ +public enum ArtemisMode { + + /** + * Connect to a broker using the native Artemis protocol (i.e. netty). + */ + NATIVE, + + /** + * Embed (i.e. start) the broker in the application. + */ + EMBEDDED + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisNoOpBindingRegistry.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisNoOpBindingRegistry.java new file mode 100644 index 0000000000..6d991be7b3 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisNoOpBindingRegistry.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import org.apache.activemq.artemis.spi.core.naming.BindingRegistry; + +/** + * A no-op implementation of the {@link BindingRegistry}. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + * @since 1.3.0 + */ +public class ArtemisNoOpBindingRegistry implements BindingRegistry { + + @Override + public Object lookup(String s) { + return null; + } + + @Override + public boolean bind(String s, Object o) { + return false; + } + + @Override + public void unbind(String s) { + } + + @Override + public void close() { + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java new file mode 100644 index 0000000000..249d6f0d8c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisProperties.java @@ -0,0 +1,202 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.activemq.artemis.core.remoting.impl.invm.TransportConstants; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for Artemis + * + * @author Eddú Meléndez + * @author Stephane Nicoll + * @since 1.3.0 + */ +@ConfigurationProperties(prefix = "spring.artemis") +public class ArtemisProperties { + + /** + * Artemis deployment mode, auto-detected by default. Can be explicitly set to + * "native" or "embedded". + */ + private ArtemisMode mode; + + /** + * Artemis broker host. + */ + private String host = "localhost"; + + /** + * Artemis broker port. + */ + private int port = 61616; + + private final Embedded embedded = new Embedded(); + + public ArtemisMode getMode() { + return this.mode; + } + + public void setMode(ArtemisMode mode) { + this.mode = mode; + } + + public String getHost() { + return this.host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return this.port; + } + + public void setPort(int port) { + this.port = port; + } + + public Embedded getEmbedded() { + return this.embedded; + } + + /** + * Configuration for an embedded Artemis server. + */ + public static class Embedded { + + private static final AtomicInteger serverIdCounter = new AtomicInteger(); + + /** + * Server id. By default, an auto-incremented counter is used. + */ + private int serverId = serverIdCounter.getAndIncrement(); + + /** + * Enable embedded mode if the Artemis server APIs are available. + */ + private boolean enabled = true; + + /** + * Enable persistent store. + */ + private boolean persistent; + + /** + * Journal file directory. Not necessary if persistence is turned off. + */ + private String dataDirectory; + + /** + * Comma-separated list of queues to create on startup. + */ + private String[] queues = new String[0]; + + /** + * Comma-separated list of topics to create on startup. + */ + private String[] topics = new String[0]; + + /** + * Cluster password. Randomly generated on startup by default. + */ + private String clusterPassword = UUID.randomUUID().toString(); + + private boolean defaultClusterPassword = true; + + public int getServerId() { + return this.serverId; + } + + public void setServerId(int serverId) { + this.serverId = serverId; + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isPersistent() { + return this.persistent; + } + + public void setPersistent(boolean persistent) { + this.persistent = persistent; + } + + public String getDataDirectory() { + return this.dataDirectory; + } + + public void setDataDirectory(String dataDirectory) { + this.dataDirectory = dataDirectory; + } + + public String[] getQueues() { + return this.queues; + } + + public void setQueues(String[] queues) { + this.queues = queues; + } + + public String[] getTopics() { + return this.topics; + } + + public void setTopics(String[] topics) { + this.topics = topics; + } + + public String getClusterPassword() { + return this.clusterPassword; + } + + public void setClusterPassword(String clusterPassword) { + this.clusterPassword = clusterPassword; + this.defaultClusterPassword = false; + } + + public boolean isDefaultClusterPassword() { + return this.defaultClusterPassword; + } + + /** + * Creates the minimal transport parameters for an embedded transport + * configuration. + * @return the transport parameters + * @see TransportConstants#SERVER_ID_PROP_NAME + */ + public Map generateTransportParameters() { + Map parameters = new HashMap(); + parameters.put(TransportConstants.SERVER_ID_PROP_NAME, getServerId()); + return parameters; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java new file mode 100644 index 0000000000..783154a4c1 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisXAConnectionFactoryConfiguration.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import javax.jms.ConnectionFactory; +import javax.transaction.TransactionManager; + +import org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory; +import org.springframework.beans.factory.ListableBeanFactory; +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.jta.XAConnectionFactoryWrapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * Configuration for Artemis XA {@link ConnectionFactory}. + * + * @author Eddú Meléndez + * @author Phillip Webb + * @since 1.3.0 + */ +@Configuration +@ConditionalOnMissingBean(ConnectionFactory.class) +@ConditionalOnClass(TransactionManager.class) +@ConditionalOnBean(XAConnectionFactoryWrapper.class) +class ArtemisXAConnectionFactoryConfiguration { + + @Primary + @Bean(name = { "jmsConnectionFactory", "xaJmsConnectionFactory" }) + public ConnectionFactory jmsConnectionFactory(ListableBeanFactory beanFactory, + ArtemisProperties properties, XAConnectionFactoryWrapper wrapper) + throws Exception { + return wrapper.wrapConnectionFactory(new ArtemisConnectionFactoryFactory( + beanFactory, properties) + .createConnectionFactory(ActiveMQXAConnectionFactory.class)); + } + + @Bean + public ConnectionFactory nonXaJmsConnectionFactory(ListableBeanFactory beanFactory, + ArtemisProperties properties) { + return new ArtemisConnectionFactoryFactory(beanFactory, properties) + .createConnectionFactory(ActiveMQXAConnectionFactory.class); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/package-info.java new file mode 100644 index 0000000000..d069cf1234 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/artemis/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012-2015 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 + * + * http://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 Artemis. + * + * @author Eddú Meléndez + */ +package org.springframework.boot.autoconfigure.jms.artemis; diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 171256c361..05834600d2 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -32,6 +32,7 @@ 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.jms.hornetq.HornetQAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchDataAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java new file mode 100644 index 0000000000..95b880757b --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisAutoConfigurationTests.java @@ -0,0 +1,421 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory; +import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory; +import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; +import org.apache.activemq.artemis.jms.server.config.JMSConfiguration; +import org.apache.activemq.artemis.jms.server.config.JMSQueueConfiguration; +import org.apache.activemq.artemis.jms.server.config.TopicConfiguration; +import org.apache.activemq.artemis.jms.server.config.impl.JMSConfigurationImpl; +import org.apache.activemq.artemis.jms.server.config.impl.JMSQueueConfigurationImpl; +import org.apache.activemq.artemis.jms.server.config.impl.TopicConfigurationImpl; +import org.apache.activemq.artemis.jms.server.embedded.EmbeddedJMS; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jms.core.JmsTemplate; +import org.springframework.jms.core.MessageCreator; +import org.springframework.jms.core.SessionCallback; +import org.springframework.jms.support.destination.DestinationResolver; +import org.springframework.jms.support.destination.DynamicDestinationResolver; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link ArtemisAutoConfiguration}. + * + * @author Eddú Meléndez + * @author Stephane Nicoll + */ +public class ArtemisAutoConfigurationTests { + + @Rule + public final TemporaryFolder folder = new TemporaryFolder(); + + private AnnotationConfigApplicationContext context; + + @After + public void tearDown() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void nativeConnectionFactory() { + load(EmptyConfiguration.class, "spring.artemis.mode:native"); + JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class); + ActiveMQConnectionFactory connectionFactory = this.context + .getBean(ActiveMQConnectionFactory.class); + assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory); + assertNettyConnectionFactory(connectionFactory, "localhost", 61616); + } + + @Test + public void nativeConnectionFactoryCustomHost() { + load(EmptyConfiguration.class, "spring.artemis.mode:native", + "spring.artemis.host:192.168.1.144", "spring.artemis.port:9876"); + ActiveMQConnectionFactory connectionFactory = this.context + .getBean(ActiveMQConnectionFactory.class); + assertNettyConnectionFactory(connectionFactory, "192.168.1.144", 9876); + } + + @Test + public void embeddedConnectionFactory() { + load(EmptyConfiguration.class, "spring.artemis.mode:embedded"); + ArtemisProperties properties = this.context.getBean(ArtemisProperties.class); + assertEquals(ArtemisMode.EMBEDDED, properties.getMode()); + assertEquals(1, this.context.getBeansOfType(EmbeddedJMS.class).size()); + org.apache.activemq.artemis.core.config.Configuration configuration = this.context + .getBean(org.apache.activemq.artemis.core.config.Configuration.class); + assertFalse("Persistence disabled by default", + configuration.isPersistenceEnabled()); + assertFalse("Security disabled by default", configuration.isSecurityEnabled()); + + ActiveMQConnectionFactory connectionFactory = this.context + .getBean(ActiveMQConnectionFactory.class); + assertInVmConnectionFactory(connectionFactory); + } + + @Test + public void embeddedConnectionFactoryByDefault() { + // No mode is specified + load(EmptyConfiguration.class); + assertEquals(1, this.context.getBeansOfType(EmbeddedJMS.class).size()); + org.apache.activemq.artemis.core.config.Configuration configuration = this.context + .getBean(org.apache.activemq.artemis.core.config.Configuration.class); + assertFalse("Persistence disabled by default", + configuration.isPersistenceEnabled()); + assertFalse("Security disabled by default", configuration.isSecurityEnabled()); + + ActiveMQConnectionFactory connectionFactory = this.context + .getBean(ActiveMQConnectionFactory.class); + assertInVmConnectionFactory(connectionFactory); + } + + @Test + public void nativeConnectionFactoryIfEmbeddedServiceDisabledExplicitly() { + // No mode is specified + load(EmptyConfiguration.class, "spring.artemis.embedded.enabled:false"); + assertEquals(0, this.context.getBeansOfType(EmbeddedJMS.class).size()); + ActiveMQConnectionFactory connectionFactory = this.context + .getBean(ActiveMQConnectionFactory.class); + assertNettyConnectionFactory(connectionFactory, "localhost", 61616); + } + + @Test + public void embeddedConnectionFactorEvenIfEmbeddedServiceDisabled() { + // No mode is specified + load(EmptyConfiguration.class, "spring.artemis.mode:embedded", + "spring.artemis.embedded.enabled:false"); + assertEquals(0, this.context.getBeansOfType(EmbeddedJMS.class).size()); + ActiveMQConnectionFactory connectionFactory = this.context + .getBean(ActiveMQConnectionFactory.class); + assertInVmConnectionFactory(connectionFactory); + } + + @Test + public void embeddedServerWithDestinations() { + load(EmptyConfiguration.class, "spring.artemis.embedded.queues=Queue1,Queue2", + "spring.artemis.embedded.topics=Topic1"); + DestinationChecker checker = new DestinationChecker(this.context); + checker.checkQueue("Queue1", true); + checker.checkQueue("Queue2", true); + checker.checkQueue("QueueDoesNotExist", true); + checker.checkTopic("Topic1", true); + checker.checkTopic("TopicDoesNotExist", false); + } + + @Test + public void embeddedServerWithDestinationConfig() { + load(DestinationConfiguration.class); + DestinationChecker checker = new DestinationChecker(this.context); + checker.checkQueue("sampleQueue", true); + checker.checkTopic("sampleTopic", true); + } + + @Test + public void embeddedServiceWithCustomJmsConfiguration() { + // Ignored with custom config + load(CustomJmsConfiguration.class, "spring.artemis.embedded.queues=Queue1,Queue2"); + DestinationChecker checker = new DestinationChecker(this.context); + checker.checkQueue("custom", true); // See CustomJmsConfiguration + checker.checkQueue("Queue1", true); + checker.checkQueue("Queue2", true); + } + + @Test + public void embeddedServiceWithCustomArtemisConfiguration() { + load(CustomArtemisConfiguration.class); + org.apache.activemq.artemis.core.config.Configuration configuration = this.context + .getBean(org.apache.activemq.artemis.core.config.Configuration.class); + assertEquals("customFooBar", configuration.getName()); + } + + @Test + public void embeddedWithPersistentMode() throws IOException, JMSException { + File dataFolder = this.folder.newFolder(); + + // Start the server and post a message to some queue + load(EmptyConfiguration.class, "spring.artemis.embedded.queues=TestQueue", + "spring.artemis.embedded.persistent:true", + "spring.artemis.embedded.dataDirectory:" + dataFolder.getAbsolutePath()); + + final String msgId = UUID.randomUUID().toString(); + JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class); + jmsTemplate.send("TestQueue", new MessageCreator() { + @Override + public Message createMessage(Session session) throws JMSException { + return session.createTextMessage(msgId); + } + }); + this.context.close(); // Shutdown the broker + + // Start the server again and check if our message is still here + load(EmptyConfiguration.class, "spring.artemis.embedded.queues=TestQueue", + "spring.artemis.embedded.persistent:true", + "spring.artemis.embedded.dataDirectory:" + dataFolder.getAbsolutePath()); + + JmsTemplate jmsTemplate2 = this.context.getBean(JmsTemplate.class); + jmsTemplate2.setReceiveTimeout(1000L); + Message message = jmsTemplate2.receive("TestQueue"); + assertNotNull("No message on persistent queue", message); + assertEquals("Invalid message received on queue", msgId, + ((TextMessage) message).getText()); + } + + @Test + public void severalEmbeddedBrokers() { + load(EmptyConfiguration.class, "spring.artemis.embedded.queues=Queue1"); + AnnotationConfigApplicationContext anotherContext = doLoad( + EmptyConfiguration.class, "spring.artemis.embedded.queues=Queue2"); + try { + ArtemisProperties properties = this.context.getBean(ArtemisProperties.class); + ArtemisProperties anotherProperties = anotherContext + .getBean(ArtemisProperties.class); + assertTrue("ServerId should not match", properties.getEmbedded() + .getServerId() < anotherProperties.getEmbedded().getServerId()); + + DestinationChecker checker = new DestinationChecker(this.context); + checker.checkQueue("Queue1", true); + checker.checkQueue("Queue2", true); + + DestinationChecker anotherChecker = new DestinationChecker(anotherContext); + anotherChecker.checkQueue("Queue2", true); + anotherChecker.checkQueue("Queue1", true); + } + finally { + anotherContext.close(); + } + } + + @Test + public void connectToASpecificEmbeddedBroker() { + load(EmptyConfiguration.class, "spring.artemis.embedded.serverId=93", + "spring.artemis.embedded.queues=Queue1"); + AnnotationConfigApplicationContext anotherContext = doLoad( + EmptyConfiguration.class, "spring.artemis.mode=embedded", + "spring.artemis.embedded.serverId=93", // Connect to the "main" broker + "spring.artemis.embedded.enabled=false"); // do not start a specific one + try { + DestinationChecker checker = new DestinationChecker(this.context); + checker.checkQueue("Queue1", true); + + DestinationChecker anotherChecker = new DestinationChecker(anotherContext); + anotherChecker.checkQueue("Queue1", true); + } + finally { + anotherContext.close(); + } + } + + private TransportConfiguration assertInVmConnectionFactory( + ActiveMQConnectionFactory connectionFactory) { + TransportConfiguration transportConfig = getSingleTransportConfiguration(connectionFactory); + assertEquals(InVMConnectorFactory.class.getName(), + transportConfig.getFactoryClassName()); + return transportConfig; + } + + private TransportConfiguration assertNettyConnectionFactory( + ActiveMQConnectionFactory connectionFactory, String host, int port) { + TransportConfiguration transportConfig = getSingleTransportConfiguration(connectionFactory); + assertEquals(NettyConnectorFactory.class.getName(), + transportConfig.getFactoryClassName()); + assertEquals(host, transportConfig.getParams().get("host")); + assertEquals(port, transportConfig.getParams().get("port")); + return transportConfig; + } + + private TransportConfiguration getSingleTransportConfiguration( + ActiveMQConnectionFactory connectionFactory) { + TransportConfiguration[] transportConfigurations = connectionFactory + .getServerLocator().getStaticTransportConfigurations(); + assertEquals(1, transportConfigurations.length); + return transportConfigurations[0]; + } + + private void load(Class config, String... environment) { + this.context = doLoad(config, environment); + } + + private AnnotationConfigApplicationContext doLoad(Class config, + String... environment) { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.register(config); + applicationContext.register(ArtemisAutoConfigurationWithoutXA.class, + JmsAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(applicationContext, environment); + applicationContext.refresh(); + return applicationContext; + } + + private static class DestinationChecker { + + private final JmsTemplate jmsTemplate; + + private final DestinationResolver destinationResolver; + + private DestinationChecker(ApplicationContext applicationContext) { + this.jmsTemplate = applicationContext.getBean(JmsTemplate.class); + this.destinationResolver = new DynamicDestinationResolver(); + } + + public void checkQueue(String name, boolean shouldExist) { + checkDestination(name, false, shouldExist); + } + + public void checkTopic(String name, boolean shouldExist) { + checkDestination(name, true, shouldExist); + } + + public void checkDestination(final String name, final boolean pubSub, + final boolean shouldExist) { + this.jmsTemplate.execute(new SessionCallback() { + @Override + public Void doInJms(Session session) throws JMSException { + try { + Destination destination = DestinationChecker.this.destinationResolver + .resolveDestinationName(session, name, pubSub); + if (!shouldExist) { + throw new IllegalStateException("Destination '" + name + + "' was not expected but got " + destination); + } + } + catch (JMSException e) { + if (shouldExist) { + throw new IllegalStateException("Destination '" + name + + "' was expected but got " + e.getMessage()); + } + } + return null; + } + }); + } + } + + @Configuration + protected static class EmptyConfiguration { + } + + @Configuration + protected static class DestinationConfiguration { + + @Bean + JMSQueueConfiguration sampleQueueConfiguration() { + JMSQueueConfigurationImpl jmsQueueConfiguration = new JMSQueueConfigurationImpl(); + jmsQueueConfiguration.setName("sampleQueue"); + jmsQueueConfiguration.setSelector("foo=bar"); + jmsQueueConfiguration.setDurable(false); + jmsQueueConfiguration.setBindings("/queue/1"); + return jmsQueueConfiguration; + } + + @Bean + TopicConfiguration sampleTopicConfiguration() { + TopicConfigurationImpl topicConfiguration = new TopicConfigurationImpl(); + topicConfiguration.setName("sampleTopic"); + topicConfiguration.setBindings("/topic/1"); + return topicConfiguration; + } + } + + @Configuration + protected static class CustomJmsConfiguration { + + @Bean + public JMSConfiguration myJmsConfiguration() { + JMSConfiguration config = new JMSConfigurationImpl(); + JMSQueueConfiguration jmsQueueConfiguration = new JMSQueueConfigurationImpl(); + jmsQueueConfiguration.setName("custom"); + jmsQueueConfiguration.setDurable(false); + config.getQueueConfigurations().add(jmsQueueConfiguration); + return config; + } + } + + @Configuration + protected static class CustomArtemisConfiguration { + + @Bean + public ArtemisConfigurationCustomizer myArtemisCustomize() { + return new ArtemisConfigurationCustomizer() { + @Override + public void customize( + org.apache.activemq.artemis.core.config.Configuration configuration) { + configuration.setClusterPassword("Foobar"); + configuration.setName("customFooBar"); + } + }; + } + + } + + @Configuration + @EnableConfigurationProperties(ArtemisProperties.class) + @Import({ ArtemisEmbeddedServerConfiguration.class, + ArtemisConnectionFactoryConfiguration.class }) + protected static class ArtemisAutoConfigurationWithoutXA { + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java new file mode 100644 index 0000000000..81320f36de --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/artemis/ArtemisEmbeddedConfigurationFactoryTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2015 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jms.artemis; + +import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.server.JournalType; +import org.junit.Test; + +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link ArtemisEmbeddedConfigurationFactory} + * + * @author Eddú Meléndez + * @author Stephane Nicol + * @author Phillip Webb + */ +public class ArtemisEmbeddedConfigurationFactoryTests { + + @Test + public void defaultDataDir() { + ArtemisProperties properties = new ArtemisProperties(); + properties.getEmbedded().setPersistent(true); + Configuration configuration = new ArtemisEmbeddedConfigurationFactory(properties) + .createConfiguration(); + assertThat(configuration.getJournalDirectory(), + startsWith(System.getProperty("java.io.tmpdir"))); + assertThat(configuration.getJournalDirectory(), endsWith("/journal")); + } + + @Test + public void persistenceSetup() { + ArtemisProperties properties = new ArtemisProperties(); + properties.getEmbedded().setPersistent(true); + Configuration configuration = new ArtemisEmbeddedConfigurationFactory(properties) + .createConfiguration(); + assertThat(configuration.isPersistenceEnabled(), equalTo(true)); + assertThat(configuration.getJournalType(), equalTo(JournalType.NIO)); + } + + @Test + public void generatedClusterPassword() throws Exception { + ArtemisProperties properties = new ArtemisProperties(); + Configuration configuration = new ArtemisEmbeddedConfigurationFactory(properties) + .createConfiguration(); + assertThat(configuration.getClusterPassword().length(), equalTo(36)); + } + + @Test + public void specificClusterPassword() throws Exception { + ArtemisProperties properties = new ArtemisProperties(); + properties.getEmbedded().setClusterPassword("password"); + Configuration configuration = new ArtemisEmbeddedConfigurationFactory(properties) + .createConfiguration(); + assertThat(configuration.getClusterPassword(), equalTo("password")); + } + +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 223babbdad..a22347cfed 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -48,6 +48,7 @@ 5.11.1 2.7.7 + 1.0.0 1.8.6 3.9.3 2.1.4 @@ -933,6 +934,16 @@ activemq-web-console ${activemq.version} + + org.apache.activemq + artemis-jms-client + ${artemis.version} + + + org.apache.activemq + artemis-jms-server + ${artemis.version} + org.apache.commons commons-dbcp2 From 6877c4c40f38415e2ba3f064e4c7185278eed3d6 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 18 Jun 2015 20:59:55 -0700 Subject: [PATCH 2/3] Add Apache Artemis Starter POM See gh-3154 --- spring-boot-starters/pom.xml | 1 + .../spring-boot-starter-artemis/pom.xml | 34 +++++++++++++++++++ .../main/resources/META-INF/spring.provides | 1 + 3 files changed, 36 insertions(+) create mode 100644 spring-boot-starters/spring-boot-starter-artemis/pom.xml create mode 100644 spring-boot-starters/spring-boot-starter-artemis/src/main/resources/META-INF/spring.provides diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index 9b56298077..c683196be9 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -23,6 +23,7 @@ spring-boot-starter spring-boot-starter-amqp spring-boot-starter-aop + spring-boot-starter-artemis spring-boot-starter-batch spring-boot-starter-cache spring-boot-starter-cloud-connectors diff --git a/spring-boot-starters/spring-boot-starter-artemis/pom.xml b/spring-boot-starters/spring-boot-starter-artemis/pom.xml new file mode 100644 index 0000000000..5b4c7582c3 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-artemis/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 1.3.0.BUILD-SNAPSHOT + + spring-boot-starter-artemis + Spring Boot Artemis Starter + Spring Boot Artemis Starter + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.springframework + spring-jms + + + org.apache.activemq + artemis-jms-client + + + diff --git a/spring-boot-starters/spring-boot-starter-artemis/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-artemis/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000000..7c604a7157 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-artemis/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: artemis-jms-client,spring-jms From 3e4bbf05cd9aa433b77a00385b49a362651b0be5 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 18 Jun 2015 20:51:48 -0700 Subject: [PATCH 3/3] Document Apache Artemis support See gh-3154 --- .../appendix-application-properties.adoc | 14 ++++- .../main/asciidoc/spring-boot-features.adoc | 63 +++++++++++-------- .../src/main/asciidoc/using-spring-boot.adoc | 3 + 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index aaebefbed2..0fae679d16 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -484,7 +484,19 @@ content into your application; rather pick only the properties that you need. spring.activemq.in-memory=true # broker kind to create if no broker-url is specified spring.activemq.pooled=false - # HornetQ ({sc-spring-boot-autoconfigure}/jms/hornetq/HornetQProperties.{sc-ext}[HornetQProperties]) + # ARTEMIS ({sc-spring-boot-autoconfigure}/jms/artemis/ArtemisProperties.{sc-ext}[ArtemisProperties]) + spring.artemis.mode= # connection mode (native, embedded) + spring.artemis.host=localhost # hornetQ host (native mode) + spring.artemis.port=5445 # hornetQ port (native mode) + spring.artemis.embedded.enabled=true # if the embedded server is enabled (needs hornetq-jms-server.jar) + spring.artemis.embedded.server-id= # auto-generated id of the embedded server (integer) + spring.artemis.embedded.persistent=false # message persistence + spring.artemis.embedded.data-directory= # location of data content (when persistence is enabled) + spring.artemis.embedded.queues= # comma-separated queues to create on startup + spring.artemis.embedded.topics= # comma-separated topics to create on startup + spring.artemis.embedded.cluster-password= # customer password (randomly generated by default) + + # HORNETQ ({sc-spring-boot-autoconfigure}/jms/hornetq/HornetQProperties.{sc-ext}[HornetQProperties]) spring.hornetq.mode= # connection mode (native, embedded) spring.hornetq.host=localhost # hornetQ host (native mode) spring.hornetq.port=5445 # hornetQ port (native mode) diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 7abe2979ce..5461bdbd13 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2515,6 +2515,43 @@ to send and receive messages. +[[boot-features-activemq]] +==== ActiveMQ support +Spring Boot can also configure a `ConnectionFactory` when it detects that ActiveMQ is +available on the classpath. If the broker is present, an embedded broker is started and +configured automatically (as long as no broker URL is specified through configuration). + +ActiveMQ configuration is controlled by external configuration properties in +`+spring.activemq.*+`. For example, you might declare the following section in +`application.properties`: + +[source,properties,indent=0] +---- + spring.activemq.broker-url=tcp://192.168.1.210:9876 + spring.activemq.user=admin + spring.activemq.password=secret +---- + +See +{sc-spring-boot-autoconfigure}/jms/activemq/ActiveMQProperties.{sc-ext}[`ActiveMQProperties`] +for more of the supported options. + +By default, ActiveMQ creates a destination if it does not exist yet, so destinations are +resolved against their provided names. + + + +[[boot-features-artemis]] +==== Artemis support +Apache Artemis was formed in 2015 when HornetQ was donated to the Apache Foundation. All +the features listed in the <> section below can be applied to +Artemis. Simply replace `+++spring.hornetq.*+++` properties with `+++spring.artemis.*+++` +and use `spring-boot-starter-artemis` instead of `spring-boot-starter-hornetq`. + +NOTE: You should not try and use Artemis and HornetQ and the same time. + + + [[boot-features-hornetq]] ==== HornetQ support Spring Boot can auto-configure a `ConnectionFactory` when it detects that HornetQ is @@ -2559,32 +2596,6 @@ through configuration. -[[boot-features-activemq]] -==== ActiveMQ support -Spring Boot can also configure a `ConnectionFactory` when it detects that ActiveMQ is -available on the classpath. If the broker is present, an embedded broker is started and -configured automatically (as long as no broker URL is specified through configuration). - -ActiveMQ configuration is controlled by external configuration properties in -`+spring.activemq.*+`. For example, you might declare the following section in -`application.properties`: - -[source,properties,indent=0] ----- - spring.activemq.broker-url=tcp://192.168.1.210:9876 - spring.activemq.user=admin - spring.activemq.password=secret ----- - -See -{sc-spring-boot-autoconfigure}/jms/activemq/ActiveMQProperties.{sc-ext}[`ActiveMQProperties`] -for more of the supported options. - -By default, ActiveMQ creates a destination if it does not exist yet, so destinations are -resolved against their provided names. - - - [[boot-features-jms-jndi]] ==== Using a JNDI ConnectionFactory If you are running your application in an Application Server Spring Boot will attempt to diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 5dcc8ba3ee..362bae95f1 100644 --- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -264,6 +264,9 @@ The following application starters are provided by Spring Boot under the |`spring-boot-starter-aop` |Support for aspect-oriented programming including `spring-aop` and AspectJ. +|`spring-boot-starter-artemis` +|Support for "`Java Message Service API`" via Apache Artemis. + |`spring-boot-starter-batch` |Support for "`Spring Batch`" including HSQLDB database.