Merge branch '4.x' into main
This commit is contained in:
26
README.adoc
26
README.adoc
@@ -176,20 +176,20 @@ You can also build and package your application into a boot jar (by using `./mvn
|
||||
Now you have a working (albeit very basic) Spring Cloud Stream application.
|
||||
|
||||
|
||||
[[spring-cloud-stream-preface-notable-deprecations]]
|
||||
== Notable Deprecations
|
||||
[[spel-and-streaming-data]]
|
||||
|
||||
- Annotation-based programming model. Basically the @EnableBInding, @StreamListener and all related annotations are now deprecated in
|
||||
favor of the functional programming model. See <<Spring Cloud Function support>> for more details.
|
||||
- _Reactive module_ (`spring-cloud-stream-reactive`) is discontinued and no longer distributed in favor of native support via spring-cloud-function.
|
||||
For backward
|
||||
compatibility you can still bring `spring-cloud-stream-reactive` from previous versions.
|
||||
- _Test support binder_ `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <<Testing>> for more details.
|
||||
- _@StreamMessageConverter_ - deprecated as it is no longer required.
|
||||
- The `original-content-type` header references have been removed after it's been deprecated in v2.0.
|
||||
- The `BinderAwareChannelResolver` is deprecated in favor if providing `spring.cloud.stream.sendto.destination` property.
|
||||
This is primarily for function-based programming model. For StreamListener it would still be required and thus will stay until we deprecate and eventually discontinue StreamListener
|
||||
and annotation-based programming model.
|
||||
== Spring Expression Language (SpEL) in the context of Streaming data
|
||||
|
||||
Throughout this reference manual you will encounter many features and examples where you can utilize Spring Expression Language (SpEL). It is important to understand certain limitations when it comes to using it.
|
||||
|
||||
SpEL gives you access to the current Message as well as the Application Context you are running in.
|
||||
However it is important to understand what type of data SpEL can see especially in the context of the incoming Message.
|
||||
From the broker, the message arrives in a form of a byte[]. It is then transformed to a `Message<byte[]>` by the binders where as you can see the payload of the message maintains its raw form. The headers of the message are `<String, Object>`, where values are typically another primitive or a collection/array of primitives, hence Object.
|
||||
That is because binder does not know the required input type as it has no access to the user code (function). So effectively binder delivered an envelope with the payload and some readable meta-data in the form of message headers, just like the letter delivered by mail.
|
||||
This means that while accessing payload of the message is possible you will only have access to it as raw data (i.e., byte[]). And while it may be very common for developers to ask for ability to have SpEL access to fields of a payload object as concrete type (e.g., Foo, Bar etc), you can see how difficult or even impossible would it be to achieve.
|
||||
Here is one example to demonstrate the problem; Imagine you have a routing expression to route to different functions based on payload type. This requirement would imply payload conversion from byte[] to a specific type and then applying the SpEL. However, in order to perform such conversion we would need to know the actual type to pass to converter and that comes from function's signature which we don’t know which one. A better approach to solve this requirement would be to pass the type information as message headers (e.g., `application/json;type=foo.bar.Baz` ). You’ll get a clear readable String value that could be accessed and evaluated in a year and easy to read SpEL expression.
|
||||
|
||||
Additionally it is considered very bad practice to use payload for routing decisions, since the payload is considered to be privileged data - data only to be read by its final recipient. Again, using the mail delivery analogy you would not want the mailman to open your envelope and read the contents of the letter to make some delivery decisions. The same concept applies here, especially when it is relatively easy to include such information when generating a Message. It enforces certain level of discipline related to the design of data to be transmitted over the network and which pieces of such data can be considered as public and which are privileged.
|
||||
|
||||
[[spel-and-streaming-data]]
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-stream-docs</name>
|
||||
|
||||
@@ -166,7 +166,6 @@ compatibility you can still bring `spring-cloud-stream-reactive` from previous v
|
||||
- _Test support binder_ `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <<Testing>> for more details.
|
||||
- _@StreamMessageConverter_ - deprecated as it is no longer required.
|
||||
- The `original-content-type` header references have been removed after it's been deprecated in v2.0.
|
||||
- The `BinderAwareChannelResolver` is deprecated in favor if providing `spring.cloud.stream.sendto.destination` property.
|
||||
This is primarily for function-based programming model. For StreamListener it would still be required and thus will stay until we deprecate and eventually discontinue StreamListener
|
||||
and annotation-based programming model.
|
||||
|
||||
|
||||
@@ -1398,44 +1398,6 @@ Aside from static destinations, Spring Cloud Stream lets applications send messa
|
||||
This is useful, for example, when the target destination needs to be determined at runtime.
|
||||
Applications can do so in one of two ways.
|
||||
|
||||
===== BinderAwareChannelResolver
|
||||
|
||||
The `BinderAwareChannelResolver` is a special bean registered automatically by the framework.
|
||||
You can autowire this bean into your application and use it to resolve output destination at runtime
|
||||
|
||||
The 'spring.cloud.stream.dynamicDestinations' property can be used for restricting the dynamic destination names to a known set (that is, intentionally allowed values).
|
||||
If this property is not set, any destination can be bound dynamically.
|
||||
|
||||
The following example demonstrates one of the common scenarios where REST controller uses a path variable to determine target destination:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@Controller
|
||||
public class SourceWithDynamicDestination {
|
||||
|
||||
@Autowired
|
||||
private BinderAwareChannelResolver resolver;
|
||||
|
||||
@RequestMapping(value="/{target}")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
public void send(@RequestBody String body, @PathVariable("target") String target){
|
||||
resolver.resolveDestination(target).send(new GenericMessage<String>(body));
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Now consider what happens when we start the application on the default port (8080) and make the following requests with CURL:
|
||||
|
||||
----
|
||||
curl -H "Content-Type: application/json" -X POST -d "customer-1" http://localhost:8080/customers
|
||||
|
||||
curl -H "Content-Type: application/json" -X POST -d "order-1" http://localhost:8080/orders
|
||||
----
|
||||
|
||||
The destinations, 'customers' and 'orders', are created in the broker (in the exchange for Rabbit or in the topic for Kafka)
|
||||
with names of 'customers' and 'orders', and the data is published to the appropriate destinations.
|
||||
|
||||
===== spring.cloud.stream.sendto.destination
|
||||
|
||||
You can also delegate to the framework to dynamically resolve the output destination by specifying `spring.cloud.stream.sendto.destination` header
|
||||
|
||||
10
pom.xml
10
pom.xml
@@ -4,12 +4,16 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<<<<<<< HEAD
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
=======
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
>>>>>>> 4.x
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -22,7 +26,7 @@
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<java.version>17</java.version>
|
||||
<reactor.version>2020.0.7</reactor.version>
|
||||
<objenesis.version>2.1</objenesis.version>
|
||||
<spring-cloud-function.version>3.2.2-SNAPSHOT</spring-cloud-function.version>
|
||||
@@ -130,7 +134,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<!-- <version>1.7</version>-->
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -34,9 +31,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
import org.springframework.cloud.stream.binder.AbstractBinderTests.Station.Readings;
|
||||
import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerMessageHandler;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
|
||||
@@ -51,12 +46,8 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.converter.SmartMessageConverter;
|
||||
import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver;
|
||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -424,193 +415,6 @@ public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends
|
||||
return ".";
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testSendPojoReceivePojoWithStreamListenerDefaultContentType(TestInfo testInfo)
|
||||
throws Exception {
|
||||
StreamListenerMessageHandler handler = this.buildStreamListener(
|
||||
AbstractBinderTests.class, "echoStation", Station.class);
|
||||
|
||||
Binder binder = getBinder();
|
||||
|
||||
BindingProperties producerBindingProperties = createProducerBindingProperties(
|
||||
createProducerProperties(testInfo));
|
||||
|
||||
DirectChannel moduleOutputChannel = createBindableChannel("output",
|
||||
producerBindingProperties);
|
||||
|
||||
BindingProperties consumerBindingProperties = createConsumerBindingProperties(
|
||||
createConsumerProperties());
|
||||
|
||||
DirectChannel moduleInputChannel = createBindableChannel("input",
|
||||
consumerBindingProperties);
|
||||
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(
|
||||
String.format("bad%s0a", getDestinationNameDelimiter()),
|
||||
moduleOutputChannel, producerBindingProperties.getProducer());
|
||||
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(
|
||||
String.format("bad%s0a", getDestinationNameDelimiter()), "test-1",
|
||||
moduleInputChannel, consumerBindingProperties.getConsumer());
|
||||
|
||||
Station station = new Station();
|
||||
Message<?> message = MessageBuilder.withPayload(station).build();
|
||||
moduleInputChannel.subscribe(handler);
|
||||
moduleOutputChannel.send(message);
|
||||
|
||||
QueueChannel replyChannel = (QueueChannel) handler.getOutputChannel();
|
||||
|
||||
Message<?> replyMessage = replyChannel.receive(5000);
|
||||
assertThat(replyMessage.getPayload() instanceof Station).isTrue();
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testSendJsonReceivePojoWithStreamListener(TestInfo testInfo) throws Exception {
|
||||
StreamListenerMessageHandler handler = this.buildStreamListener(
|
||||
AbstractBinderTests.class, "echoStation", Station.class);
|
||||
Binder binder = getBinder();
|
||||
|
||||
BindingProperties producerBindingProperties = createProducerBindingProperties(
|
||||
createProducerProperties(testInfo));
|
||||
|
||||
DirectChannel moduleOutputChannel = createBindableChannel("output",
|
||||
producerBindingProperties);
|
||||
|
||||
BindingProperties consumerBindingProperties = createConsumerBindingProperties(
|
||||
createConsumerProperties());
|
||||
|
||||
DirectChannel moduleInputChannel = createBindableChannel("input",
|
||||
consumerBindingProperties);
|
||||
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(
|
||||
String.format("bad%s0d", getDestinationNameDelimiter()),
|
||||
moduleOutputChannel, producerBindingProperties.getProducer());
|
||||
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(
|
||||
String.format("bad%s0d", getDestinationNameDelimiter()), "test-4",
|
||||
moduleInputChannel, consumerBindingProperties.getConsumer());
|
||||
|
||||
String value = "{\"readings\":[{\"stationid\":\"fgh\","
|
||||
+ "\"customerid\":\"12345\",\"timestamp\":null},"
|
||||
+ "{\"stationid\":\"hjk\",\"customerid\":\"222\",\"timestamp\":null}]}";
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload(value)
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
|
||||
.build();
|
||||
moduleInputChannel.subscribe(handler);
|
||||
moduleOutputChannel.send(message);
|
||||
|
||||
QueueChannel channel = (QueueChannel) handler.getOutputChannel();
|
||||
|
||||
Message<Station> reply = (Message<Station>) channel.receive(5000);
|
||||
|
||||
assertThat(reply).isNotNull();
|
||||
assertThat(reply.getPayload() instanceof Station).isTrue();
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testSendJsonReceiveJsonWithStreamListener(TestInfo testInfo) throws Exception {
|
||||
StreamListenerMessageHandler handler = this.buildStreamListener(
|
||||
AbstractBinderTests.class, "echoStationString", String.class);
|
||||
Binder binder = getBinder();
|
||||
|
||||
BindingProperties producerBindingProperties = createProducerBindingProperties(
|
||||
createProducerProperties(testInfo));
|
||||
|
||||
DirectChannel moduleOutputChannel = createBindableChannel("output",
|
||||
producerBindingProperties);
|
||||
|
||||
BindingProperties consumerBindingProperties = createConsumerBindingProperties(
|
||||
createConsumerProperties());
|
||||
|
||||
DirectChannel moduleInputChannel = createBindableChannel("input",
|
||||
consumerBindingProperties);
|
||||
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(
|
||||
String.format("bad%s0e", getDestinationNameDelimiter()),
|
||||
moduleOutputChannel, producerBindingProperties.getProducer());
|
||||
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(
|
||||
String.format("bad%s0e", getDestinationNameDelimiter()), "test-5",
|
||||
moduleInputChannel, consumerBindingProperties.getConsumer());
|
||||
|
||||
String value = "{\"readings\":[{\"stationid\":\"fgh\","
|
||||
+ "\"customerid\":\"12345\",\"timestamp\":null},"
|
||||
+ "{\"stationid\":\"hjk\",\"customerid\":\"222\",\"timestamp\":null}]}";
|
||||
|
||||
Message<?> message = MessageBuilder.withPayload(value)
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
|
||||
.build();
|
||||
moduleInputChannel.subscribe(handler);
|
||||
moduleOutputChannel.send(message);
|
||||
|
||||
QueueChannel channel = (QueueChannel) handler.getOutputChannel();
|
||||
|
||||
Message<String> reply = (Message<String>) channel.receive(5000);
|
||||
|
||||
assertThat(reply).isNotNull();
|
||||
assertThat(reply.getPayload() instanceof String).isTrue();
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testSendPojoReceivePojoWithStreamListener(TestInfo testInfo) throws Exception {
|
||||
StreamListenerMessageHandler handler = this.buildStreamListener(
|
||||
AbstractBinderTests.class, "echoStation", Station.class);
|
||||
Binder binder = getBinder();
|
||||
|
||||
BindingProperties producerBindingProperties = createProducerBindingProperties(
|
||||
createProducerProperties(testInfo));
|
||||
|
||||
DirectChannel moduleOutputChannel = createBindableChannel("output",
|
||||
producerBindingProperties);
|
||||
|
||||
BindingProperties consumerBindingProperties = createConsumerBindingProperties(
|
||||
createConsumerProperties());
|
||||
|
||||
DirectChannel moduleInputChannel = createBindableChannel("input",
|
||||
consumerBindingProperties);
|
||||
|
||||
Binding<MessageChannel> producerBinding = binder.bindProducer(
|
||||
String.format("bad%s0f", getDestinationNameDelimiter()),
|
||||
moduleOutputChannel, producerBindingProperties.getProducer());
|
||||
|
||||
Binding<MessageChannel> consumerBinding = binder.bindConsumer(
|
||||
String.format("bad%s0f", getDestinationNameDelimiter()), "test-6",
|
||||
moduleInputChannel, consumerBindingProperties.getConsumer());
|
||||
|
||||
Readings r1 = new Readings();
|
||||
r1.setCustomerid("123");
|
||||
r1.setStationid("XYZ");
|
||||
Readings r2 = new Readings();
|
||||
r2.setCustomerid("546");
|
||||
r2.setStationid("ABC");
|
||||
Station station = new Station();
|
||||
station.setReadings(Arrays.asList(r1, r2));
|
||||
Message<?> message = MessageBuilder.withPayload(station)
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
|
||||
.build();
|
||||
moduleInputChannel.subscribe(handler);
|
||||
moduleOutputChannel.send(message);
|
||||
|
||||
QueueChannel channel = (QueueChannel) handler.getOutputChannel();
|
||||
|
||||
Message<Station> reply = (Message<Station>) channel.receive(5000);
|
||||
|
||||
assertThat(reply).isNotNull();
|
||||
assertThat(reply.getPayload() instanceof Station).isTrue();
|
||||
producerBinding.unbind();
|
||||
consumerBinding.unbind();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // it is used via reflection
|
||||
private Station echoStation(Station station) {
|
||||
return station;
|
||||
@@ -621,32 +425,6 @@ public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends
|
||||
return station;
|
||||
}
|
||||
|
||||
private StreamListenerMessageHandler buildStreamListener(Class<?> handlerClass,
|
||||
String handlerMethodName, Class<?>... parameters) throws Exception {
|
||||
String channelName = "reply_" + System.nanoTime();
|
||||
|
||||
this.applicationContext.getBeanFactory().registerSingleton(channelName, new QueueChannel());
|
||||
|
||||
Method m = ReflectionUtils.findMethod(handlerClass, handlerMethodName,
|
||||
parameters);
|
||||
InvocableHandlerMethod method = new InvocableHandlerMethod(this, m);
|
||||
HandlerMethodArgumentResolverComposite resolver = new HandlerMethodArgumentResolverComposite();
|
||||
CompositeMessageConverterFactory factory = new CompositeMessageConverterFactory();
|
||||
resolver.addResolver(new PayloadArgumentResolver(
|
||||
factory.getMessageConverterForAllRegistered()));
|
||||
method.setMessageMethodArgumentResolvers(resolver);
|
||||
Constructor<?> c = ReflectionUtils.accessibleConstructor(
|
||||
StreamListenerMessageHandler.class, InvocableHandlerMethod.class,
|
||||
boolean.class, String[].class);
|
||||
StreamListenerMessageHandler handler = (StreamListenerMessageHandler) c
|
||||
.newInstance(method, false, new String[] {});
|
||||
handler.setOutputChannelName(channelName);
|
||||
handler.setBeanFactory(this.applicationContext);
|
||||
handler.afterPropertiesSet();
|
||||
// context.refresh();
|
||||
return handler;
|
||||
}
|
||||
|
||||
public static class Station {
|
||||
|
||||
List<Readings> readings = new ArrayList<>();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamMessageConverter;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.messaging.Source;
|
||||
import org.springframework.cloud.stream.test.binder.TestSupportBinder;
|
||||
@@ -59,12 +58,10 @@ public class CustomMessageConverterTests {
|
||||
private BinderFactory binderFactory;
|
||||
|
||||
@Autowired
|
||||
@StreamMessageConverter
|
||||
private List<MessageConverter> customMessageConverters;
|
||||
|
||||
@Test
|
||||
public void testCustomMessageConverter() throws Exception {
|
||||
assertThat(this.customMessageConverters).hasSize(2);
|
||||
assertThat(this.customMessageConverters).extracting("class")
|
||||
.contains(FooConverter.class, BarConverter.class);
|
||||
this.testSource.output().send(MessageBuilder.withPayload(new Foo("hi")).build());
|
||||
@@ -84,13 +81,11 @@ public class CustomMessageConverterTests {
|
||||
public static class TestSource {
|
||||
|
||||
@Bean
|
||||
@StreamMessageConverter
|
||||
public MessageConverter fooConverter() {
|
||||
return new FooConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@StreamMessageConverter
|
||||
public MessageConverter barConverter() {
|
||||
return new BarConverter();
|
||||
}
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.messaging.handler.annotation.Headers;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class StreamListenerAnnotatedMethodArgumentsTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
Locale.setDefault(Locale.US);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testAnnotatedArguments() {
|
||||
ConfigurableApplicationContext context = SpringApplication
|
||||
.run(TestPojoWithAnnotatedArguments.class, "--server.port=0");
|
||||
|
||||
TestPojoWithAnnotatedArguments testPojoWithAnnotatedArguments = context
|
||||
.getBean(TestPojoWithAnnotatedArguments.class);
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
sink.input()
|
||||
.send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", MimeType.valueOf("application/json"))
|
||||
.setHeader("testHeader", "testValue").build());
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedArguments).hasSize(3);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedArguments.get(0))
|
||||
.isInstanceOf(StreamListenerTestUtils.FooPojo.class);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedArguments.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedArguments.get(1))
|
||||
.isInstanceOf(Map.class);
|
||||
assertThat((Map<String, Object>) testPojoWithAnnotatedArguments.receivedArguments
|
||||
.get(1)).containsEntry(MessageHeaders.CONTENT_TYPE,
|
||||
MimeType.valueOf("application/json"));
|
||||
assertThat((Map<String, String>) testPojoWithAnnotatedArguments.receivedArguments
|
||||
.get(1)).containsEntry("testHeader", "testValue");
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedArguments.get(2))
|
||||
.isEqualTo("application/json");
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInputAnnotationAtMethodParameter() {
|
||||
try {
|
||||
SpringApplication.run(TestPojoWithInvalidInputAnnotatedArgument.class,
|
||||
"--server.port=0");
|
||||
fail("Exception expected: " + INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidAnnotationAtMethodParameterWithPojoThatPassesValidation() {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestPojoWithValidAnnotationThatPassesValidation.class, "--server.port=0");
|
||||
|
||||
TestPojoWithValidAnnotationThatPassesValidation testPojoWithValidAnnotationThatPassesValidation = context
|
||||
.getBean(TestPojoWithValidAnnotationThatPassesValidation.class);
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
sink.input().send(MessageBuilder.withPayload("{\"foo\":\"" + id + "\"}")
|
||||
.setHeader("contentType", MimeType.valueOf("application/json")).build());
|
||||
assertThat(
|
||||
testPojoWithValidAnnotationThatPassesValidation.receivedArguments.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidAnnotationAtMethodParameterWithPojoThatFailsValidation() {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestPojoWithValidAnnotationThatPassesValidation.class, "--server.port=0");
|
||||
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
try {
|
||||
sink.input().send(MessageBuilder.withPayload("{\"foo\":\"\"}")
|
||||
.setHeader("contentType", MimeType.valueOf("application/json"))
|
||||
.build());
|
||||
fail("Exception expected: MethodArgumentNotValidException!");
|
||||
}
|
||||
catch (MethodArgumentNotValidException e) {
|
||||
assertThat(e.getMessage()).contains(
|
||||
"default message [foo]]; default message [must not be blank]]");
|
||||
}
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithAnnotatedArguments {
|
||||
|
||||
List<Object> receivedArguments = new ArrayList<>();
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo,
|
||||
@Headers Map<String, Object> headers,
|
||||
@Header(MessageHeaders.CONTENT_TYPE) String contentType) {
|
||||
this.receivedArguments.add(fooPojo);
|
||||
this.receivedArguments.add(headers);
|
||||
this.receivedArguments.add(contentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithInvalidInputAnnotatedArgument {
|
||||
|
||||
List<Object> receivedArguments = new ArrayList<>();
|
||||
|
||||
@StreamListener
|
||||
public void receive(
|
||||
@Input(Processor.INPUT) @Payload StreamListenerTestUtils.FooPojo fooPojo,
|
||||
@Headers Map<String, Object> headers,
|
||||
@Header(MessageHeaders.CONTENT_TYPE) String contentType) {
|
||||
this.receivedArguments.add(fooPojo);
|
||||
this.receivedArguments.add(headers);
|
||||
this.receivedArguments.add(contentType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithValidAnnotationThatPassesValidation {
|
||||
|
||||
List<Object> receivedArguments = new ArrayList<>();
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
public void receive(
|
||||
@Valid StreamListenerTestUtils.PojoWithValidation pojoWithValidation) {
|
||||
this.receivedArguments.add(pojoWithValidation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.stream.config.BindingServiceConfiguration.STREAM_LISTENER_ANNOTATION_BEAN_POST_PROCESSOR_NAME;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class StreamListenerAnnotationBeanPostProcessorOverrideTest {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testOverrideStreamListenerAnnotationBeanPostProcessor() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication
|
||||
.run(TestPojoWithAnnotatedArguments.class, "--server.port=0");
|
||||
|
||||
TestPojoWithAnnotatedArguments testPojoWithAnnotatedArguments = context
|
||||
.getBean(TestPojoWithAnnotatedArguments.class);
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
sink.input().send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json")
|
||||
.setHeader("testHeader", "testValue").setHeader("type", "foo").build());
|
||||
sink.input().send(MessageBuilder.withPayload("{\"bar\":\"foofoo" + id + "\"}")
|
||||
.setHeader("contentType", "application/json")
|
||||
.setHeader("testHeader", "testValue").setHeader("type", "bar").build());
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo).hasSize(1);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithAnnotatedArguments {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedFoo = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Overrides the default {@link StreamListenerAnnotationBeanPostProcessor}.
|
||||
*/
|
||||
@Bean(name = STREAM_LISTENER_ANNOTATION_BEAN_POST_PROCESSOR_NAME)
|
||||
public static StreamListenerAnnotationBeanPostProcessor streamListenerAnnotationBeanPostProcessor() {
|
||||
return new StreamListenerAnnotationBeanPostProcessor() {
|
||||
@Override
|
||||
protected StreamListener postProcessAnnotation(
|
||||
StreamListener originalAnnotation, Method annotatedMethod) {
|
||||
Map<String, Object> attributes = new HashMap<>(
|
||||
AnnotationUtils.getAnnotationAttributes(originalAnnotation));
|
||||
attributes.put("condition",
|
||||
"headers['type']=='" + originalAnnotation.condition() + "'");
|
||||
return AnnotationUtils.synthesizeAnnotation(attributes,
|
||||
StreamListener.class, annotatedMethod);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@StreamListener(value = Sink.INPUT, condition = "foo")
|
||||
public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedFoo.add(fooPojo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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.stream.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MessageMapping
|
||||
@Documented
|
||||
@StreamListener
|
||||
@interface EventHandler {
|
||||
|
||||
/**
|
||||
* The name of the binding target (e.g. channel) that the method subscribes to.
|
||||
* @return the name of the binding target.
|
||||
*/
|
||||
@AliasFor(annotation = StreamListener.class, attribute = "target")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The name of the binding target (e.g. channel) that the method subscribes to.
|
||||
* @return the name of the binding target.
|
||||
*/
|
||||
@AliasFor(annotation = StreamListener.class, attribute = "target")
|
||||
String target() default "";
|
||||
|
||||
/**
|
||||
* A condition that must be met by all items that are dispatched to this method.
|
||||
* @return a SpEL expression that must evaluate to a {@code boolean} value.
|
||||
*/
|
||||
@AliasFor(annotation = StreamListener.class, attribute = "condition")
|
||||
String condition() default "";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*/
|
||||
public class StreamListenerAsMetaAnnotationTests {
|
||||
|
||||
@Test
|
||||
public void testCustomAnnotation() {
|
||||
ConfigurableApplicationContext context = SpringApplication
|
||||
.run(TestPojoWithCustomAnnotatedArguments.class, "--server.port=0");
|
||||
|
||||
TestPojoWithCustomAnnotatedArguments testPojoWithAnnotatedArguments = context
|
||||
.getBean(TestPojoWithCustomAnnotatedArguments.class);
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
sink.input().send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json")
|
||||
.setHeader("testHeader", "testValue").setHeader("type", "foo").build());
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo).hasSize(1);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotation() {
|
||||
ConfigurableApplicationContext context = SpringApplication
|
||||
.run(TestPojoWithAnnotatedArguments.class, "--server.port=0");
|
||||
|
||||
TestPojoWithAnnotatedArguments testPojoWithAnnotatedArguments = context
|
||||
.getBean(TestPojoWithAnnotatedArguments.class);
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
sink.input().send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json")
|
||||
.setHeader("testHeader", "testValue").setHeader("type", "foo").build());
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo).hasSize(1);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithCustomAnnotatedArguments {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedFoo = new ArrayList<>();
|
||||
|
||||
List<StreamListenerTestUtils.BarPojo> receivedBar = new ArrayList<>();
|
||||
|
||||
@EventHandler(value = Sink.INPUT, condition = "headers['type']=='foo'")
|
||||
public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedFoo.add(fooPojo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithAnnotatedArguments {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedFoo = new ArrayList<>();
|
||||
|
||||
List<StreamListenerTestUtils.BarPojo> receivedBar = new ArrayList<>();
|
||||
|
||||
@StreamListener(value = Sink.INPUT, condition = "headers['type']=='foo'")
|
||||
public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedFoo.add(fooPojo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class StreamListenerContentTypeConversionTests {
|
||||
|
||||
@Test
|
||||
public void testContentTypeConversion() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication
|
||||
.run(TestSinkWithContentTypeConversion.class, "--server.port=0");
|
||||
@SuppressWarnings("unchecked")
|
||||
TestSinkWithContentTypeConversion testSink = context
|
||||
.getBean(TestSinkWithContentTypeConversion.class);
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
sink.input().send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
assertThat(testSink.latch.await(10, TimeUnit.SECONDS));
|
||||
assertThat(testSink.receivedArguments).hasSize(1);
|
||||
assertThat(testSink.receivedArguments.get(0)).hasFieldOrPropertyWithValue("foo",
|
||||
"barbar" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestSinkWithContentTypeConversion {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedArguments = new ArrayList<>();
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void receive(StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedArguments.add(fooPojo);
|
||||
this.latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerErrorMessages;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class StreamListenerDuplicateMappingTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMultipleMappingsWithReturnValue() {
|
||||
ConfigurableApplicationContext context = null;
|
||||
try {
|
||||
context = SpringApplication.run(TestMultipleMappingsWithReturnValue.class,
|
||||
"--server.port=0");
|
||||
fail("Exception expected on duplicate mapping");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).startsWith(
|
||||
StreamListenerErrorMessages.MULTIPLE_VALUE_RETURNING_METHODS);
|
||||
}
|
||||
finally {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateMappingFromAbstractMethod() {
|
||||
ConfigurableApplicationContext context = null;
|
||||
try {
|
||||
context = SpringApplication.run(TestDuplicateMappingFromAbstractMethod.class,
|
||||
"--server.port=0");
|
||||
}
|
||||
catch (BeanCreationException e) {
|
||||
String errorMessage = e.getCause().getMessage()
|
||||
.startsWith("Duplicate @StreamListener mapping")
|
||||
? "Duplicate mapping exception is not expected"
|
||||
: "Test failed with exception";
|
||||
fail(errorMessage + ": " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface GenericSink<T extends Base> {
|
||||
|
||||
void testMethod(T msg);
|
||||
|
||||
}
|
||||
|
||||
public interface Base {
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMultipleMappingsWithReturnValue {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public String receive(Message<String> fooMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public String receiveDuplicateMapping(Message<String> fooMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestDuplicateMappingFromAbstractMethod
|
||||
implements GenericSink<TestBase> {
|
||||
|
||||
@Override
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void testMethod(TestBase msg) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class TestBase implements Base {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class StreamListenerHandlerBeanTests {
|
||||
|
||||
private Class<?> configClass;
|
||||
|
||||
public StreamListenerHandlerBeanTests(Class<?> configClass) {
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<?> InputConfigs() {
|
||||
return Arrays.asList(TestHandlerBeanWithSendTo.class, TestHandlerBean2.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testHandlerBean() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(this.configClass,
|
||||
"--spring.cloud.stream.bindings.output.contentType=application/json",
|
||||
"--server.port=0");
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
processor.input()
|
||||
.send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
HandlerBean handlerBean = context.getBean(HandlerBean.class);
|
||||
Assertions.assertThat(handlerBean.receivedPojos).hasSize(1);
|
||||
Assertions.assertThat(handlerBean.receivedPojos.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).isEqualTo("{\"bar\":\"barbar" + id + "\"}");
|
||||
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestHandlerBeanWithSendTo {
|
||||
|
||||
@Bean
|
||||
public HandlerBeanWithSendTo handlerBean() {
|
||||
return new HandlerBeanWithSendTo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestHandlerBean2 {
|
||||
|
||||
@Bean
|
||||
public HandlerBeanWithOutput handlerBean() {
|
||||
return new HandlerBeanWithOutput();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class HandlerBeanWithSendTo extends HandlerBean {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public StreamListenerTestUtils.BarPojo receive(
|
||||
StreamListenerTestUtils.FooPojo fooMessage) {
|
||||
this.receivedPojos.add(fooMessage);
|
||||
StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo();
|
||||
barPojo.setBar(fooMessage.getFoo());
|
||||
return barPojo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class HandlerBeanWithOutput extends HandlerBean {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@Output(Processor.OUTPUT)
|
||||
public StreamListenerTestUtils.BarPojo receive(
|
||||
StreamListenerTestUtils.FooPojo fooMessage) {
|
||||
this.receivedPojos.add(fooMessage);
|
||||
StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo();
|
||||
barPojo.setBar(fooMessage.getFoo());
|
||||
return barPojo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class HandlerBean {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedPojos = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,623 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerErrorMessages;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.annotation.Router;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.INPUT_AT_STREAM_LISTENER;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.INVALID_INBOUND_NAME;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.INVALID_OUTBOUND_NAME;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.INVALID_OUTPUT_VALUES;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.NO_INPUT_DESTINATION;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.RETURN_TYPE_NO_OUTBOUND_SPECIFIED;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class StreamListenerHandlerMethodTests {
|
||||
|
||||
@Test
|
||||
public void testInvalidInputOnMethod() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestInvalidInputOnMethod.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
fail("Exception expected: " + INPUT_AT_STREAM_LISTENER);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(INPUT_AT_STREAM_LISTENER);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testMethodWithObjectAsMethodArgument() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestMethodWithObjectAsMethodArgument.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false",
|
||||
"--spring.cloud.stream.bindings.input.contentType=text/plain",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain");
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
final String testMessage = "testing";
|
||||
processor.input().send(MessageBuilder.withPayload(testMessage).build());
|
||||
MessageCollector messageCollector = context.getBean(MessageCollector.class);
|
||||
Message<String> result = (Message<String>) messageCollector
|
||||
.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
/**
|
||||
* @since 2.0 : This test is an example of the new behavior of 2.0 when it comes to
|
||||
* contentType handling. The default contentType being JSON in order to be able to
|
||||
* check a message without quotes the user needs to set the input/output contentType
|
||||
* accordingly Also, received messages are always of Message<byte[]> now.
|
||||
*/
|
||||
public void testMethodHeadersPropagatged() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestMethodHeadersPropagated.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false",
|
||||
"--spring.cloud.stream.bindings.input.contentType=text/plain",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain");
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
final String testMessage = "testing";
|
||||
processor.input().send(
|
||||
MessageBuilder.withPayload(testMessage).setHeader("foo", "bar").build());
|
||||
MessageCollector messageCollector = context.getBean(MessageCollector.class);
|
||||
Message<String> result = (Message<String>) messageCollector
|
||||
.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase());
|
||||
assertThat(result.getHeaders().get("foo")).isEqualTo("bar");
|
||||
context.close();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
@Disabled
|
||||
public void testMethodHeadersNotPropagatged() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestMethodHeadersNotPropagated.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false",
|
||||
"--spring.cloud.stream.bindings.input.contentType=text/plain",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain");
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
final String testMessage = "testing";
|
||||
processor.input().send(
|
||||
MessageBuilder.withPayload(testMessage).setHeader("foo", "bar").build());
|
||||
MessageCollector messageCollector = context.getBean(MessageCollector.class);
|
||||
Message<String> result = (Message<String>) messageCollector
|
||||
.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase());
|
||||
assertThat(result.getHeaders().get("foo")).isNull();
|
||||
context.close();
|
||||
}
|
||||
|
||||
// TODO: Handle dynamic destinations and contentType
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testStreamListenerMethodWithTargetBeanFromOutside() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestStreamListenerMethodWithTargetBeanFromOutside.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false",
|
||||
"--spring.cloud.stream.bindings.input.contentType=text/plain",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain");
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
final String testMessageToSend = "testing";
|
||||
sink.input().send(MessageBuilder.withPayload(testMessageToSend).build());
|
||||
DirectChannel directChannel = (DirectChannel) context
|
||||
.getBean(testMessageToSend.toUpperCase(), MessageChannel.class);
|
||||
MessageCollector messageCollector = context.getBean(MessageCollector.class);
|
||||
Message<String> result = (Message<String>) messageCollector
|
||||
.forChannel(directChannel).poll(1000, TimeUnit.MILLISECONDS);
|
||||
sink.input().send(MessageBuilder.withPayload(testMessageToSend).build());
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPayload()).isEqualTo(testMessageToSend.toUpperCase());
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidReturnTypeWithSendToAndOutput() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestReturnTypeWithMultipleOutput.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
fail("Exception expected: " + RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidReturnTypeWithNoOutput() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestInvalidReturnTypeWithNoOutput.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
fail("Exception expected: " + RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidInputAnnotationWithNoValue() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestInvalidInputAnnotationWithNoValue.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
fail("Exception expected: " + INVALID_INBOUND_NAME);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(INVALID_INBOUND_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidOutputAnnotationWithNoValue() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestInvalidOutputAnnotationWithNoValue.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
fail("Exception expected: " + INVALID_OUTBOUND_NAME);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(INVALID_OUTBOUND_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodInvalidInboundName() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestMethodInvalidInboundName.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
fail("Exception expected on using invalid inbound name");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(
|
||||
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodInvalidOutboundName() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestMethodInvalidOutboundName.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
fail("Exception expected on using invalid outbound name");
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException e) {
|
||||
assertThat(e.getMessage()).contains("invalid");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAmbiguousMethodArguments1() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestAmbiguousMethodArguments1.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
fail("Exception expected: " + AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage())
|
||||
.contains(AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAmbiguousMethodArguments2() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestAmbiguousMethodArguments2.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
fail("Exception expected:" + AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage())
|
||||
.contains(AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodWithInputAsMethodAndParameter() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestMethodWithInputAsMethodAndParameter.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
fail("Exception expected: " + INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodWithOutputAsMethodAndParameter() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestMethodWithOutputAsMethodAndParameter.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
fail("Exception expected:" + INVALID_OUTPUT_VALUES);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).startsWith(INVALID_OUTPUT_VALUES);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodWithoutInput() throws Exception {
|
||||
try {
|
||||
SpringApplication.run(TestMethodWithoutInput.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
fail("Exception expected when inbound target is not set");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(NO_INPUT_DESTINATION);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodWithMultipleInputParameters() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestMethodWithMultipleInputParameters.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
StreamListenerTestUtils.FooInboundChannel1 inboundChannel2 = context
|
||||
.getBean(StreamListenerTestUtils.FooInboundChannel1.class);
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
((SubscribableChannel) processor.output()).subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
Assert.isTrue(
|
||||
message.getPayload().equals("footesting")
|
||||
|| message.getPayload().equals("BARTESTING"),
|
||||
"Assert failed");
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
processor.input().send(MessageBuilder.withPayload("{\"foo\":\"fooTESTing\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
inboundChannel2.input()
|
||||
.send(MessageBuilder.withPayload("{\"bar\":\"bartestING\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
assertThat(latch.await(1, TimeUnit.SECONDS));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodWithMultipleOutputParameters() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestMethodWithMultipleOutputParameters.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false");
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
StreamListenerTestUtils.FooOutboundChannel1 source2 = context
|
||||
.getBean(StreamListenerTestUtils.FooOutboundChannel1.class);
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
((SubscribableChannel) processor.output()).subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
Assert.isTrue(message.getPayload().equals("testing"), "Assert failed");
|
||||
Assert.isTrue(message.getHeaders().get("output").equals("output2"),
|
||||
"Assert failed");
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
((SubscribableChannel) source2.output()).subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
Assert.isTrue(message.getPayload().equals("TESTING"), "Assert failed");
|
||||
Assert.isTrue(message.getHeaders().get("output").equals("output1"),
|
||||
"Assert failed");
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
processor.input().send(MessageBuilder.withPayload("testING")
|
||||
.setHeader("output", "output1").build());
|
||||
processor.input().send(MessageBuilder.withPayload("TESTing")
|
||||
.setHeader("output", "output2").build());
|
||||
assertThat(latch.await(1, TimeUnit.SECONDS));
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class, StreamListenerTestUtils.FooOutboundChannel1.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodWithMultipleOutputParameters {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input(Processor.INPUT) SubscribableChannel input,
|
||||
@Output(Processor.OUTPUT) final MessageChannel output1,
|
||||
@Output(StreamListenerTestUtils.FooOutboundChannel1.OUTPUT) final MessageChannel output2) {
|
||||
input.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
if (message.getHeaders().get("output").equals("output1")) {
|
||||
output1.send(org.springframework.messaging.support.MessageBuilder
|
||||
.withPayload(
|
||||
message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
else if (message.getHeaders().get("output").equals("output2")) {
|
||||
output2.send(org.springframework.messaging.support.MessageBuilder
|
||||
.withPayload(
|
||||
message.getPayload().toString().toLowerCase())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Sink.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodWithoutInput {
|
||||
|
||||
@StreamListener
|
||||
public void receive(StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodWithObjectAsMethodArgument {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public String receive(Object received) {
|
||||
return received.toString().toUpperCase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodHeadersPropagated {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public String receive(String received) {
|
||||
return received.toUpperCase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodHeadersNotPropagated {
|
||||
|
||||
@StreamListener(value = Processor.INPUT, copyHeaders = "${foo.bar:false}")
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public String receive(String received) {
|
||||
return received.toUpperCase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestStreamListenerMethodWithTargetBeanFromOutside {
|
||||
|
||||
private static final String ROUTER_QUEUE = "routeInstruction";
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
@SendTo(ROUTER_QUEUE)
|
||||
public Message<String> convertMessageBody(Message<String> message) {
|
||||
return new DefaultMessageBuilderFactory()
|
||||
.withPayload(message.getPayload().toUpperCase()).build();
|
||||
}
|
||||
|
||||
@Router(inputChannel = ROUTER_QUEUE)
|
||||
public String route(String message) {
|
||||
return message.toUpperCase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Sink.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInvalidInputOnMethod {
|
||||
|
||||
@StreamListener
|
||||
@Input(Sink.INPUT)
|
||||
public void receive(StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Sink.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestAmbiguousMethodArguments1 {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo,
|
||||
String value) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Sink.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestAmbiguousMethodArguments2 {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo,
|
||||
@Payload StreamListenerTestUtils.BarPojo barPojo) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class, StreamListenerTestUtils.FooOutboundChannel1.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestReturnTypeWithMultipleOutput {
|
||||
|
||||
@StreamListener
|
||||
public String receive(@Input(Processor.INPUT) SubscribableChannel input1,
|
||||
@Output(Processor.OUTPUT) MessageChannel output1,
|
||||
@Output(StreamListenerTestUtils.FooOutboundChannel1.OUTPUT) MessageChannel output2) {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class, StreamListenerTestUtils.FooOutboundChannel1.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInvalidReturnTypeWithNoOutput {
|
||||
|
||||
@StreamListener
|
||||
public String receive(@Input(Processor.INPUT) SubscribableChannel input1) {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInvalidInputAnnotationWithNoValue {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input SubscribableChannel input) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInvalidOutputAnnotationWithNoValue {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input(Processor.OUTPUT) SubscribableChannel input,
|
||||
@Output MessageChannel output) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Sink.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodInvalidInboundName {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input("invalid") SubscribableChannel input) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodInvalidOutboundName {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input(Processor.INPUT) SubscribableChannel input,
|
||||
@Output("invalid") MessageChannel output) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Sink.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodWithInputAsMethodAndParameter {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input(Sink.INPUT) StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class, StreamListenerTestUtils.FooOutboundChannel1.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodWithOutputAsMethodAndParameter {
|
||||
|
||||
@StreamListener
|
||||
@Output(StreamListenerTestUtils.FooOutboundChannel1.OUTPUT)
|
||||
public void receive(@Input(Processor.INPUT) SubscribableChannel input,
|
||||
@Output(Processor.OUTPUT) final MessageChannel output1) {
|
||||
input.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
output1.send(org.springframework.messaging.support.MessageBuilder
|
||||
.withPayload(message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class, StreamListenerTestUtils.FooInboundChannel1.class })
|
||||
@EnableAutoConfiguration
|
||||
public static class TestMethodWithMultipleInputParameters {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input(Processor.INPUT) SubscribableChannel input1,
|
||||
@Input(StreamListenerTestUtils.FooInboundChannel1.INPUT) SubscribableChannel input2,
|
||||
final @Output(Processor.OUTPUT) MessageChannel output) {
|
||||
input1.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
output.send(org.springframework.messaging.support.MessageBuilder
|
||||
.withPayload(message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
input2.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
output.send(org.springframework.messaging.support.MessageBuilder
|
||||
.withPayload(message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class StreamListenerMessageArgumentTests {
|
||||
|
||||
private Class<?> configClass;
|
||||
|
||||
public StreamListenerMessageArgumentTests(Class<?> configClass) {
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<?> InputConfigs() {
|
||||
return Arrays.asList(new Class[] { TestPojoWithMessageArgument1.class,
|
||||
TestPojoWithMessageArgument2.class });
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMessageArgument() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(this.configClass,
|
||||
"--server.port=0",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain",
|
||||
"--spring.jmx.enabled=false");
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
processor.input().send(MessageBuilder.withPayload("barbar" + id)
|
||||
.setHeader("contentType", "text/plain").build());
|
||||
TestPojoWithMessageArgument testPojoWithMessageArgument = context
|
||||
.getBean(TestPojoWithMessageArgument.class);
|
||||
assertThat(testPojoWithMessageArgument.receivedMessages).hasSize(1);
|
||||
assertThat(testPojoWithMessageArgument.receivedMessages.get(0).getPayload())
|
||||
.isEqualTo("barbar" + id);
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).contains("barbar" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithMessageArgument1 extends TestPojoWithMessageArgument {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public StreamListenerTestUtils.BarPojo receive(Message<String> fooMessage) {
|
||||
this.receivedMessages.add(fooMessage);
|
||||
StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo();
|
||||
barPojo.setBar(fooMessage.getPayload());
|
||||
return barPojo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithMessageArgument2 extends TestPojoWithMessageArgument {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@Output(Processor.OUTPUT)
|
||||
public StreamListenerTestUtils.BarPojo receive(Message<String> fooMessage) {
|
||||
this.receivedMessages.add(fooMessage);
|
||||
StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo();
|
||||
barPojo.setBar(fooMessage.getPayload());
|
||||
return barPojo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestPojoWithMessageArgument {
|
||||
|
||||
List<Message<String>> receivedMessages = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* See issue https://github.com/spring-cloud/spring-cloud-stream/issues/1080
|
||||
*
|
||||
* StreamListener method called twice when using @SpyBean
|
||||
*
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest
|
||||
public class StreamListenerMethodRegisteredOnlyOnceTest {
|
||||
|
||||
@Autowired
|
||||
private SomeSink sink;
|
||||
|
||||
@SpyBean
|
||||
private SomeHandler handler;
|
||||
|
||||
@Test
|
||||
public void should_handleSomeMessage() {
|
||||
this.sink.channel().send(new GenericMessage<>("Payload"));
|
||||
verify(this.handler).handleMessage(); // should only be invoked once.
|
||||
}
|
||||
|
||||
public interface SomeSink {
|
||||
|
||||
@Input(Sink.INPUT)
|
||||
SubscribableChannel channel();
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(SomeSink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class SomeHandler {
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void handleMessage() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.model.InitializationError;
|
||||
import org.junit.runners.model.RunnerBuilder;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
@RunWith(StreamListenerMethodReturnWithConversionTests.class)
|
||||
@Suite.SuiteClasses({
|
||||
StreamListenerMethodReturnWithConversionTests.TestReturnConversion.class,
|
||||
StreamListenerMethodReturnWithConversionTests.TestReturnNoConversion.class })
|
||||
public class StreamListenerMethodReturnWithConversionTests extends Suite {
|
||||
|
||||
public StreamListenerMethodReturnWithConversionTests(Class<?> klass,
|
||||
RunnerBuilder builder) throws InitializationError {
|
||||
super(klass, builder);
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class TestReturnConversion {
|
||||
|
||||
private Class<?> configClass;
|
||||
|
||||
public TestReturnConversion(Class<?> configClass) {
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<?> InputConfigs() {
|
||||
return Arrays.asList(new Class[] { TestPojoWithMimeType1.class,
|
||||
TestPojoWithMimeType2.class });
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testReturnConversion() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
this.configClass,
|
||||
"--spring.cloud.stream.bindings.output.contentType=application/json",
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
processor.input()
|
||||
.send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
TestPojoWithMimeType testPojoWithMimeType = context
|
||||
.getBean(TestPojoWithMimeType.class);
|
||||
Assertions.assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
|
||||
Assertions.assertThat(testPojoWithMimeType.receivedPojos.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(new String(message.getPayload()))
|
||||
.isEqualTo("{\"bar\":\"barbar" + id + "\"}");
|
||||
assertThat(
|
||||
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
context.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class TestReturnNoConversion {
|
||||
|
||||
private Class<?> configClass;
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public TestReturnNoConversion(Class<?> configClass) {
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<?> InputConfigs() {
|
||||
return Arrays.asList(new Class[] { TestPojoWithMimeType1.class,
|
||||
TestPojoWithMimeType2.class });
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testReturnNoConversion() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
this.configClass, "--server.port=0", "--spring.jmx.enabled=false");
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
processor.input()
|
||||
.send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
TestPojoWithMimeType testPojoWithMimeType = context
|
||||
.getBean(TestPojoWithMimeType.class);
|
||||
Assertions.assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
|
||||
Assertions.assertThat(testPojoWithMimeType.receivedPojos.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(message).isNotNull();
|
||||
StreamListenerTestUtils.BarPojo barPojo = this.mapper.readValue(
|
||||
message.getPayload(), StreamListenerTestUtils.BarPojo.class);
|
||||
assertThat(barPojo.getBar()).isEqualTo("barbar" + id);
|
||||
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE,
|
||||
MimeType.class) != null);
|
||||
context.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithMimeType1 extends TestPojoWithMimeType {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public StreamListenerTestUtils.BarPojo receive(
|
||||
StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedPojos.add(fooPojo);
|
||||
StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo();
|
||||
barPojo.setBar(fooPojo.getFoo());
|
||||
return barPojo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithMimeType2 extends TestPojoWithMimeType {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@Output(Processor.OUTPUT)
|
||||
public StreamListenerTestUtils.BarPojo receive(
|
||||
StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedPojos.add(fooPojo);
|
||||
StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo();
|
||||
barPojo.setBar(fooPojo.getFoo());
|
||||
return barPojo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestPojoWithMimeType {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedPojos = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2019 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.stream.config;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerSetupMethodOrchestrator;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.cloud.stream.messaging.Source;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest
|
||||
public class StreamListenerMethodSetupOrchestratorTests {
|
||||
|
||||
@SpyBean
|
||||
CustomOrchestrator customOrchestrator;
|
||||
|
||||
@SpyBean
|
||||
MultipleStreamListenerProcessor multipleStreamListenerProcessor;
|
||||
|
||||
@SpyBean
|
||||
StreamListenerAnnotationBeanPostProcessor streamListenerAnnotationBeanPostProcessor;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testCustomStreamListenerOrchestratorAndDefaultTogetherInSameContext()
|
||||
throws Exception {
|
||||
|
||||
// Two StreamListener methods, so 2 invocations
|
||||
verify(this.customOrchestrator, times(2)).supports(any());
|
||||
|
||||
Method method = this.multipleStreamListenerProcessor.getClass()
|
||||
.getMethod("handleMessage");
|
||||
StreamListener streamListener = AnnotatedElementUtils.findMergedAnnotation(method,
|
||||
StreamListener.class);
|
||||
// verify that the invocation happened on the custom Orchestrator
|
||||
verify(this.customOrchestrator).orchestrateStreamListenerSetupMethod(
|
||||
streamListener, method, this.multipleStreamListenerProcessor);
|
||||
|
||||
Method method1 = this.multipleStreamListenerProcessor.getClass()
|
||||
.getMethod("produceString");
|
||||
StreamListener streamListener1 = AnnotatedElementUtils
|
||||
.findMergedAnnotation(method, StreamListener.class);
|
||||
|
||||
// Verify that the invocation did not happen on the custom orchestrator
|
||||
verify(this.customOrchestrator, never()).orchestrateStreamListenerSetupMethod(
|
||||
streamListener1, method1, this.multipleStreamListenerProcessor);
|
||||
|
||||
Field field = ReflectionUtils.findField(
|
||||
this.streamListenerAnnotationBeanPostProcessor.getClass(),
|
||||
"streamListenerSetupMethodOrchestrators");
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
|
||||
Set<StreamListenerSetupMethodOrchestrator> field1;
|
||||
field1 = (LinkedHashSet<StreamListenerSetupMethodOrchestrator>) ReflectionUtils
|
||||
.getField(field, this.streamListenerAnnotationBeanPostProcessor);
|
||||
List<StreamListenerSetupMethodOrchestrator> list = new ArrayList<>(field1);
|
||||
|
||||
// Ensure that the custom orchestrator did not support this request
|
||||
assertThat(list.get(0).supports(method1)).isEqualTo(false);
|
||||
// Ensure that we are using the default Orchestrator in
|
||||
// StreamListenerAnnoatationBeanPostProcessor
|
||||
assertThat(list.get(1).supports(method1)).isEqualTo(true);
|
||||
}
|
||||
|
||||
public interface SomeProcessor {
|
||||
|
||||
@Input(Sink.INPUT)
|
||||
SubscribableChannel channel1();
|
||||
|
||||
@Input("foobar")
|
||||
SubscribableChannel channel2();
|
||||
|
||||
@Output(Source.OUTPUT)
|
||||
MessageChannel channel3();
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(SomeProcessor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class MultipleStreamListenerProcessor {
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void handleMessage() {
|
||||
}
|
||||
|
||||
@StreamListener("foobar")
|
||||
@SendTo("output")
|
||||
public String produceString() {
|
||||
return "foobar";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CustomOrchestrator myOrchestrator() {
|
||||
return new CustomOrchestrator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CustomOrchestrator implements StreamListenerSetupMethodOrchestrator {
|
||||
|
||||
@Override
|
||||
public boolean supports(Method method) {
|
||||
return method.getReturnType() != String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orchestrateStreamListenerSetupMethod(StreamListener streamListener,
|
||||
Method method, Object bean) {
|
||||
// stub method
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class StreamListenerMethodWithReturnMessageTests {
|
||||
|
||||
private Class<?> configClass;
|
||||
|
||||
public StreamListenerMethodWithReturnMessageTests(Class<?> configClass) {
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<?> InputConfigs() {
|
||||
return Arrays.asList(new Class[] { TestPojoWithMessageReturn1.class,
|
||||
TestPojoWithMessageReturn2.class });
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testReturnMessage() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(this.configClass,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
processor.input()
|
||||
.send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
TestPojoWithMessageReturn testPojoWithMessageReturn = context
|
||||
.getBean(TestPojoWithMessageReturn.class);
|
||||
Assertions.assertThat(testPojoWithMessageReturn.receivedPojos).hasSize(1);
|
||||
Assertions.assertThat(testPojoWithMessageReturn.receivedPojos.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).contains("barbar" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithMessageReturn1 extends TestPojoWithMessageReturn {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public Message<?> receive(StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedPojos.add(fooPojo);
|
||||
StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo();
|
||||
barPojo.setBar(fooPojo.getFoo());
|
||||
return MessageBuilder.withPayload(barPojo).setHeader("foo", "bar").build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithMessageReturn2 extends TestPojoWithMessageReturn {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@Output(Processor.OUTPUT)
|
||||
public Message<?> receive(StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedPojos.add(fooPojo);
|
||||
StreamListenerTestUtils.BarPojo bazPojo = new StreamListenerTestUtils.BarPojo();
|
||||
bazPojo.setBar(fooPojo.getFoo());
|
||||
return MessageBuilder.withPayload(bazPojo).setHeader("foo", "bar").build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestPojoWithMessageReturn {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedPojos = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
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.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class StreamListenerMethodWithReturnValueTests {
|
||||
|
||||
private Class<?> configClass;
|
||||
|
||||
public StreamListenerMethodWithReturnValueTests(Class<?> configClass) {
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<?> InputConfigs() {
|
||||
return Arrays.asList(
|
||||
new Class[] { TestStringProcessor1.class, TestStringProcessor2.class });
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testReturn() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(this.configClass,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
processor.input()
|
||||
.send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json").build());
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
|
||||
TestStringProcessor testStringProcessor = context
|
||||
.getBean(TestStringProcessor.class);
|
||||
Assertions.assertThat(testStringProcessor.receivedPojos).hasSize(1);
|
||||
Assertions.assertThat(testStringProcessor.receivedPojos.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).contains("barbar" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestStringProcessor1 extends TestStringProcessor {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public String receive(StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedPojos.add(fooPojo);
|
||||
return fooPojo.getFoo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestStringProcessor2 extends TestStringProcessor {
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@Output(Processor.OUTPUT)
|
||||
public String receive(StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedPojos.add(fooPojo);
|
||||
return fooPojo.getFoo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestStringProcessor {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedPojos = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 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.stream.config;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
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 Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class StreamListenerTestUtils {
|
||||
|
||||
public interface FooInboundChannel1 {
|
||||
|
||||
String INPUT = "foo1-input";
|
||||
|
||||
@Input(FooInboundChannel1.INPUT)
|
||||
SubscribableChannel input();
|
||||
|
||||
}
|
||||
|
||||
public interface FooOutboundChannel1 {
|
||||
|
||||
String OUTPUT = "foo1-output";
|
||||
|
||||
@Output(FooOutboundChannel1.OUTPUT)
|
||||
MessageChannel output();
|
||||
|
||||
}
|
||||
|
||||
public static class FooPojo {
|
||||
|
||||
private String foo;
|
||||
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("FooPojo{");
|
||||
sb.append("foo='").append(this.foo).append('\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class BarPojo {
|
||||
|
||||
private String bar;
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("BarPojo{");
|
||||
sb.append("bar='").append(this.bar).append('\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PojoWithValidation {
|
||||
|
||||
@NotBlank
|
||||
private String foo;
|
||||
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerErrorMessages;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.springframework.cloud.stream.binding.StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class StreamListenerWithAnnotatedInputOutputArgsTests {
|
||||
|
||||
@Test
|
||||
public void testInputOutputArgs() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestInputOutputArgs.class, "--server.port=0",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain",
|
||||
"--spring.jmx.enabled=false");
|
||||
sendMessageAndValidate(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInputOutputArgsWithMoreParameters() {
|
||||
try {
|
||||
SpringApplication.run(TestInputOutputArgsWithMoreParameters.class,
|
||||
"--server.port=0");
|
||||
fail("Expected exception: " + INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInputOutputArgsWithInvalidBindableTarget() {
|
||||
try {
|
||||
SpringApplication.run(TestInputOutputArgsWithInvalidBindableTarget.class,
|
||||
"--server.port=0", "--spring.jmx.enabled=false");
|
||||
fail("Exception expected on using invalid bindable target as method parameter");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(
|
||||
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInputOutputArgsWithParameterOrderChanged() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestInputOutputArgsWithParameterOrderChanged.class, "--server.port=0",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain",
|
||||
"--spring.jmx.enabled=false");
|
||||
sendMessageAndValidate(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void sendMessageAndValidate(ConfigurableApplicationContext context)
|
||||
throws InterruptedException {
|
||||
Processor processor = context.getBean(Processor.class);
|
||||
processor.input().send(MessageBuilder.withPayload("hello")
|
||||
.setHeader("contentType", "text/plain").build());
|
||||
MessageCollector messageCollector = context.getBean(MessageCollector.class);
|
||||
Message<String> result = (Message<String>) messageCollector
|
||||
.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getPayload()).isEqualTo("HELLO");
|
||||
context.close();
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInputOutputArgs {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input(Processor.INPUT) SubscribableChannel input,
|
||||
@Output(Processor.OUTPUT) final MessageChannel output) {
|
||||
input.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
output.send(MessageBuilder
|
||||
.withPayload(message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInputOutputArgsWithMoreParameters {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input(Processor.INPUT) SubscribableChannel input,
|
||||
@Output(Processor.OUTPUT) final MessageChannel output, String someArg) {
|
||||
input.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
output.send(MessageBuilder
|
||||
.withPayload(message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInputOutputArgsWithInvalidBindableTarget {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Input("invalid") SubscribableChannel input,
|
||||
@Output(Processor.OUTPUT) final MessageChannel output) {
|
||||
input.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
output.send(MessageBuilder
|
||||
.withPayload(message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestInputOutputArgsWithParameterOrderChanged {
|
||||
|
||||
@StreamListener
|
||||
public void receive(@Output(Processor.OUTPUT) final MessageChannel output,
|
||||
@Input("input") SubscribableChannel input) {
|
||||
input.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
output.send(MessageBuilder
|
||||
.withPayload(message.getPayload().toString().toUpperCase())
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerErrorMessages;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public class StreamListenerWithConditionsTest {
|
||||
|
||||
@Test
|
||||
public void testAnnotatedArgumentsWithConditionalClass() throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication
|
||||
.run(TestPojoWithAnnotatedArguments.class, "--server.port=0");
|
||||
|
||||
TestPojoWithAnnotatedArguments testPojoWithAnnotatedArguments = context
|
||||
.getBean(TestPojoWithAnnotatedArguments.class);
|
||||
Sink sink = context.getBean(Sink.class);
|
||||
String id = UUID.randomUUID().toString();
|
||||
sink.input().send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
|
||||
.setHeader("contentType", "application/json")
|
||||
.setHeader("testHeader", "testValue").setHeader("type", "foo").build());
|
||||
sink.input().send(MessageBuilder.withPayload("{\"bar\":\"foofoo" + id + "\"}")
|
||||
.setHeader("contentType", "application/json")
|
||||
.setHeader("testHeader", "testValue").setHeader("type", "bar").build());
|
||||
sink.input().send(MessageBuilder.withPayload("{\"bar\":\"foofoo" + id + "\"}")
|
||||
.setHeader("contentType", "application/json")
|
||||
.setHeader("testHeader", "testValue").setHeader("type", "qux").build());
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo).hasSize(1);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedFoo.get(0))
|
||||
.hasFieldOrPropertyWithValue("foo", "barbar" + id);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedBar).hasSize(1);
|
||||
assertThat(testPojoWithAnnotatedArguments.receivedBar.get(0))
|
||||
.hasFieldOrPropertyWithValue("bar", "foofoo" + id);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionalFailsWithReturnValue() throws Exception {
|
||||
try {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestConditionalOnMethodWithReturnValueFails.class, "--server.port=0");
|
||||
context.close();
|
||||
fail("Context creation failure expected");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(
|
||||
StreamListenerErrorMessages.CONDITION_ON_METHOD_RETURNING_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConditionalFailsWithDeclarativeMethod() throws Exception {
|
||||
try {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(
|
||||
TestConditionalOnDeclarativeMethodFails.class, "--server.port=0");
|
||||
context.close();
|
||||
fail("Context creation failure expected");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(
|
||||
StreamListenerErrorMessages.CONDITION_ON_DECLARATIVE_METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestPojoWithAnnotatedArguments {
|
||||
|
||||
List<StreamListenerTestUtils.FooPojo> receivedFoo = new ArrayList<>();
|
||||
|
||||
List<StreamListenerTestUtils.BarPojo> receivedBar = new ArrayList<>();
|
||||
|
||||
@StreamListener(value = Sink.INPUT, condition = "headers['type']=='foo'")
|
||||
public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo) {
|
||||
this.receivedFoo.add(fooPojo);
|
||||
}
|
||||
|
||||
@StreamListener(target = Sink.INPUT, condition = "headers['type']=='bar'")
|
||||
public void receive(@Payload StreamListenerTestUtils.BarPojo barPojo) {
|
||||
this.receivedBar.add(barPojo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestConditionalOnDeclarativeMethodFails {
|
||||
|
||||
@StreamListener(condition = "headers['type']=='foo'")
|
||||
public void receive(@Input("input") MessageChannel input) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Sink.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestConditionalOnMethodWithReturnValueFails {
|
||||
|
||||
@StreamListener(value = Sink.INPUT, condition = "headers['type']=='foo'")
|
||||
public String receive(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2018 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.stream.config;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.test.binder.TestSupportBinder;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 1.2
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
// @checkstyle:off
|
||||
@SpringBootTest(classes = TextPlainToJsonConversionTest.FooProcessor.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
// @checkstyle:on
|
||||
public class TextPlainToJsonConversionTest {
|
||||
|
||||
@Autowired
|
||||
private Processor testProcessor;
|
||||
|
||||
@Autowired
|
||||
private BinderFactory binderFactory;
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testNoContentTypeToJsonConversionOnInput() throws Exception {
|
||||
this.testProcessor.input()
|
||||
.send(MessageBuilder.withPayload("{\"name\":\"Bar\"}").build());
|
||||
Message<String> received = (Message<String>) ((TestSupportBinder) this.binderFactory
|
||||
.getBinder(null, MessageChannel.class)).messageCollector()
|
||||
.forChannel(this.testProcessor.output())
|
||||
.poll(1, TimeUnit.SECONDS);
|
||||
assertThat(received).isNotNull();
|
||||
Foo foo = this.mapper.readValue(received.getPayload(), Foo.class);
|
||||
assertThat(foo.getName()).isEqualTo("transformed-Bar");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0: Conversion from text/plain -> json is no longer supported. Strict
|
||||
* contentType only.
|
||||
*/
|
||||
@Test(expected = MessagingException.class)
|
||||
public void testTextPlainToJsonConversionOnInput() {
|
||||
this.testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}")
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, "text/plain").build());
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class FooProcessor {
|
||||
|
||||
@StreamListener("input")
|
||||
@SendTo("output")
|
||||
public Foo consume(Foo foo) {
|
||||
Foo returnFoo = new Foo();
|
||||
returnFoo.setName("transformed-" + foo.getName());
|
||||
return returnFoo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Foo {
|
||||
|
||||
private String name;
|
||||
|
||||
public Foo() {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Foo{name='" + this.name + "'}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
///*
|
||||
// * Copyright 2017-2018 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.stream.config;
|
||||
//
|
||||
//import java.util.concurrent.TimeUnit;
|
||||
//
|
||||
//import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
//import org.junit.Test;
|
||||
//import org.junit.runner.RunWith;
|
||||
//
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
//import org.springframework.boot.test.context.SpringBootTest;
|
||||
//import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
//import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
//import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
//import org.springframework.cloud.stream.messaging.Processor;
|
||||
//import org.springframework.cloud.stream.test.binder.TestSupportBinder;
|
||||
//import org.springframework.integration.support.MessageBuilder;
|
||||
//import org.springframework.messaging.Message;
|
||||
//import org.springframework.messaging.MessageChannel;
|
||||
//import org.springframework.messaging.MessageHeaders;
|
||||
//import org.springframework.messaging.MessagingException;
|
||||
//import org.springframework.messaging.handler.annotation.SendTo;
|
||||
//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
//
|
||||
//import static org.assertj.core.api.Assertions.assertThat;
|
||||
//
|
||||
///**
|
||||
// * @author Marius Bogoevici
|
||||
// * @author Vinicius Carvalho
|
||||
// * @author Oleg Zhurakousky
|
||||
// * @since 1.2
|
||||
// */
|
||||
//@RunWith(SpringJUnit4ClassRunner.class)
|
||||
//// @checkstyle:off
|
||||
//@SpringBootTest(classes = TextPlainToJsonConversionTest.FooProcessor.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||
//// @checkstyle:on
|
||||
//public class TextPlainToJsonConversionTest {
|
||||
//
|
||||
// @Autowired
|
||||
// private Processor testProcessor;
|
||||
//
|
||||
// @Autowired
|
||||
// private BinderFactory binderFactory;
|
||||
//
|
||||
// private ObjectMapper mapper = new ObjectMapper();
|
||||
//
|
||||
// @SuppressWarnings("unchecked")
|
||||
// @Test
|
||||
// public void testNoContentTypeToJsonConversionOnInput() throws Exception {
|
||||
// this.testProcessor.input()
|
||||
// .send(MessageBuilder.withPayload("{\"name\":\"Bar\"}").build());
|
||||
// Message<String> received = (Message<String>) ((TestSupportBinder) this.binderFactory
|
||||
// .getBinder(null, MessageChannel.class)).messageCollector()
|
||||
// .forChannel(this.testProcessor.output())
|
||||
// .poll(1, TimeUnit.SECONDS);
|
||||
// assertThat(received).isNotNull();
|
||||
// Foo foo = this.mapper.readValue(received.getPayload(), Foo.class);
|
||||
// assertThat(foo.getName()).isEqualTo("transformed-Bar");
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @since 2.0: Conversion from text/plain -> json is no longer supported. Strict
|
||||
// * contentType only.
|
||||
// */
|
||||
// @Test(expected = MessagingException.class)
|
||||
// public void testTextPlainToJsonConversionOnInput() {
|
||||
// this.testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}")
|
||||
// .setHeader(MessageHeaders.CONTENT_TYPE, "text/plain").build());
|
||||
// }
|
||||
//
|
||||
// @EnableBinding(Processor.class)
|
||||
// @EnableAutoConfiguration
|
||||
// public static class FooProcessor {
|
||||
//
|
||||
// @StreamListener("input")
|
||||
// @SendTo("output")
|
||||
// public Foo consume(Foo foo) {
|
||||
// Foo returnFoo = new Foo();
|
||||
// returnFoo.setName("transformed-" + foo.getName());
|
||||
// return returnFoo;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// public static class Foo {
|
||||
//
|
||||
// private String name;
|
||||
//
|
||||
// public Foo() {
|
||||
// }
|
||||
//
|
||||
// public String getName() {
|
||||
// return this.name;
|
||||
// }
|
||||
//
|
||||
// public void setName(String name) {
|
||||
// this.name = name;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public String toString() {
|
||||
// return "Foo{name='" + this.name + "'}";
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -1,272 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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.stream.config.contentType;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.messaging.Source;
|
||||
import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.handler.annotation.Headers;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Vinicius Carvalho
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class ContentTypeTests {
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
public void testSendWithDefaultContentType() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SourceApplication.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false")) {
|
||||
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Source source = context.getBean(Source.class);
|
||||
User user = new User("Alice");
|
||||
source.output().send(MessageBuilder.withPayload(user).build());
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
User received = this.mapper.readValue(message.getPayload(), User.class);
|
||||
assertThat(
|
||||
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
assertThat(user.getName()).isEqualTo(received.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendJsonAsString() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SourceApplication.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false")) {
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Source source = context.getBean(Source.class);
|
||||
User user = new User("Alice");
|
||||
String json = this.mapper.writeValueAsString(user);
|
||||
source.output().send(MessageBuilder.withPayload(user).build());
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(
|
||||
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
assertThat(json).isEqualTo(message.getPayload());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendJsonString() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SourceApplication.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false")) {
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Source source = context.getBean(Source.class);
|
||||
source.output().send(MessageBuilder.withPayload("foo").build());
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(
|
||||
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
assertThat("foo").isEqualTo(message.getPayload());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendBynaryData() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SourceApplication.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false")) {
|
||||
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Source source = context.getBean(Source.class);
|
||||
byte[] data = new byte[] { 0, 1, 2, 3 };
|
||||
source.output()
|
||||
.send(MessageBuilder.withPayload(data)
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
MimeTypeUtils.APPLICATION_OCTET_STREAM)
|
||||
.build());
|
||||
Message<byte[]> message = (Message<byte[]>) collector
|
||||
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(
|
||||
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.APPLICATION_OCTET_STREAM));
|
||||
assertThat(message.getPayload()).isEqualTo(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendBinaryDataWithContentType() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SourceApplication.class, "--server.port=0", "--spring.jmx.enabled=false",
|
||||
"--spring.cloud.stream.bindings.output.contentType=image/jpeg")) {
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Source source = context.getBean(Source.class);
|
||||
byte[] data = new byte[] { 0, 1, 2, 3 };
|
||||
source.output().send(MessageBuilder.withPayload(data).build());
|
||||
Message<byte[]> message = (Message<byte[]>) collector
|
||||
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(message.getPayload()).isEqualTo(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendBinaryDataWithContentTypeUsingHeaders() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SourceApplication.class, "--server.port=0",
|
||||
"--spring.jmx.enabled=false")) {
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Source source = context.getBean(Source.class);
|
||||
byte[] data = new byte[] { 0, 1, 2, 3 };
|
||||
source.output().send(MessageBuilder.withPayload(data)
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.IMAGE_JPEG)
|
||||
.build());
|
||||
Message<byte[]> message = (Message<byte[]>) collector
|
||||
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(
|
||||
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.IMAGE_JPEG));
|
||||
assertThat(message.getPayload()).isEqualTo(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendStringType() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SourceApplication.class, "--server.port=0", "--spring.jmx.enabled=false",
|
||||
"--spring.cloud.stream.bindings.output.contentType=text/plain")) {
|
||||
MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
Source source = context.getBean(Source.class);
|
||||
User user = new User("Alice");
|
||||
source.output().send(MessageBuilder.withPayload(user).build());
|
||||
Message<String> message = (Message<String>) collector
|
||||
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
assertThat(
|
||||
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
.includes(MimeTypeUtils.TEXT_PLAIN));
|
||||
assertThat(message.getPayload()).isEqualTo(user.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveWithDefaults() throws Exception {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SinkApplication.class, "--server.port=0", "--spring.jmx.enabled=false")) {
|
||||
TestSink testSink = context.getBean(TestSink.class);
|
||||
SinkApplication sourceApp = context.getBean(SinkApplication.class);
|
||||
User user = new User("Alice");
|
||||
testSink.pojo().send(MessageBuilder
|
||||
.withPayload(this.mapper.writeValueAsBytes(user)).build());
|
||||
Map<String, Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
|
||||
User received = (User) sourceApp.arguments.pop();
|
||||
assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE))
|
||||
.includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
assertThat(user.getName()).isEqualTo(received.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveRawWithDifferentContentTypes() {
|
||||
try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
SinkApplication.class, "--server.port=0", "--spring.jmx.enabled=false")) {
|
||||
TestSink testSink = context.getBean(TestSink.class);
|
||||
SinkApplication sourceApp = context.getBean(SinkApplication.class);
|
||||
testSink.raw().send(MessageBuilder.withPayload(new byte[4])
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.IMAGE_JPEG)
|
||||
.build());
|
||||
testSink.raw().send(MessageBuilder.withPayload(new byte[4])
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.IMAGE_GIF)
|
||||
.build());
|
||||
Map<String, Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
|
||||
sourceApp.arguments.pop();
|
||||
assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE))
|
||||
.includes(MimeTypeUtils.IMAGE_GIF));
|
||||
headers = (Map<String, Object>) sourceApp.arguments.pop();
|
||||
sourceApp.arguments.pop();
|
||||
assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE))
|
||||
.includes(MimeTypeUtils.IMAGE_JPEG));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface TestSink {
|
||||
|
||||
@Input("POJO_INPUT")
|
||||
SubscribableChannel pojo();
|
||||
|
||||
@Input("STRING_INPUT")
|
||||
SubscribableChannel string();
|
||||
|
||||
@Input("TUPLE_INPUT")
|
||||
SubscribableChannel tuple();
|
||||
|
||||
@Input("RAW_INPUT")
|
||||
SubscribableChannel raw();
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Source.class)
|
||||
@SpringBootApplication
|
||||
public static class SourceApplication {
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(TestSink.class)
|
||||
@SpringBootApplication
|
||||
public static class SinkApplication {
|
||||
|
||||
public LinkedList<? super Object> arguments = new LinkedList<>();
|
||||
|
||||
@StreamListener("POJO_INPUT")
|
||||
public void receive(User user, @Headers Map<String, Object> headers) {
|
||||
this.arguments.push(user);
|
||||
this.arguments.push(headers);
|
||||
}
|
||||
|
||||
@StreamListener("STRING_INPUT")
|
||||
public void receive(String string) {
|
||||
}
|
||||
|
||||
@StreamListener("RAW_INPUT")
|
||||
public void receive(byte[] data, @Headers Map<String, Object> headers) {
|
||||
this.arguments.push(data);
|
||||
this.arguments.push(headers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
///*
|
||||
// * Copyright 2017-2019 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.stream.config.contentType;
|
||||
//
|
||||
//import java.util.LinkedList;
|
||||
//import java.util.Map;
|
||||
//import java.util.concurrent.TimeUnit;
|
||||
//
|
||||
//import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
//import org.junit.Test;
|
||||
//
|
||||
//import org.springframework.boot.SpringApplication;
|
||||
//import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
//import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
//import org.springframework.cloud.stream.annotation.Input;
|
||||
//import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
//import org.springframework.cloud.stream.messaging.Source;
|
||||
//import org.springframework.cloud.stream.test.binder.MessageCollector;
|
||||
//import org.springframework.context.ConfigurableApplicationContext;
|
||||
//import org.springframework.integration.support.MessageBuilder;
|
||||
//import org.springframework.messaging.Message;
|
||||
//import org.springframework.messaging.MessageHeaders;
|
||||
//import org.springframework.messaging.SubscribableChannel;
|
||||
//import org.springframework.messaging.handler.annotation.Headers;
|
||||
//import org.springframework.util.MimeType;
|
||||
//import org.springframework.util.MimeTypeUtils;
|
||||
//
|
||||
//import static org.assertj.core.api.Assertions.assertThat;
|
||||
//
|
||||
///**
|
||||
// * @author Vinicius Carvalho
|
||||
// * @author Oleg Zhurakousky
|
||||
// */
|
||||
//@SuppressWarnings("unchecked")
|
||||
//public class ContentTypeTests {
|
||||
//
|
||||
// private ObjectMapper mapper = new ObjectMapper();
|
||||
//
|
||||
// @Test
|
||||
// public void testSendWithDefaultContentType() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SourceApplication.class, "--server.port=0",
|
||||
// "--spring.jmx.enabled=false")) {
|
||||
//
|
||||
// MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
// Source source = context.getBean(Source.class);
|
||||
// User user = new User("Alice");
|
||||
// source.output().send(MessageBuilder.withPayload(user).build());
|
||||
// Message<String> message = (Message<String>) collector
|
||||
// .forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
// User received = this.mapper.readValue(message.getPayload(), User.class);
|
||||
// assertThat(
|
||||
// message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
// .includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
// assertThat(user.getName()).isEqualTo(received.getName());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testSendJsonAsString() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SourceApplication.class, "--server.port=0",
|
||||
// "--spring.jmx.enabled=false")) {
|
||||
// MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
// Source source = context.getBean(Source.class);
|
||||
// User user = new User("Alice");
|
||||
// String json = this.mapper.writeValueAsString(user);
|
||||
// source.output().send(MessageBuilder.withPayload(user).build());
|
||||
// Message<String> message = (Message<String>) collector
|
||||
// .forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
// assertThat(
|
||||
// message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
// .includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
// assertThat(json).isEqualTo(message.getPayload());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testSendJsonString() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SourceApplication.class, "--server.port=0",
|
||||
// "--spring.jmx.enabled=false")) {
|
||||
// MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
// Source source = context.getBean(Source.class);
|
||||
// source.output().send(MessageBuilder.withPayload("foo").build());
|
||||
// Message<String> message = (Message<String>) collector
|
||||
// .forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
// assertThat(
|
||||
// message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
// .includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
// assertThat("foo").isEqualTo(message.getPayload());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testSendBynaryData() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SourceApplication.class, "--server.port=0",
|
||||
// "--spring.jmx.enabled=false")) {
|
||||
//
|
||||
// MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
// Source source = context.getBean(Source.class);
|
||||
// byte[] data = new byte[] { 0, 1, 2, 3 };
|
||||
// source.output()
|
||||
// .send(MessageBuilder.withPayload(data)
|
||||
// .setHeader(MessageHeaders.CONTENT_TYPE,
|
||||
// MimeTypeUtils.APPLICATION_OCTET_STREAM)
|
||||
// .build());
|
||||
// Message<byte[]> message = (Message<byte[]>) collector
|
||||
// .forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
// assertThat(
|
||||
// message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
// .includes(MimeTypeUtils.APPLICATION_OCTET_STREAM));
|
||||
// assertThat(message.getPayload()).isEqualTo(data);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testSendBinaryDataWithContentType() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SourceApplication.class, "--server.port=0", "--spring.jmx.enabled=false",
|
||||
// "--spring.cloud.stream.bindings.output.contentType=image/jpeg")) {
|
||||
// MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
// Source source = context.getBean(Source.class);
|
||||
// byte[] data = new byte[] { 0, 1, 2, 3 };
|
||||
// source.output().send(MessageBuilder.withPayload(data).build());
|
||||
// Message<byte[]> message = (Message<byte[]>) collector
|
||||
// .forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
// assertThat(message.getPayload()).isEqualTo(data);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testSendBinaryDataWithContentTypeUsingHeaders() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SourceApplication.class, "--server.port=0",
|
||||
// "--spring.jmx.enabled=false")) {
|
||||
// MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
// Source source = context.getBean(Source.class);
|
||||
// byte[] data = new byte[] { 0, 1, 2, 3 };
|
||||
// source.output().send(MessageBuilder.withPayload(data)
|
||||
// .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.IMAGE_JPEG)
|
||||
// .build());
|
||||
// Message<byte[]> message = (Message<byte[]>) collector
|
||||
// .forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
// assertThat(
|
||||
// message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
// .includes(MimeTypeUtils.IMAGE_JPEG));
|
||||
// assertThat(message.getPayload()).isEqualTo(data);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testSendStringType() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SourceApplication.class, "--server.port=0", "--spring.jmx.enabled=false",
|
||||
// "--spring.cloud.stream.bindings.output.contentType=text/plain")) {
|
||||
// MessageCollector collector = context.getBean(MessageCollector.class);
|
||||
// Source source = context.getBean(Source.class);
|
||||
// User user = new User("Alice");
|
||||
// source.output().send(MessageBuilder.withPayload(user).build());
|
||||
// Message<String> message = (Message<String>) collector
|
||||
// .forChannel(source.output()).poll(1, TimeUnit.SECONDS);
|
||||
// assertThat(
|
||||
// message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
|
||||
// .includes(MimeTypeUtils.TEXT_PLAIN));
|
||||
// assertThat(message.getPayload()).isEqualTo(user.toString());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testReceiveWithDefaults() throws Exception {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SinkApplication.class, "--server.port=0", "--spring.jmx.enabled=false")) {
|
||||
// TestSink testSink = context.getBean(TestSink.class);
|
||||
// SinkApplication sourceApp = context.getBean(SinkApplication.class);
|
||||
// User user = new User("Alice");
|
||||
// testSink.pojo().send(MessageBuilder
|
||||
// .withPayload(this.mapper.writeValueAsBytes(user)).build());
|
||||
// Map<String, Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
|
||||
// User received = (User) sourceApp.arguments.pop();
|
||||
// assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE))
|
||||
// .includes(MimeTypeUtils.APPLICATION_JSON));
|
||||
// assertThat(user.getName()).isEqualTo(received.getName());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testReceiveRawWithDifferentContentTypes() {
|
||||
// try (ConfigurableApplicationContext context = SpringApplication.run(
|
||||
// SinkApplication.class, "--server.port=0", "--spring.jmx.enabled=false")) {
|
||||
// TestSink testSink = context.getBean(TestSink.class);
|
||||
// SinkApplication sourceApp = context.getBean(SinkApplication.class);
|
||||
// testSink.raw().send(MessageBuilder.withPayload(new byte[4])
|
||||
// .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.IMAGE_JPEG)
|
||||
// .build());
|
||||
// testSink.raw().send(MessageBuilder.withPayload(new byte[4])
|
||||
// .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.IMAGE_GIF)
|
||||
// .build());
|
||||
// Map<String, Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
|
||||
// sourceApp.arguments.pop();
|
||||
// assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE))
|
||||
// .includes(MimeTypeUtils.IMAGE_GIF));
|
||||
// headers = (Map<String, Object>) sourceApp.arguments.pop();
|
||||
// sourceApp.arguments.pop();
|
||||
// assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE))
|
||||
// .includes(MimeTypeUtils.IMAGE_JPEG));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public interface TestSink {
|
||||
//
|
||||
// @Input("POJO_INPUT")
|
||||
// SubscribableChannel pojo();
|
||||
//
|
||||
// @Input("STRING_INPUT")
|
||||
// SubscribableChannel string();
|
||||
//
|
||||
// @Input("TUPLE_INPUT")
|
||||
// SubscribableChannel tuple();
|
||||
//
|
||||
// @Input("RAW_INPUT")
|
||||
// SubscribableChannel raw();
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @EnableBinding(Source.class)
|
||||
// @SpringBootApplication
|
||||
// public static class SourceApplication {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @EnableBinding(TestSink.class)
|
||||
// @SpringBootApplication
|
||||
// public static class SinkApplication {
|
||||
//
|
||||
// public LinkedList<? super Object> arguments = new LinkedList<>();
|
||||
//
|
||||
// @StreamListener("POJO_INPUT")
|
||||
// public void receive(User user, @Headers Map<String, Object> headers) {
|
||||
// this.arguments.push(user);
|
||||
// this.arguments.push(headers);
|
||||
// }
|
||||
//
|
||||
// @StreamListener("STRING_INPUT")
|
||||
// public void receive(String string) {
|
||||
// }
|
||||
//
|
||||
// @StreamListener("RAW_INPUT")
|
||||
// public void receive(byte[] data, @Headers Map<String, Object> headers) {
|
||||
// this.arguments.push(data);
|
||||
// this.arguments.push(headers);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
|
||||
<description>Set of classes and utility code that may assist in testing both
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-stream-test-support</artifactId>
|
||||
<description>A set of classes to ease testing of Spring Cloud Stream modules.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-parent</artifactId>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
<version>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.cloud.stream.binding.StreamListenerParameterAdapter;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
|
||||
/**
|
||||
* <b>NOTE: It is no longer recommended to use StreamListener in favor of functional programming model.
|
||||
* It will be deprecated and subsequently removed in the future</b>
|
||||
* <br>
|
||||
* <br>
|
||||
* Annotation that marks a method to be a listener to inputs declared via
|
||||
* {@link EnableBinding} (e.g. channels).
|
||||
*
|
||||
* Annotated methods are allowed to have flexible signatures, which determine how the
|
||||
* method is invoked and how their return results are processed. This annotation can be
|
||||
* applied for two separate classes of methods.
|
||||
*
|
||||
* <h3>Declarative mode</h3>
|
||||
*
|
||||
* A method is considered declarative if all its method parameter types and return type
|
||||
* (if not void) are binding targets or conversion targets from binding targets via a
|
||||
* registered {@link StreamListenerParameterAdapter}.
|
||||
*
|
||||
* Only declarative methods can have binding targets or conversion targets as arguments
|
||||
* and return type.
|
||||
*
|
||||
* Declarative methods must specify what inputs and outputs correspond to their arguments
|
||||
* and return type, and can do this in one of the following ways.
|
||||
*
|
||||
* <ul>
|
||||
* <li>By using either the {@link Input} or {@link Output} annotation for each of the
|
||||
* parameters and the {@link Output} annotation on the method for the return type (if
|
||||
* applicable). The use of annotations in this case is mandatory. In this case the
|
||||
* {@link StreamListener} annotation must not specify a value.</li>
|
||||
* <li>By setting an {@link Input} bound target as the annotation value of
|
||||
* {@link StreamListener} and using
|
||||
* {@link org.springframework.messaging.handler.annotation.SendTo} on the method for
|
||||
* the return type (if applicable). In this case the method must have exactly one
|
||||
* parameter, corresponding to an input.
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* An example of declarative method signature using the former idiom is as follows:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @StreamListener
|
||||
* public @Output("joined") Flux<String> join(
|
||||
* @Input("input1") Flux<String> input1,
|
||||
* @Input("input2") Flux<String> input2) {
|
||||
* // ... join the two input streams via functional operators
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* An example of declarative method signature using the latter idiom is as follows:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @StreamListener(Processor.INPUT)
|
||||
* @SendTo(Processor.OUTPUT)
|
||||
* public Flux<String> convert(Flux<String> input) {
|
||||
* return input.map(String::toUppercase);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Declarative methods are invoked only once, when the context is refreshed.
|
||||
*
|
||||
* <h3>Individual message handler mode</h3>
|
||||
*
|
||||
* Non declarative methods are treated as message handler based, and are invoked for each
|
||||
* incoming message received from that target. In this case, the method can have a
|
||||
* flexible signature, as described by {@link MessageMapping}.
|
||||
*
|
||||
* If the method returns a {@link org.springframework.messaging.Message}, the result will
|
||||
* be automatically sent to a binding target, as follows:
|
||||
* <ul>
|
||||
* <li>A result of the type {@link org.springframework.messaging.Message} will be sent
|
||||
* as-is</li>
|
||||
* <li>All other results will become the payload of a
|
||||
* {@link org.springframework.messaging.Message}</li>
|
||||
* </ul>
|
||||
*
|
||||
* The output binding target where the return message is sent is determined by consulting
|
||||
* in the following order:
|
||||
* <ul>
|
||||
* <li>The {@link org.springframework.messaging.MessageHeaders} of the resulting
|
||||
* message.</li>
|
||||
* <li>The value set on the
|
||||
* {@link org.springframework.messaging.handler.annotation.SendTo} annotation, if
|
||||
* present</li>
|
||||
* </ul>
|
||||
*
|
||||
* An example of individual message handler signature is as follows:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @StreamListener(Processor.INPUT)
|
||||
* @SendTo(Processor.OUTPUT)
|
||||
* public String convert(String input) {
|
||||
* return input.toUppercase();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
* @see MessageMapping
|
||||
* @see EnableBinding
|
||||
* @see org.springframework.messaging.handler.annotation.SendTo
|
||||
*
|
||||
* @deprecated as of 3.1 in favor of functional programming model
|
||||
*/
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@MessageMapping
|
||||
@Documented
|
||||
@Deprecated
|
||||
public @interface StreamListener {
|
||||
|
||||
/**
|
||||
* The name of the binding target (e.g. channel) that the method subscribes to.
|
||||
* @return the name of the binding target.
|
||||
*/
|
||||
@AliasFor("target")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The name of the binding target (e.g. channel) that the method subscribes to.
|
||||
* @return the name of the binding target.
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String target() default "";
|
||||
|
||||
/**
|
||||
* A condition that must be met by all items that are dispatched to this method.
|
||||
* @return a SpEL expression that must evaluate to a {@code boolean} value.
|
||||
*/
|
||||
String condition() default "";
|
||||
|
||||
/**
|
||||
* When "true" (default), and a {@code @SendTo} annotation is present, copy the
|
||||
* inbound headers to the outbound message (if the header is absent on the outbound
|
||||
* message). Can be an expression ({@code #{...}}) or property placeholder. Must
|
||||
* resolve to a boolean or a string that is parsed by {@code Boolean.parseBoolean()}.
|
||||
* An expression that resolves to {@code null} is interpreted to mean {@code false}.
|
||||
*
|
||||
* The expression is evaluated during application initialization, and not for each
|
||||
* individual message.
|
||||
*
|
||||
* Prior to version 1.3.0, the default value used to be "false" and headers were not
|
||||
* propagated by default.
|
||||
*
|
||||
* Starting with version 1.3.0, the default value is "true".
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @return {@link Boolean} in a String format
|
||||
*/
|
||||
String copyHeaders() default "true";
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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.stream.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Marker to tag {@link org.springframework.messaging.converter.MessageConverter} beans
|
||||
* that will be added to the
|
||||
* {@link org.springframework.cloud.stream.converter.CompositeMessageConverterFactory}.
|
||||
*
|
||||
* @author Vinicius Carvalho
|
||||
* @author Arten Bilan
|
||||
*
|
||||
* @deprecated as of 3.0 and is not used by the framework anymore.
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Qualifier
|
||||
@Bean
|
||||
@Deprecated
|
||||
public @interface StreamMessageConverter {
|
||||
|
||||
}
|
||||
@@ -20,9 +20,8 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
|
||||
@@ -18,14 +18,13 @@ package org.springframework.cloud.stream.binder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.stream.binding;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.core.BeanFactoryMessageChannelDestinationResolver;
|
||||
import org.springframework.messaging.core.DestinationResolutionException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.messaging.core.DestinationResolver} implementation that
|
||||
* resolves the channel from the bean factory and, if not present, creates a new channel
|
||||
* and adds it to the factory after binding it to the binder.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
* @deprecated As of 3.0.0 in favor if providing `spring.cloud.stream.sendto.destination` property.
|
||||
* This is primarily for function-based programming model. For StreamListener it would still be
|
||||
* required and thus will stay until we deprecate and eventually discontinue StreamListener
|
||||
* and annotation-based programming model.
|
||||
*/
|
||||
@Deprecated
|
||||
public class BinderAwareChannelResolver
|
||||
extends BeanFactoryMessageChannelDestinationResolver {
|
||||
|
||||
private final BindingService bindingService;
|
||||
|
||||
private final AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory;
|
||||
|
||||
private final DynamicDestinationsBindable dynamicDestinationsBindable;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final NewDestinationBindingCallback newBindingCallback;
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
public BinderAwareChannelResolver(BindingService bindingService,
|
||||
AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory,
|
||||
DynamicDestinationsBindable dynamicDestinationsBindable) {
|
||||
this(bindingService, bindingTargetFactory, dynamicDestinationsBindable, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public BinderAwareChannelResolver(BindingService bindingService,
|
||||
AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory,
|
||||
DynamicDestinationsBindable dynamicDestinationsBindable,
|
||||
NewDestinationBindingCallback callback) {
|
||||
this.dynamicDestinationsBindable = dynamicDestinationsBindable;
|
||||
Assert.notNull(bindingService, "'bindingService' cannot be null");
|
||||
Assert.notNull(bindingTargetFactory, "'bindingTargetFactory' cannot be null");
|
||||
this.bindingService = bindingService;
|
||||
this.bindingTargetFactory = bindingTargetFactory;
|
||||
this.newBindingCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
super.setBeanFactory(beanFactory);
|
||||
Assert.isTrue(beanFactory instanceof ConfigurableListableBeanFactory,
|
||||
"'beanFactory' must be an instance of ConfigurableListableBeanFactory");
|
||||
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* See the following for more discussion on it as well as demo reproducing it, thanks
|
||||
* to Anshul Mehra (@Walliee)
|
||||
* https://github.com/spring-cloud/spring-cloud-stream/issues/1603
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public synchronized MessageChannel resolveDestination(String channelName) {
|
||||
BindingServiceProperties bindingServiceProperties = this.bindingService
|
||||
.getBindingServiceProperties();
|
||||
String[] dynamicDestinations = bindingServiceProperties.getDynamicDestinations();
|
||||
|
||||
MessageChannel channel;
|
||||
boolean dynamicAllowed = ObjectUtils.isEmpty(dynamicDestinations)
|
||||
|| ObjectUtils.containsElement(dynamicDestinations, channelName);
|
||||
try {
|
||||
channel = super.resolveDestination(channelName);
|
||||
}
|
||||
catch (DestinationResolutionException e) {
|
||||
if (!dynamicAllowed) {
|
||||
throw e;
|
||||
}
|
||||
else {
|
||||
channel = this.bindingTargetFactory.createOutput(channelName);
|
||||
ProducerProperties producerProperties = bindingServiceProperties
|
||||
.getProducerProperties(channelName);
|
||||
if (this.newBindingCallback != null) {
|
||||
Object extendedProducerProperties = this.bindingService
|
||||
.getExtendedProducerProperties(channel, channelName);
|
||||
this.newBindingCallback.configure(channelName, channel,
|
||||
producerProperties, extendedProducerProperties);
|
||||
}
|
||||
bindingServiceProperties.updateProducerProperties(channelName,
|
||||
producerProperties);
|
||||
this.beanFactory.registerSingleton(channelName, channel);
|
||||
channel = (MessageChannel) this.beanFactory.initializeBean(channel,
|
||||
channelName);
|
||||
Binding<MessageChannel> binding = this.bindingService
|
||||
.bindProducer(channel, channelName);
|
||||
this.dynamicDestinationsBindable.addOutputBinding(channelName, binding);
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a new destination before it is bound.
|
||||
*
|
||||
* @param <T> the extended properties type. If you need to support dynamic binding
|
||||
* with multiple binders, use {@link Object} and cast as needed.
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NewDestinationBindingCallback<T> {
|
||||
|
||||
/**
|
||||
* Configure the properties or channel before binding.
|
||||
* @param channelName the name of the new channel.
|
||||
* @param channel the channel that is about to be bound.
|
||||
* @param producerProperties the producer properties.
|
||||
* @param extendedProducerProperties the extended producer properties (type
|
||||
* depends on binder type and may be null if the binder doesn't support extended
|
||||
* properties).
|
||||
*/
|
||||
void configure(String channelName, MessageChannel channel,
|
||||
ProducerProperties producerProperties, T extendedProducerProperties);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.core.DestinationResolver;
|
||||
|
||||
/**
|
||||
* A {@link BeanPostProcessor} that sets a {@link BinderAwareChannelResolver} on any bean
|
||||
* A {@link BeanPostProcessor} that sets a BinderAwareChannelResolver on any bean
|
||||
* of type {@link AbstractMappingMessageRouter} within the context.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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.stream.binding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AbstractReplyProducingMessageHandler} that delegates to a collection of
|
||||
* internal {@link ConditionalStreamListenerMessageHandlerWrapper} instances, executing
|
||||
* the ones that match the given expression.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @since 1.2
|
||||
*/
|
||||
final class DispatchingStreamListenerMessageHandler
|
||||
extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
private final List<ConditionalStreamListenerMessageHandlerWrapper> handlerMethods;
|
||||
|
||||
private final boolean evaluateExpressions;
|
||||
|
||||
private final EvaluationContext evaluationContext;
|
||||
|
||||
DispatchingStreamListenerMessageHandler(
|
||||
Collection<ConditionalStreamListenerMessageHandlerWrapper> handlerMethods,
|
||||
EvaluationContext evaluationContext) {
|
||||
Assert.notEmpty(handlerMethods, "'handlerMethods' cannot be empty");
|
||||
this.handlerMethods = Collections
|
||||
.unmodifiableList(new ArrayList<>(handlerMethods));
|
||||
boolean evaluateExpressions = false;
|
||||
for (ConditionalStreamListenerMessageHandlerWrapper handlerMethod : handlerMethods) {
|
||||
if (handlerMethod.getCondition() != null) {
|
||||
evaluateExpressions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.evaluateExpressions = evaluateExpressions;
|
||||
if (evaluateExpressions) {
|
||||
Assert.notNull(evaluationContext,
|
||||
"'evaluationContext' cannot be null if conditions are used");
|
||||
}
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldCopyRequestHeaders() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
List<ConditionalStreamListenerMessageHandlerWrapper> matchingHandlers = this.evaluateExpressions
|
||||
? findMatchingHandlers(requestMessage) : this.handlerMethods;
|
||||
if (matchingHandlers.size() == 0) {
|
||||
if (this.logger.isWarnEnabled()) {
|
||||
this.logger.warn(
|
||||
"Cannot find a @StreamListener matching for message with id: "
|
||||
+ requestMessage.getHeaders().getId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else if (matchingHandlers.size() > 1) {
|
||||
for (ConditionalStreamListenerMessageHandlerWrapper matchingMethod : matchingHandlers) {
|
||||
matchingMethod.getStreamListenerMessageHandler()
|
||||
.handleMessage(requestMessage);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
final ConditionalStreamListenerMessageHandlerWrapper singleMatchingHandler = matchingHandlers
|
||||
.get(0);
|
||||
singleMatchingHandler.getStreamListenerMessageHandler()
|
||||
.handleMessage(requestMessage);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<ConditionalStreamListenerMessageHandlerWrapper> findMatchingHandlers(
|
||||
Message<?> message) {
|
||||
ArrayList<ConditionalStreamListenerMessageHandlerWrapper> matchingMethods = new ArrayList<>();
|
||||
for (ConditionalStreamListenerMessageHandlerWrapper wrapper : this.handlerMethods) {
|
||||
if (wrapper.getCondition() == null) {
|
||||
matchingMethods.add(wrapper);
|
||||
}
|
||||
else {
|
||||
boolean conditionMetOnMessage = wrapper.getCondition()
|
||||
.getValue(this.evaluationContext, message, Boolean.class);
|
||||
if (conditionMetOnMessage) {
|
||||
matchingMethods.add(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
return matchingMethods;
|
||||
}
|
||||
|
||||
static class ConditionalStreamListenerMessageHandlerWrapper {
|
||||
|
||||
private final Expression condition;
|
||||
|
||||
private final StreamListenerMessageHandler streamListenerMessageHandler;
|
||||
|
||||
ConditionalStreamListenerMessageHandlerWrapper(Expression condition,
|
||||
StreamListenerMessageHandler streamListenerMessageHandler) {
|
||||
Assert.notNull(streamListenerMessageHandler,
|
||||
"the message handler cannot be null");
|
||||
Assert.isTrue(condition == null || streamListenerMessageHandler.isVoid(),
|
||||
"cannot specify a condition and a return value at the same time");
|
||||
this.condition = condition;
|
||||
this.streamListenerMessageHandler = streamListenerMessageHandler;
|
||||
}
|
||||
|
||||
public Expression getCondition() {
|
||||
return this.condition;
|
||||
}
|
||||
|
||||
public boolean isVoid() {
|
||||
return this.streamListenerMessageHandler.isVoid();
|
||||
}
|
||||
|
||||
public StreamListenerMessageHandler getStreamListenerMessageHandler() {
|
||||
return this.streamListenerMessageHandler;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.binding;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.integration.handler.BridgeHandler;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
/**
|
||||
* A {@link StreamListenerResultAdapter} used for bridging an
|
||||
* {@link org.springframework.cloud.stream.annotation.Output} {@link MessageChannel} to a
|
||||
* bound {@link MessageChannel}.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
public class MessageChannelStreamListenerResultAdapter
|
||||
implements StreamListenerResultAdapter<MessageChannel, MessageChannel> {
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> resultType, Class<?> bindingTarget) {
|
||||
return MessageChannel.class.isAssignableFrom(resultType)
|
||||
&& MessageChannel.class.isAssignableFrom(bindingTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Closeable adapt(MessageChannel streamListenerResult,
|
||||
MessageChannel bindingTarget) {
|
||||
BridgeHandler handler = new BridgeHandler();
|
||||
handler.setOutputChannel(bindingTarget);
|
||||
handler.afterPropertiesSet();
|
||||
((SubscribableChannel) streamListenerResult).subscribe(handler);
|
||||
|
||||
return new NoOpCloseeable();
|
||||
}
|
||||
|
||||
private static final class NoOpCloseeable implements Closeable {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2022-2022 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.stream.binding;
|
||||
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
* Configure a new destination before it is bound.
|
||||
*
|
||||
* @param <T> the extended properties type. If you need to support dynamic binding
|
||||
* with multiple binders, use {@link Object} and cast as needed.
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface NewDestinationBindingCallback<T> {
|
||||
|
||||
/**
|
||||
* Configure the properties or channel before binding.
|
||||
* @param channelName the name of the new channel.
|
||||
* @param channel the channel that is about to be bound.
|
||||
* @param producerProperties the producer properties.
|
||||
* @param extendedProducerProperties the extended producer properties (type
|
||||
* depends on binder type and may be null if the binder doesn't support extended
|
||||
* properties).
|
||||
*/
|
||||
void configure(String channelName, MessageChannel channel,
|
||||
ProducerProperties producerProperties, T extendedProducerProperties);
|
||||
|
||||
}
|
||||
@@ -1,572 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-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.stream.binding;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.config.BeanExpressionContext;
|
||||
import org.springframework.beans.factory.config.BeanExpressionResolver;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.config.SpringIntegrationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.core.DestinationResolver;
|
||||
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} that handles {@link StreamListener} annotations found on bean
|
||||
* methods.
|
||||
*
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Soby Chacko
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProcessor,
|
||||
ApplicationContextAware, SmartInitializingSingleton {
|
||||
|
||||
private static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
// @checkstyle:off
|
||||
private final MultiValueMap<String, StreamListenerHandlerMethodMapping> mappedListenerMethods = new LinkedMultiValueMap<>();
|
||||
|
||||
// @checkstyle:on
|
||||
|
||||
private final Set<Runnable> streamListenerCallbacks = new HashSet<>();
|
||||
|
||||
// == dependencies that are injected in 'afterSingletonsInstantiated' to avoid early
|
||||
// initialization
|
||||
private DestinationResolver<MessageChannel> binderAwareChannelResolver;
|
||||
|
||||
private MessageHandlerMethodFactory messageHandlerMethodFactory;
|
||||
|
||||
// == end dependencies
|
||||
private SpringIntegrationProperties springIntegrationProperties;
|
||||
|
||||
private ConfigurableApplicationContext applicationContext;
|
||||
|
||||
private BeanExpressionResolver resolver;
|
||||
|
||||
private BeanExpressionContext expressionContext;
|
||||
|
||||
private Set<StreamListenerSetupMethodOrchestrator> streamListenerSetupMethodOrchestrators = new LinkedHashSet<>();
|
||||
|
||||
private boolean streamListenerPresent;
|
||||
|
||||
@Override
|
||||
public final void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
|
||||
this.resolver = this.applicationContext.getBeanFactory()
|
||||
.getBeanExpressionResolver();
|
||||
this.expressionContext = new BeanExpressionContext(
|
||||
this.applicationContext.getBeanFactory(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void afterSingletonsInstantiated() {
|
||||
if (!this.streamListenerPresent) {
|
||||
return;
|
||||
}
|
||||
this.injectAndPostProcessDependencies();
|
||||
EvaluationContext evaluationContext = IntegrationContextUtils
|
||||
.getEvaluationContext(this.applicationContext.getBeanFactory());
|
||||
for (Map.Entry<String, List<StreamListenerHandlerMethodMapping>> mappedBindingEntry : this.mappedListenerMethods
|
||||
.entrySet()) {
|
||||
ArrayList<DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper> handlers;
|
||||
handlers = new ArrayList<>();
|
||||
for (StreamListenerHandlerMethodMapping mapping : mappedBindingEntry
|
||||
.getValue()) {
|
||||
final InvocableHandlerMethod invocableHandlerMethod = this.messageHandlerMethodFactory
|
||||
.createInvocableHandlerMethod(mapping.getTargetBean(),
|
||||
checkProxy(mapping.getMethod(), mapping.getTargetBean()));
|
||||
StreamListenerMessageHandler streamListenerMessageHandler = new StreamListenerMessageHandler(
|
||||
invocableHandlerMethod,
|
||||
resolveExpressionAsBoolean(mapping.getCopyHeaders(),
|
||||
"copyHeaders"),
|
||||
this.springIntegrationProperties
|
||||
.getMessageHandlerNotPropagatedHeaders());
|
||||
streamListenerMessageHandler
|
||||
.setApplicationContext(this.applicationContext);
|
||||
streamListenerMessageHandler
|
||||
.setBeanFactory(this.applicationContext.getBeanFactory());
|
||||
if (StringUtils.hasText(mapping.getDefaultOutputChannel())) {
|
||||
streamListenerMessageHandler
|
||||
.setOutputChannelName(mapping.getDefaultOutputChannel());
|
||||
}
|
||||
streamListenerMessageHandler.afterPropertiesSet();
|
||||
if (StringUtils.hasText(mapping.getCondition())) {
|
||||
String conditionAsString = resolveExpressionAsString(
|
||||
mapping.getCondition(), "condition");
|
||||
Expression condition = SPEL_EXPRESSION_PARSER
|
||||
.parseExpression(conditionAsString);
|
||||
handlers.add(
|
||||
new DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper(
|
||||
condition, streamListenerMessageHandler));
|
||||
}
|
||||
else {
|
||||
handlers.add(
|
||||
new DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper(
|
||||
null, streamListenerMessageHandler));
|
||||
}
|
||||
}
|
||||
if (handlers.size() > 1) {
|
||||
for (DispatchingStreamListenerMessageHandler.ConditionalStreamListenerMessageHandlerWrapper handler : handlers) {
|
||||
Assert.isTrue(handler.isVoid(),
|
||||
StreamListenerErrorMessages.MULTIPLE_VALUE_RETURNING_METHODS);
|
||||
}
|
||||
}
|
||||
AbstractReplyProducingMessageHandler handler;
|
||||
|
||||
if (handlers.size() > 1 || handlers.get(0).getCondition() != null) {
|
||||
handler = new DispatchingStreamListenerMessageHandler(handlers,
|
||||
evaluationContext);
|
||||
}
|
||||
else {
|
||||
handler = handlers.get(0).getStreamListenerMessageHandler();
|
||||
}
|
||||
handler.setApplicationContext(this.applicationContext);
|
||||
handler.setChannelResolver(this.binderAwareChannelResolver);
|
||||
handler.afterPropertiesSet();
|
||||
this.applicationContext.getBeanFactory().registerSingleton(
|
||||
handler.getClass().getSimpleName() + handler.hashCode(), handler);
|
||||
this.applicationContext
|
||||
.getBean(mappedBindingEntry.getKey(), SubscribableChannel.class)
|
||||
.subscribe(handler);
|
||||
}
|
||||
this.mappedListenerMethods.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object postProcessAfterInitialization(Object bean, final String beanName)
|
||||
throws BeansException {
|
||||
Class<?> targetClass = AopUtils.isAopProxy(bean) ? AopUtils.getTargetClass(bean)
|
||||
: bean.getClass();
|
||||
Method[] uniqueDeclaredMethods = ReflectionUtils
|
||||
.getUniqueDeclaredMethods(targetClass, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
for (Method method : uniqueDeclaredMethods) {
|
||||
StreamListener streamListener = AnnotatedElementUtils
|
||||
.findMergedAnnotation(method, StreamListener.class);
|
||||
if (streamListener != null) {
|
||||
this.streamListenerPresent = true;
|
||||
this.streamListenerCallbacks.add(() -> {
|
||||
Assert.isTrue(method.getAnnotation(Input.class) == null,
|
||||
StreamListenerErrorMessages.INPUT_AT_STREAM_LISTENER);
|
||||
this.doPostProcess(streamListener, method, bean);
|
||||
});
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point, allowing subclasses to customize the {@link StreamListener}
|
||||
* annotation detected by the postprocessor.
|
||||
* @param originalAnnotation the original annotation
|
||||
* @param annotatedMethod the method on which the annotation has been found
|
||||
* @return the postprocessed {@link StreamListener} annotation
|
||||
*/
|
||||
protected StreamListener postProcessAnnotation(StreamListener originalAnnotation,
|
||||
Method annotatedMethod) {
|
||||
return originalAnnotation;
|
||||
}
|
||||
|
||||
private void doPostProcess(StreamListener streamListener, Method method,
|
||||
Object bean) {
|
||||
streamListener = postProcessAnnotation(streamListener, method);
|
||||
Optional<StreamListenerSetupMethodOrchestrator> orchestratorOptional;
|
||||
orchestratorOptional = this.streamListenerSetupMethodOrchestrators.stream()
|
||||
.filter(t -> t.supports(method)).findFirst();
|
||||
Assert.isTrue(orchestratorOptional.isPresent(),
|
||||
"A matching StreamListenerSetupMethodOrchestrator must be present");
|
||||
StreamListenerSetupMethodOrchestrator streamListenerSetupMethodOrchestrator = orchestratorOptional
|
||||
.get();
|
||||
streamListenerSetupMethodOrchestrator
|
||||
.orchestrateStreamListenerSetupMethod(streamListener, method, bean);
|
||||
}
|
||||
|
||||
private Method checkProxy(Method methodArg, Object bean) {
|
||||
Method method = methodArg;
|
||||
if (AopUtils.isJdkDynamicProxy(bean)) {
|
||||
try {
|
||||
// Found a @StreamListener method on the target class for this JDK proxy
|
||||
// ->
|
||||
// is it also present on the proxy itself?
|
||||
method = bean.getClass().getMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
Class<?>[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces();
|
||||
for (Class<?> iface : proxiedInterfaces) {
|
||||
try {
|
||||
method = iface.getMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
break;
|
||||
}
|
||||
catch (NoSuchMethodException noMethod) {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SecurityException ex) {
|
||||
ReflectionUtils.handleReflectionException(ex);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"@StreamListener method '%s' found on bean target class '%s', "
|
||||
+ "but not found in any interface(s) for bean JDK proxy. Either "
|
||||
+ "pull the method up to an interface or switch to subclass (CGLIB) "
|
||||
+ "proxies by setting proxy-target-class/proxyTargetClass attribute to 'true'",
|
||||
method.getName(), method.getDeclaringClass().getSimpleName()),
|
||||
ex);
|
||||
}
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
private String resolveExpressionAsString(String value, String property) {
|
||||
Object resolved = resolveExpression(value);
|
||||
if (resolved instanceof String) {
|
||||
return (String) resolved;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Resolved " + property + " to ["
|
||||
+ resolved.getClass() + "] instead of String for [" + value + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean resolveExpressionAsBoolean(String value, String property) {
|
||||
Object resolved = resolveExpression(value);
|
||||
if (resolved == null) {
|
||||
return false;
|
||||
}
|
||||
else if (resolved instanceof String) {
|
||||
return Boolean.parseBoolean((String) resolved);
|
||||
}
|
||||
else if (resolved instanceof Boolean) {
|
||||
return (Boolean) resolved;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"Resolved " + property + " to [" + resolved.getClass()
|
||||
+ "] instead of String or Boolean for [" + value + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveExpression(String value) {
|
||||
String resolvedValue = this.applicationContext.getBeanFactory()
|
||||
.resolveEmbeddedValue(value);
|
||||
if (resolvedValue.startsWith("#{") && value.endsWith("}")) {
|
||||
resolvedValue = (String) this.resolver.evaluate(resolvedValue,
|
||||
this.expressionContext);
|
||||
}
|
||||
return resolvedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* This operations ensures that required dependencies are not accidentally injected
|
||||
* early given that this bean is BPP.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private void injectAndPostProcessDependencies() {
|
||||
Collection<StreamListenerParameterAdapter> streamListenerParameterAdapters = this.applicationContext
|
||||
.getBeansOfType(StreamListenerParameterAdapter.class).values();
|
||||
Collection<StreamListenerResultAdapter> streamListenerResultAdapters = this.applicationContext
|
||||
.getBeansOfType(StreamListenerResultAdapter.class).values();
|
||||
this.binderAwareChannelResolver = this.applicationContext
|
||||
.getBean("binderAwareChannelResolver", DestinationResolver.class);
|
||||
this.messageHandlerMethodFactory = this.applicationContext
|
||||
.getBean("integrationMessageHandlerMethodFactory", MessageHandlerMethodFactory.class);
|
||||
this.springIntegrationProperties = this.applicationContext
|
||||
.getBean(SpringIntegrationProperties.class);
|
||||
|
||||
this.streamListenerSetupMethodOrchestrators.addAll(this.applicationContext
|
||||
.getBeansOfType(StreamListenerSetupMethodOrchestrator.class).values());
|
||||
|
||||
// Default orchestrator for StreamListener method invocation is added last into
|
||||
// the LinkedHashSet.
|
||||
this.streamListenerSetupMethodOrchestrators.add(
|
||||
new DefaultStreamListenerSetupMethodOrchestrator(this.applicationContext,
|
||||
streamListenerParameterAdapters, streamListenerResultAdapters));
|
||||
|
||||
this.streamListenerCallbacks.forEach(Runnable::run);
|
||||
}
|
||||
|
||||
private static class StreamListenerHandlerMethodMapping {
|
||||
|
||||
private final Object targetBean;
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final String condition;
|
||||
|
||||
private final String defaultOutputChannel;
|
||||
|
||||
private final String copyHeaders;
|
||||
|
||||
StreamListenerHandlerMethodMapping(Object targetBean, Method method,
|
||||
String condition, String defaultOutputChannel, String copyHeaders) {
|
||||
this.targetBean = targetBean;
|
||||
this.method = method;
|
||||
this.condition = condition;
|
||||
this.defaultOutputChannel = defaultOutputChannel;
|
||||
this.copyHeaders = copyHeaders;
|
||||
}
|
||||
|
||||
Object getTargetBean() {
|
||||
return this.targetBean;
|
||||
}
|
||||
|
||||
Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
String getCondition() {
|
||||
return this.condition;
|
||||
}
|
||||
|
||||
String getDefaultOutputChannel() {
|
||||
return this.defaultOutputChannel;
|
||||
}
|
||||
|
||||
public String getCopyHeaders() {
|
||||
return this.copyHeaders;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final class DefaultStreamListenerSetupMethodOrchestrator
|
||||
implements StreamListenerSetupMethodOrchestrator {
|
||||
|
||||
private final ConfigurableApplicationContext applicationContext;
|
||||
|
||||
private final Collection<StreamListenerParameterAdapter> streamListenerParameterAdapters;
|
||||
|
||||
private final Collection<StreamListenerResultAdapter> streamListenerResultAdapters;
|
||||
|
||||
private DefaultStreamListenerSetupMethodOrchestrator(
|
||||
ConfigurableApplicationContext applicationContext,
|
||||
Collection<StreamListenerParameterAdapter> streamListenerParameterAdapters,
|
||||
Collection<StreamListenerResultAdapter> streamListenerResultAdapters) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.streamListenerParameterAdapters = streamListenerParameterAdapters;
|
||||
this.streamListenerResultAdapters = streamListenerResultAdapters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orchestrateStreamListenerSetupMethod(StreamListener streamListener,
|
||||
Method method, Object bean) {
|
||||
String methodAnnotatedInboundName = streamListener.value();
|
||||
|
||||
String methodAnnotatedOutboundName = StreamListenerMethodUtils
|
||||
.getOutboundBindingTargetName(method);
|
||||
int inputAnnotationCount = StreamListenerMethodUtils
|
||||
.inputAnnotationCount(method);
|
||||
int outputAnnotationCount = StreamListenerMethodUtils
|
||||
.outputAnnotationCount(method);
|
||||
boolean isDeclarative = checkDeclarativeMethod(method,
|
||||
methodAnnotatedInboundName, methodAnnotatedOutboundName);
|
||||
StreamListenerMethodUtils.validateStreamListenerMethod(method,
|
||||
inputAnnotationCount, outputAnnotationCount,
|
||||
methodAnnotatedInboundName, methodAnnotatedOutboundName,
|
||||
isDeclarative, streamListener.condition());
|
||||
if (isDeclarative) {
|
||||
StreamListenerParameterAdapter[] toSlpaArray;
|
||||
toSlpaArray = new StreamListenerParameterAdapter[this.streamListenerParameterAdapters
|
||||
.size()];
|
||||
Object[] adaptedInboundArguments = adaptAndRetrieveInboundArguments(
|
||||
method, methodAnnotatedInboundName, this.applicationContext,
|
||||
this.streamListenerParameterAdapters.toArray(toSlpaArray));
|
||||
invokeStreamListenerResultAdapter(method, bean,
|
||||
methodAnnotatedOutboundName, adaptedInboundArguments);
|
||||
}
|
||||
else {
|
||||
registerHandlerMethodOnListenedChannel(method, streamListener, bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Method method) {
|
||||
// default catch all orchestrator
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void invokeStreamListenerResultAdapter(Method method, Object bean,
|
||||
String outboundName, Object... arguments) {
|
||||
try {
|
||||
if (Void.TYPE.equals(method.getReturnType())) {
|
||||
method.invoke(bean, arguments);
|
||||
}
|
||||
else {
|
||||
Object result = method.invoke(bean, arguments);
|
||||
if (!StringUtils.hasText(outboundName)) {
|
||||
for (int parameterIndex = 0; parameterIndex < method
|
||||
.getParameterCount(); parameterIndex++) {
|
||||
MethodParameter methodParameter = MethodParameter
|
||||
.forExecutable(method, parameterIndex);
|
||||
if (methodParameter.hasParameterAnnotation(Output.class)) {
|
||||
outboundName = methodParameter
|
||||
.getParameterAnnotation(Output.class).value();
|
||||
}
|
||||
}
|
||||
}
|
||||
Object targetBean = this.applicationContext.getBean(outboundName);
|
||||
for (StreamListenerResultAdapter streamListenerResultAdapter : this.streamListenerResultAdapters) {
|
||||
if (streamListenerResultAdapter.supports(result.getClass(),
|
||||
targetBean.getClass())) {
|
||||
streamListenerResultAdapter.adapt(result, targetBean);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new BeanInitializationException(
|
||||
"Cannot setup StreamListener for " + method, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerHandlerMethodOnListenedChannel(Method method,
|
||||
StreamListener streamListener, Object bean) {
|
||||
Assert.hasText(streamListener.value(), "The binding name cannot be null");
|
||||
if (!StringUtils.hasText(streamListener.value())) {
|
||||
throw new BeanInitializationException(
|
||||
"A bound component name must be specified");
|
||||
}
|
||||
final String defaultOutputChannel = StreamListenerMethodUtils
|
||||
.getOutboundBindingTargetName(method);
|
||||
if (Void.TYPE.equals(method.getReturnType())) {
|
||||
Assert.isTrue(StringUtils.isEmpty(defaultOutputChannel),
|
||||
"An output channel cannot be specified for a method that does not return a value");
|
||||
}
|
||||
else {
|
||||
Assert.isTrue(!StringUtils.isEmpty(defaultOutputChannel),
|
||||
"An output channel must be specified for a method that can return a value");
|
||||
}
|
||||
StreamListenerMethodUtils.validateStreamListenerMessageHandler(method);
|
||||
StreamListenerAnnotationBeanPostProcessor.this.mappedListenerMethods.add(
|
||||
streamListener.value(),
|
||||
new StreamListenerHandlerMethodMapping(bean, method,
|
||||
streamListener.condition(), defaultOutputChannel,
|
||||
streamListener.copyHeaders()));
|
||||
}
|
||||
|
||||
private boolean checkDeclarativeMethod(Method method,
|
||||
String methodAnnotatedInboundName, String methodAnnotatedOutboundName) {
|
||||
int methodArgumentsLength = method.getParameterCount();
|
||||
for (int parameterIndex = 0; parameterIndex < methodArgumentsLength; parameterIndex++) {
|
||||
MethodParameter methodParameter = MethodParameter.forExecutable(method,
|
||||
parameterIndex);
|
||||
if (methodParameter.hasParameterAnnotation(Input.class)) {
|
||||
String inboundName = (String) AnnotationUtils.getValue(
|
||||
methodParameter.getParameterAnnotation(Input.class));
|
||||
Assert.isTrue(StringUtils.hasText(inboundName),
|
||||
StreamListenerErrorMessages.INVALID_INBOUND_NAME);
|
||||
Assert.isTrue(
|
||||
isDeclarativeMethodParameter(inboundName, methodParameter),
|
||||
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
return true;
|
||||
}
|
||||
else if (methodParameter.hasParameterAnnotation(Output.class)) {
|
||||
String outboundName = (String) AnnotationUtils.getValue(
|
||||
methodParameter.getParameterAnnotation(Output.class));
|
||||
Assert.isTrue(StringUtils.hasText(outboundName),
|
||||
StreamListenerErrorMessages.INVALID_OUTBOUND_NAME);
|
||||
Assert.isTrue(
|
||||
isDeclarativeMethodParameter(outboundName, methodParameter),
|
||||
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
return true;
|
||||
}
|
||||
else if (StringUtils.hasText(methodAnnotatedOutboundName)) {
|
||||
return isDeclarativeMethodParameter(methodAnnotatedOutboundName,
|
||||
methodParameter);
|
||||
}
|
||||
else if (StringUtils.hasText(methodAnnotatedInboundName)) {
|
||||
return isDeclarativeMethodParameter(methodAnnotatedInboundName,
|
||||
methodParameter);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if method parameters signify an imperative or declarative listener
|
||||
* definition. <br>
|
||||
* Imperative - where handler method is invoked on each message by the handler
|
||||
* infrastructure provided by the framework <br>
|
||||
* Declarative - where handler is provided by the method itself. <br>
|
||||
* Declarative method parameter could either be {@link MessageChannel} or any
|
||||
* other Object for which there is a {@link StreamListenerParameterAdapter} (i.e.,
|
||||
* {@link reactor.core.publisher.Flux}). Declarative method is invoked only once
|
||||
* during initialization phase.
|
||||
* @param targetBeanName name of the bean
|
||||
* @param methodParameter method parameter
|
||||
* @return {@code true} when the method parameter is declarative
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isDeclarativeMethodParameter(String targetBeanName,
|
||||
MethodParameter methodParameter) {
|
||||
boolean declarative = false;
|
||||
if (!methodParameter.getParameterType().isAssignableFrom(Object.class)
|
||||
&& this.applicationContext.containsBean(targetBeanName)) {
|
||||
declarative = MessageChannel.class
|
||||
.isAssignableFrom(methodParameter.getParameterType());
|
||||
if (!declarative) {
|
||||
Class<?> targetBeanClass = this.applicationContext
|
||||
.getType(targetBeanName);
|
||||
declarative = this.streamListenerParameterAdapters.stream().anyMatch(
|
||||
slpa -> slpa.supports(targetBeanClass, methodParameter));
|
||||
}
|
||||
}
|
||||
return declarative;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 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.stream.binding;
|
||||
|
||||
/**
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public abstract class StreamListenerErrorMessages {
|
||||
|
||||
/**
|
||||
* Error message when the inbound name was invalid.
|
||||
*/
|
||||
public static final String INVALID_INBOUND_NAME = "The @Input annotation must have the name of an input as value";
|
||||
|
||||
/**
|
||||
* Error message when the outbound name was invalid.
|
||||
*/
|
||||
public static final String INVALID_OUTBOUND_NAME = "The @Output annotation must have the name of an input as value";
|
||||
|
||||
/**
|
||||
* Error message when there were no outputs specified.
|
||||
*/
|
||||
public static final String ATLEAST_ONE_OUTPUT = "At least one output must be specified";
|
||||
|
||||
/**
|
||||
* Error message when multiple destinations were specified.
|
||||
*/
|
||||
public static final String SEND_TO_MULTIPLE_DESTINATIONS = "Multiple destinations cannot be specified";
|
||||
|
||||
/**
|
||||
* Error message when empty destination was provided.
|
||||
*/
|
||||
public static final String SEND_TO_EMPTY_DESTINATION = "An empty destination cannot be specified";
|
||||
|
||||
/**
|
||||
* Error message when the input or output annotation got placed on a method parameter.
|
||||
*/
|
||||
public static final String INVALID_INPUT_OUTPUT_METHOD_PARAMETERS = "@Input or @Output annotations "
|
||||
+ "are not permitted on "
|
||||
+ "method parameters while using the @StreamListener value and a method-level output specification";
|
||||
|
||||
/**
|
||||
* Error message when no input destination was provided.
|
||||
*/
|
||||
public static final String NO_INPUT_DESTINATION = "No input destination is configured. "
|
||||
+ "Use either the @StreamListener value or @Input";
|
||||
|
||||
/**
|
||||
* Error message when an ambiguous message handler method argument was found.
|
||||
*/
|
||||
public static final String AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS = "Ambiguous method arguments "
|
||||
+ "for the StreamListener method";
|
||||
|
||||
/**
|
||||
* Error message when invalid input values where set.
|
||||
*/
|
||||
public static final String INVALID_INPUT_VALUES = "Cannot set both @StreamListener "
|
||||
+ "value and @Input annotation as method parameter";
|
||||
|
||||
/**
|
||||
* Error message when invalid input value with output method parameter was set.
|
||||
*/
|
||||
public static final String INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM = "Setting the @StreamListener "
|
||||
+ "value when using @Output annotation as method parameter is not permitted. "
|
||||
+ "Use @Input method parameter annotation to specify inbound value instead";
|
||||
|
||||
/**
|
||||
* Error message when invalid output values were set.
|
||||
*/
|
||||
public static final String INVALID_OUTPUT_VALUES = "Cannot set both output (@Output/@SendTo) method annotation value"
|
||||
+ " and @Output annotation as a method parameter";
|
||||
|
||||
/**
|
||||
* Error message when condition was set in declarative mode.
|
||||
*/
|
||||
public static final String CONDITION_ON_DECLARATIVE_METHOD = "Cannot set a condition when "
|
||||
+ "using @StreamListener in declarative mode";
|
||||
|
||||
/**
|
||||
* Error message when condition was set for methods that return a value.
|
||||
*/
|
||||
public static final String CONDITION_ON_METHOD_RETURNING_VALUE = "Cannot set a condition "
|
||||
+ "for methods that return a value";
|
||||
|
||||
/**
|
||||
* Error message when multiple value returning methods were provided.
|
||||
*/
|
||||
public static final String MULTIPLE_VALUE_RETURNING_METHODS = "If multiple @StreamListener "
|
||||
+ "methods are listening to the same binding target, none of them may return a value";
|
||||
|
||||
private static final String PREFIX = "A method annotated with @StreamListener ";
|
||||
|
||||
/**
|
||||
* Error message when @StreamListener was used with @Input.
|
||||
*/
|
||||
public static final String INPUT_AT_STREAM_LISTENER = PREFIX
|
||||
+ "may never be annotated with @Input. "
|
||||
+ "If it should listen to a specific input, use the value of @StreamListener instead";
|
||||
|
||||
/**
|
||||
* Error message when invalid input value with output method parameter was set.
|
||||
*/
|
||||
public static final String RETURN_TYPE_NO_OUTBOUND_SPECIFIED = PREFIX
|
||||
+ "having a return type should also have an outbound target specified";
|
||||
|
||||
/**
|
||||
* Error message when return type was specified for multiple outbound targets.
|
||||
*/
|
||||
public static final String RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED = PREFIX
|
||||
+ "having a return type should have only one outbound target specified";
|
||||
|
||||
/**
|
||||
* Error message when invalid declarative method parameters were set.
|
||||
*/
|
||||
public static final String INVALID_DECLARATIVE_METHOD_PARAMETERS = PREFIX
|
||||
+ "may use @Input or @Output annotations only in declarative mode "
|
||||
+ "and for parameters that are binding targets or convertible from binding targets.";
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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.stream.binding;
|
||||
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
* @since 1.2
|
||||
*/
|
||||
public class StreamListenerMessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
private final InvocableHandlerMethod invocableHandlerMethod;
|
||||
|
||||
private final boolean copyHeaders;
|
||||
|
||||
StreamListenerMessageHandler(InvocableHandlerMethod invocableHandlerMethod,
|
||||
boolean copyHeaders, String[] notPropagatedHeaders) {
|
||||
super();
|
||||
this.invocableHandlerMethod = invocableHandlerMethod;
|
||||
this.copyHeaders = copyHeaders;
|
||||
this.setNotPropagatedHeaders(notPropagatedHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldCopyRequestHeaders() {
|
||||
return this.copyHeaders;
|
||||
}
|
||||
|
||||
public boolean isVoid() {
|
||||
return this.invocableHandlerMethod.isVoid();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
try {
|
||||
return this.invocableHandlerMethod.invoke(requestMessage);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof MessagingException) {
|
||||
throw (MessagingException) e;
|
||||
}
|
||||
else {
|
||||
throw new MessagingException(requestMessage,
|
||||
"Exception thrown while invoking "
|
||||
+ this.invocableHandlerMethod.getShortLogMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.stream.binding;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* This class contains utility methods for handling {@link StreamListener} annotated bean
|
||||
* methods.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public final class StreamListenerMethodUtils {
|
||||
|
||||
private StreamListenerMethodUtils() {
|
||||
throw new IllegalStateException("Can't instantiate a utility class");
|
||||
}
|
||||
|
||||
protected static int inputAnnotationCount(Method method) {
|
||||
int inputAnnotationCount = 0;
|
||||
for (int parameterIndex = 0; parameterIndex < method
|
||||
.getParameterTypes().length; parameterIndex++) {
|
||||
MethodParameter methodParameter = MethodParameter.forExecutable(method,
|
||||
parameterIndex);
|
||||
if (methodParameter.hasParameterAnnotation(Input.class)) {
|
||||
inputAnnotationCount++;
|
||||
}
|
||||
}
|
||||
return inputAnnotationCount;
|
||||
}
|
||||
|
||||
protected static int outputAnnotationCount(Method method) {
|
||||
int outputAnnotationCount = 0;
|
||||
for (int parameterIndex = 0; parameterIndex < method
|
||||
.getParameterTypes().length; parameterIndex++) {
|
||||
MethodParameter methodParameter = MethodParameter.forExecutable(method,
|
||||
parameterIndex);
|
||||
if (methodParameter.hasParameterAnnotation(Output.class)) {
|
||||
outputAnnotationCount++;
|
||||
}
|
||||
}
|
||||
return outputAnnotationCount;
|
||||
}
|
||||
|
||||
protected static void validateStreamListenerMethod(Method method,
|
||||
int inputAnnotationCount, int outputAnnotationCount,
|
||||
String methodAnnotatedInboundName, String methodAnnotatedOutboundName,
|
||||
boolean isDeclarative, String condition) {
|
||||
int methodArgumentsLength = method.getParameterTypes().length;
|
||||
if (!isDeclarative) {
|
||||
Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0,
|
||||
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
if (StringUtils.hasText(methodAnnotatedInboundName)
|
||||
&& StringUtils.hasText(methodAnnotatedOutboundName)) {
|
||||
Assert.isTrue(inputAnnotationCount == 0 && outputAnnotationCount == 0,
|
||||
StreamListenerErrorMessages.INVALID_INPUT_OUTPUT_METHOD_PARAMETERS);
|
||||
}
|
||||
if (StringUtils.hasText(methodAnnotatedInboundName)) {
|
||||
Assert.isTrue(inputAnnotationCount == 0,
|
||||
StreamListenerErrorMessages.INVALID_INPUT_VALUES);
|
||||
Assert.isTrue(outputAnnotationCount == 0,
|
||||
StreamListenerErrorMessages.INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM);
|
||||
}
|
||||
else {
|
||||
Assert.isTrue(inputAnnotationCount >= 1,
|
||||
StreamListenerErrorMessages.NO_INPUT_DESTINATION);
|
||||
}
|
||||
if (StringUtils.hasText(methodAnnotatedOutboundName)) {
|
||||
Assert.isTrue(outputAnnotationCount == 0,
|
||||
StreamListenerErrorMessages.INVALID_OUTPUT_VALUES);
|
||||
}
|
||||
if (!Void.TYPE.equals(method.getReturnType())) {
|
||||
Assert.isTrue(!StringUtils.hasText(condition),
|
||||
StreamListenerErrorMessages.CONDITION_ON_METHOD_RETURNING_VALUE);
|
||||
}
|
||||
if (isDeclarative) {
|
||||
Assert.isTrue(!StringUtils.hasText(condition),
|
||||
StreamListenerErrorMessages.CONDITION_ON_DECLARATIVE_METHOD);
|
||||
for (int parameterIndex = 0; parameterIndex < methodArgumentsLength; parameterIndex++) {
|
||||
MethodParameter methodParameter = MethodParameter.forExecutable(method,
|
||||
parameterIndex);
|
||||
if (methodParameter.hasParameterAnnotation(Input.class)) {
|
||||
String inboundName = (String) AnnotationUtils.getValue(
|
||||
methodParameter.getParameterAnnotation(Input.class));
|
||||
Assert.isTrue(StringUtils.hasText(inboundName),
|
||||
StreamListenerErrorMessages.INVALID_INBOUND_NAME);
|
||||
}
|
||||
if (methodParameter.hasParameterAnnotation(Output.class)) {
|
||||
String outboundName = (String) AnnotationUtils.getValue(
|
||||
methodParameter.getParameterAnnotation(Output.class));
|
||||
Assert.isTrue(StringUtils.hasText(outboundName),
|
||||
StreamListenerErrorMessages.INVALID_OUTBOUND_NAME);
|
||||
}
|
||||
}
|
||||
if (methodArgumentsLength > 1) {
|
||||
Assert.isTrue(
|
||||
inputAnnotationCount
|
||||
+ outputAnnotationCount == methodArgumentsLength,
|
||||
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
}
|
||||
|
||||
if (!method.getReturnType().equals(Void.TYPE)) {
|
||||
if (!StringUtils.hasText(methodAnnotatedOutboundName)) {
|
||||
if (outputAnnotationCount == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
StreamListenerErrorMessages.RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
|
||||
}
|
||||
Assert.isTrue((outputAnnotationCount == 1),
|
||||
StreamListenerErrorMessages.RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void validateStreamListenerMessageHandler(Method method) {
|
||||
int methodArgumentsLength = method.getParameterTypes().length;
|
||||
if (methodArgumentsLength > 1) {
|
||||
int numAnnotatedMethodParameters = 0;
|
||||
int numPayloadAnnotations = 0;
|
||||
for (int parameterIndex = 0; parameterIndex < methodArgumentsLength; parameterIndex++) {
|
||||
MethodParameter methodParameter = MethodParameter.forExecutable(method,
|
||||
parameterIndex);
|
||||
if (methodParameter.hasParameterAnnotations()) {
|
||||
numAnnotatedMethodParameters++;
|
||||
}
|
||||
if (methodParameter.hasParameterAnnotation(Payload.class)) {
|
||||
numPayloadAnnotations++;
|
||||
}
|
||||
}
|
||||
if (numPayloadAnnotations > 0) {
|
||||
Assert.isTrue(
|
||||
methodArgumentsLength == numAnnotatedMethodParameters
|
||||
&& numPayloadAnnotations <= 1,
|
||||
StreamListenerErrorMessages.AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static String getOutboundBindingTargetName(Method method) {
|
||||
SendTo sendTo = AnnotationUtils.findAnnotation(method, SendTo.class);
|
||||
if (sendTo != null) {
|
||||
Assert.isTrue(!ObjectUtils.isEmpty(sendTo.value()),
|
||||
StreamListenerErrorMessages.ATLEAST_ONE_OUTPUT);
|
||||
Assert.isTrue(sendTo.value().length == 1,
|
||||
StreamListenerErrorMessages.SEND_TO_MULTIPLE_DESTINATIONS);
|
||||
Assert.hasText(sendTo.value()[0],
|
||||
StreamListenerErrorMessages.SEND_TO_EMPTY_DESTINATION);
|
||||
return sendTo.value()[0];
|
||||
}
|
||||
Output output = AnnotationUtils.findAnnotation(method, Output.class);
|
||||
if (output != null) {
|
||||
Assert.isTrue(StringUtils.hasText(output.value()),
|
||||
StreamListenerErrorMessages.ATLEAST_ONE_OUTPUT);
|
||||
return output.value();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 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.stream.binding;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
/**
|
||||
* Strategy for adapting a method argument type annotated with
|
||||
* {@link org.springframework.cloud.stream.annotation.Input} or
|
||||
* {@link org.springframework.cloud.stream.annotation.Output} from a binding type (e.g.
|
||||
* {@link org.springframework.messaging.MessageChannel}) supported by an existing binder.
|
||||
*
|
||||
* This is a framework extension and is not primarily intended for use by end-users.
|
||||
*
|
||||
* @param <A> adapter type
|
||||
* @param <B> binding result type
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public interface StreamListenerParameterAdapter<A, B> {
|
||||
|
||||
/**
|
||||
* Return true if the conversion from the binding target type to the argument type is
|
||||
* supported.
|
||||
* @param bindingTargetType the binding target type
|
||||
* @param methodParameter the method parameter for which the conversion is performed
|
||||
* @return true if the conversion is supported
|
||||
*/
|
||||
boolean supports(Class<?> bindingTargetType, MethodParameter methodParameter);
|
||||
|
||||
/**
|
||||
* Adapts the binding target to the argument type. The result will be passed as
|
||||
* argument to a method annotated with
|
||||
* {@link org.springframework.cloud.stream.annotation.StreamListener} when used for
|
||||
* setting up a pipeline.
|
||||
* @param bindingTarget the binding target
|
||||
* @param parameter the method parameter for which the conversion is performed
|
||||
* @return an instance of the parameter type, which will be passed to the method
|
||||
*/
|
||||
A adapt(B bindingTarget, MethodParameter parameter);
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 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.stream.binding;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* A strategy for adapting the result of a
|
||||
* {@link org.springframework.cloud.stream.annotation.StreamListener} annotated method to
|
||||
* a binding target annotated with
|
||||
* {@link org.springframework.cloud.stream.annotation.Output}.
|
||||
*
|
||||
* Used when the {@link org.springframework.cloud.stream.annotation.StreamListener}
|
||||
* annotated method is operating in declarative mode.
|
||||
*
|
||||
* @param <R> stream listener result type
|
||||
* @param <B> binding target type
|
||||
* @author Marius Bogoevici
|
||||
*/
|
||||
public interface StreamListenerResultAdapter<R, B> {
|
||||
|
||||
/**
|
||||
* Return true if the result type can be converted to the binding target.
|
||||
* @param resultType the result type.
|
||||
* @param bindingTarget the binding target.
|
||||
* @return true if the conversion can take place.
|
||||
*/
|
||||
boolean supports(Class<?> resultType, Class<?> bindingTarget);
|
||||
|
||||
/**
|
||||
* Adapts the result to the binding target.
|
||||
* @param streamListenerResult the result of invoking the method.
|
||||
* @param bindingTarget the binding target.
|
||||
* @return an adapted result
|
||||
*/
|
||||
Closeable adapt(R streamListenerResult, B bindingTarget);
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2019 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.stream.binding;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.Output;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Orchestrator used for invoking the {@link StreamListener} setup method.
|
||||
*
|
||||
* By default {@link StreamListenerAnnotationBeanPostProcessor} will use an internal
|
||||
* implementation of this interface to invoke {@link StreamListenerParameterAdapter}s and
|
||||
* {@link StreamListenerResultAdapter}s or handler mappings on the method annotated with
|
||||
* {@link StreamListener}.
|
||||
*
|
||||
* By providing a different implementation of this interface and registering it as a
|
||||
* Spring Bean in the context, one can override the default invocation strategies used by
|
||||
* the {@link StreamListenerAnnotationBeanPostProcessor}. A typical usecase for such
|
||||
* overriding can happen when a downstream
|
||||
* {@link org.springframework.cloud.stream.binder.Binder} implementation wants to change
|
||||
* the way in which any of the default StreamListener handling needs to be changed in a
|
||||
* custom manner.
|
||||
*
|
||||
* When beans of this interface are present in the context, they get priority in the
|
||||
* {@link StreamListenerAnnotationBeanPostProcessor} before falling back to the default
|
||||
* implementation.
|
||||
*
|
||||
* @author Soby Chacko
|
||||
* @see StreamListener
|
||||
* @see StreamListenerAnnotationBeanPostProcessor
|
||||
*/
|
||||
public interface StreamListenerSetupMethodOrchestrator {
|
||||
|
||||
/**
|
||||
* Checks the method annotated with {@link StreamListener} to see if this
|
||||
* implementation can successfully orchestrate this method.
|
||||
* @param method annotated with {@link StreamListener}
|
||||
* @return true if this implementation can orchestrate this method, false otherwise
|
||||
*/
|
||||
boolean supports(Method method);
|
||||
|
||||
/**
|
||||
* Method that allows custom orchestration on the {@link StreamListener} setup method.
|
||||
* @param streamListener reference to the {@link StreamListener} annotation on the
|
||||
* method
|
||||
* @param method annotated with {@link StreamListener}
|
||||
* @param bean that contains the StreamListener method
|
||||
*
|
||||
*/
|
||||
void orchestrateStreamListenerSetupMethod(StreamListener streamListener,
|
||||
Method method, Object bean);
|
||||
|
||||
/**
|
||||
* Default implementation for adapting each of the incoming method arguments using an
|
||||
* available {@link StreamListenerParameterAdapter} and provide the adapted collection
|
||||
* of arguments back to the caller.
|
||||
* @param method annotated with {@link StreamListener}
|
||||
* @param inboundName inbound binding
|
||||
* @param applicationContext spring application context
|
||||
* @param streamListenerParameterAdapters used for adapting the method arguments
|
||||
* @return adapted incoming arguments
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
default Object[] adaptAndRetrieveInboundArguments(Method method, String inboundName,
|
||||
ApplicationContext applicationContext,
|
||||
StreamListenerParameterAdapter... streamListenerParameterAdapters) {
|
||||
Object[] arguments = new Object[method.getParameterTypes().length];
|
||||
for (int parameterIndex = 0; parameterIndex < arguments.length; parameterIndex++) {
|
||||
MethodParameter methodParameter = MethodParameter.forExecutable(method,
|
||||
parameterIndex);
|
||||
Class<?> parameterType = methodParameter.getParameterType();
|
||||
Object targetReferenceValue = null;
|
||||
if (methodParameter.hasParameterAnnotation(Input.class)) {
|
||||
targetReferenceValue = AnnotationUtils
|
||||
.getValue(methodParameter.getParameterAnnotation(Input.class));
|
||||
}
|
||||
else if (methodParameter.hasParameterAnnotation(Output.class)) {
|
||||
targetReferenceValue = AnnotationUtils
|
||||
.getValue(methodParameter.getParameterAnnotation(Output.class));
|
||||
}
|
||||
else if (arguments.length == 1 && StringUtils.hasText(inboundName)) {
|
||||
targetReferenceValue = inboundName;
|
||||
}
|
||||
if (targetReferenceValue != null) {
|
||||
Assert.isInstanceOf(String.class, targetReferenceValue,
|
||||
"Annotation value must be a String");
|
||||
Object targetBean = applicationContext
|
||||
.getBean((String) targetReferenceValue);
|
||||
// Iterate existing parameter adapters first
|
||||
for (StreamListenerParameterAdapter streamListenerParameterAdapter : streamListenerParameterAdapters) {
|
||||
if (streamListenerParameterAdapter.supports(targetBean.getClass(),
|
||||
methodParameter)) {
|
||||
arguments[parameterIndex] = streamListenerParameterAdapter
|
||||
.adapt(targetBean, methodParameter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (arguments[parameterIndex] == null
|
||||
&& parameterType.isAssignableFrom(targetBean.getClass())) {
|
||||
arguments[parameterIndex] = targetBean;
|
||||
}
|
||||
Assert.notNull(arguments[parameterIndex],
|
||||
"Cannot convert argument " + parameterIndex + " of " + method
|
||||
+ "from " + targetBean.getClass() + " to "
|
||||
+ parameterType);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
StreamListenerErrorMessages.INVALID_DECLARATIVE_METHOD_PARAMETERS);
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,10 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.stream.config;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
|
||||
@@ -42,18 +42,14 @@ import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.binder.BinderType;
|
||||
import org.springframework.cloud.stream.binder.BinderTypeRegistry;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinderFactory;
|
||||
import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory;
|
||||
import org.springframework.cloud.stream.binding.Bindable;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareRouter;
|
||||
import org.springframework.cloud.stream.binding.BindingService;
|
||||
import org.springframework.cloud.stream.binding.BindingsLifecycleController;
|
||||
import org.springframework.cloud.stream.binding.ContextStartAfterRefreshListener;
|
||||
import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
|
||||
import org.springframework.cloud.stream.binding.InputBindingLifecycle;
|
||||
import org.springframework.cloud.stream.binding.MessageChannelStreamListenerResultAdapter;
|
||||
import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
|
||||
import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor;
|
||||
import org.springframework.cloud.stream.config.BindingHandlerAdvise.MappingsProvider;
|
||||
import org.springframework.cloud.stream.function.StreamFunctionProperties;
|
||||
import org.springframework.cloud.stream.micrometer.DestinationPublishingMetricsAutoConfiguration;
|
||||
@@ -177,12 +173,6 @@ public class BindingServiceConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean(name = STREAM_LISTENER_ANNOTATION_BEAN_POST_PROCESSOR_NAME)
|
||||
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
|
||||
public static StreamListenerAnnotationBeanPostProcessor streamListenerAnnotationBeanPostProcessor() {
|
||||
return new StreamListenerAnnotationBeanPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BindingHandlerAdvise BindingHandlerAdvise(
|
||||
@Nullable MappingsProvider[] providers) {
|
||||
@@ -209,11 +199,6 @@ public class BindingServiceConfiguration {
|
||||
return binderFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageChannelStreamListenerResultAdapter messageChannelStreamListenerResultAdapter() {
|
||||
return new MessageChannelStreamListenerResultAdapter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
// This conditional is intentionally not in an autoconfig (usually a bad idea) because
|
||||
// it is used to detect a BindingService in the parent context (which we know
|
||||
@@ -253,18 +238,6 @@ public class BindingServiceConfiguration {
|
||||
return new ContextStartAfterRefreshListener();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Bean
|
||||
public BinderAwareChannelResolver binderAwareChannelResolver(
|
||||
BindingService bindingService,
|
||||
AbstractBindingTargetFactory<? extends MessageChannel> bindingTargetFactory,
|
||||
DynamicDestinationsBindable dynamicDestinationsBindable,
|
||||
@Nullable BinderAwareChannelResolver.NewDestinationBindingCallback callback) {
|
||||
|
||||
return new BinderAwareChannelResolver(bindingService, bindingTargetFactory,
|
||||
dynamicDestinationsBindable, callback);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DynamicDestinationsBindable dynamicDestinationsBindable() {
|
||||
return new DynamicDestinationsBindable();
|
||||
|
||||
@@ -71,7 +71,7 @@ import org.springframework.cloud.stream.binder.BindingCreatedEvent;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.binding.BindableProxyFactory;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver.NewDestinationBindingCallback;
|
||||
import org.springframework.cloud.stream.binding.NewDestinationBindingCallback;
|
||||
import org.springframework.cloud.stream.config.BinderFactoryAutoConfiguration;
|
||||
import org.springframework.cloud.stream.config.BindingBeansRegistrar;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
|
||||
@@ -35,9 +35,9 @@ import org.springframework.cloud.function.context.message.MessageUtils;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver.NewDestinationBindingCallback;
|
||||
import org.springframework.cloud.stream.binding.BindingService;
|
||||
import org.springframework.cloud.stream.binding.DefaultPartitioningInterceptor;
|
||||
import org.springframework.cloud.stream.binding.NewDestinationBindingCallback;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel;
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2019 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.stream.binder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
|
||||
import org.springframework.cloud.stream.binding.Bindable;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
|
||||
import org.springframework.cloud.stream.binding.BindingService;
|
||||
import org.springframework.cloud.stream.binding.DynamicDestinationsBindable;
|
||||
import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory;
|
||||
import org.springframework.cloud.stream.config.BindingProperties;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
|
||||
import org.springframework.messaging.support.InterceptableChannel;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.matches;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class BinderAwareChannelResolverTests {
|
||||
|
||||
protected ConfigurableApplicationContext context;
|
||||
|
||||
protected volatile BinderAwareChannelResolver resolver;
|
||||
|
||||
protected volatile Binder<MessageChannel, ConsumerProperties, ProducerProperties> binder;
|
||||
|
||||
protected volatile SubscribableChannelBindingTargetFactory bindingTargetFactory;
|
||||
|
||||
protected volatile BindingServiceProperties bindingServiceProperties;
|
||||
|
||||
protected volatile DynamicDestinationsBindable dynamicDestinationsBindable;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void setupContext() throws Exception {
|
||||
|
||||
this.context = new SpringApplicationBuilder(
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(
|
||||
BinderAwareChannelResolverTests.InterceptorConfiguration.class))
|
||||
.web(WebApplicationType.NONE).run();
|
||||
|
||||
this.resolver = this.context.getBean(BinderAwareChannelResolver.class);
|
||||
this.binder = this.context.getBean(Binder.class);
|
||||
this.bindingServiceProperties = this.context
|
||||
.getBean(BindingServiceProperties.class);
|
||||
this.bindingTargetFactory = this.context
|
||||
.getBean(SubscribableChannelBindingTargetFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveChannel() {
|
||||
Map<String, Bindable> bindables = this.context.getBeansOfType(Bindable.class);
|
||||
assertThat(bindables).hasSize(1);
|
||||
for (Bindable bindable : bindables.values()) {
|
||||
assertThat(bindable.getInputs().size()).isEqualTo(0); // producer
|
||||
assertThat(bindable.getOutputs().size()).isEqualTo(0); // consumer
|
||||
}
|
||||
MessageChannel registered = this.resolver.resolveDestination("foo");
|
||||
assertThat(((InterceptableChannel) registered).getInterceptors().size())
|
||||
.isEqualTo(2);
|
||||
assertThat(((InterceptableChannel) registered).getInterceptors()
|
||||
.get(1) instanceof ImmutableMessageChannelInterceptor).isTrue();
|
||||
|
||||
bindables = this.context.getBeansOfType(Bindable.class);
|
||||
assertThat(bindables).hasSize(1);
|
||||
for (Bindable bindable : bindables.values()) {
|
||||
assertThat(bindable.getInputs().size()).isEqualTo(0); // producer
|
||||
assertThat(bindable.getOutputs().size()).isEqualTo(1); // consumer
|
||||
}
|
||||
DirectChannel testChannel = new DirectChannel();
|
||||
testChannel.setComponentName("INPUT");
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final List<Message<?>> received = new ArrayList<>();
|
||||
testChannel.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
received.add(message);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
this.binder.bindConsumer("foo", null, testChannel, new ConsumerProperties());
|
||||
assertThat(received).hasSize(0);
|
||||
registered.send(MessageBuilder.withPayload("hello").build());
|
||||
try {
|
||||
assertThat(latch.await(1, TimeUnit.SECONDS)).describedAs("Latch timed out");
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
fail("interrupted while awaiting latch");
|
||||
}
|
||||
assertThat(received).hasSize(1);
|
||||
assertThat(new String((byte[]) received.get(0).getPayload())).isEqualTo("hello");
|
||||
this.context.close();
|
||||
for (Bindable bindable : bindables.values()) {
|
||||
assertThat(bindable.getInputs().size()).isEqualTo(0);
|
||||
assertThat(bindable.getOutputs().size()).isEqualTo(0); // Must not be bound"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNonRegisteredChannel() {
|
||||
MessageChannel other = this.resolver.resolveDestination("other");
|
||||
assertThat(this.context.getBean("other")).isSameAs(other);
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public void propertyPassthrough() {
|
||||
Map<String, BindingProperties> bindings = new HashMap<>();
|
||||
BindingProperties genericProperties = new BindingProperties();
|
||||
genericProperties.setContentType("text/plain");
|
||||
bindings.put("foo", genericProperties);
|
||||
this.bindingServiceProperties.setBindings(bindings);
|
||||
Binder binder = mock(Binder.class);
|
||||
Binder binder2 = mock(Binder.class);
|
||||
BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class);
|
||||
Binding<MessageChannel> fooBinding = Mockito.mock(Binding.class);
|
||||
Binding<MessageChannel> barBinding = Mockito.mock(Binding.class);
|
||||
when(binder.bindProducer(matches("foo"), any(DirectChannel.class),
|
||||
any(ProducerProperties.class))).thenReturn(fooBinding);
|
||||
when(binder2.bindProducer(matches("bar"), any(DirectChannel.class),
|
||||
any(ProducerProperties.class))).thenReturn(barBinding);
|
||||
when(mockBinderFactory.getBinder(null, DirectWithAttributesChannel.class))
|
||||
.thenReturn(binder);
|
||||
when(mockBinderFactory.getBinder("someTransport",
|
||||
DirectWithAttributesChannel.class)).thenReturn(binder2);
|
||||
BindingService bindingService = new BindingService(this.bindingServiceProperties,
|
||||
mockBinderFactory, new ObjectMapper());
|
||||
BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(
|
||||
bindingService, this.bindingTargetFactory,
|
||||
new DynamicDestinationsBindable());
|
||||
resolver.setBeanFactory(this.context.getBeanFactory());
|
||||
SubscribableChannel resolved = (SubscribableChannel) resolver
|
||||
.resolveDestination("foo");
|
||||
verify(binder).bindProducer(eq("foo"), any(MessageChannel.class),
|
||||
any(ProducerProperties.class));
|
||||
assertThat(resolved).isSameAs(this.context.getBean("foo"));
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class InterceptorConfiguration {
|
||||
|
||||
@Bean
|
||||
public GlobalChannelInterceptorWrapper testInterceptor() {
|
||||
return new GlobalChannelInterceptorWrapper(
|
||||
new ImmutableMessageChannelInterceptor());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.stream.binder;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@@ -23,14 +26,11 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binder.test.InputDestination;
|
||||
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
@@ -58,9 +58,9 @@ public class ErrorBindingTests {
|
||||
|
||||
Binder binder = binderFactory.getBinder(null, MessageChannel.class);
|
||||
|
||||
Mockito.verify(binder).bindConsumer(eq("input"), isNull(),
|
||||
Mockito.verify(binder).bindConsumer(eq("processor-in-0"), isNull(),
|
||||
any(MessageChannel.class), any(ConsumerProperties.class));
|
||||
Mockito.verify(binder).bindProducer(eq("output"), any(MessageChannel.class),
|
||||
Mockito.verify(binder).bindProducer(eq("processor-out-0"), any(MessageChannel.class),
|
||||
any(ProducerProperties.class));
|
||||
Mockito.verifyNoMoreInteractions(binder);
|
||||
applicationContext.close();
|
||||
@@ -72,7 +72,7 @@ public class ErrorBindingTests {
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(
|
||||
ErrorBindingTests.ErrorConfigurationDefault.class))
|
||||
.web(WebApplicationType.NONE)
|
||||
.run("--spring.cloud.stream.bindings.input.consumer.max-attempts=1",
|
||||
.run("--spring.cloud.stream.bindings.handle-in-0.consumer.max-attempts=1",
|
||||
"--spring.jmx.enabled=false");
|
||||
|
||||
InputDestination source = context.getBean(InputDestination.class);
|
||||
@@ -91,7 +91,7 @@ public class ErrorBindingTests {
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(
|
||||
ErrorBindingTests.ErrorConfigurationWithCustomErrorHandler.class))
|
||||
.web(WebApplicationType.NONE)
|
||||
.run("--spring.cloud.stream.bindings.input.consumer.max-attempts=1",
|
||||
.run("--spring.cloud.stream.bindings.handle-in-0.consumer.max-attempts=1",
|
||||
"--spring.jmx.enabled=false");
|
||||
|
||||
InputDestination source = context.getBean(InputDestination.class);
|
||||
@@ -104,36 +104,41 @@ public class ErrorBindingTests {
|
||||
assertThat(errorConfiguration.counter == 6);
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class TestProcessor {
|
||||
|
||||
@Bean
|
||||
public Function<String, String> processor() {
|
||||
return s -> s;
|
||||
}
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class ErrorConfigurationDefault {
|
||||
|
||||
private int counter;
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void handle(Object value) {
|
||||
this.counter++;
|
||||
throw new RuntimeException("BOOM!");
|
||||
@Bean
|
||||
public Consumer<Object> handle() {
|
||||
return v -> {
|
||||
this.counter++;
|
||||
throw new RuntimeException("BOOM!");
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class ErrorConfigurationWithCustomErrorHandler {
|
||||
|
||||
private int counter;
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void handle(Object value) {
|
||||
this.counter++;
|
||||
throw new RuntimeException("BOOM!");
|
||||
@Bean
|
||||
public Consumer<Object> handle() {
|
||||
return v -> {
|
||||
this.counter++;
|
||||
throw new RuntimeException("BOOM!");
|
||||
};
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = "input.anonymous.errors")
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2017 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.stream.binder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binding.Bindable;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class ExtendedPropertiesBinderAwareChannelResolverTests
|
||||
extends BinderAwareChannelResolverTests {
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void resolveChannel() {
|
||||
Map<String, Bindable> bindables = this.context.getBeansOfType(Bindable.class);
|
||||
assertThat(bindables).hasSize(1);
|
||||
for (Bindable bindable : bindables.values()) {
|
||||
assertThat(bindable.getInputs().size()).isEqualTo(0); // producer
|
||||
assertThat(bindable.getOutputs().size()).isEqualTo(0); // consumer
|
||||
}
|
||||
MessageChannel registered = this.resolver.resolveDestination("foo");
|
||||
bindables = this.context.getBeansOfType(Bindable.class);
|
||||
assertThat(bindables).hasSize(1);
|
||||
for (Bindable bindable : bindables.values()) {
|
||||
assertThat(bindable.getInputs().size()).isEqualTo(0); // producer
|
||||
assertThat(bindable.getOutputs().size()).isEqualTo(1); // consumer
|
||||
}
|
||||
DirectChannel testChannel = new DirectChannel();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final List<Message<?>> received = new ArrayList<>();
|
||||
testChannel.subscribe(new MessageHandler() {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
received.add(message);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
this.binder.bindConsumer("foo", null, testChannel,
|
||||
new ExtendedConsumerProperties<ConsumerProperties>(
|
||||
new ConsumerProperties()));
|
||||
assertThat(received).hasSize(0);
|
||||
registered.send(MessageBuilder.withPayload("hello").build());
|
||||
try {
|
||||
assertThat(latch.await(1, TimeUnit.SECONDS)).describedAs("latch timed out");
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
fail("interrupted while awaiting latch");
|
||||
}
|
||||
assertThat(received).hasSize(1);
|
||||
assertThat(new String((byte[]) received.get(0).getPayload())).isEqualTo("hello");
|
||||
this.context.close();
|
||||
for (Bindable bindable : bindables.values()) {
|
||||
assertThat(bindable.getInputs().size()).isEqualTo(0);
|
||||
assertThat(bindable.getOutputs().size()).isEqualTo(0); // Must not be bound"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2019 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.stream.binder.tck;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binder.test.InputDestination;
|
||||
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class ErrorHandlingTests {
|
||||
|
||||
@Test
|
||||
public void testGlobalErrorWithMessage() {
|
||||
ApplicationContext context = new SpringApplicationBuilder(
|
||||
GlobalErrorHandlerWithErrorMessageConfig.class)
|
||||
.web(WebApplicationType.NONE).run("--spring.jmx.enabled=false");
|
||||
InputDestination source = context.getBean(InputDestination.class);
|
||||
source.send(new GenericMessage<>("foo".getBytes()));
|
||||
GlobalErrorHandlerWithErrorMessageConfig config = context
|
||||
.getBean(GlobalErrorHandlerWithErrorMessageConfig.class);
|
||||
assertThat(config.globalErroInvoked).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGlobalErrorWithThrowable() {
|
||||
ApplicationContext context = new SpringApplicationBuilder(
|
||||
GlobalErrorHandlerWithThrowableConfig.class).web(WebApplicationType.NONE)
|
||||
.run("--spring.jmx.enabled=false");
|
||||
InputDestination source = context.getBean(InputDestination.class);
|
||||
source.send(new GenericMessage<>("foo".getBytes()));
|
||||
GlobalErrorHandlerWithThrowableConfig config = context
|
||||
.getBean(GlobalErrorHandlerWithThrowableConfig.class);
|
||||
assertThat(config.globalErroInvoked).isTrue();
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@Import(TestChannelBinderConfiguration.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class GlobalErrorHandlerWithErrorMessageConfig {
|
||||
|
||||
private boolean globalErroInvoked;
|
||||
|
||||
@StreamListener(target = Processor.INPUT)
|
||||
public void input(final String value) {
|
||||
throw new RuntimeException("test exception");
|
||||
}
|
||||
|
||||
@StreamListener("errorChannel")
|
||||
public void generalError(Message<?> message) {
|
||||
this.globalErroInvoked = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableBinding(Processor.class)
|
||||
@Import(TestChannelBinderConfiguration.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class GlobalErrorHandlerWithThrowableConfig {
|
||||
|
||||
private boolean globalErroInvoked;
|
||||
|
||||
@StreamListener(target = Processor.INPUT)
|
||||
public void input(final String value) {
|
||||
throw new RuntimeException("test exception");
|
||||
}
|
||||
|
||||
@StreamListener("errorChannel")
|
||||
public void generalError(Throwable exception) {
|
||||
this.globalErroInvoked = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
///*
|
||||
// * Copyright 2019-2019 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.stream.binder.tck;
|
||||
//
|
||||
//import org.junit.Test;
|
||||
//
|
||||
//import org.springframework.boot.WebApplicationType;
|
||||
//import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
//import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
//import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
//import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
//import org.springframework.cloud.stream.binder.test.InputDestination;
|
||||
//import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
|
||||
//import org.springframework.cloud.stream.messaging.Processor;
|
||||
//import org.springframework.context.ApplicationContext;
|
||||
//import org.springframework.context.annotation.Import;
|
||||
//import org.springframework.messaging.Message;
|
||||
//import org.springframework.messaging.support.GenericMessage;
|
||||
//
|
||||
//import static org.assertj.core.api.Assertions.assertThat;
|
||||
//
|
||||
///**
|
||||
// * @author Oleg Zhurakousky
|
||||
// *
|
||||
// */
|
||||
//public class ErrorHandlingTests {
|
||||
//
|
||||
// @Test
|
||||
// public void testGlobalErrorWithMessage() {
|
||||
// ApplicationContext context = new SpringApplicationBuilder(
|
||||
// GlobalErrorHandlerWithErrorMessageConfig.class)
|
||||
// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false");
|
||||
// InputDestination source = context.getBean(InputDestination.class);
|
||||
// source.send(new GenericMessage<>("foo".getBytes()));
|
||||
// GlobalErrorHandlerWithErrorMessageConfig config = context
|
||||
// .getBean(GlobalErrorHandlerWithErrorMessageConfig.class);
|
||||
// assertThat(config.globalErroInvoked).isTrue();
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void testGlobalErrorWithThrowable() {
|
||||
// ApplicationContext context = new SpringApplicationBuilder(
|
||||
// GlobalErrorHandlerWithThrowableConfig.class).web(WebApplicationType.NONE)
|
||||
// .run("--spring.jmx.enabled=false");
|
||||
// InputDestination source = context.getBean(InputDestination.class);
|
||||
// source.send(new GenericMessage<>("foo".getBytes()));
|
||||
// GlobalErrorHandlerWithThrowableConfig config = context
|
||||
// .getBean(GlobalErrorHandlerWithThrowableConfig.class);
|
||||
// assertThat(config.globalErroInvoked).isTrue();
|
||||
// }
|
||||
//
|
||||
// @EnableBinding(Processor.class)
|
||||
// @Import(TestChannelBinderConfiguration.class)
|
||||
// @EnableAutoConfiguration
|
||||
// public static class GlobalErrorHandlerWithErrorMessageConfig {
|
||||
//
|
||||
// private boolean globalErroInvoked;
|
||||
//
|
||||
// @StreamListener(target = Processor.INPUT)
|
||||
// public void input(final String value) {
|
||||
// throw new RuntimeException("test exception");
|
||||
// }
|
||||
//
|
||||
// @StreamListener("errorChannel")
|
||||
// public void generalError(Message<?> message) {
|
||||
// this.globalErroInvoked = true;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @EnableBinding(Processor.class)
|
||||
// @Import(TestChannelBinderConfiguration.class)
|
||||
// @EnableAutoConfiguration
|
||||
// public static class GlobalErrorHandlerWithThrowableConfig {
|
||||
//
|
||||
// private boolean globalErroInvoked;
|
||||
//
|
||||
// @StreamListener(target = Processor.INPUT)
|
||||
// public void input(final String value) {
|
||||
// throw new RuntimeException("test exception");
|
||||
// }
|
||||
//
|
||||
// @StreamListener("errorChannel")
|
||||
// public void generalError(Throwable exception) {
|
||||
// this.globalErroInvoked = true;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017-2018 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.stream.binder.test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binder.PollableMessageSource;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Sample spring cloud stream application that demonstrates the usage of
|
||||
* {@link TestChannelBinder}.
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableBinding(SampleStreamApp.PolledConsumer.class)
|
||||
@Import(TestChannelBinderConfiguration.class)
|
||||
public class SampleStreamApp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext context = new SpringApplicationBuilder(SampleStreamApp.class)
|
||||
.web(WebApplicationType.NONE).run("--server.port=0");
|
||||
InputDestination source = context.getBean(InputDestination.class);
|
||||
OutputDestination target = context.getBean(OutputDestination.class);
|
||||
source.send(new GenericMessage<byte[]>("Hello".getBytes()));
|
||||
|
||||
Message<?> message = target.receive();
|
||||
assertThat(new String((byte[]) message.getPayload(), StandardCharsets.UTF_8))
|
||||
.isEqualTo("Hello");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ApplicationRunner runner(PollableMessageSource pollableSource) {
|
||||
return args -> pollableSource.poll(message -> {
|
||||
System.out.println("Polled payload: " + message.getPayload());
|
||||
});
|
||||
}
|
||||
|
||||
@StreamListener(Processor.INPUT)
|
||||
@SendTo(Processor.OUTPUT)
|
||||
public String receive(String value) {
|
||||
System.out.println("Handling payload: " + value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = "input.anonymous.errors")
|
||||
public void error(String value) {
|
||||
System.out.println("Handling ERROR payload: " + value);
|
||||
}
|
||||
|
||||
public interface PolledConsumer extends Processor {
|
||||
|
||||
@Input
|
||||
PollableMessageSource pollableSource();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
///*
|
||||
// * Copyright 2017-2018 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.stream.binder.test;
|
||||
//
|
||||
//import java.nio.charset.StandardCharsets;
|
||||
//
|
||||
//import org.springframework.boot.ApplicationRunner;
|
||||
//import org.springframework.boot.WebApplicationType;
|
||||
//import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
//import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
//import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
//import org.springframework.cloud.stream.annotation.Input;
|
||||
//import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
//import org.springframework.cloud.stream.binder.PollableMessageSource;
|
||||
//import org.springframework.cloud.stream.messaging.Processor;
|
||||
//import org.springframework.context.ApplicationContext;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Import;
|
||||
//import org.springframework.integration.annotation.ServiceActivator;
|
||||
//import org.springframework.messaging.Message;
|
||||
//import org.springframework.messaging.handler.annotation.SendTo;
|
||||
//import org.springframework.messaging.support.GenericMessage;
|
||||
//
|
||||
//import static org.assertj.core.api.Assertions.assertThat;
|
||||
//
|
||||
///**
|
||||
// * Sample spring cloud stream application that demonstrates the usage of
|
||||
// * {@link TestChannelBinder}.
|
||||
// *
|
||||
// * @author Oleg Zhurakousky
|
||||
// * @author Gary Russell
|
||||
// *
|
||||
// */
|
||||
//@SpringBootApplication
|
||||
//@EnableBinding(SampleStreamApp.PolledConsumer.class)
|
||||
//@Import(TestChannelBinderConfiguration.class)
|
||||
//public class SampleStreamApp {
|
||||
//
|
||||
// public static void main(String[] args) {
|
||||
// ApplicationContext context = new SpringApplicationBuilder(SampleStreamApp.class)
|
||||
// .web(WebApplicationType.NONE).run("--server.port=0");
|
||||
// InputDestination source = context.getBean(InputDestination.class);
|
||||
// OutputDestination target = context.getBean(OutputDestination.class);
|
||||
// source.send(new GenericMessage<byte[]>("Hello".getBytes()));
|
||||
//
|
||||
// Message<?> message = target.receive();
|
||||
// assertThat(new String((byte[]) message.getPayload(), StandardCharsets.UTF_8))
|
||||
// .isEqualTo("Hello");
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public ApplicationRunner runner(PollableMessageSource pollableSource) {
|
||||
// return args -> pollableSource.poll(message -> {
|
||||
// System.out.println("Polled payload: " + message.getPayload());
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// @StreamListener(Processor.INPUT)
|
||||
// @SendTo(Processor.OUTPUT)
|
||||
// public String receive(String value) {
|
||||
// System.out.println("Handling payload: " + value);
|
||||
// return value;
|
||||
// }
|
||||
//
|
||||
// @ServiceActivator(inputChannel = "input.anonymous.errors")
|
||||
// public void error(String value) {
|
||||
// System.out.println("Handling ERROR payload: " + value);
|
||||
// }
|
||||
//
|
||||
// public interface PolledConsumer extends Processor {
|
||||
//
|
||||
// @Input
|
||||
// PollableMessageSource pollableSource();
|
||||
//
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -24,23 +24,16 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -58,14 +51,11 @@ import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.ConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinderFactory;
|
||||
import org.springframework.cloud.stream.binder.DefaultBinderTypeRegistry;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
|
||||
import org.springframework.cloud.stream.binder.ProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
|
||||
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.converter.CompositeMessageConverterFactory;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.cloud.stream.reflection.GenericsUtils;
|
||||
@@ -80,21 +70,16 @@ import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.core.DestinationResolutionException;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.ArgumentMatchers.matches;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -326,93 +311,94 @@ public class BindingServiceTests {
|
||||
binderFactory.destroy();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Test
|
||||
public void checkDynamicBinding() {
|
||||
BindingServiceProperties properties = new BindingServiceProperties();
|
||||
BindingProperties bindingProperties = new BindingProperties();
|
||||
bindingProperties.setProducer(new ProducerProperties());
|
||||
properties.setBindings(Collections.singletonMap("foo", bindingProperties));
|
||||
DefaultBinderFactory binderFactory = createMockBinderFactory();
|
||||
final ExtendedPropertiesBinder binder = mock(ExtendedPropertiesBinder.class);
|
||||
Properties extendedProps = new Properties();
|
||||
when(binder.getExtendedProducerProperties(anyString())).thenReturn(extendedProps);
|
||||
Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class);
|
||||
final AtomicReference<MessageChannel> dynamic = new AtomicReference<>();
|
||||
when(binder.bindProducer(matches("foo"), any(DirectChannel.class),
|
||||
any(ProducerProperties.class))).thenReturn(mockBinding);
|
||||
BindingService bindingService = new BindingService(properties, binderFactory, new ObjectMapper()) {
|
||||
|
||||
@Override
|
||||
protected <T> Binder<T, ?, ?> getBinder(String channelName,
|
||||
Class<T> bindableType) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
};
|
||||
SubscribableChannelBindingTargetFactory bindableSubscribableChannelFactory;
|
||||
bindableSubscribableChannelFactory = new SubscribableChannelBindingTargetFactory(
|
||||
new MessageConverterConfigurer(properties,
|
||||
new CompositeMessageConverterFactory().getMessageConverterForAllRegistered()));
|
||||
final AtomicBoolean callbackInvoked = new AtomicBoolean();
|
||||
BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(
|
||||
bindingService, bindableSubscribableChannelFactory,
|
||||
new DynamicDestinationsBindable(), (name, channel, props, extended) -> {
|
||||
callbackInvoked.set(true);
|
||||
assertThat(name).isEqualTo("foo");
|
||||
assertThat(channel).isNotNull();
|
||||
assertThat(props).isNotNull();
|
||||
assertThat(extended).isSameAs(extendedProps);
|
||||
props.setUseNativeEncoding(true);
|
||||
extendedProps.setProperty("bar", "baz");
|
||||
});
|
||||
ConfigurableListableBeanFactory beanFactory = mock(
|
||||
ConfigurableListableBeanFactory.class);
|
||||
when(beanFactory.getBean("foo", MessageChannel.class))
|
||||
.thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class));
|
||||
when(beanFactory.getBean("bar", MessageChannel.class))
|
||||
.thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class));
|
||||
doAnswer(new Answer<Void>() {
|
||||
|
||||
@Override
|
||||
public Void answer(InvocationOnMock invocation) throws Throwable {
|
||||
dynamic.set(invocation.getArgument(1));
|
||||
return null;
|
||||
}
|
||||
|
||||
}).when(beanFactory).registerSingleton(eq("foo"), any(MessageChannel.class));
|
||||
doAnswer(new Answer<Object>() {
|
||||
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
return dynamic.get();
|
||||
}
|
||||
|
||||
}).when(beanFactory).initializeBean(any(MessageChannel.class), eq("foo"));
|
||||
resolver.setBeanFactory(beanFactory);
|
||||
MessageChannel resolved = resolver.resolveDestination("foo");
|
||||
assertThat(resolved).isSameAs(dynamic.get());
|
||||
ArgumentCaptor<ProducerProperties> captor = ArgumentCaptor
|
||||
.forClass(ProducerProperties.class);
|
||||
verify(binder).bindProducer(eq("foo"), eq(dynamic.get()), captor.capture());
|
||||
assertThat(captor.getValue().isUseNativeEncoding()).isTrue();
|
||||
assertThat(captor.getValue()).isInstanceOf(ExtendedProducerProperties.class);
|
||||
assertThat(((ExtendedProducerProperties) captor.getValue()).getExtension())
|
||||
.isSameAs(extendedProps);
|
||||
doReturn(dynamic.get()).when(beanFactory).getBean("foo", MessageChannel.class);
|
||||
properties.setDynamicDestinations(new String[] { "foo" });
|
||||
resolved = resolver.resolveDestination("foo");
|
||||
assertThat(resolved).isSameAs(dynamic.get());
|
||||
properties.setDynamicDestinations(new String[] { "test" });
|
||||
try {
|
||||
resolver.resolveDestination("bar");
|
||||
fail("Should throw an exception");
|
||||
}
|
||||
catch (DestinationResolutionException e) {
|
||||
assertThat(e).hasMessageContaining(
|
||||
"Failed to find MessageChannel bean with name 'bar'");
|
||||
}
|
||||
}
|
||||
//TODO: Need to re-write the following test.
|
||||
//@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
// @Test
|
||||
// public void checkDynamicBinding() {
|
||||
// BindingServiceProperties properties = new BindingServiceProperties();
|
||||
// BindingProperties bindingProperties = new BindingProperties();
|
||||
// bindingProperties.setProducer(new ProducerProperties());
|
||||
// properties.setBindings(Collections.singletonMap("foo", bindingProperties));
|
||||
// DefaultBinderFactory binderFactory = createMockBinderFactory();
|
||||
// final ExtendedPropertiesBinder binder = mock(ExtendedPropertiesBinder.class);
|
||||
// Properties extendedProps = new Properties();
|
||||
// when(binder.getExtendedProducerProperties(anyString())).thenReturn(extendedProps);
|
||||
// Binding<MessageChannel> mockBinding = Mockito.mock(Binding.class);
|
||||
// final AtomicReference<MessageChannel> dynamic = new AtomicReference<>();
|
||||
// when(binder.bindProducer(matches("foo"), any(DirectChannel.class),
|
||||
// any(ProducerProperties.class))).thenReturn(mockBinding);
|
||||
// BindingService bindingService = new BindingService(properties, binderFactory, new ObjectMapper()) {
|
||||
//
|
||||
// @Override
|
||||
// protected <T> Binder<T, ?, ?> getBinder(String channelName,
|
||||
// Class<T> bindableType) {
|
||||
// return binder;
|
||||
// }
|
||||
//
|
||||
// };
|
||||
// SubscribableChannelBindingTargetFactory bindableSubscribableChannelFactory;
|
||||
// bindableSubscribableChannelFactory = new SubscribableChannelBindingTargetFactory(
|
||||
// new MessageConverterConfigurer(properties,
|
||||
// new CompositeMessageConverterFactory().getMessageConverterForAllRegistered()));
|
||||
// final AtomicBoolean callbackInvoked = new AtomicBoolean();
|
||||
// BinderAwareChannelResolver resolver = new BinderAwareChannelResolver(
|
||||
// bindingService, bindableSubscribableChannelFactory,
|
||||
// new DynamicDestinationsBindable(), (name, channel, props, extended) -> {
|
||||
// callbackInvoked.set(true);
|
||||
// assertThat(name).isEqualTo("foo");
|
||||
// assertThat(channel).isNotNull();
|
||||
// assertThat(props).isNotNull();
|
||||
// assertThat(extended).isSameAs(extendedProps);
|
||||
// props.setUseNativeEncoding(true);
|
||||
// extendedProps.setProperty("bar", "baz");
|
||||
// });
|
||||
// ConfigurableListableBeanFactory beanFactory = mock(
|
||||
// ConfigurableListableBeanFactory.class);
|
||||
// when(beanFactory.getBean("foo", MessageChannel.class))
|
||||
// .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class));
|
||||
// when(beanFactory.getBean("bar", MessageChannel.class))
|
||||
// .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class));
|
||||
// doAnswer(new Answer<Void>() {
|
||||
//
|
||||
// @Override
|
||||
// public Void answer(InvocationOnMock invocation) throws Throwable {
|
||||
// dynamic.set(invocation.getArgument(1));
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// }).when(beanFactory).registerSingleton(eq("foo"), any(MessageChannel.class));
|
||||
// doAnswer(new Answer<Object>() {
|
||||
//
|
||||
// @Override
|
||||
// public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
// return dynamic.get();
|
||||
// }
|
||||
//
|
||||
// }).when(beanFactory).initializeBean(any(MessageChannel.class), eq("foo"));
|
||||
// resolver.setBeanFactory(beanFactory);
|
||||
// MessageChannel resolved = resolver.resolveDestination("foo");
|
||||
// assertThat(resolved).isSameAs(dynamic.get());
|
||||
// ArgumentCaptor<ProducerProperties> captor = ArgumentCaptor
|
||||
// .forClass(ProducerProperties.class);
|
||||
// verify(binder).bindProducer(eq("foo"), eq(dynamic.get()), captor.capture());
|
||||
// assertThat(captor.getValue().isUseNativeEncoding()).isTrue();
|
||||
// assertThat(captor.getValue()).isInstanceOf(ExtendedProducerProperties.class);
|
||||
// assertThat(((ExtendedProducerProperties) captor.getValue()).getExtension())
|
||||
// .isSameAs(extendedProps);
|
||||
// doReturn(dynamic.get()).when(beanFactory).getBean("foo", MessageChannel.class);
|
||||
// properties.setDynamicDestinations(new String[] { "foo" });
|
||||
// resolved = resolver.resolveDestination("foo");
|
||||
// assertThat(resolved).isSameAs(dynamic.get());
|
||||
// properties.setDynamicDestinations(new String[] { "test" });
|
||||
// try {
|
||||
// resolver.resolveDestination("bar");
|
||||
// fail("Should throw an exception");
|
||||
// }
|
||||
// catch (DestinationResolutionException e) {
|
||||
// assertThat(e).hasMessageContaining(
|
||||
// "Failed to find MessageChannel bean with name 'bar'");
|
||||
// }
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testProducerPropertiesValidation() {
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.stream.config;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
|
||||
@@ -16,18 +16,15 @@
|
||||
|
||||
package org.springframework.cloud.stream.function;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy;
|
||||
import org.springframework.cloud.stream.binder.test.InputDestination;
|
||||
import org.springframework.cloud.stream.binder.test.OutputDestination;
|
||||
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.messaging.Message;
|
||||
@@ -40,6 +37,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Oleg Zhurakousky
|
||||
* @author David Turanski
|
||||
*
|
||||
*
|
||||
* TODO: Need to rewrite this test.
|
||||
*/
|
||||
public class DynamicDestinationFunctionTests {
|
||||
|
||||
@@ -50,6 +49,7 @@ public class DynamicDestinationFunctionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testEmptyConfiguration() {
|
||||
TestChannelBinderConfiguration.applicationContextRunner(SampleConfiguration.class)
|
||||
.withPropertyValues(
|
||||
@@ -72,8 +72,8 @@ public class DynamicDestinationFunctionTests {
|
||||
@EnableAutoConfiguration
|
||||
public static class SampleConfiguration {
|
||||
|
||||
@Autowired
|
||||
private BinderAwareChannelResolver resolver;
|
||||
// @Autowired
|
||||
// private BinderAwareChannelResolver resolver;
|
||||
|
||||
@Bean
|
||||
public PartitionKeyExtractorStrategy keyExtractor() {
|
||||
@@ -86,12 +86,12 @@ public class DynamicDestinationFunctionTests {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Consumer<String> cons() {
|
||||
return value -> {
|
||||
resolver.resolveDestination(value).send(new GenericMessage<String>(value));
|
||||
};
|
||||
}
|
||||
// @Bean
|
||||
// public Consumer<String> cons() {
|
||||
// return value -> {
|
||||
// resolver.resolveDestination(value).send(new GenericMessage<String>(value));
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,8 +45,6 @@ import org.springframework.cloud.function.context.FunctionType;
|
||||
import org.springframework.cloud.function.context.catalog.FunctionAroundWrapper;
|
||||
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
|
||||
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.cloud.stream.binder.Binding;
|
||||
import org.springframework.cloud.stream.binder.BindingCreatedEvent;
|
||||
import org.springframework.cloud.stream.binder.test.FunctionBindingTestUtils;
|
||||
@@ -56,7 +54,6 @@ import org.springframework.cloud.stream.binder.test.TestChannelBinderConfigurati
|
||||
import org.springframework.cloud.stream.binding.BindingsLifecycleController;
|
||||
import org.springframework.cloud.stream.binding.BindingsLifecycleController.State;
|
||||
import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel;
|
||||
import org.springframework.cloud.stream.messaging.Sink;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -466,16 +463,16 @@ public class ImplicitFunctionBindingTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctionConfigDisabledIfStreamListenerIsUsed() {
|
||||
System.clearProperty("spring.cloud.function.definition");
|
||||
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(LegacyConfiguration.class))
|
||||
.web(WebApplicationType.NONE).run("--spring.jmx.enabled=false")) {
|
||||
|
||||
assertThat(context.getBean("supplierInitializer").getClass().getSimpleName()).isEqualTo("NullBean");
|
||||
}
|
||||
}
|
||||
// @Test
|
||||
// public void testFunctionConfigDisabledIfStreamListenerIsUsed() {
|
||||
// System.clearProperty("spring.cloud.function.definition");
|
||||
// try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
|
||||
// TestChannelBinderConfiguration.getCompleteConfiguration(LegacyConfiguration.class))
|
||||
// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false")) {
|
||||
//
|
||||
// assertThat(context.getBean("supplierInitializer").getClass().getSimpleName()).isEqualTo("NullBean");
|
||||
// }
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testDeclaredTypeVsActualInstance() {
|
||||
@@ -1340,16 +1337,6 @@ public class ImplicitFunctionBindingTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@EnableBinding(Sink.class)
|
||||
public static class LegacyConfiguration {
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void handle(String value) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
public static class EmptyConfiguration {
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -70,6 +70,7 @@ public class SourceToFunctionsSupportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testFunctionIsAppliedToExistingMessageSource() {
|
||||
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(
|
||||
@@ -98,7 +99,7 @@ public class SourceToFunctionsSupportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // fails intermittently
|
||||
@Ignore // fails intermittently
|
||||
public void testFunctionsAreAppliedToExistingMessageSourceReactive() {
|
||||
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(
|
||||
@@ -113,6 +114,7 @@ public class SourceToFunctionsSupportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testImperativeSupplier() throws Exception {
|
||||
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(
|
||||
@@ -236,7 +238,7 @@ public class SourceToFunctionsSupportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
@Ignore
|
||||
public void testFiniteFluxSupplierMessage() {
|
||||
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
|
||||
TestChannelBinderConfiguration.getCompleteConfiguration(FunctionsConfiguration.class,
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper;
|
||||
import org.springframework.cloud.stream.binder.test.OutputDestination;
|
||||
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
|
||||
import org.springframework.cloud.stream.binding.BinderAwareChannelResolver.NewDestinationBindingCallback;
|
||||
import org.springframework.cloud.stream.binding.NewDestinationBindingCallback;
|
||||
import org.springframework.cloud.stream.config.BindingServiceProperties;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
Reference in New Issue
Block a user