Initial take on removing StreamListener

StreamListener was deprecated in 3.0.x.

This commit is the initial one in removing StreamListener and
it's related components completely in 4.0.x.

More tests need to be adjusted and migrated which will be addressed
in later commits.
This commit is contained in:
Soby Chacko
2022-01-07 17:53:41 -05:00
parent 0ee4b41b2f
commit 2fe7cf58c1
40 changed files with 1787 additions and 6288 deletions

View File

@@ -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.PayloadMethodArgumentResolver;
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 PayloadMethodArgumentResolver(
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<>();

View File

@@ -1,192 +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 jakarta.validation.Valid;
import org.junit.BeforeClass;
import org.junit.Ignore;
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
@Ignore
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);
}
}
}

View File

@@ -1,106 +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.Ignore;
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")
@Ignore
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);
}
}
}

View File

@@ -1,147 +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.Ignore;
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
@Ignore
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
@Ignore
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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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 {
}
}

View File

@@ -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<>();
}
}

View File

@@ -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());
}
});
}
}
}

View File

@@ -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<>();
}
}

View File

@@ -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() {
}
}
}

View File

@@ -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<>();
}
}

View File

@@ -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
}
}
}

View File

@@ -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<>();
}
}

View File

@@ -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<>();
}
}

View File

@@ -1,105 +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 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 {
private String foo;
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
}

View File

@@ -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());
}
});
}
}
}

View File

@@ -1,144 +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.Ignore;
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
@Ignore
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;
}
}
}

View File

@@ -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 + "'}";
}
}
}

View File

@@ -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 + "'}";
// }
//
// }
//
//}

View File

@@ -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);
}
}
}

View File

@@ -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);
// }
//
// }
//
//}

View File

@@ -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">
* &#064;StreamListener
* public &#064;Output("joined") Flux&lt;String&gt; join(
* &#064;Input("input1") Flux&lt;String&gt; input1,
* &#064;Input("input2") Flux&lt;String&gt; 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">
* &#064;StreamListener(Processor.INPUT)
* &#064;SendTo(Processor.OUTPUT)
* public Flux&lt;String&gt; convert(Flux&lt;String&gt; 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">
* &#064;StreamListener(Processor.INPUT)
* &#064;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";
}

View File

@@ -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;
}
}
}

View File

@@ -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 {
}
}
}

View File

@@ -1,571 +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.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;
}
}
}

View File

@@ -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.";
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -49,9 +49,7 @@ 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;
@@ -175,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) {
@@ -207,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

View File

@@ -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")

View File

@@ -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;
}
}
}

View File

@@ -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;
// }
//
// }
//
//}

View File

@@ -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();
}
}

View File

@@ -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();
//
// }
//
//}

View File

@@ -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 {