Merge branch 'master' into turbine2
Conflicts: pom.xml
This commit is contained in:
112
README.adoc
112
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.
|
||||
@@ -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<RemoteApplicationEvent>() {
|
||||
@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<RemoteApplicationEvent>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RemoteApplicationEvent>() {
|
||||
@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<RemoteApplicationEvent>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<? extends Endpoint> 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<? extends Endpoint> getEndpointType() {
|
||||
return this.delegate.getClass();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, String> params,
|
||||
@RequestParam(value = "destination", required = false) String destination) {
|
||||
publish(new EnvironmentChangeRemoteApplicationEvent(this, getAppName(), destination, params));
|
||||
publish(new EnvironmentChangeRemoteApplicationEvent(this, getInstanceId(), destination, params));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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<RefreshRemoteApplicationEvent> {
|
||||
|
||||
private RefreshRemoteApplicationEvent event;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user