Add HornetQ JMS support

Provide auto-configuration support for HornetQ JMS broker, along with
an additional starter POM.

The connection factory connects to a broker available on the local
machine by default. A configuration switch allows to enable an embedded
mode that starts HornetQ as part of the application.

In such a mode, the spring.hornetq.embedded.* properties provide
additional options to configure the embedded broker. In particular,
message persistence and data directory locations can be specified. It is
also possible to define the queue(s) and topic(s) to create on startup.

Fixes: gh-765
This commit is contained in:
Stephane Nicoll 2014-05-28 14:41:34 +02:00 committed by Phillip Webb
parent 67beba9464
commit 5a69bb9267
23 changed files with 1230 additions and 0 deletions

View File

@ -111,6 +111,16 @@
<artifactId>hibernate-jpa-2.0-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-client</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-server</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>

View File

@ -0,0 +1,212 @@
/*
* Copyright 2012-2014 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;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jms.ConnectionFactory;
import org.hornetq.api.core.TransportConfiguration;
import org.hornetq.api.core.client.HornetQClient;
import org.hornetq.api.core.client.ServerLocator;
import org.hornetq.api.jms.HornetQJMSClient;
import org.hornetq.api.jms.JMSFactoryType;
import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory;
import org.hornetq.core.remoting.impl.netty.NettyConnectorFactory;
import org.hornetq.core.remoting.impl.netty.TransportConstants;
import org.hornetq.jms.client.HornetQConnectionFactory;
import org.hornetq.jms.server.config.JMSConfiguration;
import org.hornetq.jms.server.config.JMSQueueConfiguration;
import org.hornetq.jms.server.config.TopicConfiguration;
import org.hornetq.jms.server.config.impl.JMSConfigurationImpl;
import org.hornetq.jms.server.config.impl.JMSQueueConfigurationImpl;
import org.hornetq.jms.server.config.impl.TopicConfigurationImpl;
import org.hornetq.jms.server.embedded.EmbeddedJMS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
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.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.ClassUtils;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} to integrate with an HornetQ broker. Connect by default to a broker
* available on the local machine with the default settings. If the necessary classes are
* present, the broker can also be embedded in the application itself.
*
* @author Stephane Nicoll
* @since 1.1.0
*/
@Configuration
@AutoConfigureBefore(JmsAutoConfiguration.class)
@ConditionalOnClass({ ConnectionFactory.class, HornetQJMSClient.class })
@EnableConfigurationProperties(HornetQProperties.class)
public class HornetQAutoConfiguration {
private static final String EMBEDDED_JMS_CLASS = "org.hornetq.jms.server.embedded.EmbeddedJMS";
@Autowired
private HornetQProperties properties;
/**
* Create the {@link ConnectionFactory} to use if none is provided. If no
* {@linkplain HornetQProperties#getMode() mode} has been explicitly set, connect to
* the embedded server if it has been requested or to a broker available on the local
* machine with the default settings otherwise.
*/
@Bean
@ConditionalOnMissingBean
public ConnectionFactory jmsConnectionFactory() {
HornetQMode mode = this.properties.getMode();
if (mode == null) {
mode = deduceMode();
}
if (mode == HornetQMode.EMBEDDED) {
return createEmbeddedConnectionFactory();
}
return createNativeConnectionFactory();
}
/**
* Deduce the {@link HornetQMode} to use if none has been set.
*/
private HornetQMode deduceMode() {
if (this.properties.getEmbedded().isEnabled()
&& ClassUtils.isPresent(EMBEDDED_JMS_CLASS, null)) {
return HornetQMode.EMBEDDED;
}
return HornetQMode.NATIVE;
}
private ConnectionFactory createEmbeddedConnectionFactory() {
try {
TransportConfiguration transportConfiguration = new TransportConfiguration(
InVMConnectorFactory.class.getName());
ServerLocator serviceLocator = HornetQClient
.createServerLocatorWithoutHA(transportConfiguration);
return new HornetQConnectionFactory(serviceLocator);
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Unable to create InVM "
+ "HornetQ connection, ensure that hornet-jms-server.jar "
+ "is in the classpath", ex);
}
}
private ConnectionFactory createNativeConnectionFactory() {
Map<String, Object> params = new HashMap<String, Object>();
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);
return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF,
transportConfiguration);
}
/**
* Configuration used to create the embedded HornetQ server.
*/
@Configuration
@ConditionalOnClass(name = EMBEDDED_JMS_CLASS)
@ConditionalOnProperty(prefix = "spring.hornetq.embedded", value = "enabled")
static class EmbeddedServerConfiguration {
@Autowired
private HornetQProperties properties;
@Autowired(required = false)
private List<HornetQConfigurationCustomizer> configurationCustomizers;
@Autowired(required = false)
private List<JMSQueueConfiguration> queuesConfiguration;
@Autowired(required = false)
private List<TopicConfiguration> topicsConfiguration;
@Bean
@ConditionalOnMissingBean
public org.hornetq.core.config.Configuration hornetQConfiguration() {
return new HornetQEmbeddedConfigurationFactory(this.properties)
.createConfiguration();
}
@Bean(initMethod = "start", destroyMethod = "stop")
@ConditionalOnMissingBean
public EmbeddedJMS hornetQServer(
org.hornetq.core.config.Configuration configuration,
JMSConfiguration jmsConfiguration) {
EmbeddedJMS server = new EmbeddedJMS();
applyCustomizers(configuration);
server.setConfiguration(configuration);
server.setJmsConfiguration(jmsConfiguration);
server.setRegistry(new HornetQNoOpBindingRegistry());
return server;
}
private void applyCustomizers(org.hornetq.core.config.Configuration configuration) {
if (this.configurationCustomizers != null) {
AnnotationAwareOrderComparator.sort(this.configurationCustomizers);
for (HornetQConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
}
@Bean
@ConditionalOnMissingBean
public JMSConfiguration hornetQJmsConfiguration() {
JMSConfiguration configuration = new JMSConfigurationImpl();
addAll(configuration.getQueueConfigurations(), this.queuesConfiguration);
addAll(configuration.getTopicConfigurations(), this.topicsConfiguration);
addQueues(configuration, this.properties.getEmbedded().getQueues());
addTopis(configuration, this.properties.getEmbedded().getTopics());
return configuration;
}
private <T> void addAll(List<T> list, Collection<? extends T> items) {
if (items != null) {
list.addAll(items);
}
}
private void addQueues(JMSConfiguration configuration, String[] queues) {
boolean persistent = this.properties.getEmbedded().isPersistent();
for (String queue : queues) {
configuration.getQueueConfigurations().add(
new JMSQueueConfigurationImpl(queue, null, persistent, "/queue/"
+ queue));
}
}
private void addTopis(JMSConfiguration configuration, String[] topics) {
for (String topic : topics) {
configuration.getTopicConfigurations().add(
new TopicConfigurationImpl(topic, "/topic/" + topic));
}
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2012-2014 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;
import org.hornetq.core.config.Configuration;
import org.hornetq.jms.server.embedded.EmbeddedJMS;
/**
* Callback interface that can be implemented by beans wishing to customize the HornetQ
* JMS server {@link Configuration} before it is used by an auto-configured
* {@link EmbeddedJMS} instance.
*
* @author Phillip Webb
* @since 1.1.0
* @see HornetQAutoConfiguration
*/
public interface HornetQConfigurationCustomizer {
/**
* Customize the configuration.
* @param configuration the configuration to customize
*/
void customize(Configuration configuration);
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2012-2014 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;
import java.io.File;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hornetq.api.core.TransportConfiguration;
import org.hornetq.core.config.Configuration;
import org.hornetq.core.config.impl.ConfigurationImpl;
import org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory;
import org.hornetq.core.server.JournalType;
import org.springframework.boot.autoconfigure.jms.HornetQProperties.Embedded;
/**
* Factory class to create a HornetQ {@link Configuration} from {@link HornetQProperties}.
*
* @author Stephane Nicol
* @author Phillip Webb
* @since 1.1.0
*/
class HornetQEmbeddedConfigurationFactory {
private Log logger = LogFactory.getLog(HornetQAutoConfiguration.class);
private final Embedded properties;
public HornetQEmbeddedConfigurationFactory(HornetQProperties properties) {
this.properties = properties.getEmbedded();
}
public Configuration createConfiguration() {
ConfigurationImpl configuration = new ConfigurationImpl();
configuration.setSecurityEnabled(false);
configuration.setPersistenceEnabled(this.properties.isPersistent());
String dataDir = getDataDir();
// HORNETQ-1302
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());
configuration.getAcceptorConfigurations().add(transportConfiguration);
// HORNETQ-1143
if (this.properties.isDefaultClusterPassword()) {
this.logger.debug("Using default HornetQ 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, "hornetq-data").getAbsolutePath();
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2012-2014 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;
/**
* Define the mode in which HornetQ can operate.
*
* @author Stephane Nicoll
* @since 1.1.0
*/
public enum HornetQMode {
/**
* Connect to a broker using the native HornetQ protocol (i.e. netty).
*/
NATIVE,
/**
* Embed (i.e. start) the broker in the application.
*/
EMBEDDED
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2012-2014 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;
import org.hornetq.spi.core.naming.BindingRegistry;
/**
* A no-op implementation of the {@link org.hornetq.spi.core.naming.BindingRegistry}.
*
* @author Stephane Nicoll
* @since 1.1.0
*/
public class HornetQNoOpBindingRegistry implements BindingRegistry {
@Override
public Object lookup(String name) {
// This callback is used to check if an entry is present in the context before
// creating a queue on the fly. This is actually never used to try to fetch a
// destination that is unknown.
return null;
}
@Override
public boolean bind(String name, Object obj) {
// This callback is used bind a Destination created on the fly by the embedded
// broker using the JNDI name that was specified in the configuration. This does
// not look very useful since it's used nowhere. It could be interesting to
// autowire a destination to use it but the wiring is a bit "asynchronous" so
// better not provide that feature at all.
return false;
}
@Override
public void unbind(String name) {
}
@Override
public void close() {
}
@Override
public Object getContext() {
return this;
}
@Override
public void setContext(Object ctx) {
}
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2012-2014 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;
import java.util.UUID;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for HornetQ
*
* @author Stephane Nicoll
* @since 1.1.0
*/
@ConfigurationProperties(prefix = "spring.hornetq")
public class HornetQProperties {
private HornetQMode mode;
private String host = "localhost";
private int port = 5445;
private final Embedded embedded = new Embedded();
public HornetQMode getMode() {
return this.mode;
}
public void setMode(HornetQMode 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 HornetQ server.
*/
public static class Embedded {
private boolean enabled;
private boolean persistent;
private String dataDirectory;
private String[] queues = new String[0];
private String[] topics = new String[0];
private String clusterPassword = UUID.randomUUID().toString();
private boolean defaultClusterPassword = true;
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;
}
}
}

View File

@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.jms;
import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -37,6 +38,7 @@ import org.springframework.jms.core.JmsTemplate;
@ConditionalOnClass(JmsTemplate.class)
@ConditionalOnBean(ConnectionFactory.class)
@EnableConfigurationProperties(JmsProperties.class)
@AutoConfigureAfter({ HornetQAutoConfiguration.class, ActiveMQAutoConfiguration.class })
public class JmsAutoConfiguration {
@Autowired

View File

@ -19,6 +19,7 @@ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.HornetQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\

View File

@ -30,6 +30,7 @@ import static org.junit.Assert.assertEquals;
public class ActiveMQPropertiesTests {
private final ActiveMQProperties properties = new ActiveMQProperties();
private final StandardEnvironment environment = new StandardEnvironment();
@Test

View File

@ -0,0 +1,341 @@
/*
* Copyright 2012-2014 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;
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.hornetq.api.core.TransportConfiguration;
import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory;
import org.hornetq.core.remoting.impl.netty.NettyConnectorFactory;
import org.hornetq.jms.client.HornetQConnectionFactory;
import org.hornetq.jms.server.config.JMSConfiguration;
import org.hornetq.jms.server.config.JMSQueueConfiguration;
import org.hornetq.jms.server.config.TopicConfiguration;
import org.hornetq.jms.server.config.impl.JMSConfigurationImpl;
import org.hornetq.jms.server.config.impl.JMSQueueConfigurationImpl;
import org.hornetq.jms.server.config.impl.TopicConfigurationImpl;
import org.hornetq.jms.server.embedded.EmbeddedJMS;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.beans.factory.annotation.Autowired;
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.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;
/**
* Tests for {@link HornetQAutoConfiguration}.
*
* @author Stephane Nicoll
*/
public class HornetQAutoConfigurationTests {
@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.hornetq.mode:native");
JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class);
HornetQConnectionFactory connectionFactory = this.context
.getBean(HornetQConnectionFactory.class);
assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory);
assertNettyConnectionFactory(connectionFactory, "localhost", 5445);
}
@Test
public void nativeConnectionFactoryCustomHost() {
load(EmptyConfiguration.class, "spring.hornetq.host:192.168.1.144",
"spring.hornetq.port:9876");
HornetQConnectionFactory connectionFactory = this.context
.getBean(HornetQConnectionFactory.class);
assertNettyConnectionFactory(connectionFactory, "192.168.1.144", 9876);
}
@Test
public void embeddedConnectionFactory() {
load(EmptyConfiguration.class, "spring.hornetq.mode:embedded",
"spring.hornetq.embedded.enabled:true");
HornetQProperties properties = this.context.getBean(HornetQProperties.class);
assertEquals(HornetQMode.EMBEDDED, properties.getMode());
assertEquals(1, this.context.getBeansOfType(EmbeddedJMS.class).size());
org.hornetq.core.config.Configuration configuration = this.context
.getBean(org.hornetq.core.config.Configuration.class);
assertFalse("Persistence disabled by default",
configuration.isPersistenceEnabled());
assertFalse("Security disabled by default", configuration.isSecurityEnabled());
HornetQConnectionFactory connectionFactory = this.context
.getBean(HornetQConnectionFactory.class);
assertInVmConnectionFactory(connectionFactory);
}
@Test
public void nativeConnectionFactoryByDefault() {
// No mode is specified
load(EmptyConfiguration.class);
HornetQConnectionFactory connectionFactory = this.context
.getBean(HornetQConnectionFactory.class);
assertNettyConnectionFactory(connectionFactory, "localhost", 5445);
}
@Test
public void embeddedConnectionFactoryIfEmbeddedServiceEnabled() {
// No mode enabled, embedded server required
load(EmptyConfiguration.class, "spring.hornetq.embedded.enabled:true");
HornetQConnectionFactory connectionFactory = this.context
.getBean(HornetQConnectionFactory.class);
assertInVmConnectionFactory(connectionFactory);
}
@Test
public void embeddedServerWithDestinations() {
load(EmptyConfiguration.class, "spring.hornetq.embedded.enabled:true",
"spring.hornetq.embedded.queues=Queue1,Queue2",
"spring.hornetq.embedded.topics=Topic1");
DestinationChecker checker = new DestinationChecker(this.context);
checker.checkQueue("Queue1", true);
checker.checkQueue("Queue2", true);
checker.checkQueue("QueueDoesNotExist", false);
checker.checkTopic("Topic1", true);
checker.checkTopic("TopicDoesNotExist", false);
}
@Test
public void embeddedServerWithDestinationConfig() {
load(DestinationConfiguration.class, "spring.hornetq.embedded.enabled:true");
DestinationChecker checker = new DestinationChecker(this.context);
checker.checkQueue("sampleQueue", true);
checker.checkTopic("sampleTopic", true);
}
@Test
public void embeddedServiceWithCustomJmsConfiguration() {
load(CustomJmsConfiguration.class, "spring.hornetq.embedded.enabled:true",
"spring.hornetq.embedded.queues=Queue1,Queue2"); // Ignored with custom
// config
DestinationChecker checker = new DestinationChecker(this.context);
checker.checkQueue("custom", true); // See CustomJmsConfiguration
checker.checkQueue("Queue1", false);
checker.checkQueue("Queue2", false);
}
@Test
public void embeddedServiceWithCustomHornetQConfiguration() {
load(CustomHornetQConfiguration.class, "spring.hornetq.embedded.enabled:true");
org.hornetq.core.config.Configuration configuration = this.context
.getBean(org.hornetq.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.hornetq.embedded.enabled:true",
"spring.hornetq.embedded.queues=TestQueue",
"spring.hornetq.embedded.persistent:true",
"spring.hornetq.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.hornetq.embedded.enabled:true",
"spring.hornetq.embedded.queues=TestQueue",
"spring.hornetq.embedded.persistent:true",
"spring.hornetq.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());
}
private TransportConfiguration assertInVmConnectionFactory(
HornetQConnectionFactory connectionFactory) {
TransportConfiguration transportConfig = getSingleTransportConfiguration(connectionFactory);
assertEquals(InVMConnectorFactory.class.getName(),
transportConfig.getFactoryClassName());
return transportConfig;
}
private TransportConfiguration assertNettyConnectionFactory(
HornetQConnectionFactory 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(
HornetQConnectionFactory connectionFactory) {
TransportConfiguration[] transportConfigurations = connectionFactory
.getServerLocator().getStaticTransportConfigurations();
assertEquals(1, transportConfigurations.length);
return transportConfigurations[0];
}
private void load(Class<?> config, String... environment) {
this.context = new AnnotationConfigApplicationContext();
this.context.register(config);
this.context.register(HornetQAutoConfiguration.class, JmsAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, environment);
this.context.refresh();
}
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<Void>() {
@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() {
return new JMSQueueConfigurationImpl("sampleQueue", "foo=bar", false,
"/queue/1");
}
@Bean
TopicConfiguration sampleTopicConfiguration() {
return new TopicConfigurationImpl("sampleTopic", "/topic/1");
}
}
@Configuration
protected static class CustomJmsConfiguration {
@Bean
public JMSConfiguration myJmsConfiguration() {
JMSConfiguration config = new JMSConfigurationImpl();
config.getQueueConfigurations().add(
new JMSQueueConfigurationImpl("custom", null, false));
return config;
}
}
@Configuration
protected static class CustomHornetQConfiguration {
@Autowired
private HornetQProperties properties;
@Bean
public HornetQConfigurationCustomizer myHornetQCustomize() {
return new HornetQConfigurationCustomizer() {
@Override
public void customize(org.hornetq.core.config.Configuration configuration) {
configuration.setClusterPassword("Foobar");
configuration.setName("customFooBar");
}
};
}
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2012-2014 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;
import org.hornetq.core.config.Configuration;
import org.hornetq.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 HornetQEmbeddedConfigurationFactory}.
*
* @author Stephane Nicol
* @author Phillip Webb
*/
public class HornetQEmbeddedConfigurationFactoryTests {
@Test
public void defaultDataDir() {
HornetQProperties properties = new HornetQProperties();
properties.getEmbedded().setPersistent(true);
Configuration configuration = new HornetQEmbeddedConfigurationFactory(properties)
.createConfiguration();
assertThat(configuration.getJournalDirectory(),
startsWith(System.getProperty("java.io.tmpdir")));
assertThat(configuration.getJournalDirectory(), endsWith("/journal"));
}
@Test
public void persistenceSetup() {
HornetQProperties properties = new HornetQProperties();
properties.getEmbedded().setPersistent(true);
Configuration configuration = new HornetQEmbeddedConfigurationFactory(properties)
.createConfiguration();
assertThat(configuration.isPersistenceEnabled(), equalTo(true));
assertThat(configuration.getJournalType(), equalTo(JournalType.NIO));
}
@Test
public void generatedClusterPassoword() throws Exception {
HornetQProperties properties = new HornetQProperties();
Configuration configuration = new HornetQEmbeddedConfigurationFactory(properties)
.createConfiguration();
assertThat(configuration.getClusterPassword().length(), equalTo(36));
}
@Test
public void specificClusterPassoword() throws Exception {
HornetQProperties properties = new HornetQProperties();
properties.getEmbedded().setClusterPassword("password");
Configuration configuration = new HornetQEmbeddedConfigurationFactory(properties)
.createConfiguration();
assertThat(configuration.getClusterPassword(), equalTo("password"));
}
}

View File

@ -68,6 +68,7 @@
<hikaricp.version>1.3.8</hikaricp.version>
<httpclient.version>4.3.3</httpclient.version>
<httpasyncclient.version>4.0.1</httpasyncclient.version>
<hornetq.version>2.4.1.Final</hornetq.version>
<hsqldb.version>2.3.2</hsqldb.version>
<jackson.version>2.3.3</jackson.version>
<javassist.version>3.18.1-GA</javassist.version> <!-- Same as Hibernate -->
@ -212,6 +213,11 @@
<artifactId>spring-boot-starter-groovy-templates</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hornetq</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
@ -714,6 +720,16 @@
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>${hibernate-jpa-api.version}</version>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-server</artifactId>
<version>${hornetq.version}</version>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-client</artifactId>
<version>${hornetq.version}</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>

View File

@ -242,6 +242,17 @@ 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/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)
spring.hornetq.embedded.enabled=true # if the embedded server is enabled (needs hornetq-jms-server.jar)
spring.hornetq.embedded.persistent=false # message persistence
spring.hornetq.embedded.data-directory= # location of data content (when persistence is enabled)
spring.hornetq.embedded.queues= # comma separate queues to create on startup
spring.hornetq.embedded.topics= # comma separate topics to create on startup
spring.hornetq.embedded.cluster-password = # customer password (randomly generated by default)
# JMS ({sc-spring-boot-autoconfigure}/jms/JmsTemplateProperties.{sc-ext}[JmsTemplateProperties])
spring.jms.pub-sub-domain= # false for queue (default), true for topic

View File

@ -53,6 +53,12 @@ The following auto-configuration classes are from the `spring-boot-autoconfigure
|{sc-spring-boot-autoconfigure}/web/HttpMessageConvertersAutoConfiguration.{sc-ext}[HttpMessageConvertersAutoConfiguration]
|{dc-spring-boot-autoconfigure}/web/HttpMessageConvertersAutoConfiguration.{dc-ext}[javadoc]
|{sc-spring-boot-autoconfigure}/jms/ActiveMQAutoConfiguration.{sc-ext}[ActiveMQAutoConfiguration]
|{dc-spring-boot-autoconfigure}/jms/ActiveMQAutoConfiguration.{dc-ext}[javadoc]
|{sc-spring-boot-autoconfigure}/jms/HornetQAutoConfiguration.{sc-ext}[HornetQAutoConfiguration]
|{dc-spring-boot-autoconfigure}/jms/HornetQAutoConfiguration.{dc-ext}[javadoc]
|{sc-spring-boot-autoconfigure}/jms/JmsTemplateAutoConfiguration.{sc-ext}[JmsTemplateAutoConfiguration]
|{dc-spring-boot-autoconfigure}/jms/JmsTemplateAutoConfiguration.{dc-ext}[javadoc]

View File

@ -245,6 +245,9 @@ and Hibernate.
|`spring-boot-starter-groovy-templates`
|Support for the Groovy templating engine
|`spring-boot-starter-hornetq`
|Support for ``Java Message Service API'' via HornetQ.
|`spring-boot-starter-integration`
|Support for common `spring-integration` modules.

View File

@ -34,6 +34,7 @@
<module>spring-boot-sample-data-rest</module>
<module>spring-boot-sample-data-solr</module>
<module>spring-boot-sample-flyway</module>
<module>spring-boot-sample-hornetq</module>
<module>spring-boot-sample-integration</module>
<module>spring-boot-sample-jetty</module>
<module>spring-boot-sample-liquibase</module>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-hornetq</artifactId>
<name>Spring Boot HornetQ Sample</name>
<description>Spring Boot HornetQ Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hornetq</artifactId>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,68 @@
/*
* Copyright 2012-2013 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 sample.hornetq;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class SampleHornetQApplication {
@Autowired
private ConnectionFactory connectionFactory;
@Bean
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
@Bean
public DefaultMessageListenerContainer messageListener() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(this.connectionFactory);
container.setDestinationName("testQueue");
container.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
System.out.println(message.getBody(Object.class));
}
catch (JMSException ex) {
ex.printStackTrace();
}
}
});
return container;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleHornetQApplication.class, args);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2012-2013 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 sample.hornetq;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
private JmsTemplate jmsTemplate;
@Scheduled(fixedDelay = 1000L)
public void send() {
this.jmsTemplate.convertAndSend("testQueue", "Hello");
}
}

View File

@ -0,0 +1,3 @@
spring.hornetq.mode=embedded
spring.hornetq.embedded.enabled=true
spring.hornetq.embedded.queues=testQueue,anotherQueue

View File

@ -31,6 +31,7 @@
<module>spring-boot-starter-data-solr</module>
<module>spring-boot-starter-freemarker</module>
<module>spring-boot-starter-groovy-templates</module>
<module>spring-boot-starter-hornetq</module>
<module>spring-boot-starter-integration</module>
<module>spring-boot-starter-jdbc</module>
<module>spring-boot-starter-jetty</module>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-starter-hornetq</artifactId>
<name>Spring Boot HornetQ Starter</name>
<description>Spring Boot HornetQ Starter</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-client</artifactId>
</dependency>
</dependencies>
</project>