From 5801e422cf6d011a405ca174cbe3b704637bb395 Mon Sep 17 00:00:00 2001 From: Greg Turnquist Date: Mon, 16 Sep 2013 10:11:56 -0500 Subject: [PATCH] [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 Simplify ActiveMQ configuration Update ActiveMQ to 5.7.0 --- .gitignore | 2 +- spring-boot-autoconfigure/pom.xml | 14 ++ .../jms/JmsTemplateAutoConfiguration.java | 65 ++++++++ .../main/resources/META-INF/spring.factories | 1 + .../JmsTemplateAutoConfigurationTests.java | 156 ++++++++++++++++++ spring-boot-cli/samples/jms.groovy | 48 ++++++ .../JmsCompilerAutoConfiguration.java | 55 ++++++ ...oot.cli.compiler.CompilerAutoConfiguration | 1 + .../boot/cli/SampleIntegrationTests.java | 17 +- .../src/test/resources/logback.xml | 2 +- spring-boot-dependencies/pom.xml | 11 ++ 11 files changed, 367 insertions(+), 5 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfigurationTests.java create mode 100644 spring-boot-cli/samples/jms.groovy create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java diff --git a/.gitignore b/.gitignore index 4e580f3d8b..8c14632e59 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ _site/ manifest.yml MANIFEST.MF settings.xml - +activemq-data diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 0d09b51cc7..fc45fad4fb 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -26,6 +26,11 @@ commons-dbcp true + + org.apache.activemq + activemq-core + true + org.apache.tomcat.embed tomcat-embed-core @@ -56,6 +61,11 @@ spring-jdbc true + + org.springframework + spring-jms + true + org.springframework spring-orm @@ -126,6 +136,10 @@ reactor-spring true + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + ${project.groupId} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfiguration.java new file mode 100644 index 0000000000..bb08f175b2 --- /dev/null +++ b/spring-boot-autoconfigure/src/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"); + } + } + +} 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 7123ea0f52..f150d12c56 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/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,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsTemplateAutoConfigurationTests.java new file mode 100644 index 0000000000..840761d873 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/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; + } + } + +} diff --git a/spring-boot-cli/samples/jms.groovy b/spring-boot-cli/samples/jms.groovy new file mode 100644 index 0000000000..54454fa1e8 --- /dev/null +++ b/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() + } +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/JmsCompilerAutoConfiguration.java new file mode 100644 index 0000000000..5c6d47454a --- /dev/null +++ b/spring-boot-cli/src/main/java/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"); + } + +} diff --git a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration b/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration index ac2b474d5b..0f28cef01e 100644 --- a/spring-boot-cli/src/main/resources/META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration +++ b/spring-boot-cli/src/main/resources/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 diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java index f0fae17ea7..28d38b66fc 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java +++ b/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.OutputCapture; 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 class SampleIntegrationTests { 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 + } + } diff --git a/spring-boot-cli/src/test/resources/logback.xml b/spring-boot-cli/src/test/resources/logback.xml index ab0a65c9a9..29a2b9b369 100644 --- a/spring-boot-cli/src/test/resources/logback.xml +++ b/spring-boot-cli/src/test/resources/logback.xml @@ -1,5 +1,5 @@ - + diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index eff1bf0e0f..3c6e4a53f8 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -7,6 +7,7 @@ 0.5.0.BUILD-SNAPSHOT pom + 5.7.0 1.7.3 1.4 3.1 @@ -105,6 +106,11 @@ thymeleaf-layout-dialect ${thymeleaf-layout-dialect.version} + + org.apache.activemq + activemq-core + ${activemq.version} + org.apache.tomcat.embed tomcat-embed-core @@ -429,6 +435,11 @@ snakeyaml ${snakeyaml.version} + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + 1.1 +