Refactors bus to use spring cloud function. (#236)
Rather than the annotation model. The monolithic `BusAutoConfiguration` was split. BusBridge is now an interface with a send() method. In the future, there will be an RSocketBusBridge, the default is `StreamBusBridge`. StreamBusBridge uses StreamBridge to send messages. RemoteApplicationEventListener listens for remote events sends them to bus if needed. Adds an amqp integration test. fixes gh-227
This commit is contained in:
@@ -1,24 +1,24 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: springcloud/pipeline-base
|
||||
user: appuser
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
environment:
|
||||
_JAVA_OPTIONS: "-Xms1024m -Xmx2048m"
|
||||
TERM: dumb
|
||||
CACHE_VERSION: 2
|
||||
branches:
|
||||
ignore:
|
||||
- gh-pages # list of branches to ignore
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: sc-bus-{{ .Branch }}
|
||||
key: sc-bus-{{ .Environment.CACHE_VERSION }}-{{ .Branch }}
|
||||
- run:
|
||||
name: "Download dependencies"
|
||||
command: ./mvnw -s .settings.xml -U --fail-never dependency:go-offline || true
|
||||
- save_cache:
|
||||
key: sc-bus-{{ .Branch }}
|
||||
key: sc-bus-{{ .Environment.CACHE_VERSION }}-{{ .Branch }}
|
||||
paths:
|
||||
- ~/.m2
|
||||
- run:
|
||||
|
||||
@@ -17,12 +17,21 @@
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<testcontainers.version>1.15.0-rc1</testcontainers.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
@@ -30,7 +39,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-bus</artifactId>
|
||||
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -45,12 +54,19 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<artifactId>spring-cloud-stream-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support</artifactId>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>rabbitmq</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* https://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.cloud.bus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.RabbitMQContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.bus.event.EnvironmentChangeRemoteApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = { "management.endpoints.web.exposure.include=*",
|
||||
"logging.level.org.springframework.cloud.bus=TRACE", "spring.cloud.bus.id=app:1",
|
||||
"spring.autoconfigure.exclude=org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration" })
|
||||
@Testcontainers
|
||||
public class BusAmqpIntegrationTests {
|
||||
|
||||
@Container
|
||||
private static final RabbitMQContainer rabbitMQContainer = new RabbitMQContainer();
|
||||
|
||||
private static ConfigurableApplicationContext context;
|
||||
|
||||
@DynamicPropertySource
|
||||
static void properties(DynamicPropertyRegistry registry) {
|
||||
registry.add("spring.rabbitmq.host", rabbitMQContainer::getHost);
|
||||
registry.add("spring.rabbitmq.port", rabbitMQContainer::getAmqpPort);
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void before() {
|
||||
context = new SpringApplicationBuilder(TestConfig.class).properties("server.port=0",
|
||||
"spring.rabbitmq.host=" + rabbitMQContainer.getHost(),
|
||||
"spring.rabbitmq.port=" + rabbitMQContainer.getAmqpPort(),
|
||||
"management.endpoints.web.exposure.include=*", "spring.cloud.bus.id=app:2",
|
||||
"spring.autoconfigure.exclude=org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration")
|
||||
.run();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void after() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void remoteEventsAreSentViaAmqp(@Autowired WebTestClient client, @Autowired TestConfig testConfig)
|
||||
throws InterruptedException {
|
||||
assertThat(rabbitMQContainer.isRunning());
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("name", "foo");
|
||||
map.put("value", "bar");
|
||||
client.post().uri("/actuator/busenv").bodyValue(map).exchange().expectStatus().is2xxSuccessful();
|
||||
TestConfig remoteTestConfig = context.getBean(TestConfig.class);
|
||||
assertThat(remoteTestConfig.latch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(testConfig.latch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
static class TestConfig implements ApplicationListener<EnvironmentChangeRemoteApplicationEvent> {
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(EnvironmentChangeRemoteApplicationEvent event) {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,12 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.bus;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.trace.http.HttpTraceRepository;
|
||||
@@ -34,26 +28,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration;
|
||||
import org.springframework.cloud.bus.endpoint.EnvironmentBusEndpoint;
|
||||
import org.springframework.cloud.bus.event.AckRemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.EnvironmentChangeListener;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.SentApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.TraceListener;
|
||||
import org.springframework.cloud.context.environment.EnvironmentManager;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.BindingServiceConfiguration;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
||||
import static org.springframework.cloud.bus.BusConstants.BUS_CONSUMER;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -61,122 +46,37 @@ import org.springframework.messaging.support.MessageBuilder;
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBusEnabled
|
||||
@EnableBinding(SpringCloudBusClient.class)
|
||||
@EnableConfigurationProperties(BusProperties.class)
|
||||
@AutoConfigureBefore(BindingServiceConfiguration.class)
|
||||
// so stream bindings work properly
|
||||
@AutoConfigureAfter({ LifecycleMvcEndpointAutoConfiguration.class, ServiceMatcherAutoConfiguration.class })
|
||||
// so actuator endpoints have needed dependencies
|
||||
public class BusAutoConfiguration implements ApplicationEventPublisherAware {
|
||||
public class BusAutoConfiguration {
|
||||
|
||||
private static final Log log = LogFactory.getLog(BusAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* Name of the Bus path matcher.
|
||||
*/
|
||||
public static final String BUS_PATH_MATCHER_NAME = "busPathMatcher";
|
||||
|
||||
/**
|
||||
* Name of the Spring Cloud Config property.
|
||||
*/
|
||||
public static final String CLOUD_CONFIG_NAME_PROPERTY = "spring.cloud.config.name";
|
||||
|
||||
private final ServiceMatcher serviceMatcher;
|
||||
|
||||
private final BindingServiceProperties bindings;
|
||||
|
||||
private final BusProperties bus;
|
||||
|
||||
private MessageChannel cloudBusOutboundChannel;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
public BusAutoConfiguration(ServiceMatcher serviceMatcher, BindingServiceProperties bindings, BusProperties bus) {
|
||||
this.serviceMatcher = serviceMatcher;
|
||||
this.bindings = bindings;
|
||||
this.bus = bus;
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(BusBridge.class)
|
||||
public StreamBusBridge streamBusBridge(StreamBridge streamBridge, BusProperties properties) {
|
||||
return new StreamBusBridge(streamBridge, properties);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
BindingProperties inputBinding = this.bindings.getBindings().get(SpringCloudBusClient.INPUT);
|
||||
if (inputBinding == null) {
|
||||
this.bindings.getBindings().put(SpringCloudBusClient.INPUT, new BindingProperties());
|
||||
}
|
||||
BindingProperties input = this.bindings.getBindings().get(SpringCloudBusClient.INPUT);
|
||||
if (input.getDestination() == null || input.getDestination().equals(SpringCloudBusClient.INPUT)) {
|
||||
input.setDestination(this.bus.getDestination());
|
||||
}
|
||||
BindingProperties outputBinding = this.bindings.getBindings().get(SpringCloudBusClient.OUTPUT);
|
||||
if (outputBinding == null) {
|
||||
this.bindings.getBindings().put(SpringCloudBusClient.OUTPUT, new BindingProperties());
|
||||
}
|
||||
BindingProperties output = this.bindings.getBindings().get(SpringCloudBusClient.OUTPUT);
|
||||
if (output.getDestination() == null || output.getDestination().equals(SpringCloudBusClient.OUTPUT)) {
|
||||
output.setDestination(this.bus.getDestination());
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RemoteApplicationEventListener busRemoteApplicationEventListener(ServiceMatcher serviceMatcher,
|
||||
BusBridge busBridge) {
|
||||
return new RemoteApplicationEventListener(serviceMatcher, busBridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Output(SpringCloudBusClient.OUTPUT)
|
||||
public void setCloudBusOutboundChannel(MessageChannel cloudBusOutboundChannel) {
|
||||
this.cloudBusOutboundChannel = cloudBusOutboundChannel;
|
||||
}
|
||||
|
||||
@EventListener(classes = RemoteApplicationEvent.class)
|
||||
public void acceptLocal(RemoteApplicationEvent event) {
|
||||
if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Sending remote event on bus: " + event);
|
||||
}
|
||||
this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build());
|
||||
}
|
||||
}
|
||||
|
||||
@StreamListener(SpringCloudBusClient.INPUT)
|
||||
public void acceptRemote(RemoteApplicationEvent event) {
|
||||
if (event instanceof AckRemoteApplicationEvent) {
|
||||
if (this.bus.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event)
|
||||
&& this.applicationEventPublisher != null) {
|
||||
this.applicationEventPublisher.publishEvent(event);
|
||||
}
|
||||
// If it's an ACK we are finished processing at this point
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Received remote event from bus: " + event);
|
||||
}
|
||||
|
||||
if (this.serviceMatcher.isForSelf(event) && this.applicationEventPublisher != null) {
|
||||
if (!this.serviceMatcher.isFromSelf(event)) {
|
||||
this.applicationEventPublisher.publishEvent(event);
|
||||
}
|
||||
if (this.bus.getAck().isEnabled()) {
|
||||
AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent(this, this.serviceMatcher.getServiceId(),
|
||||
this.bus.getAck().getDestinationService(), event.getDestinationService(), event.getId(),
|
||||
event.getClass());
|
||||
this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(ack).build());
|
||||
this.applicationEventPublisher.publishEvent(ack);
|
||||
}
|
||||
}
|
||||
if (this.bus.getTrace().isEnabled() && this.applicationEventPublisher != null) {
|
||||
// We are set to register sent events so publish it for local consumption,
|
||||
// irrespective of the origin
|
||||
this.applicationEventPublisher.publishEvent(new SentApplicationEvent(this, event.getOriginService(),
|
||||
event.getDestinationService(), event.getId(), event.getClass()));
|
||||
}
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = BUS_CONSUMER)
|
||||
public BusConsumer busConsumer(ApplicationEventPublisher applicationEventPublisher, ServiceMatcher serviceMatcher,
|
||||
BusBridge busBridge, BusProperties properties) {
|
||||
return new BusConsumer(applicationEventPublisher, serviceMatcher, busBridge, properties);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Endpoint.class })
|
||||
@ConditionalOnBean(HttpTraceRepository.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.bus.trace.enabled", matchIfMissing = false)
|
||||
@ConditionalOnProperty(BusProperties.PREFIX + ".trace.enabled")
|
||||
protected static class BusAckTraceConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* https://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.cloud.bus;
|
||||
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
public interface BusBridge {
|
||||
|
||||
void send(RemoteApplicationEvent event);
|
||||
|
||||
}
|
||||
@@ -16,31 +16,30 @@
|
||||
|
||||
package org.springframework.cloud.bus;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface SpringCloudBusClient {
|
||||
abstract class BusConstants {
|
||||
|
||||
/**
|
||||
* Name of the input channel for Spring Cloud Bus.
|
||||
*/
|
||||
String INPUT = "springCloudBusInput";
|
||||
public static final String INPUT = "springCloudBusInput";
|
||||
|
||||
/**
|
||||
* Name of the output channel for Spring Cloud Bus.
|
||||
*/
|
||||
String OUTPUT = "springCloudBusOutput";
|
||||
public static final String OUTPUT = "springCloudBusOutput";
|
||||
|
||||
@Output(SpringCloudBusClient.OUTPUT)
|
||||
MessageChannel springCloudBusOutput();
|
||||
/**
|
||||
* Name of the output channel for Spring Cloud Bus.
|
||||
*/
|
||||
public static final String DESTINATION = "springCloudBus";
|
||||
|
||||
@Input(SpringCloudBusClient.INPUT)
|
||||
SubscribableChannel springCloudBusInput();
|
||||
/**
|
||||
* Name of the Spring Cloud Bus function.
|
||||
*/
|
||||
public static final String BUS_CONSUMER = "busConsumer";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* https://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.cloud.bus;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.bus.event.AckRemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.SentApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
public class BusConsumer implements Consumer<RemoteApplicationEvent> {
|
||||
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private final ApplicationEventPublisher publisher;
|
||||
|
||||
private final ServiceMatcher serviceMatcher;
|
||||
|
||||
private final BusBridge busBridge;
|
||||
|
||||
private final BusProperties properties;
|
||||
|
||||
public BusConsumer(ApplicationEventPublisher publisher, ServiceMatcher serviceMatcher, BusBridge busBridge,
|
||||
BusProperties properties) {
|
||||
this.publisher = publisher;
|
||||
this.serviceMatcher = serviceMatcher;
|
||||
this.busBridge = busBridge;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(RemoteApplicationEvent event) {
|
||||
if (event instanceof AckRemoteApplicationEvent) {
|
||||
if (this.properties.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event)
|
||||
&& this.publisher != null) {
|
||||
this.publisher.publishEvent(event);
|
||||
}
|
||||
// If it's an ACK we are finished processing at this point
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Received remote event from bus: " + event);
|
||||
}
|
||||
|
||||
if (this.serviceMatcher.isForSelf(event) && this.publisher != null) {
|
||||
if (!this.serviceMatcher.isFromSelf(event)) {
|
||||
this.publisher.publishEvent(event);
|
||||
}
|
||||
if (this.properties.getAck().isEnabled()) {
|
||||
AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent(this, this.serviceMatcher.getServiceId(),
|
||||
this.properties.getAck().getDestinationService(), event.getDestinationService(), event.getId(),
|
||||
event.getClass());
|
||||
this.busBridge.send(ack);
|
||||
this.publisher.publishEvent(ack);
|
||||
}
|
||||
}
|
||||
if (this.properties.getTrace().isEnabled() && this.publisher != null) {
|
||||
// We are set to register sent events so publish it for local consumption,
|
||||
// irrespective of the origin
|
||||
this.publisher.publishEvent(new SentApplicationEvent(this, event.getOriginService(),
|
||||
event.getDestinationService(), event.getId(), event.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,11 +22,14 @@ import java.util.Map;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.cloud.commons.util.IdUtils;
|
||||
import org.springframework.cloud.function.context.FunctionProperties;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
import static org.springframework.cloud.bus.BusProperties.PREFIX;
|
||||
|
||||
/**
|
||||
* {@link EnvironmentPostProcessor} that sets the default properties for the Bus.
|
||||
*
|
||||
@@ -35,21 +38,43 @@ import org.springframework.core.env.PropertySource;
|
||||
*/
|
||||
public class BusEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
|
||||
static final String DEFAULTS_PROPERTY_SOURCE_NAME = "springCloudBusDefaultProperties";
|
||||
|
||||
static final String OVERRIDES_PROPERTY_SOURCE_NAME = "springCloudBusOverridesProperties";
|
||||
|
||||
private static final String FN_DEF_PROP = FunctionProperties.PREFIX + ".definition";
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("spring.cloud.stream.bindings." + SpringCloudBusClient.OUTPUT + ".content-type",
|
||||
environment.getProperty("spring.cloud.bus.content-type", "application/json"));
|
||||
map.put("spring.cloud.bus.id", IdUtils.getUnresolvedServiceId());
|
||||
addOrReplace(environment.getPropertySources(), map);
|
||||
Map<String, Object> overrides = new HashMap<>();
|
||||
String definition = BusConstants.BUS_CONSUMER;
|
||||
if (environment.containsProperty(FN_DEF_PROP)) {
|
||||
String property = environment.getProperty(FN_DEF_PROP);
|
||||
if (property != null && property.contains(BusConstants.BUS_CONSUMER)) {
|
||||
// in the case that EnvironmentPostProcessor are run more than once.
|
||||
return;
|
||||
}
|
||||
definition = property + ";" + definition;
|
||||
}
|
||||
overrides.put(FN_DEF_PROP, definition);
|
||||
addOrReplace(environment.getPropertySources(), overrides, OVERRIDES_PROPERTY_SOURCE_NAME, true);
|
||||
|
||||
Map<String, Object> defaults = new HashMap<>();
|
||||
defaults.put("spring.cloud.stream.function.bindings." + BusConstants.BUS_CONSUMER + "-in-0",
|
||||
BusConstants.INPUT);
|
||||
String destination = environment.getProperty(PREFIX + ".destination", BusConstants.DESTINATION);
|
||||
defaults.put("spring.cloud.stream.bindings." + BusConstants.INPUT + ".destination", destination);
|
||||
if (!environment.containsProperty(PREFIX + ".id")) {
|
||||
defaults.put(PREFIX + ".id", IdUtils.getUnresolvedServiceId());
|
||||
}
|
||||
addOrReplace(environment.getPropertySources(), defaults, DEFAULTS_PROPERTY_SOURCE_NAME, false);
|
||||
}
|
||||
|
||||
private void addOrReplace(MutablePropertySources propertySources, Map<String, Object> map) {
|
||||
private void addOrReplace(MutablePropertySources propertySources, Map<String, Object> map,
|
||||
String propertySourceName, boolean first) {
|
||||
MapPropertySource target = null;
|
||||
if (propertySources.contains(PROPERTY_SOURCE_NAME)) {
|
||||
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
|
||||
if (propertySources.contains(propertySourceName)) {
|
||||
PropertySource<?> source = propertySources.get(propertySourceName);
|
||||
if (source instanceof MapPropertySource) {
|
||||
target = (MapPropertySource) source;
|
||||
for (String key : map.keySet()) {
|
||||
@@ -60,10 +85,15 @@ public class BusEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
}
|
||||
}
|
||||
if (target == null) {
|
||||
target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);
|
||||
target = new MapPropertySource(propertySourceName, map);
|
||||
}
|
||||
if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {
|
||||
propertySources.addLast(target);
|
||||
if (!propertySources.contains(propertySourceName)) {
|
||||
if (first) {
|
||||
propertySources.addFirst(target);
|
||||
}
|
||||
else {
|
||||
propertySources.addLast(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,57 +17,52 @@
|
||||
package org.springframework.cloud.bus;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.bus")
|
||||
@ConfigurationProperties(BusProperties.PREFIX)
|
||||
public class BusProperties {
|
||||
|
||||
/**
|
||||
* Environment change event related properties.
|
||||
* Configuration prefix for spring cloud bus.
|
||||
*/
|
||||
private Env env = new Env();
|
||||
|
||||
/**
|
||||
* Refresh event related properties.
|
||||
*/
|
||||
private Refresh refresh = new Refresh();
|
||||
public static final String PREFIX = "spring.cloud.bus";
|
||||
|
||||
/**
|
||||
* Properties related to acks.
|
||||
*/
|
||||
private Ack ack = new Ack();
|
||||
private final Ack ack = new Ack();
|
||||
|
||||
/**
|
||||
* Properties related to tracing of acks.
|
||||
*/
|
||||
private Trace trace = new Trace();
|
||||
private final Trace trace = new Trace();
|
||||
|
||||
/**
|
||||
* Name of Spring Cloud Stream destination for messages.
|
||||
*/
|
||||
private String destination = "springCloudBus";
|
||||
private String destination = BusConstants.DESTINATION;
|
||||
|
||||
/**
|
||||
* The identifier for this application instance.
|
||||
*/
|
||||
private String id = "application";
|
||||
|
||||
/**
|
||||
* The bus mime-type.
|
||||
*/
|
||||
private MimeType contentType = MimeTypeUtils.APPLICATION_JSON;
|
||||
|
||||
/**
|
||||
* Flag to indicate that the bus is enabled.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public Env getEnv() {
|
||||
return this.env;
|
||||
}
|
||||
|
||||
public Refresh getRefresh() {
|
||||
return this.refresh;
|
||||
}
|
||||
|
||||
public Ack getAck() {
|
||||
return this.ack;
|
||||
}
|
||||
@@ -100,43 +95,18 @@ public class BusProperties {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring Cloud Bus environment related properties.
|
||||
*/
|
||||
public static class Env {
|
||||
|
||||
/**
|
||||
* Flag to switch off environment change events (default on).
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public MimeType getContentType() {
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spring Cloud Bus properties related to refreshing.
|
||||
*/
|
||||
public static class Refresh {
|
||||
public void setContentType(MimeType contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to switch off refresh events (default on).
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("ack", ack).append("trace", trace).append("destination", destination)
|
||||
.append("id", id).append("contentType", contentType).append("enabled", enabled).toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -171,6 +141,12 @@ public class BusProperties {
|
||||
this.destinationService = destinationService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("enabled", enabled).append("destinationService", destinationService)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +167,11 @@ public class BusProperties {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("enabled", enabled).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ public class DefaultBusPathMatcher implements PathMatcher {
|
||||
|
||||
protected boolean matchMultiProfile(String pattern, String idToMatch) {
|
||||
|
||||
log.debug("matchMultiProfile : " + pattern + ", " + idToMatch);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("matchMultiProfile : " + pattern + ", " + idToMatch);
|
||||
}
|
||||
|
||||
// parse the id
|
||||
String[] tokens = tokenizeToStringArray(idToMatch, ":");
|
||||
@@ -82,12 +84,16 @@ public class DefaultBusPathMatcher implements PathMatcher {
|
||||
|
||||
for (String id : idsWithSingleProfile) {
|
||||
if (this.delagateMatcher.match(pattern, id)) {
|
||||
log.debug("matched true");
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("matched true");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("matched false");
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("matched false");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -98,7 +104,9 @@ public class DefaultBusPathMatcher implements PathMatcher {
|
||||
|
||||
@Override
|
||||
public boolean match(String pattern, String path) {
|
||||
log.debug("In match: " + pattern + ", " + path);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("In match: " + pattern + ", " + path);
|
||||
}
|
||||
if (!this.delagateMatcher.match(pattern, path)) {
|
||||
return matchMultiProfile(pattern, path);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* https://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.cloud.bus;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.bus.event.AckRemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
public class RemoteApplicationEventListener implements ApplicationListener<RemoteApplicationEvent> {
|
||||
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private final ServiceMatcher serviceMatcher;
|
||||
|
||||
private final BusBridge busBridge;
|
||||
|
||||
public RemoteApplicationEventListener(ServiceMatcher serviceMatcher, BusBridge busBridge) {
|
||||
this.serviceMatcher = serviceMatcher;
|
||||
this.busBridge = busBridge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(RemoteApplicationEvent event) {
|
||||
if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Sending remote event on bus: " + event);
|
||||
}
|
||||
// TODO: configurable mimetype?
|
||||
this.busBridge.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,8 +24,6 @@ import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
import static org.springframework.cloud.bus.BusAutoConfiguration.CLOUD_CONFIG_NAME_PROPERTY;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@@ -34,10 +32,20 @@ import static org.springframework.cloud.bus.BusAutoConfiguration.CLOUD_CONFIG_NA
|
||||
@EnableConfigurationProperties(BusProperties.class)
|
||||
public class ServiceMatcherAutoConfiguration {
|
||||
|
||||
/**
|
||||
* Name of the Bus path matcher.
|
||||
*/
|
||||
public static final String BUS_PATH_MATCHER_NAME = "busPathMatcher";
|
||||
|
||||
/**
|
||||
* Name of the Spring Cloud Config property.
|
||||
*/
|
||||
public static final String CLOUD_CONFIG_NAME_PROPERTY = "spring.cloud.config.name";
|
||||
|
||||
@BusPathMatcher
|
||||
// There is a @Bean of type PathMatcher coming from Spring MVC
|
||||
@ConditionalOnMissingBean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME)
|
||||
@Bean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME)
|
||||
@ConditionalOnMissingBean(name = BUS_PATH_MATCHER_NAME)
|
||||
@Bean(name = BUS_PATH_MATCHER_NAME)
|
||||
public PathMatcher busPathMatcher() {
|
||||
return new DefaultBusPathMatcher(new AntPathMatcher(":"));
|
||||
}
|
||||
@@ -46,8 +54,7 @@ public class ServiceMatcherAutoConfiguration {
|
||||
public ServiceMatcher serviceMatcher(@BusPathMatcher PathMatcher pathMatcher, BusProperties properties,
|
||||
Environment environment) {
|
||||
String[] configNames = environment.getProperty(CLOUD_CONFIG_NAME_PROPERTY, String[].class, new String[] {});
|
||||
ServiceMatcher serviceMatcher = new ServiceMatcher(pathMatcher, properties.getId(), configNames);
|
||||
return serviceMatcher;
|
||||
return new ServiceMatcher(pathMatcher, properties.getId(), configNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* https://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.cloud.bus;
|
||||
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
||||
public class StreamBusBridge implements BusBridge {
|
||||
|
||||
private final StreamBridge streamBridge;
|
||||
|
||||
private final BusProperties properties;
|
||||
|
||||
public StreamBusBridge(StreamBridge streamBridge, BusProperties properties) {
|
||||
this.streamBridge = streamBridge;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public void send(RemoteApplicationEvent event) {
|
||||
// TODO: configurable mimetype?
|
||||
this.streamBridge.send(properties.getDestination(), MessageBuilder.withPayload(event).build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.cloud.bus.env.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Flag to switch off environment change events (default on).",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.bus.refresh.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Flag to switch off refresh events (default on).",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.bus.trace.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Flag to switch on tracing of acks (default off).",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,36 +20,29 @@ import java.util.HashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.cloud.bus.event.AckRemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.SentApplicationEvent;
|
||||
import org.springframework.cloud.bus.event.UnknownRemoteApplicationEvent;
|
||||
import org.springframework.cloud.context.refresh.ContextRefresher;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.channel.DirectChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.ChannelInterceptorAdapter;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -78,7 +71,7 @@ public class BusAutoConfigurationTests {
|
||||
public void inboundNotForSelf() {
|
||||
this.context = SpringApplication.run(InboundMessageHandlerConfiguration.class, "--spring.cloud.bus.id=foo",
|
||||
"--server.port=0");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "bar", "bar")));
|
||||
assertThat(this.context.getBean(InboundMessageHandlerConfiguration.class).refresh).isNull();
|
||||
}
|
||||
@@ -87,7 +80,7 @@ public class BusAutoConfigurationTests {
|
||||
public void inboundFromSelf() {
|
||||
this.context = SpringApplication.run(InboundMessageHandlerConfiguration.class, "--spring.cloud.bus.id=foo",
|
||||
"--server.port=0");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", null)));
|
||||
assertThat(this.context.getBean(InboundMessageHandlerConfiguration.class).refresh).isNull();
|
||||
}
|
||||
@@ -96,7 +89,7 @@ public class BusAutoConfigurationTests {
|
||||
public void inboundNotFromSelf() {
|
||||
this.context = SpringApplication.run(InboundMessageHandlerConfiguration.class, "--spring.cloud.bus.id=bar",
|
||||
"--server.port=0");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", null)));
|
||||
assertThat(this.context.getBean(InboundMessageHandlerConfiguration.class).refresh).isNotNull();
|
||||
}
|
||||
@@ -106,26 +99,27 @@ public class BusAutoConfigurationTests {
|
||||
this.context = SpringApplication.run(
|
||||
new Class[] { InboundMessageHandlerConfiguration.class, OutboundMessageHandlerConfiguration.class,
|
||||
SentMessageConfiguration.class },
|
||||
new String[] { "--spring.cloud.bus.id=bar", "--server.port=0" });
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
new String[] { "--spring.cloud.bus.id=bar", "--server.port=0",
|
||||
"--spring.main.allow-bean-definition-overriding=true" });
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", null)));
|
||||
RefreshRemoteApplicationEvent refresh = this.context.getBean(InboundMessageHandlerConfiguration.class).refresh;
|
||||
assertThat(refresh).isNotNull();
|
||||
OutboundMessageHandlerConfiguration outbound = this.context.getBean(OutboundMessageHandlerConfiguration.class);
|
||||
outbound.latch.await(2000L, TimeUnit.MILLISECONDS);
|
||||
String message = (String) outbound.message.getPayload();
|
||||
assertThat(message.contains("\"ackId\":\"" + refresh.getId())).as("Wrong ackId: " + message).isTrue();
|
||||
TestStreamBusBridge busBridge = this.context.getBean(TestStreamBusBridge.class);
|
||||
busBridge.latch.await(200, TimeUnit.SECONDS);
|
||||
assertThat(busBridge.message).isInstanceOf(AckRemoteApplicationEvent.class);
|
||||
AckRemoteApplicationEvent message = (AckRemoteApplicationEvent) busBridge.message;
|
||||
assertThat(message.getAckId()).as("Wrong ackId: %s", message).isEqualTo(refresh.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inboundNotFromSelfWithTrace() throws Exception {
|
||||
public void inboundNotFromSelfWithTrace() {
|
||||
this.context = SpringApplication.run(
|
||||
new Class[] { InboundMessageHandlerConfiguration.class, OutboundMessageHandlerConfiguration.class,
|
||||
SentMessageConfiguration.class },
|
||||
new String[] { "--spring.cloud.bus.trace.enabled=true", "--spring.cloud.bus.id=bar",
|
||||
"--server.port=0" });
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", null)));
|
||||
this.context.getBean(BusConsumer.class).accept(new RefreshRemoteApplicationEvent(this, "foo", null));
|
||||
RefreshRemoteApplicationEvent refresh = this.context.getBean(InboundMessageHandlerConfiguration.class).refresh;
|
||||
assertThat(refresh).isNotNull();
|
||||
SentMessageConfiguration sent = this.context.getBean(SentMessageConfiguration.class);
|
||||
@@ -134,18 +128,18 @@ public class BusAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inboundAckWithTrace() throws Exception {
|
||||
public void inboundAckWithTrace() throws InterruptedException {
|
||||
this.context = SpringApplication.run(
|
||||
new Class[] { InboundMessageHandlerConfiguration.class, OutboundMessageHandlerConfiguration.class,
|
||||
AckMessageConfiguration.class },
|
||||
new String[] { "--spring.cloud.bus.trace.enabled=true", "--spring.cloud.bus.id=bar",
|
||||
"--server.port=0" });
|
||||
this.context.getBean(BusProperties.class).setId("bar");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class).send(new GenericMessage<>(
|
||||
new AckRemoteApplicationEvent(this, "foo", null, "ID", "bar", RefreshRemoteApplicationEvent.class)));
|
||||
AckMessageConfiguration sent = this.context.getBean(AckMessageConfiguration.class);
|
||||
assertThat(sent.event).isNotNull();
|
||||
assertThat(sent.count).isEqualTo(1);
|
||||
this.context.getBean(BusConsumer.class).accept(
|
||||
new AckRemoteApplicationEvent(this, "foo", null, "ID", "bar", RefreshRemoteApplicationEvent.class));
|
||||
AckMessageConfiguration ack = this.context.getBean(AckMessageConfiguration.class);
|
||||
assertThat(ack.latch.await(5, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(ack.event).isNotNull();
|
||||
assertThat(ack.count).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -153,9 +147,9 @@ public class BusAutoConfigurationTests {
|
||||
this.context = SpringApplication.run(OutboundMessageHandlerConfiguration.class, "--debug=true",
|
||||
"--spring.cloud.bus.id=foo", "--server.port=0");
|
||||
this.context.publishEvent(new RefreshRemoteApplicationEvent(this, "foo", null));
|
||||
OutboundMessageHandlerConfiguration outbound = this.context.getBean(OutboundMessageHandlerConfiguration.class);
|
||||
outbound.latch.await(2000L, TimeUnit.MILLISECONDS);
|
||||
assertThat(outbound.message).as("message was null").isNotNull();
|
||||
TestStreamBusBridge busBridge = this.context.getBean(TestStreamBusBridge.class);
|
||||
busBridge.latch.await(2, TimeUnit.SECONDS);
|
||||
assertThat(busBridge.message).as("message was null").isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -163,14 +157,14 @@ public class BusAutoConfigurationTests {
|
||||
this.context = SpringApplication.run(OutboundMessageHandlerConfiguration.class, "--spring.cloud.bus.id=bar",
|
||||
"--server.port=0");
|
||||
this.context.publishEvent(new RefreshRemoteApplicationEvent(this, "foo", null));
|
||||
assertThat(this.context.getBean(OutboundMessageHandlerConfiguration.class).message).isNull();
|
||||
assertThat(this.context.getBean(TestStreamBusBridge.class).message).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inboundNotFromSelfPathPattern() {
|
||||
this.context = SpringApplication.run(InboundMessageHandlerConfiguration.class, "--spring.cloud.bus.id=bar:1000",
|
||||
"--server.port=0");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", "bar:*")));
|
||||
assertThat(this.context.getBean(InboundMessageHandlerConfiguration.class).refresh).isNotNull();
|
||||
}
|
||||
@@ -179,7 +173,7 @@ public class BusAutoConfigurationTests {
|
||||
public void inboundNotFromSelfDeepPathPattern() {
|
||||
this.context = SpringApplication.run(InboundMessageHandlerConfiguration.class,
|
||||
"--spring.cloud.bus.id=bar:test:1000", "--server.port=0");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", "bar:**")));
|
||||
assertThat(this.context.getBean(InboundMessageHandlerConfiguration.class).refresh).isNotNull();
|
||||
}
|
||||
@@ -188,7 +182,7 @@ public class BusAutoConfigurationTests {
|
||||
public void inboundNotFromSelfFlatPattern() {
|
||||
this.context = SpringApplication.run(InboundMessageHandlerConfiguration.class, "--spring.cloud.bus.id=bar",
|
||||
"--server.port=0");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new RefreshRemoteApplicationEvent(this, "foo", "bar*")));
|
||||
assertThat(this.context.getBean(InboundMessageHandlerConfiguration.class).refresh).isNotNull();
|
||||
}
|
||||
@@ -198,79 +192,44 @@ public class BusAutoConfigurationTests {
|
||||
public void inboundNotFromSelfUnknown() {
|
||||
this.context = SpringApplication.run(InboundMessageHandlerConfiguration.class, "--spring.cloud.bus.id=bar",
|
||||
"--server.port=0");
|
||||
this.context.getBean(SpringCloudBusClient.INPUT, MessageChannel.class)
|
||||
this.context.getBean(BusConstants.INPUT, MessageChannel.class)
|
||||
.send(new GenericMessage<>(new UnknownRemoteApplicationEvent(this, "UnknownEvent", "yada".getBytes())));
|
||||
// No Exception expected
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initSetsBindingDestinationIfNullDefault() {
|
||||
HashMap<String, BindingProperties> properties = new HashMap<>();
|
||||
properties.put(SpringCloudBusClient.INPUT, new BindingProperties());
|
||||
properties.put(SpringCloudBusClient.OUTPUT, new BindingProperties());
|
||||
|
||||
testDestinations(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initSetsBindingDestinationIfNotNullDefault() {
|
||||
HashMap<String, BindingProperties> properties = new HashMap<>();
|
||||
BindingProperties input = new BindingProperties();
|
||||
input.setDestination(SpringCloudBusClient.INPUT);
|
||||
properties.put(SpringCloudBusClient.INPUT, input);
|
||||
BindingProperties output = new BindingProperties();
|
||||
output.setDestination(SpringCloudBusClient.OUTPUT);
|
||||
properties.put(SpringCloudBusClient.OUTPUT, output);
|
||||
|
||||
testDestinations(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initDoesNotOverrideCustomDestination() {
|
||||
HashMap<String, BindingProperties> properties = new HashMap<>();
|
||||
BindingProperties input = new BindingProperties();
|
||||
input.setDestination("mydestination");
|
||||
properties.put(SpringCloudBusClient.INPUT, input);
|
||||
properties.put(BusConstants.INPUT, input);
|
||||
BindingProperties output = new BindingProperties();
|
||||
output.setDestination("mydestination");
|
||||
properties.put(SpringCloudBusClient.OUTPUT, output);
|
||||
properties.put(BusConstants.OUTPUT, output);
|
||||
|
||||
setupBusAutoConfig(properties);
|
||||
|
||||
BindingProperties inputProps = properties.get(SpringCloudBusClient.INPUT);
|
||||
BindingProperties inputProps = properties.get(BusConstants.INPUT);
|
||||
assertThat(inputProps.getDestination()).isEqualTo("mydestination");
|
||||
|
||||
BindingProperties outputProps = properties.get(SpringCloudBusClient.OUTPUT);
|
||||
BindingProperties outputProps = properties.get(BusConstants.OUTPUT);
|
||||
assertThat(outputProps.getDestination()).isEqualTo("mydestination");
|
||||
}
|
||||
|
||||
private void testDestinations(HashMap<String, BindingProperties> properties) {
|
||||
BusProperties bus = setupBusAutoConfig(properties);
|
||||
|
||||
BindingProperties input = properties.get(SpringCloudBusClient.INPUT);
|
||||
assertThat(input.getDestination()).isEqualTo(bus.getDestination());
|
||||
|
||||
BindingProperties output = properties.get(SpringCloudBusClient.OUTPUT);
|
||||
assertThat(output.getDestination()).isEqualTo(bus.getDestination());
|
||||
}
|
||||
|
||||
private BusProperties setupBusAutoConfig(HashMap<String, BindingProperties> properties) {
|
||||
BindingServiceProperties serviceProperties = mock(BindingServiceProperties.class);
|
||||
when(serviceProperties.getBindings()).thenReturn(properties);
|
||||
|
||||
BusProperties bus = new BusProperties();
|
||||
BusAutoConfiguration configuration = new BusAutoConfiguration(mock(ServiceMatcher.class), serviceProperties,
|
||||
bus);
|
||||
configuration.init();
|
||||
BusAutoConfiguration configuration = new BusAutoConfiguration();
|
||||
return bus;
|
||||
}
|
||||
|
||||
// see https://github.com/spring-cloud/spring-cloud-bus/issues/101
|
||||
@Test
|
||||
@Ignore // TODO: replicate problem
|
||||
public void serviceMatcherIdIsConstantAfterRefresh() {
|
||||
this.context = SpringApplication.run(new Class[] { RefreshConfig.class, },
|
||||
new String[] { "--spring.main.allow-bean-definition-overriding=true" });
|
||||
new String[] { "--server.port=0", "--spring.main.allow-bean-definition-overriding=true" });
|
||||
String originalServiceId = this.context.getBean(ServiceMatcher.class).getServiceId();
|
||||
this.context.getBean(ContextRefresher.class).refresh();
|
||||
String newServiceId = this.context.getBean(ServiceMatcher.class).getServiceId();
|
||||
@@ -285,48 +244,40 @@ public class BusAutoConfigurationTests {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@Import({ MessageConsumer.class, BusAutoConfiguration.class, TestSupportBinderAutoConfiguration.class,
|
||||
@ImportAutoConfiguration({ BusAutoConfiguration.class, TestSupportBinderAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class })
|
||||
protected static class OutboundMessageHandlerConfiguration {
|
||||
|
||||
@Autowired
|
||||
@Output(SpringCloudBusClient.OUTPUT)
|
||||
private MessageChannel cloudBusOutboundChannel;
|
||||
|
||||
private CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
private Message<?> message;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
((DirectChannel) this.cloudBusOutboundChannel).addInterceptor(interceptor());
|
||||
}
|
||||
|
||||
private ChannelInterceptor interceptor() {
|
||||
return new ChannelInterceptorAdapter() {
|
||||
@Override
|
||||
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
|
||||
OutboundMessageHandlerConfiguration.this.message = message;
|
||||
OutboundMessageHandlerConfiguration.this.latch.countDown();
|
||||
}
|
||||
};
|
||||
@Bean
|
||||
@Primary
|
||||
StreamBusBridge testStreamBusBridge(StreamBridge streamBridge, BusProperties properties) {
|
||||
return new TestStreamBusBridge(streamBridge, properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@MessageEndpoint
|
||||
protected static class MessageConsumer {
|
||||
protected static class TestStreamBusBridge extends StreamBusBridge {
|
||||
|
||||
@ServiceActivator(inputChannel = SpringCloudBusClient.OUTPUT)
|
||||
public void handle(Message<?> msg) {
|
||||
private CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
private RemoteApplicationEvent message;
|
||||
|
||||
public TestStreamBusBridge(StreamBridge streamBridge, BusProperties properties) {
|
||||
super(streamBridge, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(RemoteApplicationEvent event) {
|
||||
latch.countDown();
|
||||
message = event;
|
||||
super.send(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@Import({ MessageConsumer.class, BusAutoConfiguration.class, TestSupportBinderAutoConfiguration.class,
|
||||
@ImportAutoConfiguration({ BusAutoConfiguration.class, TestSupportBinderAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class })
|
||||
protected static class InboundMessageHandlerConfiguration
|
||||
implements ApplicationListener<RefreshRemoteApplicationEvent> {
|
||||
@@ -358,6 +309,8 @@ public class BusAutoConfigurationTests {
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class AckMessageConfiguration implements ApplicationListener<AckRemoteApplicationEvent> {
|
||||
|
||||
private CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
private AckRemoteApplicationEvent event;
|
||||
|
||||
private int count;
|
||||
@@ -366,6 +319,7 @@ public class BusAutoConfigurationTests {
|
||||
public void onApplicationEvent(AckRemoteApplicationEvent event) {
|
||||
this.event = event;
|
||||
this.count++;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2015-2020 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
|
||||
*
|
||||
* https://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.cloud.bus;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.cloud.function.context.FunctionProperties;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.cloud.bus.BusConstants.BUS_CONSUMER;
|
||||
import static org.springframework.cloud.bus.BusConstants.DESTINATION;
|
||||
import static org.springframework.cloud.bus.BusConstants.INPUT;
|
||||
import static org.springframework.cloud.bus.BusEnvironmentPostProcessor.DEFAULTS_PROPERTY_SOURCE_NAME;
|
||||
import static org.springframework.cloud.bus.BusEnvironmentPostProcessor.OVERRIDES_PROPERTY_SOURCE_NAME;
|
||||
|
||||
public class BusEnvironmentPostProcessorTests {
|
||||
|
||||
@Test
|
||||
void testDefaults() {
|
||||
MockEnvironment env = new MockEnvironment().withProperty("cachedrandom.application.value", "123");
|
||||
new BusEnvironmentPostProcessor().postProcessEnvironment(env, mock(SpringApplication.class));
|
||||
assertThat(env.getProperty(FunctionProperties.PREFIX + ".definition")).isEqualTo(BUS_CONSUMER);
|
||||
assertThat(env.getProperty("spring.cloud.stream.function.bindings." + BUS_CONSUMER + "-in-0")).isEqualTo(INPUT);
|
||||
assertThat(env.getProperty("spring.cloud.stream.bindings." + INPUT + ".destination")).isEqualTo(DESTINATION);
|
||||
assertThat(env.getProperty(BusProperties.PREFIX + ".id")).isNotBlank();
|
||||
assertThat(env.getPropertySources().contains(OVERRIDES_PROPERTY_SOURCE_NAME));
|
||||
assertThat(env.getPropertySources().contains(DEFAULTS_PROPERTY_SOURCE_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOverrides() {
|
||||
String fnDefKey = FunctionProperties.PREFIX + ".definition";
|
||||
String idKey = BusProperties.PREFIX + ".id";
|
||||
MockEnvironment env = new MockEnvironment().withProperty("cachedrandom.application.value", "123")
|
||||
.withProperty(BusProperties.PREFIX + ".destination", "mydestination").withProperty(idKey, "app:1")
|
||||
.withProperty(fnDefKey, "uppercase");
|
||||
new BusEnvironmentPostProcessor().postProcessEnvironment(env, mock(SpringApplication.class));
|
||||
assertThat(env.getProperty(fnDefKey)).isEqualTo("uppercase;" + BUS_CONSUMER);
|
||||
assertThat(env.getProperty("spring.cloud.stream.function.bindings." + BUS_CONSUMER + "-in-0")).isEqualTo(INPUT);
|
||||
assertThat(env.getProperty("spring.cloud.stream.bindings." + INPUT + ".destination"))
|
||||
.isEqualTo("mydestination");
|
||||
assertThat(env.getProperty(idKey)).isEqualTo("app:1");
|
||||
assertThat(env.getPropertySources().contains(OVERRIDES_PROPERTY_SOURCE_NAME));
|
||||
assertThat(env.getPropertySources().contains(DEFAULTS_PROPERTY_SOURCE_NAME));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user