From 10823ed1694e35ebdc92cb6c7620c5e3b5d960e5 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 23 Oct 2014 07:34:17 -0700 Subject: [PATCH 1/5] Upgrade to Boot 1.1.8 --- pom.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pom.xml b/pom.xml index ed615b0..7fd3014 100644 --- a/pom.xml +++ b/pom.xml @@ -16,18 +16,6 @@ - - - - org.springframework.boot - spring-boot - 1.1.5.RELEASE - pom - import - - - - org.springframework.boot From b0c915a380f8a5e2795c970aa84685e4497c59f7 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 31 Oct 2014 15:59:32 +0000 Subject: [PATCH 2/5] Update README with building section --- src/main/asciidoc/README.adoc | 4 ++++ src/main/ruby/generate_readme.sh | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/asciidoc/README.adoc b/src/main/asciidoc/README.adoc index 72b2f70..56252bf 100644 --- a/src/main/asciidoc/README.adoc +++ b/src/main/asciidoc/README.adoc @@ -5,3 +5,7 @@ include::intro.adoc[] == Quick Start include::quickstart.adoc[] + +== Building + +include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/src/main/asciidoc/building.adoc[] diff --git a/src/main/ruby/generate_readme.sh b/src/main/ruby/generate_readme.sh index fc5b7f1..79aadce 100755 --- a/src/main/ruby/generate_readme.sh +++ b/src/main/ruby/generate_readme.sh @@ -18,7 +18,7 @@ file = ARGV[0] if ARGV.length>0 srcDir = File.dirname(file) out = "// Do not edit this file (e.g. go instead to src/main/asciidoc)\n\n" -doc = Asciidoctor.load_file file, safe: :safe, parse: false +doc = Asciidoctor.load_file file, safe: :safe, parse: false, attributes: 'allow-uri-read' out << doc.reader.read unless options[:to_file] From bc9a59182e912a3d05f2f63c96d9d4d3b19d1ea1 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 31 Oct 2014 16:17:30 +0000 Subject: [PATCH 3/5] Update README --- README.adoc | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/README.adoc b/README.adoc index d04dc76..0fbce23 100644 --- a/README.adoc +++ b/README.adoc @@ -20,3 +20,115 @@ spring: ---- The bus currently supports sending messages to all nodes listening or all nodes for a particular service (as defined by Eureka). More selector criteria will be added in the future (ie. only service X nodes in data center Y, etc...). The http endpoints are under the `/bus/*` actuator namespace. There are currently two implemented. The first, `/bus/env`, sends key/values pairs to update each nodes Spring Environment. The second, `/bus/refresh`, will reload each application's configuration, just as if they had all been pinged on their `/refresh` endpoint. + +== Building + +== Basic Compile and Test + +To build the source you will need to install +http://maven.apache.org/run-maven/index.html[Apache Maven] v3.0.6 or above and JDK 1.7. + +Spring Cloud uses Maven for most build-related activities, and you +should be able to get off the ground quite quickly by cloning the +project you are interested in and typing + +---- +$ mvn install -s .settings.xml +---- + +NOTE: You may need to increase the amount of memory available to Maven by setting +a `MAVEN_OPTS` environment variable with the value `-Xmx512m -XX:MaxPermSize=128m` + +The `.settings.xml` is only required the first time (or after updates +to dependencies). It is there to provide repository declarations so +that those do not need to be hard coded in the project poms. + +For hints on how to build the project look in `.travis.yml` if there +is one. There should be a "script" and maybe "install" command. Also +look at the "services" section to see if any services need to be +running locally (e.g. mongo or rabbit). Ignore the git-related bits +that you might find in "before_install" since they will be able git +credentials and you already have those. + +If you need mongo, rabbit or redis, see the README in the [scripts +demo repository](https://github.com/spring-cloud-samples/scripts) for +instructions. For example consider using the "fig.yml" with +[Fig](http://www.fig.sh/) to run them in Docker containers. + +== Documentation + +The spring-cloud-build module has a "docs" profile, and if you switch +that on it will try to build asciidoc sources from +`src/main/asciidoc`. As part of that process it will look for a +`README.adoc` and process it by loading all the includes, but not +parsing or rendering it, just copying it to `${main.basedir}` +(defaults to `${basedir}`, i.e. the root of the project). If there are +any changes in the README it will then show up after a Maven build as +a modified file in the correct place. Just commit it and push the change. + +== Pull Requests + +Spring Cloud is released under the non-restrictive Apache 2.0 license, +and follows a very standard Github development process, using Github +tracker for issues and merging pull requests into master. If you want +to contribute even something trivial please do not hesitate, but +follow the guidelines below. + +=== Sign the Contributor License Agreement +Before we accept a non-trivial patch or pull request we will need you +to sign the +https://support.springsource.com/spring_committer_signup[contributor's +agreement]. Signing the contributor's agreement does not grant anyone +commit rights to the main repository, but it does mean that we can +accept your contributions, and you will get an author credit if we do. +Active contributors might be asked to join the core team, and given +the ability to merge pull requests. + +=== Code Conventions and Housekeeping +None of these is essential for a pull request, but they will all help. They can also be +added after the original pull request but before a merge. + +* Use the Spring Framework code format conventions. If you use Eclipse and you follow + the ``Importing into eclipse'' instructions below you should get project specific + formatting automatically. You can also import formatter settings using the + `eclipse-code-formatter.xml` file from the `eclipse` folder. If using IntelliJ, you can + use the http://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter Plugin] + to import the same file. +* Make sure all new `.java` files to have a simple Javadoc class comment with at least an + `@author` tag identifying you, and preferably at least a paragraph on what the class is + for. +* Add the ASF license header comment to all new `.java` files (copy from existing files + in the project) +* Add yourself as an `@author` to the .java files that you modify substantially (more + than cosmetic changes). +* Add some Javadocs and, if you change the namespace, some XSD doc elements. +* A few unit tests would help a lot as well -- someone has to do it. +* If no-one else is using your branch, please rebase it against the current master (or + other target branch in the main project). + +=== Working with the code +If you don't have an IDE preference we would recommend that you use +http://www.springsource.com/developer/sts[Spring Tools Suite] or +http://eclipse.org[Eclipse] when working with the code. We use the +http://eclipse.org/m2e/[m2eclipe] eclipse plugin for maven support. Other IDEs and tools +should also work without issue. + +=== Importing into eclipse with m2eclipse +We recommend the http://eclipse.org/m2e/[m2eclipe] eclipse plugin when working with +eclipse. If you don't already have m2eclipse installed it is available from the "eclipse +marketplace". + +=== Importing into eclipse without m2eclipse +If you prefer not to use m2eclipse you can generate eclipse project metadata using the +following command: + +[indent=0] +---- + $ mvn eclipse:eclipse +---- + +The generated eclipse projects can be imported by selecting `import existing projects` +from the `file` menu. + +=== Importing into other IDEs +Maven is well supported by most Java IDEs. Refer to you vendor documentation. \ No newline at end of file From 2c027b2f02c1873fb34852c264418b01274267c6 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 3 Nov 2014 12:53:30 -0700 Subject: [PATCH 4/5] bump spring-integration-java-dsl to 1.0.0.RC1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7fd3014..470604c 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ org.springframework.integration spring-integration-java-dsl - 1.0.0.M3 + 1.0.0.RC1 org.springframework.integration From 2943b385b836901c9f5ec34068337586cef2c35e Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 7 Nov 2014 18:35:50 +0000 Subject: [PATCH 5/5] Make filter based on context ID instead of name Also added unit tests and separated AMQP from native ApplicationContext event features. Fixes gh-6 --- pom.xml | 5 + .../cloud/bus/BusAutoConfiguration.java | 180 ++++++++++++++---- .../bus/amqp/AmqpBusAutoConfiguration.java | 151 ++++----------- .../bus/endpoint/AbstractBusEndpoint.java | 60 +++--- .../bus/endpoint/EnvironmentBusEndpoint.java | 11 +- .../bus/endpoint/RefreshBusEndpoint.java | 9 +- .../cloud/bus/BusAutoConfigurationTests.java | 109 +++++++++++ .../bus/endpoint/RefreshBusEndpointTests.java | 20 ++ 8 files changed, 366 insertions(+), 179 deletions(-) create mode 100644 src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java create mode 100644 src/test/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpointTests.java diff --git a/pom.xml b/pom.xml index 470604c..a3345ff 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,11 @@ 1.12.6 provided + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java b/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java index 6f57042..ae0b9ed 100644 --- a/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java @@ -1,5 +1,6 @@ package org.springframework.cloud.bus; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -8,52 +9,167 @@ import org.springframework.cloud.bus.endpoint.EnvironmentBusEndpoint; import org.springframework.cloud.bus.endpoint.RefreshBusEndpoint; import org.springframework.cloud.bus.event.EnvironmentChangeListener; import org.springframework.cloud.bus.event.RefreshListener; +import org.springframework.cloud.bus.event.RemoteApplicationEvent; import org.springframework.cloud.config.client.RefreshEndpoint; import org.springframework.cloud.context.environment.EnvironmentManager; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.interceptor.WireTap; +import org.springframework.integration.config.GlobalChannelInterceptor; +import org.springframework.integration.core.GenericSelector; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer; +import org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler; +import org.springframework.integration.handler.LoggingHandler; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.SubscribableChannel; /** * @author Spencer Gibb + * @author Dave Syer */ @Configuration @ConditionalOnExpression("${bus.enabled:true}") public class BusAutoConfiguration { - @Bean - public BusEndpoint busEndpoint() { - return new BusEndpoint(); - } + @Autowired + private ConfigurableEnvironment environment; - @ConditionalOnClass(RefreshEndpoint.class) - @ConditionalOnBean(RefreshEndpoint.class) - protected static class BusRefreshConfiguration { - @Bean - @ConditionalOnExpression("${bus.refresh.enabled:true}") - public RefreshListener refreshListener() { - return new RefreshListener(); - } + @Autowired + private ConfigurableApplicationContext context; - @Bean - @ConditionalOnExpression("${endpoints.bus.refresh.enabled:true}") - public RefreshBusEndpoint refreshBusEndpoint() { - return new RefreshBusEndpoint(); - } - } + @Bean + public SubscribableChannel cloudBusOutboundChannel() { + return new DirectChannel(); + } - @ConditionalOnClass(EnvironmentManager.class) - @ConditionalOnBean(EnvironmentManager.class) - protected static class BusEnvironmentConfiguration { - @Bean - @ConditionalOnExpression("${bus.env.enabled:true}") - public EnvironmentChangeListener environmentChangeListener() { - return new EnvironmentChangeListener(); - } + // TODO: is there a way to move these filters to rabbit while not losing the + // information once it is published to spring? + @Bean + public GenericSelector outboundFilter() { + return new GenericSelector() { + @Override + public boolean accept(RemoteApplicationEvent source) { + return isFromSelf(source); + } + }; + } + + @SuppressWarnings("unchecked") + private ApplicationEventListeningMessageProducer cloudBusOutboundMessageProducer() { + ApplicationEventListeningMessageProducer producer = new ApplicationEventListeningMessageProducer(); + producer.setEventTypes(RemoteApplicationEvent.class); + return producer; + } + + @Bean + public IntegrationFlow cloudBusOutboundFlow() { + ApplicationEventListeningMessageProducer producer = cloudBusOutboundMessageProducer(); + // Workaround for bug in IntegrationFlow (it won't register the listener) + context.addApplicationListener(producer); + return IntegrationFlows.from(producer) + .filter(outboundFilter()).channel(cloudBusOutboundChannel()).get(); + } + + @Bean + public MessageChannel cloudBusInboundChannel() { + return new DirectChannel(); + } + + @Bean + public GenericSelector inboundFilter() { + return new GenericSelector() { + @Override + public boolean accept(RemoteApplicationEvent event) { + return !isFromSelf(event) && isForSelf(event); + } + }; + } + + @Bean + public IntegrationFlow cloudBusInboundFlow() { + ApplicationEventPublishingMessageHandler messageHandler = new ApplicationEventPublishingMessageHandler(); + return IntegrationFlows.from(cloudBusInboundChannel()).filter(inboundFilter()) + .handle(messageHandler).get(); + } + + @Bean + @GlobalChannelInterceptor(patterns = "cloudBusInboundFlow*") + public WireTap wireTap() { + return new WireTap(cloudBusWiretapChannel()); + } + + @Bean + public DirectChannel cloudBusWiretapChannel() { + return MessageChannels.direct().get(); + } + + @Bean + public IntegrationFlow loggingFlow() { + LoggingHandler handler = new LoggingHandler("INFO"); + handler.setShouldLogFullMessage(true); + return IntegrationFlows.from(cloudBusWiretapChannel()).handle(handler).get(); + } + + @Bean + public BusEndpoint busEndpoint() { + return new BusEndpoint(); + } + + @ConditionalOnClass(RefreshEndpoint.class) + @ConditionalOnBean(RefreshEndpoint.class) + protected static class BusRefreshConfiguration { + @Bean + @ConditionalOnExpression("${bus.refresh.enabled:true}") + public RefreshListener refreshListener() { + return new RefreshListener(); + } + + @Bean + @ConditionalOnExpression("${endpoints.bus.refresh.enabled:true}") + public RefreshBusEndpoint refreshBusEndpoint(ApplicationContext context, + BusEndpoint busEndpoint) { + return new RefreshBusEndpoint(context, context.getId(), busEndpoint); + } + } + + @ConditionalOnClass(EnvironmentManager.class) + @ConditionalOnBean(EnvironmentManager.class) + protected static class BusEnvironmentConfiguration { + @Bean + @ConditionalOnExpression("${bus.env.enabled:true}") + public EnvironmentChangeListener environmentChangeListener() { + return new EnvironmentChangeListener(); + } + + @Bean + @ConditionalOnExpression("${endpoints.bus.env.enabled:true}") + public EnvironmentBusEndpoint environmentBusEndpoint(ApplicationContext context, + BusEndpoint busEndpoint) { + return new EnvironmentBusEndpoint(context, context.getId(), busEndpoint); + } + } + + private boolean isFromSelf(RemoteApplicationEvent event) { + String originService = event.getOriginService(); + String appName = getAppName(); + return originService.equals(appName); + } + + private boolean isForSelf(RemoteApplicationEvent event) { + return (event.getDestinationService() == null + || event.getDestinationService().trim().isEmpty() || event + .getDestinationService().equals(getAppName())); + } + + private String getAppName() { + return context.getId(); + } - @Bean - @ConditionalOnExpression("${endpoints.bus.env.enabled:true}") - public EnvironmentBusEndpoint environmentBusEndpoint() { - return new EnvironmentBusEndpoint(); - } - } } diff --git a/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java b/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java index ace837d..cbc3769 100644 --- a/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/bus/amqp/AmqpBusAutoConfiguration.java @@ -7,24 +7,15 @@ import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.cloud.bus.event.RemoteApplicationEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.interceptor.WireTap; -import org.springframework.integration.config.GlobalChannelInterceptor; -import org.springframework.integration.core.GenericSelector; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.dsl.amqp.Amqp; -import org.springframework.integration.dsl.channel.MessageChannels; -import org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer; -import org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler; -import org.springframework.integration.handler.LoggingHandler; +import org.springframework.messaging.MessageChannel; /** * @author Spencer Gibb @@ -34,116 +25,48 @@ import org.springframework.integration.handler.LoggingHandler; @ConditionalOnExpression("${bus.amqp.enabled:true}") public class AmqpBusAutoConfiguration { - public static final String SPRING_CLOUD_BUS = "spring.cloud.bus"; + public static final String SPRING_CLOUD_BUS = "spring.cloud.bus"; - @Autowired - private ConnectionFactory connectionFactory; + @Autowired + private ConnectionFactory connectionFactory; - @Autowired - private AmqpAdmin amqpAdmin; + @Autowired + private AmqpAdmin amqpAdmin; - @Autowired - private AmqpTemplate amqpTemplate; + @Autowired + private AmqpTemplate amqpTemplate; - @Autowired - private ConfigurableEnvironment env; - - //TODO: how to fail gracefully if no rabbit? - @Bean - protected FanoutExchange cloudBusExchange() { - //TODO: change to TopicExchange? - FanoutExchange exchange = new FanoutExchange(SPRING_CLOUD_BUS); - amqpAdmin.declareExchange(exchange); - return exchange; - } - - @Bean - protected Queue localCloudBusQueue() { - Queue queue = amqpAdmin.declareQueue(); - amqpAdmin.declareBinding(BindingBuilder.bind(queue).to(cloudBusExchange())); - return queue; - } - - @SuppressWarnings("unchecked") + // TODO: how to fail gracefully if no rabbit? @Bean - public ApplicationEventListeningMessageProducer cloudBusProducer() { - ApplicationEventListeningMessageProducer producer = new ApplicationEventListeningMessageProducer(); - producer.setEventTypes(RemoteApplicationEvent.class); - producer.setOutputChannel(new DirectChannel()); - return producer; - } + protected FanoutExchange cloudBusExchange() { + // TODO: change to TopicExchange? + FanoutExchange exchange = new FanoutExchange(SPRING_CLOUD_BUS); + amqpAdmin.declareExchange(exchange); + return exchange; + } - @Bean - public IntegrationFlow cloudBusOutboundFlow() { - return IntegrationFlows.from(cloudBusProducer()) - .filter(outboundFilter()) - .handle(Amqp.outboundAdapter(this.amqpTemplate).exchangeName(SPRING_CLOUD_BUS)) - .get(); - } + @Bean + protected Queue localCloudBusQueue() { + Queue queue = amqpAdmin.declareQueue(); + amqpAdmin.declareBinding(BindingBuilder.bind(queue).to(cloudBusExchange())); + return queue; + } - //TODO: is there a way to move these filters to rabbit while not loosing the information once it is published to spring? - @Bean - public GenericSelector outboundFilter() { - return new GenericSelector() { - @Override - public boolean accept(RemoteApplicationEvent source) { - return isFromSelf(source); - } - }; - } + @Bean + public IntegrationFlow cloudBusAmqpOutboundFlow( + @Qualifier("cloudBusOutboundChannel") MessageChannel cloudBusOutboundChannel) { + return IntegrationFlows + .from(cloudBusOutboundChannel) + .handle(Amqp.outboundAdapter(this.amqpTemplate).exchangeName( + SPRING_CLOUD_BUS)).get(); + } - @Bean - public GenericSelector inboundFilter() { - return new GenericSelector() { - @Override - public boolean accept(RemoteApplicationEvent event) { - return !isFromSelf(event) && isForSelf(event); - } - }; - } + @Bean + public IntegrationFlow cloudBusAmqpInboundFlow( + @Qualifier("cloudBusInboundChannel") MessageChannel cloudBusInboundChannel) { + return IntegrationFlows + .from(Amqp.inboundAdapter(connectionFactory, localCloudBusQueue())) + .channel(cloudBusInboundChannel).get(); + } - private boolean isForSelf(RemoteApplicationEvent event) { - return (event.getDestinationService() == null - || event.getDestinationService().trim().isEmpty() - || event.getDestinationService().equals(getAppName())); - } - - private boolean isFromSelf(RemoteApplicationEvent event) { - String originService = event.getOriginService(); - String appName = getAppName(); - return originService.equals(appName); - } - - private String getAppName() { - return env.getProperty("spring.application.name"); - } - - @Bean - public IntegrationFlow cloudBusInboundFlow(Environment env) { - ApplicationEventPublishingMessageHandler messageHandler = new ApplicationEventPublishingMessageHandler(); - return IntegrationFlows.from(Amqp.inboundAdapter(connectionFactory, localCloudBusQueue())) - .filter(inboundFilter()) - .handle(messageHandler) - .get(); - } - - @Bean - public DirectChannel wiretapChannel() { - return MessageChannels.direct().get(); - } - - @Bean - @GlobalChannelInterceptor(patterns = "cloudBusInboundFlow*") - public WireTap wireTap() { - return new WireTap(wiretapChannel()); - } - - @Bean - public IntegrationFlow loggingFlow() { - LoggingHandler handler = new LoggingHandler("INFO"); - handler.setShouldLogFullMessage(true); - return IntegrationFlows.from(wiretapChannel()) - .handle(handler) - .get(); - } } diff --git a/src/main/java/org/springframework/cloud/bus/endpoint/AbstractBusEndpoint.java b/src/main/java/org/springframework/cloud/bus/endpoint/AbstractBusEndpoint.java index a3d0f53..c5f46aa 100644 --- a/src/main/java/org/springframework/cloud/bus/endpoint/AbstractBusEndpoint.java +++ b/src/main/java/org/springframework/cloud/bus/endpoint/AbstractBusEndpoint.java @@ -1,44 +1,48 @@ package org.springframework.cloud.bus.endpoint; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.context.ApplicationEvent; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.context.ApplicationEventPublisher; /** * @author Spencer Gibb */ public class AbstractBusEndpoint implements MvcEndpoint { - @Autowired - protected ConfigurableEnvironment env; - @Autowired - protected ConfigurableApplicationContext context; - @Autowired - private BusEndpoint delegate; - protected String getAppName() { - return env.getProperty("spring.application.name"); - } + private ApplicationEventPublisher context; - protected void publish(ApplicationEvent event) { - context.publishEvent(event); - } + private BusEndpoint delegate; - @Override - public String getPath() { - return "/" + this.delegate.getId(); - } + private String appId; - @Override - public boolean isSensitive() { - return this.delegate.isSensitive(); - } + public AbstractBusEndpoint(ApplicationEventPublisher context, String appId, BusEndpoint busEndpoint) { + this.context = context; + this.appId = appId; + this.delegate = busEndpoint; + } - @Override - @SuppressWarnings("rawtypes") - public Class getEndpointType() { - return this.delegate.getClass(); - } + protected String getInstanceId() { + return this.appId; + } + + protected void publish(ApplicationEvent event) { + context.publishEvent(event); + } + + @Override + public String getPath() { + return "/" + this.delegate.getId(); + } + + @Override + public boolean isSensitive() { + return this.delegate.isSensitive(); + } + + @Override + @SuppressWarnings("rawtypes") + public Class getEndpointType() { + return this.delegate.getClass(); + } } diff --git a/src/main/java/org/springframework/cloud/bus/endpoint/EnvironmentBusEndpoint.java b/src/main/java/org/springframework/cloud/bus/endpoint/EnvironmentBusEndpoint.java index 0871add..4f3c60c 100644 --- a/src/main/java/org/springframework/cloud/bus/endpoint/EnvironmentBusEndpoint.java +++ b/src/main/java/org/springframework/cloud/bus/endpoint/EnvironmentBusEndpoint.java @@ -1,24 +1,29 @@ package org.springframework.cloud.bus.endpoint; +import java.util.Map; + import org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; -import java.util.Map; - /** * @author Spencer Gibb */ public class EnvironmentBusEndpoint extends AbstractBusEndpoint { + public EnvironmentBusEndpoint(ApplicationEventPublisher context, String id, BusEndpoint delegate) { + super(context, id, delegate); + } + @RequestMapping(value = "env", method = RequestMethod.POST) @ResponseBody //TODO: make this an abstract method in AbstractBusEndpoint? public void env(@RequestParam Map params, @RequestParam(value = "destination", required = false) String destination) { - publish(new EnvironmentChangeRemoteApplicationEvent(this, getAppName(), destination, params)); + publish(new EnvironmentChangeRemoteApplicationEvent(this, getInstanceId(), destination, params)); } diff --git a/src/main/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpoint.java b/src/main/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpoint.java index 50a5c40..cf03099 100644 --- a/src/main/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpoint.java +++ b/src/main/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpoint.java @@ -1,6 +1,7 @@ package org.springframework.cloud.bus.endpoint; import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -11,10 +12,14 @@ import org.springframework.web.bind.annotation.ResponseBody; */ public class RefreshBusEndpoint extends AbstractBusEndpoint { - @RequestMapping(value = "refresh", method = RequestMethod.POST) + public RefreshBusEndpoint(ApplicationEventPublisher context, String id, BusEndpoint delegate) { + super(context, id, delegate); + } + + @RequestMapping(value = "refresh", method = RequestMethod.POST) @ResponseBody public void refresh(@RequestParam(value = "destination", required = false) String destination) { - publish(new RefreshRemoteApplicationEvent(this, getAppName(), destination)); + publish(new RefreshRemoteApplicationEvent(this, getInstanceId(), destination)); } diff --git a/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java new file mode 100644 index 0000000..870ba36 --- /dev/null +++ b/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java @@ -0,0 +1,109 @@ +package org.springframework.cloud.bus; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.integration.annotation.MessageEndpoint; +import org.springframework.integration.annotation.ServiceActivator; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.GenericMessage; + +public class BusAutoConfigurationTests { + + private ConfigurableApplicationContext context; + + @After + public void close() { + if (context != null) { + context.close(); + } + } + + @Test + public void inboundNotForSelf() { + context = SpringApplication.run(InboundMessageHandlerConfiguration.class); + context.setId("foo"); + context.getBean("cloudBusInboundChannel", MessageChannel.class) + .send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "bar", + "bar"))); + assertNull(context.getBean(InboundMessageHandlerConfiguration.class).event); + } + + @Test + public void inboundFromSelf() { + context = SpringApplication.run(InboundMessageHandlerConfiguration.class); + context.setId("foo"); + context.getBean("cloudBusInboundChannel", MessageChannel.class) + .send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", + null))); + assertNull(context.getBean(InboundMessageHandlerConfiguration.class).event); + } + + @Test + public void inboundNotFromSelf() { + context = SpringApplication.run(InboundMessageHandlerConfiguration.class); + context.setId("bar"); + context.getBean("cloudBusInboundChannel", MessageChannel.class) + .send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", + null))); + assertNotNull(context.getBean(InboundMessageHandlerConfiguration.class).event); + } + + @Test + public void outboundFromSelf() { + context = SpringApplication.run(OutboundMessageHandlerConfiguration.class); + context.setId("foo"); + context.publishEvent(new RefreshRemoteApplicationEvent(this, "foo", null)); + assertNotNull(context.getBean(OutboundMessageHandlerConfiguration.class).message); + } + + @Test + public void outboundNotFromSelf() { + context = SpringApplication.run(OutboundMessageHandlerConfiguration.class); + context.setId("bar"); + context.publishEvent(new RefreshRemoteApplicationEvent(this, "foo", null)); + assertNull(context.getBean(OutboundMessageHandlerConfiguration.class).message); + } + + @Configuration + @Import(BusAutoConfiguration.class) + @MessageEndpoint + @EnableIntegration + protected static class OutboundMessageHandlerConfiguration { + + private Message message; + + @ServiceActivator(inputChannel = "cloudBusOutboundChannel") + public void handle(Message message) { + this.message = message; + } + + } + + @Configuration + @Import(BusAutoConfiguration.class) + @MessageEndpoint + @EnableIntegration + protected static class InboundMessageHandlerConfiguration implements + ApplicationListener { + + private RefreshRemoteApplicationEvent event; + + @Override + public void onApplicationEvent(RefreshRemoteApplicationEvent event) { + this.event = event; + } + + } + +} diff --git a/src/test/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpointTests.java b/src/test/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpointTests.java new file mode 100644 index 0000000..9621e51 --- /dev/null +++ b/src/test/java/org/springframework/cloud/bus/endpoint/RefreshBusEndpointTests.java @@ -0,0 +1,20 @@ +package org.springframework.cloud.bus.endpoint; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + + + +/** + * @author Dave Syer + */ +public class RefreshBusEndpointTests { + + @Test + public void instanceId() throws Exception { + RefreshBusEndpoint endpoint = new RefreshBusEndpoint(null, "foo", new BusEndpoint()); + assertEquals("foo", endpoint.getInstanceId()); + } + +}