From ca7aa9a49c4199c58b0bce990f8cb38febd1454a Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 8 Apr 2015 13:43:37 -0600 Subject: [PATCH] treat the 'destination' param as path while match using a PathMatcher Specifically an AntPathMatcher with the separator as a colon (:) fixes gh-17 --- docs/src/main/asciidoc/spring-cloud-bus.adoc | 4 ++ .../cloud/bus/BusAutoConfiguration.java | 30 ++++++------- .../cloud/bus/ServiceMatcher.java | 44 +++++++++++++++++++ .../cloud/bus/BusAutoConfigurationTests.java | 30 +++++++++++++ 4 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java diff --git a/docs/src/main/asciidoc/spring-cloud-bus.adoc b/docs/src/main/asciidoc/spring-cloud-bus.adoc index 22f418d..51da1c5 100644 --- a/docs/src/main/asciidoc/spring-cloud-bus.adoc +++ b/docs/src/main/asciidoc/spring-cloud-bus.adoc @@ -16,6 +16,10 @@ include::quickstart.adoc[] The HTTP endpoints accept a "destination" parameter, e.g. "/bus/refresh?destination=customers:9000", where the destination is an `ApplicationContext` ID. If the ID is owned by an instance on the Bus then it will process the message and all other instances will ignore it. Spring Boot sets the ID for you in the `ContextIdApplicationContextInitializer` to a combination of the `spring.application.name`, active profiles and `server.port` by default. +== Addressing all instances of a service + +The "destination" parameter is used in a Spring `PathMatcher` (with the path separator as a colon `:`) to determine if an instance will process the message. Using the example from above, "/bus/refresh?destination=customers:**" will target all instances of the "customers" service regardless of the profiles and ports set as the `ApplicationContext` ID. + == Customizing the AMQP ConnectionFactory If you are using AMQP there needs to be a `ConnectionFactory` (from diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java index 9c0f9da..8b0ee97 100644 --- a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/BusAutoConfiguration.java @@ -30,6 +30,8 @@ import org.springframework.integration.event.outbound.ApplicationEventPublishing import org.springframework.integration.handler.LoggingHandler; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.SubscribableChannel; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; /** * @author Spencer Gibb @@ -54,7 +56,7 @@ public class BusAutoConfiguration { return new GenericSelector() { @Override public boolean accept(RemoteApplicationEvent source) { - return isFromSelf(source); + return serviceMatcher().isFromSelf(source); } }; } @@ -85,7 +87,7 @@ public class BusAutoConfiguration { return new GenericSelector() { @Override public boolean accept(RemoteApplicationEvent event) { - return !isFromSelf(event) && isForSelf(event); + return !serviceMatcher().isFromSelf(event) && serviceMatcher().isForSelf(event); } }; } @@ -115,6 +117,16 @@ public class BusAutoConfiguration { return IntegrationFlows.from(cloudBusWiretapChannel()).handle(handler).get(); } + @Bean + public PathMatcher busPathMatcher() { + return new AntPathMatcher(":"); + } + + @Bean + public ServiceMatcher serviceMatcher() { + return new ServiceMatcher(); + } + @Configuration @ConditionalOnClass(Endpoint.class) protected static class BusEndpointConfiguration { @@ -169,20 +181,6 @@ public class BusAutoConfiguration { } } - private boolean isFromSelf(RemoteApplicationEvent event) { - String originService = event.getOriginService(); - String serviceId = getServiceId(); - return originService.equals(serviceId); - } - private boolean isForSelf(RemoteApplicationEvent event) { - return (event.getDestinationService() == null - || event.getDestinationService().trim().isEmpty() || event - .getDestinationService().equals(getServiceId())); - } - - private String getServiceId() { - return context.getId(); - } } diff --git a/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java new file mode 100644 index 0000000..6a37a0e --- /dev/null +++ b/spring-cloud-bus/src/main/java/org/springframework/cloud/bus/ServiceMatcher.java @@ -0,0 +1,44 @@ +package org.springframework.cloud.bus; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.cloud.bus.event.RemoteApplicationEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.PathMatcher; + +/** + * @author Spencer Gibb + */ +public class ServiceMatcher implements ApplicationContextAware { + private ApplicationContext context; + private PathMatcher matcher; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Autowired + @Qualifier("busPathMatcher") + public void setMatcher(PathMatcher matcher) { + this.matcher = matcher; + } + + public boolean isFromSelf(RemoteApplicationEvent event) { + String originService = event.getOriginService(); + String serviceId = getServiceId(); + return matcher.match(originService, serviceId); + } + + public boolean isForSelf(RemoteApplicationEvent event) { + String destinationService = event.getDestinationService(); + return (destinationService == null || destinationService.trim().isEmpty() || matcher + .match(destinationService, getServiceId())); + } + + private String getServiceId() { + return context.getId(); + } +} diff --git a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java index ecd3cf6..7e9ce18 100644 --- a/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java +++ b/spring-cloud-bus/src/test/java/org/springframework/cloud/bus/BusAutoConfigurationTests.java @@ -75,6 +75,36 @@ public class BusAutoConfigurationTests { assertNull(context.getBean(OutboundMessageHandlerConfiguration.class).message); } + @Test + public void inboundNotFromSelfPathPattern() { + context = SpringApplication.run(InboundMessageHandlerConfiguration.class); + context.setId("bar:1000"); + context.getBean("cloudBusInboundChannel", MessageChannel.class).send( + new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", + "bar:*"))); + assertNotNull(context.getBean(InboundMessageHandlerConfiguration.class).event); + } + + @Test + public void inboundNotFromSelfDeepPathPattern() { + context = SpringApplication.run(InboundMessageHandlerConfiguration.class); + context.setId("bar:test:1000"); + context.getBean("cloudBusInboundChannel", MessageChannel.class).send( + new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", + "bar:**"))); + assertNotNull(context.getBean(InboundMessageHandlerConfiguration.class).event); + } + + @Test + public void inboundNotFromSelfFlatPattern() { + context = SpringApplication.run(InboundMessageHandlerConfiguration.class); + context.setId("bar"); + context.getBean("cloudBusInboundChannel", MessageChannel.class).send( + new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", + "bar*"))); + assertNotNull(context.getBean(InboundMessageHandlerConfiguration.class).event); + } + @Configuration @Import(BusAutoConfiguration.class) @MessageEndpoint