Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

[BS-48] Add autoconfigured JMS support #49

Closed
wants to merge 3 commits into from

2 participants

@gregturn
Collaborator
  • Add ability to detect spring-jms on the path and create a JmsTemplate with ActiveMQConnectionFactory
  • Create tests showing autoconfigured JmsTemplate with ActiveMQ, but prove it backs off if a separate ConnectionFactory exists.
  • Add support to spring-boot-cli to that it detects JmsTemplate, DefaultMessageListenerContainer or SimpleMessageListenerContainer, and turns on autoconfiguration as well as add proper @Grab's and import statements.
  • Write a jms.groovy test showing proper CLI support
@gregturn gregturn [BS-48] Add autoconfigured JMS support
* Add ability to detect spring-jms on the path and create a JmsTemplate with
  ActiveMQConnectionFactory
* Create tests showing autoconfigured JmsTemplate with ActiveMQ, but prove it
  backs off if a separate ConnectionFactory exists.
* Add support to spring-boot-cli to that it detects JmsTemplate, DefaultMessageListenerContainer,
  or SimpleMessageListenerContainer, and turns on autoconfiguration as well as
  add proper @Grab's and import statements.
* Write a jms.groovy test showing proper CLI support
cd37323
@dsyer
Owner

Any reason not to simply use @ConditionalOnClass to detect ActiveMQ (seems to be pretty much all you do in the custom condition)? ActiveMQ version is really old as well? If we get those things sorted out I'd want to maybe use some spring.jms.* properties to configure the ConnectionFactory. Otherwise looks good. Thanks for doing the Groovy integration too!

@gregturn
Collaborator

Somehow I thought testing for activemq would have pulled it in. I can fix that. Didn't realize the version number was so old. Will do.

@gregturn
Collaborator

P.S. I'm already drafting a blog entry capturing this process. I think it provides a nice insight into how Spring Boot works.

@dsyer
Owner

Merged, thanks. If you'd like to play around with a different JMS provider and find some spring.jms.* and spring.activemq.* properties that make sense for quick switching of defaults or setting up the ConnectionFactory that would be awesome (I know you have another provider in mind - maybe they both accept a URL as input to the ConnectionFactory or something?). We should have a proper think about what production defaults should look like as well for ActiveMQ.

@dsyer dsyer closed this
@gregturn gregturn deleted the gregturn:BS-48 branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 16, 2013
  1. @gregturn

    [BS-48] Add autoconfigured JMS support

    gregturn authored
    * Add ability to detect spring-jms on the path and create a JmsTemplate with
      ActiveMQConnectionFactory
    * Create tests showing autoconfigured JmsTemplate with ActiveMQ, but prove it
      backs off if a separate ConnectionFactory exists.
    * Add support to spring-boot-cli to that it detects JmsTemplate, DefaultMessageListenerContainer,
      or SimpleMessageListenerContainer, and turns on autoconfiguration as well as
      add proper @Grab's and import statements.
    * Write a jms.groovy test showing proper CLI support
Commits on Sep 18, 2013
  1. @gregturn
  2. @gregturn

    Update ActiveMQ to 5.7.0

    gregturn authored
This page is out of date. Refresh to see the latest.
View
2  .gitignore
@@ -18,4 +18,4 @@ _site/
manifest.yml
MANIFEST.MF
settings.xml
-
+activemq-data
View
14 spring-boot-autoconfigure/pom.xml
@@ -27,6 +27,11 @@
<optional>true</optional>
</dependency>
<dependency>
+ <groupId>org.apache.activemq</groupId>
+ <artifactId>activemq-core</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<optional>true</optional>
@@ -58,6 +63,11 @@
</dependency>
<dependency>
<groupId>org.springframework</groupId>
+ <artifactId>spring-jms</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<optional>true</optional>
</dependency>
@@ -126,6 +136,10 @@
<artifactId>reactor-spring</artifactId>
<optional>true</optional>
</dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jms_1.1_spec</artifactId>
+ </dependency>
<!-- Test -->
<dependency>
<groupId>${project.groupId}</groupId>
View
65 ...rc/main/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfiguration.java
@@ -0,0 +1,65 @@
+/*
+ * 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 org.springframework.boot.autoconfigure.jms;
+
+import javax.jms.ConnectionFactory;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jms.core.JmsTemplate;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for {@link JmsTemplate}.
+ *
+ * @author Greg Turnquist
+ */
+@Configuration
+@ConditionalOnClass(JmsTemplate.class)
+public class JmsTemplateAutoConfiguration {
+
+ @Configuration
+ @ConditionalOnMissingBean(JmsTemplate.class)
+ protected static class JmsTemplateCreator {
+
+ @Autowired
+ ConnectionFactory connectionFactory;
+
+ @Bean
+ public JmsTemplate jmsTemplate() {
+ JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
+ jmsTemplate.setPubSubDomain(true);
+ return jmsTemplate;
+ }
+
+ }
+
+ @Configuration
+ @ConditionalOnClass(ActiveMQConnectionFactory.class)
+ @ConditionalOnMissingBean(ConnectionFactory.class)
+ protected static class ActiveMQConnectionFactoryCreator {
+ @Bean
+ ConnectionFactory connectionFactory() {
+ return new ActiveMQConnectionFactory("vm://localhost");
+ }
+ }
+
+}
View
1  spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -8,6 +8,7 @@ org.springframework.boot.autoconfigure.data.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
+org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
View
156 ...st/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfigurationTests.java
@@ -0,0 +1,156 @@
+/*
+ * 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 org.springframework.boot.autoconfigure.jms;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.jms.ConnectionFactory;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.junit.Test;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jms.core.JmsTemplate;
+
+/**
+ * Tests for {@link JmsTemplateAutoConfiguration}.
+ *
+ * @author Greg Turnquist
+ */
+public class JmsTemplateAutoConfigurationTests {
+
+ private AnnotationConfigApplicationContext context;
+
+ @Test
+ public void testDefaultJmsTemplate() {
+ this.context = new AnnotationConfigApplicationContext();
+ this.context
+ .register(TestConfiguration.class, JmsTemplateAutoConfiguration.class);
+ this.context.refresh();
+ JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class);
+ ActiveMQConnectionFactory connectionFactory = this.context
+ .getBean(ActiveMQConnectionFactory.class);
+ assertNotNull(jmsTemplate);
+ assertNotNull(connectionFactory);
+ assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory);
+ }
+
+ @Configuration
+ protected static class TestConfiguration {
+ }
+
+ @Test
+ public void testConnectionFactoryBackoff() {
+ this.context = new AnnotationConfigApplicationContext();
+ this.context.register(TestConfiguration2.class,
+ JmsTemplateAutoConfiguration.class);
+ this.context.refresh();
+ assertEquals("foobar", this.context.getBean(ActiveMQConnectionFactory.class)
+ .getBrokerURL());
+ }
+
+ @Configuration
+ protected static class TestConfiguration2 {
+ @Bean
+ ConnectionFactory connectionFactory() {
+ return new ActiveMQConnectionFactory() {
+ {
+ setBrokerURL("foobar");
+ }
+ };
+ }
+ }
+
+ @Test
+ public void testJmsTemplateBackoff() {
+ this.context = new AnnotationConfigApplicationContext();
+ this.context.register(TestConfiguration3.class,
+ JmsTemplateAutoConfiguration.class);
+ this.context.refresh();
+ JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class);
+ assertEquals(999, jmsTemplate.getPriority());
+ }
+
+ @Configuration
+ protected static class TestConfiguration3 {
+ @Bean
+ JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
+ JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
+ jmsTemplate.setPriority(999);
+ return jmsTemplate;
+ }
+
+ }
+
+ @Test
+ public void testJmsTemplateBackoffEverything() {
+ this.context = new AnnotationConfigApplicationContext();
+ this.context.register(TestConfiguration2.class, TestConfiguration3.class,
+ JmsTemplateAutoConfiguration.class);
+ this.context.refresh();
+ JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class);
+ assertEquals(999, jmsTemplate.getPriority());
+ assertEquals("foobar", this.context.getBean(ActiveMQConnectionFactory.class)
+ .getBrokerURL());
+ }
+
+ @Test
+ public void testPubSubEnabledByDefault() {
+ this.context = new AnnotationConfigApplicationContext();
+ this.context
+ .register(TestConfiguration.class, JmsTemplateAutoConfiguration.class);
+ this.context.refresh();
+ JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class);
+ assertTrue(jmsTemplate.isPubSubDomain());
+ }
+
+ @Test
+ public void testJmsTemplatePostProcessedSoThatPubSubIsFalse() {
+ this.context = new AnnotationConfigApplicationContext();
+ this.context.register(TestConfiguration4.class,
+ JmsTemplateAutoConfiguration.class);
+ this.context.refresh();
+ JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class);
+ assertFalse(jmsTemplate.isPubSubDomain());
+ }
+
+ @Configuration
+ protected static class TestConfiguration4 implements BeanPostProcessor {
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName)
+ throws BeansException {
+ if (bean.getClass().isAssignableFrom(JmsTemplate.class)) {
+ JmsTemplate jmsTemplate = (JmsTemplate) bean;
+ jmsTemplate.setPubSubDomain(false);
+ }
+ return bean;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ return bean;
+ }
+ }
+
+}
View
48 spring-boot-cli/samples/jms.groovy
@@ -0,0 +1,48 @@
+package org.test
+
+@Grab("org.apache.activemq:activemq-all:5.2.0")
+
+import java.util.concurrent.CountDownLatch
+
+@Configuration
+@Log
+class JmsExample implements CommandLineRunner {
+
+ private CountDownLatch latch = new CountDownLatch(1)
+
+ @Autowired
+ JmsTemplate jmsTemplate
+
+ @Bean
+ DefaultMessageListenerContainer jmsListener(ConnectionFactory connectionFactory) {
+ new DefaultMessageListenerContainer([
+ connectionFactory: connectionFactory,
+ destinationName: "spring-boot",
+ pubSubDomain: true,
+ messageListener: new MessageListenerAdapter(new Receiver(latch:latch)) {{
+ defaultListenerMethod = "receive"
+ }}
+ ])
+ }
+
+ void run(String... args) {
+ def messageCreator = { session ->
+ session.createObjectMessage("Greetings from Spring Boot via ActiveMQ")
+ } as MessageCreator
+ log.info "Sending JMS message..."
+ jmsTemplate.pubSubDomain = true
+ jmsTemplate.send("spring-boot", messageCreator)
+ latch.await()
+ }
+
+}
+
+@Log
+class Receiver {
+ CountDownLatch latch
+
+ def receive(String message) {
+ log.info "Received ${message}"
+ latch.countDown()
+ }
+}
View
55 ...ava/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java
@@ -0,0 +1,55 @@
+/*
+ * 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 org.springframework.boot.cli.compiler.autoconfigure;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.customizers.ImportCustomizer;
+import org.springframework.boot.cli.compiler.AstUtils;
+import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
+import org.springframework.boot.cli.compiler.DependencyCustomizer;
+
+/**
+ * {@link CompilerAutoConfiguration} for Spring JMS.
+ *
+ * @author Greg Turnquist
+ */
+public class JmsCompilerAutoConfiguration extends CompilerAutoConfiguration {
+
+ @Override
+ public boolean matches(ClassNode classNode) {
+ return AstUtils.hasAtLeastOneFieldOrMethod(classNode, "JmsTemplate",
+ "DefaultMessageListenerContainer", "SimpleMessageListenerContainer");
+ }
+
+ @Override
+ public void applyDependencies(DependencyCustomizer dependencies)
+ throws CompilationFailedException {
+ dependencies.add("org.springframework", "spring-jms",
+ dependencies.getProperty("spring.version")).add(
+ "org.apache.geronimo.specs", "geronimo-jms_1.1_spec", "1.1");
+
+ }
+
+ @Override
+ public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
+ imports.addStarImports("javax.jms", "org.springframework.jms.core",
+ "org.springframework.jms.listener",
+ "org.springframework.jms.listener.adapter");
+ }
+
+}
View
1  ...sources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration
@@ -3,6 +3,7 @@ org.springframework.boot.cli.compiler.autoconfigure.SpringMvcCompilerAutoConfigu
org.springframework.boot.cli.compiler.autoconfigure.SpringBatchCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.ReactorCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.JdbcCompilerAutoConfiguration
+org.springframework.boot.cli.compiler.autoconfigure.JmsCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.TransactionManagementCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringIntegrationCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSecurityCompilerAutoConfiguration
View
17 spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java
@@ -16,6 +16,10 @@
package org.springframework.boot.cli;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
@@ -32,13 +36,11 @@
import org.springframework.boot.cli.command.CleanCommand;
import org.springframework.boot.cli.command.RunCommand;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
/**
* Integration tests to exercise the samples.
*
* @author Dave Syer
+ * @author Greg Turnquist
*/
public class SampleIntegrationTests {
@@ -185,4 +187,13 @@ public void txSample() throws Exception {
assertTrue("Wrong output: " + output, output.contains("Foo count="));
}
+ @Test
+ public void jmsSample() throws Exception {
+ start("samples/app.xml", "samples/jms.groovy");
+ String output = this.outputCapture.getOutputAndRelease();
+ assertTrue("Wrong output: " + output,
+ output.contains("Received Greetings from Spring Boot via ActiveMQ"));
+ FileUtil.forceDelete(new File("activemq-data")); // cleanup ActiveMQ cruft
+ }
+
}
View
2  spring-boot-cli/src/test/resources/logback.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
- <!-- logger name="org.springframework.jdbc" level="DEBUG"/-->
+ <!-- logger name="org.springframework.jms" level="DEBUG"/-->
</configuration>
View
11 spring-boot-dependencies/pom.xml
@@ -7,6 +7,7 @@
<version>0.5.0.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
+ <activemq.version>5.7.0</activemq.version>
<aspectj.version>1.7.3</aspectj.version>
<commons-dbcp.version>1.4</commons-dbcp.version>
<commons-httpclient.version>3.1</commons-httpclient.version>
@@ -106,6 +107,11 @@
<version>${thymeleaf-layout-dialect.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.activemq</groupId>
+ <artifactId>activemq-core</artifactId>
+ <version>${activemq.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
@@ -429,6 +435,11 @@
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-jms_1.1_spec</artifactId>
+ <version>1.1</version>
+ </dependency>
</dependencies>
</dependencyManagement>
<build>
Something went wrong with that request. Please try again.