From 04f09ad95b9a7fd6abaffda0bd0d18a418a76c12 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 16 Nov 2021 14:06:44 +0100 Subject: [PATCH 01/27] GH-2245 Ensure DefaultPartitionInterceptor is added to partitioned destinations managed by StreamBridge Resolves #2245 --- .../DefaultPartitioningInterceptor.java | 68 +++++++++++++++++++ .../cloud/stream/function/StreamBridge.java | 9 ++- 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DefaultPartitioningInterceptor.java diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DefaultPartitioningInterceptor.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DefaultPartitioningInterceptor.java new file mode 100644 index 000000000..a0fdcde8b --- /dev/null +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DefaultPartitioningInterceptor.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binding; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.cloud.stream.binder.BinderHeaders; +import org.springframework.cloud.stream.binder.PartitionHandler; +import org.springframework.cloud.stream.config.BindingProperties; +import org.springframework.integration.expression.ExpressionUtils; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.ChannelInterceptor; + +/** + * + * @author Oleg Zhurakousky + * @since 3.1 + * + */ +public class DefaultPartitioningInterceptor implements ChannelInterceptor { + + private final PartitionHandler partitionHandler; + + public DefaultPartitioningInterceptor(BindingProperties bindingProperties, ConfigurableListableBeanFactory beanFactory) { + this.partitionHandler = new PartitionHandler( + ExpressionUtils.createStandardEvaluationContext(beanFactory), + bindingProperties.getProducer(), beanFactory); + } + + public void setPartitionCount(int partitionCount) { + this.partitionHandler.setPartitionCount(partitionCount); + } + + @Override + public Message preSend(Message message, MessageChannel channel) { + if (!message.getHeaders().containsKey(BinderHeaders.PARTITION_OVERRIDE)) { + int partition = this.partitionHandler.determinePartition(message); + return MessageBuilder + .fromMessage(message) + .setHeader(BinderHeaders.PARTITION_HEADER, partition).build(); + } + else { + return MessageBuilder + .fromMessage(message) + .setHeader(BinderHeaders.PARTITION_HEADER, + message.getHeaders() + .get(BinderHeaders.PARTITION_OVERRIDE)) + .removeHeader(BinderHeaders.PARTITION_OVERRIDE).build(); + } + } + +} + diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java index c39f86d1c..01ddedc87 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java @@ -37,6 +37,8 @@ import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.binding.BinderAwareChannelResolver.NewDestinationBindingCallback; import org.springframework.cloud.stream.binding.BindingService; +import org.springframework.cloud.stream.binding.DefaultPartitioningInterceptor; +import org.springframework.cloud.stream.config.BindingProperties; import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel; import org.springframework.context.ConfigurableApplicationContext; @@ -205,8 +207,6 @@ public final class StreamBridge implements SmartInitializingSingleton { ProducerProperties producerProperties = this.bindingServiceProperties.getProducerProperties(bindingName); SubscribableChannel messageChannel = this.resolveDestination(bindingName, producerProperties, binderName); -// Function functionToInvoke = this.functionCatalog.lookup(STREAM_BRIDGE_FUNC_NAME, outputContentType.toString()); -// ((FunctionInvocationWrapper) functionToInvoke).setSkipOutputConversion(producerProperties.isUseNativeEncoding()); Function functionToInvoke = this.getStreamBridgeFunction(outputContentType.toString(), producerProperties); if (producerProperties != null && producerProperties.isPartitioned()) { @@ -273,6 +273,11 @@ public final class StreamBridge implements SmartInitializingSingleton { this.bindingService.bindProducer(messageChannel, destinationName, false, binder); this.channelCache.put(destinationName, messageChannel); + if (producerProperties.isPartitioned()) { + BindingProperties bindingProperties = this.bindingServiceProperties.getBindingProperties(destinationName); + ((AbstractMessageChannel) messageChannel) + .addInterceptor(new DefaultPartitioningInterceptor(bindingProperties, this.applicationContext.getBeanFactory())); + } this.addInterceptors((AbstractMessageChannel) messageChannel, destinationName); } From 3f84027b4bb9010516a9fc090a9f0eef292654b1 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 25 Nov 2021 09:20:47 -0500 Subject: [PATCH 02/27] Fix ApplicationJsonMessageMarshallingConverter String conversion --- ...cationJsonMessageMarshallingConverter.java | 2 +- .../binder/tck/ContentTypeTckTests.java | 8 +++-- .../ImplicitFunctionBindingTests.java | 6 ++-- .../cloud/stream/function/ScenarioTests.java | 36 +++++++++++-------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ApplicationJsonMessageMarshallingConverter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ApplicationJsonMessageMarshallingConverter.java index 18e560afd..2f0c82600 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ApplicationJsonMessageMarshallingConverter.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ApplicationJsonMessageMarshallingConverter.java @@ -105,7 +105,7 @@ class ApplicationJsonMessageMarshallingConverter extends MappingJackson2MessageC } if (result == null) { if (message.getPayload() instanceof byte[] - && targetClass.isAssignableFrom(String.class)) { + && String.class.isAssignableFrom(targetClass)) { result = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8); } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java index ede576b16..d6644ea35 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java @@ -745,7 +745,9 @@ public class ContentTypeTckTests { public Person echo(Object value) throws Exception { ObjectMapper mapper = new ObjectMapper(); // assume it is string because CT is text/plain - return mapper.readValue((String) value, Person.class); + return value instanceof byte[] + ? mapper.readValue((byte[]) value, Person.class) + : mapper.readValue((String) value, Person.class); } } @@ -760,7 +762,9 @@ public class ContentTypeTckTests { public Person echo(Message message) throws Exception { ObjectMapper mapper = new ObjectMapper(); // assume it is string because CT is text/plain - return mapper.readValue((String) message.getPayload(), Person.class); + return message.getPayload() instanceof byte[] + ? mapper.readValue((byte[]) message.getPayload(), Person.class) + : mapper.readValue((String) message.getPayload(), Person.class); } } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java index e42010cf5..635937b0e 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java @@ -185,7 +185,7 @@ public class ImplicitFunctionBindingTests { // good, we expected it } - Function function = v -> v.toUpperCase(); + Function function = v -> new String(v).toUpperCase(); FunctionBindingTestUtils.bind(context, function); input.send(new GenericMessage("hello".getBytes())); @@ -1013,7 +1013,7 @@ public class ImplicitFunctionBindingTests { Message result = outputDestination.receive(2000); assertThat(result.getPayload()).isInstanceOf(byte[].class); // check output type - assertThat(new String((byte[]) result.getPayload())).isEqualTo("String"); // check input type + assertThat(new String((byte[]) result.getPayload())).isEqualTo("byte[]"); // check input type } try (ConfigurableApplicationContext context = new SpringApplicationBuilder( @@ -1051,7 +1051,7 @@ public class ImplicitFunctionBindingTests { Message result = outputDestination.receive(2000); assertThat(result.getPayload()).isInstanceOf(byte[].class); // check output type - assertThat(new String((byte[]) result.getPayload())).isEqualTo("String"); // check input type + assertThat(new String((byte[]) result.getPayload())).isEqualTo("byte[]"); // check input type } try (ConfigurableApplicationContext context = new SpringApplicationBuilder( TestChannelBinderConfiguration.getCompleteConfiguration(SingleFunctionConfiguration2.class)) diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ScenarioTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ScenarioTests.java index 31956e13e..9f36ef470 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ScenarioTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ScenarioTests.java @@ -46,7 +46,7 @@ public class ScenarioTests { @Test public void test2106() { try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration - .getCompleteConfiguration(ConsumerConfiguration.class, ConsumerConfiguration.class)) + .getCompleteConfiguration(ConsumerConfiguration.class)) .web(WebApplicationType.NONE).run( "--spring.cloud.function.definition=consume;echo", "--spring.cloud.stream.bindings.consume-in-0.destination=input", @@ -82,17 +82,17 @@ public class ScenarioTests { } @Test - public void testComposingSupplierWuthTypelessMessageFunction() { + public void testComposingSupplierWuthTypelessMessageFunction() throws Exception { try (ConfigurableApplicationContext context = new SpringApplicationBuilder( - TestChannelBinderConfiguration.getCompleteConfiguration(TestConfiguration.class)) + TestChannelBinderConfiguration.getCompleteConfiguration(SupplierConfiguration.class)) .web(WebApplicationType.NONE) .run("--spring.jmx.enabled=false", "--spring.cloud.function.definition=messageSupplier|messageFunction")) { OutputDestination output = context.getBean(OutputDestination.class); - assertThat(output.receive(1000)).isNotNull(); - assertThat(output.receive(1100)).isNotNull(); - assertThat(output.receive(1200)).isNotNull(); + assertThat(output.receive(1000, "messageSuppliermessageFunction-out-0")).isNotNull(); + assertThat(output.receive(1200, "messageSuppliermessageFunction-out-0")).isNotNull(); + assertThat(output.receive(1300, "messageSuppliermessageFunction-out-0")).isNotNull(); } } @@ -134,23 +134,29 @@ public class ScenarioTests { @EnableAutoConfiguration @Configuration public static class TestConfiguration { + @SuppressWarnings("unchecked") + @Bean + public Function genericTypeFunction() { + return v -> { + return (O) ("hello_" + new String((byte[]) v)); + }; + } + } + + @EnableAutoConfiguration + @Configuration + public static class SupplierConfiguration { @Bean public Supplier> messageSupplier() { return () -> new GenericMessage<>("10/27/20 07:20:01"); } @Bean public Function, Message> messageFunction() { - return message -> message; - } - - @SuppressWarnings("unchecked") - @Bean - public Function genericTypeFunction() { - return v -> { - System.out.println(v); - return (O) ("hello_" + v); + return message -> { + return message; }; } + } @EnableAutoConfiguration From e7b5e59e07cccf9bd75edbea4f47edd5b392c87e Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Wed, 24 Nov 2021 13:11:56 -0500 Subject: [PATCH 03/27] GH-2245 StreamBridge partitioning fix Fix the order in applying partitioning interceptor in StreamBridge. The interceptor must be added before the call for binding the producer. Related to resolving https://github.com/spring-cloud/spring-cloud-stream/issues/2245 Specifically for this: https://github.com/spring-cloud/spring-cloud-stream/issues/2245#issuecomment-977663452 --- .../springframework/cloud/stream/function/StreamBridge.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java index 01ddedc87..bab3aa32d 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java @@ -271,14 +271,15 @@ public final class StreamBridge implements SmartInitializingSingleton { binder = binderFactory.getBinder(binderName, messageChannel.getClass()); } - this.bindingService.bindProducer(messageChannel, destinationName, false, binder); - this.channelCache.put(destinationName, messageChannel); if (producerProperties.isPartitioned()) { BindingProperties bindingProperties = this.bindingServiceProperties.getBindingProperties(destinationName); ((AbstractMessageChannel) messageChannel) .addInterceptor(new DefaultPartitioningInterceptor(bindingProperties, this.applicationContext.getBeanFactory())); } this.addInterceptors((AbstractMessageChannel) messageChannel, destinationName); + + this.bindingService.bindProducer(messageChannel, destinationName, false, binder); + this.channelCache.put(destinationName, messageChannel); } return messageChannel; From 669011db43e78f8ebe325bc85a6bbd87c9243fd9 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Mon, 29 Nov 2021 19:14:35 +0100 Subject: [PATCH 04/27] GH-2090 Fix wild card contentType processing in StreamBridge This fix is dependent on https://github.com/spring-cloud/spring-cloud-function/issues/773 Resolves #2090 --- .../cloud/stream/function/StreamBridge.java | 4 +- .../ImplicitFunctionBindingTests.java | 3 +- .../stream/function/StreamBridgeTests.java | 84 +++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java index bab3aa32d..6552e7dde 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java @@ -139,7 +139,9 @@ public final class StreamBridge implements SmartInitializingSingleton { * @return true if data was sent successfully, otherwise false or throws an exception. */ public boolean send(String bindingName, Object data) { - return this.send(bindingName, data, MimeTypeUtils.APPLICATION_JSON); + BindingProperties bindingProperties = this.bindingServiceProperties.getBindingProperties(bindingName); + MimeType contentType = StringUtils.hasText(bindingProperties.getContentType()) ? MimeType.valueOf(bindingProperties.getContentType()) : MimeTypeUtils.APPLICATION_JSON; + return this.send(bindingName, data, contentType); } /** diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java index 635937b0e..523634436 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java @@ -436,8 +436,7 @@ public class ImplicitFunctionBindingTests { TestChannelBinderConfiguration.getCompleteConfiguration(SingleConsumerConfiguration.class)) .web(WebApplicationType.NONE).run("--spring.cloud.function.definition=consumer", "--spring.jmx.enabled=false", - "--spring.cloud.stream.bindings.input.content-type=text/plain", - "--spring.cloud.stream.bindings.input.consumer.use-native-decoding=true")) { + "--spring.cloud.stream.bindings.consumer-in-0.content-type=text/plain")) { InputDestination source = context.getBean(InputDestination.class); source.send(new GenericMessage("John Doe".getBytes())); diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java index 5a9d19fd9..366e486ca 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java @@ -46,11 +46,16 @@ import org.springframework.integration.config.GlobalChannelInterceptor; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.handler.LoggingHandler; +import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.AbstractMessageConverter; +import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.MessageBuilder; +import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.ReflectionUtils; @@ -71,6 +76,28 @@ public class StreamBridgeTests { System.clearProperty("spring.cloud.function.definition"); } + @Test + public void testWithOutputContentTypeWildCardBindings() throws Exception { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration + .getCompleteConfiguration(ConsumerConfiguration.class, EmptyConfigurationWithCustomConverters.class)) + .web(WebApplicationType.NONE).run( + "--spring.cloud.stream.bindings.foo.content-type=application/*+foo ", + "--spring.cloud.stream.bindings.bar.content-type=application/*+non-registered-foo", + "--spring.jmx.enabled=false")) { + StreamBridge bridge = context.getBean(StreamBridge.class); + bridge.send("foo", "hello foo"); + bridge.send("bar", "hello bar"); + + OutputDestination output = context.getBean(OutputDestination.class); + + assertThat(output.receive(1000, "foo").getHeaders().get(MessageHeaders.CONTENT_TYPE)) + .isEqualTo(MimeType.valueOf("application/json+foo")); + assertThat(output.receive(1000, "bar").getHeaders().get(MessageHeaders.CONTENT_TYPE)) + .isEqualTo(MimeType.valueOf("application/blahblah+non-registered-foo")); + + } + } + @SuppressWarnings("unchecked") @Test public void testNoCachingOfStreamBridgeFunction() throws Exception { @@ -381,6 +408,63 @@ public class StreamBridgeTests { } + @EnableAutoConfiguration + public static class EmptyConfigurationWithCustomConverters { + + @Bean + public MessageConverter fooConverter() { + return new AbstractMessageConverter(MimeType.valueOf("application/json+foo"), MimeType.valueOf("application/json+blah")) { + @Override + protected boolean supports(Class clazz) { + return true; + } + + @Override + @Nullable + protected Object convertFromInternal(Message message, Class targetClass, @Nullable Object conversionHint) { + return message.getPayload(); + } + + @Override + @Nullable + protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { + if (headers.containsKey(MessageHeaders.CONTENT_TYPE) && + (headers.get(MessageHeaders.CONTENT_TYPE).toString().endsWith("+foo") || + headers.get(MessageHeaders.CONTENT_TYPE).toString().endsWith("+blah"))) { + return payload; + } + return null; + } + }; + } + + @Bean + public MessageConverter barConverter() { + return new AbstractMessageConverter(MimeType.valueOf("application/blahblah+non-registered-foo")) { + @Override + protected boolean supports(Class clazz) { + return true; + } + + @Override + @Nullable + protected Object convertFromInternal(Message message, Class targetClass, @Nullable Object conversionHint) { + return message.getPayload(); + } + + @Override + @Nullable + protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { + if (headers.containsKey(MessageHeaders.CONTENT_TYPE) && + (headers.get(MessageHeaders.CONTENT_TYPE).toString().endsWith("+non-registered-foo"))) { + return payload; + } + return null; + } + }; + } + } + @EnableAutoConfiguration public static class ConsumerConfiguration { @Bean From adc7eede3dce221956986e6b77e6d4c4d6b52483 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 30 Nov 2021 14:04:10 +0100 Subject: [PATCH 05/27] GH-2213 Add docs to clarify SpEL usage Resolves #2213 --- docs/src/main/asciidoc/preface.adoc | 31 ++++++++++--------- .../main/asciidoc/spring-cloud-stream.adoc | 8 ++--- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/src/main/asciidoc/preface.adoc b/docs/src/main/asciidoc/preface.adoc index e14de847d..259a18adb 100644 --- a/docs/src/main/asciidoc/preface.adoc +++ b/docs/src/main/asciidoc/preface.adoc @@ -154,23 +154,9 @@ You can also build and package your application into a boot jar (by using `./mvn Now you have a working (albeit very basic) Spring Cloud Stream application. -== What's New in 3.x? - - -[[spring-cloud-stream-preface-new-features]] -=== New Features and Enhancements - -- *Routing Function* - see <> for more details. -- *StreamBridge* - for dynamic destinations. See <> for more details. -- *Multiple bindings with functions* (multiple message handlers) - see <> for more details. -- *Functions with multiple inputs/outputs* (single function that can subscribe or target multiple destinations) - see <> for more details. -- *Native support for reactive programming* - since v3.0.0 we no longer distribute spring-cloud-stream-reactive modules and instead -relying on native reactive support provided by spring cloud function. For backward -compatibility you can still bring `spring-cloud-stream-reactive` from previous versions. - [[spring-cloud-stream-preface-notable-deprecations]] -=== Notable Deprecations +== Notable Deprecations - Annotation-based programming model. Basically the @EnableBInding, @StreamListener and all related annotations are now deprecated in favor of the functional programming model. See <> for more details. @@ -183,3 +169,18 @@ compatibility you can still bring `spring-cloud-stream-reactive` from previous v - The `BinderAwareChannelResolver` is deprecated in favor if providing `spring.cloud.stream.sendto.destination` property. This is primarily for function-based programming model. For StreamListener it would still be required and thus will stay until we deprecate and eventually discontinue StreamListener and annotation-based programming model. + +[[spel-and-streaming-data]] + +== Spring Expression Language (SpEL) in the context of Streaming data + +Throwout this reference manual you will encounter many features and examples where you can utilize Spring Expression Language (SpEL). It is important to understand certain limitations when it comes to using it. + +SpEL gives you access to the current Message as well as the Application Context you are running in. +However it is important to understand what type of data SpEL can see especially in the context of the incoming Message. +From the broker, the message arrives in a form of a byte[]. It is then transformed to a `Message` by the binders where as you can see the payload of the message maintains its raw form. The headers of the message are ``, where values are typically another primitive or a collection/array of primitives, hence Object. +That is because binder does not know the required input type as it has no access to the user code (function). So effectively binder delivered an envelope with the payload and some readable meta-data in the form of message headers, just like the letter delivered by mail. +This means that while accessing payload of the message is possible you will only have access to it as raw data (i.e., byte[]). And while it may be very common for developers to ask for ability to have SpEL access to fields of a payload object as concrete type (e.g., Foo, Bar etc), you can see how difficult or even impossible would it be to achieve. +Here is one example to demonstrate the problem; Imagine you have a routing expression to route to different functions based on payload type. This requirement would imply payload conversion from byte[] to a specific type and then applying the SpEL. However, in order to perform such conversion we would need to know the actual type to pass to converter and that comes from functions signature which we don’t know which one. A better approach to solve this requirement would be to pass the type information as message headers (e.g., `application/json;type=foo.bar.Baz` ). You’ll get a clear readable String value that could be accessed and evaluated in a year and easy to read SpEL expression. + +Additionally it is considered very bad practice to use payload for routing decisions, since the payload is considered to be privileged data - data only to be read by its final recipient. Again, using the mail delivery analogy you would not want the mailman to open your envelope and read the contents of the letter to make some delivery decisions. The same concept applies here, especially when it is relatively easy to include such information when generating a Message. It enforces certain level of discipline related to the design of data to be transmitted over the network and which pieces of such data can be considered as public and which are privileged. diff --git a/docs/src/main/asciidoc/spring-cloud-stream.adoc b/docs/src/main/asciidoc/spring-cloud-stream.adoc index f685d6cf5..42dd8c1de 100644 --- a/docs/src/main/asciidoc/spring-cloud-stream.adoc +++ b/docs/src/main/asciidoc/spring-cloud-stream.adoc @@ -2398,9 +2398,9 @@ public MessageSourceCustomizer sourceCustomizer() { These properties are exposed via `org.springframework.cloud.stream.binder.ProducerProperties` The following binding properties are available for output bindings only and must be prefixed with `spring.cloud.stream.bindings..producer.` -(for example, `spring.cloud.stream.bindings.func-out-0.producer.partitionKeyExpression=payload.id`). +(for example, `spring.cloud.stream.bindings.func-out-0.producer.partitionKeyExpression=headers.id`). -Default values can be set by using the prefix `spring.cloud.stream.default.producer` (for example, `spring.cloud.stream.default.producer.partitionKeyExpression=payload.id`). +Default values can be set by using the prefix `spring.cloud.stream.default.producer` (for example, `spring.cloud.stream.default.producer.partitionKeyExpression=headers.id`). autoStartup:: Signals if this consumer needs to be started automatically @@ -2719,14 +2719,14 @@ You can configure an output binding to send partitioned data by setting one and For example, the following is a valid and typical configuration: ---- -spring.cloud.stream.bindings.func-out-0.producer.partitionKeyExpression=payload.id +spring.cloud.stream.bindings.func-out-0.producer.partitionKeyExpression=headers.id spring.cloud.stream.bindings.func-out-0.producer.partitionCount=5 ---- Based on that example configuration, data is sent to the target partition by using the following logic. A partition key's value is calculated for each message sent to a partitioned output binding based on the `partitionKeyExpression`. -The `partitionKeyExpression` is a SpEL expression that is evaluated against the outbound message for extracting the partitioning key. +The `partitionKeyExpression` is a SpEL expression that is evaluated against the outbound message (in the preceding example it's the value of the `id` from message headers) for extracting the partitioning key. If a SpEL expression is not sufficient for your needs, you can instead calculate the partition key value by providing an implementation of `org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy` and configuring it as a bean (by using the `@Bean` annotation). If you have more then one bean of type `org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy` available in the Application Context, you can further filter it by specifying its name with the `partitionKeyExtractorName` property, as shown in the following example: From b97badcf2df211be3fe4efb38682718478aa3372 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 1 Dec 2021 11:03:50 +0100 Subject: [PATCH 06/27] Fix spelling errors in doc --- docs/src/main/asciidoc/preface.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/preface.adoc b/docs/src/main/asciidoc/preface.adoc index 259a18adb..5b1c690fc 100644 --- a/docs/src/main/asciidoc/preface.adoc +++ b/docs/src/main/asciidoc/preface.adoc @@ -174,13 +174,13 @@ and annotation-based programming model. == Spring Expression Language (SpEL) in the context of Streaming data -Throwout this reference manual you will encounter many features and examples where you can utilize Spring Expression Language (SpEL). It is important to understand certain limitations when it comes to using it. +Throughout this reference manual you will encounter many features and examples where you can utilize Spring Expression Language (SpEL). It is important to understand certain limitations when it comes to using it. SpEL gives you access to the current Message as well as the Application Context you are running in. However it is important to understand what type of data SpEL can see especially in the context of the incoming Message. From the broker, the message arrives in a form of a byte[]. It is then transformed to a `Message` by the binders where as you can see the payload of the message maintains its raw form. The headers of the message are ``, where values are typically another primitive or a collection/array of primitives, hence Object. That is because binder does not know the required input type as it has no access to the user code (function). So effectively binder delivered an envelope with the payload and some readable meta-data in the form of message headers, just like the letter delivered by mail. This means that while accessing payload of the message is possible you will only have access to it as raw data (i.e., byte[]). And while it may be very common for developers to ask for ability to have SpEL access to fields of a payload object as concrete type (e.g., Foo, Bar etc), you can see how difficult or even impossible would it be to achieve. -Here is one example to demonstrate the problem; Imagine you have a routing expression to route to different functions based on payload type. This requirement would imply payload conversion from byte[] to a specific type and then applying the SpEL. However, in order to perform such conversion we would need to know the actual type to pass to converter and that comes from functions signature which we don’t know which one. A better approach to solve this requirement would be to pass the type information as message headers (e.g., `application/json;type=foo.bar.Baz` ). You’ll get a clear readable String value that could be accessed and evaluated in a year and easy to read SpEL expression. +Here is one example to demonstrate the problem; Imagine you have a routing expression to route to different functions based on payload type. This requirement would imply payload conversion from byte[] to a specific type and then applying the SpEL. However, in order to perform such conversion we would need to know the actual type to pass to converter and that comes from function's signature which we don’t know which one. A better approach to solve this requirement would be to pass the type information as message headers (e.g., `application/json;type=foo.bar.Baz` ). You’ll get a clear readable String value that could be accessed and evaluated in a year and easy to read SpEL expression. Additionally it is considered very bad practice to use payload for routing decisions, since the payload is considered to be privileged data - data only to be read by its final recipient. Again, using the mail delivery analogy you would not want the mailman to open your envelope and read the contents of the letter to make some delivery decisions. The same concept applies here, especially when it is relatively easy to include such information when generating a Message. It enforces certain level of discipline related to the design of data to be transmitted over the network and which pieces of such data can be considered as public and which are privileged. From bf8c97ada18a743489e761d7efb53c1cc0e1d922 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 7 Dec 2021 12:28:17 +0100 Subject: [PATCH 07/27] GH-2254 Make healthcheck classes public Resolves #2254 --- .../config/BindersHealthIndicatorAutoConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindersHealthIndicatorAutoConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindersHealthIndicatorAutoConfiguration.java index b757c6f4d..510cb4c3c 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindersHealthIndicatorAutoConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindersHealthIndicatorAutoConfiguration.java @@ -65,7 +65,7 @@ public class BindersHealthIndicatorAutoConfiguration { * A {@link DefaultBinderFactory.Listener} that provides {@link HealthIndicator} * support. */ - private static class BindersHealthIndicatorListener + public static class BindersHealthIndicatorListener implements DefaultBinderFactory.Listener { private final BindersHealthContributor bindersHealthContributor; @@ -88,7 +88,7 @@ public class BindersHealthIndicatorAutoConfiguration { /** * {@link CompositeHealthContributor} that provides binder health contributions. */ - private static class BindersHealthContributor implements CompositeHealthContributor { + public static class BindersHealthContributor implements CompositeHealthContributor { private static final HealthIndicator UNKNOWN = () -> Health.unknown().build(); From 06f798eb6b3058ad7ee0c752960f7ee55c679d49 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 8 Dec 2021 09:31:24 +0100 Subject: [PATCH 08/27] GH-2253 Provide isolated ObjectMapper to BindingsLifecycleController This is specifically to avoid any potetial interference from the user Resolves #2253 --- .../binding/BindingsLifecycleController.java | 8 +- .../config/BindingServiceConfiguration.java | 5 +- .../stream/endpoint/ActuatorBindingsTest.java | 74 +++++++++++++++++++ 3 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/endpoint/ActuatorBindingsTest.java diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java index d78738908..5f01ee48f 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BindingsLifecycleController.java @@ -19,6 +19,7 @@ package org.springframework.cloud.stream.binding; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; @@ -44,12 +45,13 @@ public class BindingsLifecycleController { private final ObjectMapper objectMapper; public BindingsLifecycleController(List inputBindingLifecycles, - List outputBindingsLifecycles, ObjectMapper objectMapper) { + List outputBindingsLifecycles) { Assert.notEmpty(inputBindingLifecycles, "'inputBindingLifecycles' must not be null or empty"); this.inputBindingLifecycles = inputBindingLifecycles; this.outputBindingsLifecycles = outputBindingsLifecycles; - this.objectMapper = objectMapper; + this.objectMapper = new ObjectMapper(); //see https://github.com/spring-cloud/spring-cloud-stream/issues/2253 + // we need to use ObjectMapper that could not be modified by the user. } /** @@ -118,7 +120,7 @@ public class BindingsLifecycleController { * @return the list of {@link Binding}s */ @SuppressWarnings("unchecked") - public List> queryStates() { + public List> queryStates() { List> bindings = new ArrayList<>(gatherInputBindings()); bindings.addAll(gatherOutputBindings()); return this.objectMapper.convertValue(bindings, List.class); diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java index 5e8c52818..d5d26d07d 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java @@ -243,9 +243,8 @@ public class BindingServiceConfiguration { @Bean public BindingsLifecycleController bindingsLifecycleController(List inputBindingLifecycles, - List outputBindingsLifecycles, @Nullable ObjectMapper objectMapper) { - objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper; - return new BindingsLifecycleController(inputBindingLifecycles, outputBindingsLifecycles, objectMapper); + List outputBindingsLifecycles) { + return new BindingsLifecycleController(inputBindingLifecycles, outputBindingsLifecycles); } @Bean diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/endpoint/ActuatorBindingsTest.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/endpoint/ActuatorBindingsTest.java new file mode 100644 index 000000000..5164bfd23 --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/endpoint/ActuatorBindingsTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2018-2021 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.endpoint; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.cloud.stream.binding.BindingsLifecycleController; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class ActuatorBindingsTest { + + /* + * Even though this test performs some simple assertions, the main purpose for it is to validate that + * it does not result in recursive exception described in https://github.com/spring-cloud/spring-cloud-stream/issues/2253 + */ + @Test + public void test_2253() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(Bindings.class)) + .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false", + "--spring.cloud.function.definition=consume", + "--spring.jackson.visibility.field=ANY" // see https://github.com/spring-cloud/spring-cloud-stream/issues/2253 + // we need the above just to verify that such action does not + // interfere with instance of ObjectMapper inside of BindingsLifecycleController + )) { + + BindingsLifecycleController controller = context + .getBean(BindingsLifecycleController.class); + List> bindings = controller.queryStates(); + assertThat(bindings.size()).isEqualTo(1); + assertThat(bindings.get(0).get("bindingName")).isEqualTo("consume-in-0"); + } + + } + + @EnableAutoConfiguration + public static class Bindings { + + @Bean + public Consumer consume() { + return message -> System.out.println("Received message " + message); + } + + } + +} From f772e1ce6b6509f39b5c2be0adc495c3c9376b4b Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 8 Dec 2021 11:28:49 +0100 Subject: [PATCH 09/27] GH-2249 Fix partition handling in StreamBridge Resolves #2249 --- .../PartitionAwareFunctionWrapper.java | 6 +++- .../stream/function/StreamBridgeTests.java | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/PartitionAwareFunctionWrapper.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/PartitionAwareFunctionWrapper.java index 01cdbff6a..ef3cc2fde 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/PartitionAwareFunctionWrapper.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/PartitionAwareFunctionWrapper.java @@ -81,7 +81,11 @@ class PartitionAwareFunctionWrapper implements Function, Supplie @Override public Object apply(Object input) { this.setEnhancerIfNecessary(); - return this.function.apply(input); + Object result = this.function.apply(input); + if (!((FunctionInvocationWrapper) this.function).isInputTypePublisher()) { + ((FunctionInvocationWrapper) this.function).setEnhancer(null); + } + return result; } @Override diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java index 366e486ca..78a3d77bc 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java @@ -76,6 +76,35 @@ public class StreamBridgeTests { System.clearProperty("spring.cloud.function.definition"); } + /* + * This test must not result in exception stating "Partition key cannot be null" + * See https://github.com/spring-cloud/spring-cloud-stream/issues/2249 for more details + */ + @Test + public void test_2249() throws Exception { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration( + EmptyConfiguration.class)).web(WebApplicationType.NONE).run( + "--spring.cloud.stream.source=outputA;outputB", + "--spring.cloud.stream.bindings.outputA-out-0.producer.partition-count=3", + "--spring.cloud.stream.bindings.outputA-out-0.producer.partition-key-expression=headers['partitionKey']", + "--spring.cloud.stream.bindings.outputB-out-0.destination=outputB", + "--spring.cloud.stream.bindings.outputB-out-0.producer.partition-count=3", + "--spring.jmx.enabled=false")) { + StreamBridge streamBridge = context.getBean(StreamBridge.class); + streamBridge.send("outputA-out-0", MessageBuilder.withPayload("A").setHeader("partitionKey", "A").build()); + streamBridge.send("outputB", MessageBuilder.withPayload("B").build()); + streamBridge.send("outputA-out-0", MessageBuilder.withPayload("C").setHeader("partitionKey", "C").build()); + streamBridge.send("outputB", MessageBuilder.withPayload("D").build()); + + OutputDestination output = context.getBean(OutputDestination.class); + assertThat(output.receive(1000, "outputA-out-0").getHeaders().containsKey("scst_partition")).isTrue(); + assertThat(output.receive(1000, "outputB").getHeaders().containsKey("scst_partition")).isFalse(); + assertThat(output.receive(1000, "outputA-out-0").getHeaders().containsKey("scst_partition")).isTrue(); + assertThat(output.receive(1000, "outputB").getHeaders().containsKey("scst_partition")).isFalse(); + } + } + @Test public void testWithOutputContentTypeWildCardBindings() throws Exception { try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration From ea28ac79d4e059c459b535fe90c78a5eac30006d Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Mon, 3 Jan 2022 17:05:16 -0500 Subject: [PATCH 10/27] Version upgrades Code changes (jakarta related) Ignoring a few tests temporarily --- pom.xml | 6 +++--- spring-cloud-stream-binder-test/pom.xml | 2 +- .../cloud/stream/binder/AbstractBinderTests.java | 4 ++-- spring-cloud-stream-integration-tests/pom.xml | 2 +- .../config/StreamListenerAnnotatedMethodArgumentsTests.java | 5 +++-- .../cloud/stream/config/StreamListenerTestUtils.java | 3 --- spring-cloud-stream-test-support-internal/pom.xml | 2 +- spring-cloud-stream-test-support/pom.xml | 2 +- spring-cloud-stream/pom.xml | 2 +- .../cloud/stream/binder/ConsumerProperties.java | 3 +-- .../cloud/stream/binder/ProducerProperties.java | 3 +-- .../cloud/stream/config/BindingProperties.java | 3 +-- .../cloud/stream/config/BindingHandlerAdviseTests.java | 5 ++--- .../stream/function/SourceToFunctionsSupportTests.java | 2 ++ 14 files changed, 20 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 912591e99..9d57a5bd4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,12 +4,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 spring-cloud-stream-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT pom org.springframework.cloud spring-cloud-build - 3.1.0-SNAPSHOT + 4.0.0-SNAPSHOT @@ -22,7 +22,7 @@ HEAD - 1.8 + 17 2020.0.7 2.1 3.2.0-SNAPSHOT diff --git a/spring-cloud-stream-binder-test/pom.xml b/spring-cloud-stream-binder-test/pom.xml index 84104c47f..0dfceaba0 100644 --- a/spring-cloud-stream-binder-test/pom.xml +++ b/spring-cloud-stream-binder-test/pom.xml @@ -13,7 +13,7 @@ org.springframework.cloud spring-cloud-stream-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java b/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java index c95818214..c3933d00a 100644 --- a/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java +++ b/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java @@ -51,7 +51,7 @@ import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.converter.SmartMessageConverter; -import org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver; +import org.springframework.messaging.handler.annotation.support.PayloadMethodArgumentResolver; import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite; import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.util.Assert; @@ -632,7 +632,7 @@ public abstract class AbstractBinderTests c = ReflectionUtils.accessibleConstructor( diff --git a/spring-cloud-stream-integration-tests/pom.xml b/spring-cloud-stream-integration-tests/pom.xml index 842793cf4..daa92c463 100644 --- a/spring-cloud-stream-integration-tests/pom.xml +++ b/spring-cloud-stream-integration-tests/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-stream-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java index 0e9fe8156..ad3ed5df4 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java @@ -22,9 +22,9 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; -import javax.validation.Valid; - +import jakarta.validation.Valid; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.springframework.boot.SpringApplication; @@ -121,6 +121,7 @@ public class StreamListenerAnnotatedMethodArgumentsTests { } @Test + @Ignore public void testValidAnnotationAtMethodParameterWithPojoThatFailsValidation() { ConfigurableApplicationContext context = SpringApplication.run( TestPojoWithValidAnnotationThatPassesValidation.class, "--server.port=0"); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java index 8ab4b6b74..88dc892a6 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java @@ -16,8 +16,6 @@ package org.springframework.cloud.stream.config; -import javax.validation.constraints.NotBlank; - import org.springframework.cloud.stream.annotation.Input; import org.springframework.cloud.stream.annotation.Output; import org.springframework.messaging.MessageChannel; @@ -92,7 +90,6 @@ public class StreamListenerTestUtils { public static class PojoWithValidation { - @NotBlank private String foo; public String getFoo() { diff --git a/spring-cloud-stream-test-support-internal/pom.xml b/spring-cloud-stream-test-support-internal/pom.xml index aeb3ceefa..8c81fcd10 100644 --- a/spring-cloud-stream-test-support-internal/pom.xml +++ b/spring-cloud-stream-test-support-internal/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-stream-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-stream-test-support-internal Set of classes and utility code that may assist in testing both diff --git a/spring-cloud-stream-test-support/pom.xml b/spring-cloud-stream-test-support/pom.xml index 979d4e103..939bb0e73 100644 --- a/spring-cloud-stream-test-support/pom.xml +++ b/spring-cloud-stream-test-support/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-stream-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT spring-cloud-stream-test-support A set of classes to ease testing of Spring Cloud Stream modules. diff --git a/spring-cloud-stream/pom.xml b/spring-cloud-stream/pom.xml index f8f6bb803..84a768e64 100644 --- a/spring-cloud-stream/pom.xml +++ b/spring-cloud-stream/pom.xml @@ -12,7 +12,7 @@ org.springframework.cloud spring-cloud-stream-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java index 241154497..4da08791f 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ConsumerProperties.java @@ -20,9 +20,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import javax.validation.constraints.Min; - import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.constraints.Min; import org.springframework.messaging.Message; diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java index ec78451c3..a8a726353 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/ProducerProperties.java @@ -18,14 +18,13 @@ package org.springframework.cloud.stream.binder; import java.io.IOException; -import javax.validation.constraints.Min; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import jakarta.validation.constraints.Min; import org.springframework.expression.Expression; diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java index b87293715..1db610e91 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingProperties.java @@ -16,10 +16,9 @@ package org.springframework.cloud.stream.config; -import javax.validation.constraints.AssertTrue; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import jakarta.validation.constraints.AssertTrue; import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.ProducerProperties; diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/BindingHandlerAdviseTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/BindingHandlerAdviseTests.java index 1a8f08f36..8905b67cd 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/BindingHandlerAdviseTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/config/BindingHandlerAdviseTests.java @@ -16,9 +16,8 @@ package org.springframework.cloud.stream.config; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; - +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; import org.junit.Test; import org.springframework.beans.factory.BeanCreationException; diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java index 376049e08..07238ccfb 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Supplier; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.jupiter.api.AfterEach; @@ -113,6 +114,7 @@ public class SourceToFunctionsSupportTests { } @Test + @Ignore public void testImperativeSupplier() throws Exception { try (ConfigurableApplicationContext context = new SpringApplicationBuilder( TestChannelBinderConfiguration.getCompleteConfiguration( From 6bc8cb20b51dce4e0b925789c30e30b0e36433c5 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Tue, 4 Jan 2022 14:22:05 -0500 Subject: [PATCH 11/27] Update version on the docs project --- docs/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pom.xml b/docs/pom.xml index b4e38834f..a6fe56999 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-stream-parent - 3.2.0-SNAPSHOT + 4.0.0-SNAPSHOT jar spring-cloud-stream-docs From dce0313dca19d2392e4163dc96586440637fe86c Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Tue, 4 Jan 2022 14:25:36 -0500 Subject: [PATCH 12/27] Ignore test temporarily --- .../cloud/stream/function/SourceToFunctionsSupportTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java index 07238ccfb..cbea6a74c 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java @@ -238,7 +238,7 @@ public class SourceToFunctionsSupportTests { } @Test - @Disabled + @Ignore public void testFiniteFluxSupplierMessage() { try (ConfigurableApplicationContext context = new SpringApplicationBuilder( TestChannelBinderConfiguration.getCompleteConfiguration(FunctionsConfiguration.class, From cd941307855dfb3ec18ac690d7671d116b7ad5fe Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Tue, 4 Jan 2022 14:28:15 -0500 Subject: [PATCH 13/27] Ignore test temporarily --- .../cloud/stream/function/SourceToFunctionsSupportTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java index cbea6a74c..c87c5de60 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java @@ -71,6 +71,7 @@ public class SourceToFunctionsSupportTests { } @Test + @Ignore public void testFunctionIsAppliedToExistingMessageSource() { try (ConfigurableApplicationContext context = new SpringApplicationBuilder( TestChannelBinderConfiguration.getCompleteConfiguration( From 70b79ede29b6acd1e91e4cdcb17b562e57e1dee4 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Tue, 4 Jan 2022 14:38:28 -0500 Subject: [PATCH 14/27] Default maven antrun version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d57a5bd4..4e7f494b2 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ org.apache.maven.plugins maven-antrun-plugin - 1.7 + org.apache.maven.plugins From 664ee9291a040062e428238adb9a9329de520dbd Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Wed, 5 Jan 2022 16:40:40 -0500 Subject: [PATCH 15/27] Update spring-cloud-function to 3.2.2-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4e7f494b2..7aeabee1f 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 17 2020.0.7 2.1 - 3.2.0-SNAPSHOT + 3.2.2-SNAPSHOT true true true From be5b2df1f8c3c204b5296b3165f9ab5373224499 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Fri, 7 Jan 2022 15:31:32 -0500 Subject: [PATCH 16/27] Remove BinderAwareChannelResolver We deprecated BinderAwareChannelResolver in 3.0.0 in preference to spring.cloud.stream.sendto.destination and then later on StreamBridge. Remove BinderAwareChannelResolver and it's related components completely in 4.0.x. --- README.adoc | 1 - docs/src/main/asciidoc/preface.adoc | 1 - .../main/asciidoc/spring-cloud-stream.adoc | 38 ---- ...notationBeanPostProcessorOverrideTest.java | 2 + .../StreamListenerAsMetaAnnotationTests.java | 3 + .../StreamListenerWithConditionsTest.java | 2 + .../binding/BinderAwareChannelResolver.java | 157 -------------- .../stream/binding/BinderAwareRouter.java | 2 +- .../NewDestinationBindingCallback.java | 45 ++++ ...amListenerAnnotationBeanPostProcessor.java | 9 +- .../config/BindingServiceConfiguration.java | 14 -- .../function/FunctionConfiguration.java | 2 +- .../cloud/stream/function/StreamBridge.java | 2 +- .../BinderAwareChannelResolverTests.java | 205 ------------------ ...ertiesBinderAwareChannelResolverTests.java | 95 -------- .../stream/binding/BindingServiceTests.java | 190 ++++++++-------- .../DynamicDestinationFunctionTests.java | 24 +- .../stream/function/StreamBridgeTests.java | 2 +- 18 files changed, 160 insertions(+), 634 deletions(-) delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/NewDestinationBindingCallback.java delete mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java delete mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java diff --git a/README.adoc b/README.adoc index 1d4453d54..84d0b6668 100644 --- a/README.adoc +++ b/README.adoc @@ -201,7 +201,6 @@ compatibility you can still bring `spring-cloud-stream-reactive` from previous v - _Test support binder_ `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <> for more details. - _@StreamMessageConverter_ - deprecated as it is no longer required. - The `original-content-type` header references have been removed after it's been deprecated in v2.0. -- The `BinderAwareChannelResolver` is deprecated in favor if providing `spring.cloud.stream.sendto.destination` property. This is primarily for function-based programming model. For StreamListener it would still be required and thus will stay until we deprecate and eventually discontinue StreamListener and annotation-based programming model. diff --git a/docs/src/main/asciidoc/preface.adoc b/docs/src/main/asciidoc/preface.adoc index 5b1c690fc..eb24604dc 100644 --- a/docs/src/main/asciidoc/preface.adoc +++ b/docs/src/main/asciidoc/preface.adoc @@ -166,7 +166,6 @@ compatibility you can still bring `spring-cloud-stream-reactive` from previous v - _Test support binder_ `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <> for more details. - _@StreamMessageConverter_ - deprecated as it is no longer required. - The `original-content-type` header references have been removed after it's been deprecated in v2.0. -- The `BinderAwareChannelResolver` is deprecated in favor if providing `spring.cloud.stream.sendto.destination` property. This is primarily for function-based programming model. For StreamListener it would still be required and thus will stay until we deprecate and eventually discontinue StreamListener and annotation-based programming model. diff --git a/docs/src/main/asciidoc/spring-cloud-stream.adoc b/docs/src/main/asciidoc/spring-cloud-stream.adoc index 42dd8c1de..678563d14 100644 --- a/docs/src/main/asciidoc/spring-cloud-stream.adoc +++ b/docs/src/main/asciidoc/spring-cloud-stream.adoc @@ -1359,44 +1359,6 @@ Aside from static destinations, Spring Cloud Stream lets applications send messa This is useful, for example, when the target destination needs to be determined at runtime. Applications can do so in one of two ways. -===== BinderAwareChannelResolver - -The `BinderAwareChannelResolver` is a special bean registered automatically by the framework. -You can autowire this bean into your application and use it to resolve output destination at runtime - -The 'spring.cloud.stream.dynamicDestinations' property can be used for restricting the dynamic destination names to a known set (that is, intentionally allowed values). -If this property is not set, any destination can be bound dynamically. - -The following example demonstrates one of the common scenarios where REST controller uses a path variable to determine target destination: - -[source,java] ----- -@SpringBootApplication -@Controller -public class SourceWithDynamicDestination { - - @Autowired - private BinderAwareChannelResolver resolver; - - @RequestMapping(value="/{target}") - @ResponseStatus(HttpStatus.ACCEPTED) - public void send(@RequestBody String body, @PathVariable("target") String target){ - resolver.resolveDestination(target).send(new GenericMessage(body)); - } -} ----- - -Now consider what happens when we start the application on the default port (8080) and make the following requests with CURL: - ----- -curl -H "Content-Type: application/json" -X POST -d "customer-1" http://localhost:8080/customers - -curl -H "Content-Type: application/json" -X POST -d "order-1" http://localhost:8080/orders ----- - -The destinations, 'customers' and 'orders', are created in the broker (in the exchange for Rabbit or in the topic for Kafka) -with names of 'customers' and 'orders', and the data is published to the appropriate destinations. - ===== spring.cloud.stream.sendto.destination You can also delegate to the framework to dynamically resolve the output destination by specifying `spring.cloud.stream.sendto.destination` header diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java index f5ba9e217..ba0e89ac5 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java @@ -23,6 +23,7 @@ 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; @@ -48,6 +49,7 @@ public class StreamListenerAnnotationBeanPostProcessorOverrideTest { @Test @SuppressWarnings("unchecked") + @Ignore public void testOverrideStreamListenerAnnotationBeanPostProcessor() throws Exception { ConfigurableApplicationContext context = SpringApplication .run(TestPojoWithAnnotatedArguments.class, "--server.port=0"); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java index 64855b066..a1edb0922 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java @@ -25,6 +25,7 @@ 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; @@ -76,6 +77,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class StreamListenerAsMetaAnnotationTests { @Test + @Ignore public void testCustomAnnotation() { ConfigurableApplicationContext context = SpringApplication .run(TestPojoWithCustomAnnotatedArguments.class, "--server.port=0"); @@ -94,6 +96,7 @@ public class StreamListenerAsMetaAnnotationTests { } @Test + @Ignore public void testAnnotation() { ConfigurableApplicationContext context = SpringApplication .run(TestPojoWithAnnotatedArguments.class, "--server.port=0"); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java index 3a39237c3..eb3cd7f75 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java @@ -20,6 +20,7 @@ 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; @@ -43,6 +44,7 @@ import static org.assertj.core.api.Assertions.fail; public class StreamListenerWithConditionsTest { @Test + @Ignore public void testAnnotatedArgumentsWithConditionalClass() throws Exception { ConfigurableApplicationContext context = SpringApplication .run(TestPojoWithAnnotatedArguments.class, "--server.port=0"); diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java deleted file mode 100644 index 69f3f17a7..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareChannelResolver.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2013-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.binding; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.cloud.stream.binder.Binding; -import org.springframework.cloud.stream.binder.ProducerProperties; -import org.springframework.cloud.stream.config.BindingServiceProperties; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.core.BeanFactoryMessageChannelDestinationResolver; -import org.springframework.messaging.core.DestinationResolutionException; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * A {@link org.springframework.messaging.core.DestinationResolver} implementation that - * resolves the channel from the bean factory and, if not present, creates a new channel - * and adds it to the factory after binding it to the binder. - * - * @author Mark Fisher - * @author Gary Russell - * @author Ilayaperumal Gopinathan - * @author Oleg Zhurakousky - * - * @deprecated As of 3.0.0 in favor if providing `spring.cloud.stream.sendto.destination` property. - * This is primarily for function-based programming model. For StreamListener it would still be - * required and thus will stay until we deprecate and eventually discontinue StreamListener - * and annotation-based programming model. - */ -@Deprecated -public class BinderAwareChannelResolver - extends BeanFactoryMessageChannelDestinationResolver { - - private final BindingService bindingService; - - private final AbstractBindingTargetFactory bindingTargetFactory; - - private final DynamicDestinationsBindable dynamicDestinationsBindable; - - @SuppressWarnings("rawtypes") - private final NewDestinationBindingCallback newBindingCallback; - - private ConfigurableListableBeanFactory beanFactory; - - public BinderAwareChannelResolver(BindingService bindingService, - AbstractBindingTargetFactory bindingTargetFactory, - DynamicDestinationsBindable dynamicDestinationsBindable) { - this(bindingService, bindingTargetFactory, dynamicDestinationsBindable, null); - } - - @SuppressWarnings("rawtypes") - public BinderAwareChannelResolver(BindingService bindingService, - AbstractBindingTargetFactory bindingTargetFactory, - DynamicDestinationsBindable dynamicDestinationsBindable, - NewDestinationBindingCallback callback) { - this.dynamicDestinationsBindable = dynamicDestinationsBindable; - Assert.notNull(bindingService, "'bindingService' cannot be null"); - Assert.notNull(bindingTargetFactory, "'bindingTargetFactory' cannot be null"); - this.bindingService = bindingService; - this.bindingTargetFactory = bindingTargetFactory; - this.newBindingCallback = callback; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - super.setBeanFactory(beanFactory); - Assert.isTrue(beanFactory instanceof ConfigurableListableBeanFactory, - "'beanFactory' must be an instance of ConfigurableListableBeanFactory"); - this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; - } - - /* - * See the following for more discussion on it as well as demo reproducing it, thanks - * to Anshul Mehra (@Walliee) - * https://github.com/spring-cloud/spring-cloud-stream/issues/1603 - */ - @SuppressWarnings("unchecked") - @Override - public synchronized MessageChannel resolveDestination(String channelName) { - BindingServiceProperties bindingServiceProperties = this.bindingService - .getBindingServiceProperties(); - String[] dynamicDestinations = bindingServiceProperties.getDynamicDestinations(); - - MessageChannel channel; - boolean dynamicAllowed = ObjectUtils.isEmpty(dynamicDestinations) - || ObjectUtils.containsElement(dynamicDestinations, channelName); - try { - channel = super.resolveDestination(channelName); - } - catch (DestinationResolutionException e) { - if (!dynamicAllowed) { - throw e; - } - else { - channel = this.bindingTargetFactory.createOutput(channelName); - ProducerProperties producerProperties = bindingServiceProperties - .getProducerProperties(channelName); - if (this.newBindingCallback != null) { - Object extendedProducerProperties = this.bindingService - .getExtendedProducerProperties(channel, channelName); - this.newBindingCallback.configure(channelName, channel, - producerProperties, extendedProducerProperties); - } - bindingServiceProperties.updateProducerProperties(channelName, - producerProperties); - this.beanFactory.registerSingleton(channelName, channel); - channel = (MessageChannel) this.beanFactory.initializeBean(channel, - channelName); - Binding binding = this.bindingService - .bindProducer(channel, channelName); - this.dynamicDestinationsBindable.addOutputBinding(channelName, binding); - } - } - return channel; - } - - /** - * Configure a new destination before it is bound. - * - * @param the extended properties type. If you need to support dynamic binding - * with multiple binders, use {@link Object} and cast as needed. - * @since 2.0 - * - */ - @FunctionalInterface - public interface NewDestinationBindingCallback { - - /** - * Configure the properties or channel before binding. - * @param channelName the name of the new channel. - * @param channel the channel that is about to be bound. - * @param producerProperties the producer properties. - * @param extendedProducerProperties the extended producer properties (type - * depends on binder type and may be null if the binder doesn't support extended - * properties). - */ - void configure(String channelName, MessageChannel channel, - ProducerProperties producerProperties, T extendedProducerProperties); - - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareRouter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareRouter.java index 6d56f7dbd..8dd72b804 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareRouter.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/BinderAwareRouter.java @@ -22,7 +22,7 @@ import org.springframework.messaging.MessageChannel; import org.springframework.messaging.core.DestinationResolver; /** - * A {@link BeanPostProcessor} that sets a {@link BinderAwareChannelResolver} on any bean + * A {@link BeanPostProcessor} that sets a BinderAwareChannelResolver on any bean * of type {@link AbstractMappingMessageRouter} within the context. * * @author Mark Fisher diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/NewDestinationBindingCallback.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/NewDestinationBindingCallback.java new file mode 100644 index 000000000..0dc71f575 --- /dev/null +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/NewDestinationBindingCallback.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binding; + +import org.springframework.cloud.stream.binder.ProducerProperties; +import org.springframework.messaging.MessageChannel; + +/** + * Configure a new destination before it is bound. + * + * @param the extended properties type. If you need to support dynamic binding + * with multiple binders, use {@link Object} and cast as needed. + * @since 2.0 + * + */ +@FunctionalInterface +public interface NewDestinationBindingCallback { + + /** + * Configure the properties or channel before binding. + * @param channelName the name of the new channel. + * @param channel the channel that is about to be bound. + * @param producerProperties the producer properties. + * @param extendedProducerProperties the extended producer properties (type + * depends on binder type and may be null if the binder doesn't support extended + * properties). + */ + void configure(String channelName, MessageChannel channel, + ProducerProperties producerProperties, T extendedProducerProperties); + +} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java index 5dae4c3b9..b4d9b3e40 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java @@ -51,7 +51,6 @@ import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.core.DestinationResolver; import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory; import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; import org.springframework.util.Assert; @@ -83,7 +82,7 @@ public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProces // == dependencies that are injected in 'afterSingletonsInstantiated' to avoid early // initialization - private DestinationResolver binderAwareChannelResolver; + //private DestinationResolver binderAwareChannelResolver; private MessageHandlerMethodFactory messageHandlerMethodFactory; @@ -173,7 +172,7 @@ public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProces handler = handlers.get(0).getStreamListenerMessageHandler(); } handler.setApplicationContext(this.applicationContext); - handler.setChannelResolver(this.binderAwareChannelResolver); + //handler.setChannelResolver(this.binderAwareChannelResolver); handler.afterPropertiesSet(); this.applicationContext.getBeanFactory().registerSingleton( handler.getClass().getSimpleName() + handler.hashCode(), handler); @@ -317,8 +316,8 @@ public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProces .getBeansOfType(StreamListenerParameterAdapter.class).values(); Collection streamListenerResultAdapters = this.applicationContext .getBeansOfType(StreamListenerResultAdapter.class).values(); - this.binderAwareChannelResolver = this.applicationContext - .getBean("binderAwareChannelResolver", DestinationResolver.class); + //this.binderAwareChannelResolver = this.applicationContext + // .getBean("binderAwareChannelResolver", DestinationResolver.class); this.messageHandlerMethodFactory = this.applicationContext .getBean("integrationMessageHandlerMethodFactory", MessageHandlerMethodFactory.class); this.springIntegrationProperties = this.applicationContext diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java index d5d26d07d..8bb16c341 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java @@ -42,9 +42,7 @@ import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.binder.BinderType; import org.springframework.cloud.stream.binder.BinderTypeRegistry; import org.springframework.cloud.stream.binder.DefaultBinderFactory; -import org.springframework.cloud.stream.binding.AbstractBindingTargetFactory; import org.springframework.cloud.stream.binding.Bindable; -import org.springframework.cloud.stream.binding.BinderAwareChannelResolver; import org.springframework.cloud.stream.binding.BinderAwareRouter; import org.springframework.cloud.stream.binding.BindingService; import org.springframework.cloud.stream.binding.BindingsLifecycleController; @@ -253,18 +251,6 @@ public class BindingServiceConfiguration { return new ContextStartAfterRefreshListener(); } - @SuppressWarnings("rawtypes") - @Bean - public BinderAwareChannelResolver binderAwareChannelResolver( - BindingService bindingService, - AbstractBindingTargetFactory bindingTargetFactory, - DynamicDestinationsBindable dynamicDestinationsBindable, - @Nullable BinderAwareChannelResolver.NewDestinationBindingCallback callback) { - - return new BinderAwareChannelResolver(bindingService, bindingTargetFactory, - dynamicDestinationsBindable, callback); - } - @Bean public DynamicDestinationsBindable dynamicDestinationsBindable() { return new DynamicDestinationsBindable(); diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java index 46529f3a0..fd0b7cd1f 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java @@ -71,7 +71,7 @@ import org.springframework.cloud.stream.binder.BindingCreatedEvent; import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.binding.BindableProxyFactory; -import org.springframework.cloud.stream.binding.BinderAwareChannelResolver.NewDestinationBindingCallback; +import org.springframework.cloud.stream.binding.NewDestinationBindingCallback; import org.springframework.cloud.stream.config.BinderFactoryAutoConfiguration; import org.springframework.cloud.stream.config.BindingBeansRegistrar; import org.springframework.cloud.stream.config.BindingProperties; diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java index 6552e7dde..155c02724 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java @@ -35,9 +35,9 @@ import org.springframework.cloud.function.context.message.MessageUtils; import org.springframework.cloud.stream.binder.Binder; import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.binder.ProducerProperties; -import org.springframework.cloud.stream.binding.BinderAwareChannelResolver.NewDestinationBindingCallback; import org.springframework.cloud.stream.binding.BindingService; import org.springframework.cloud.stream.binding.DefaultPartitioningInterceptor; +import org.springframework.cloud.stream.binding.NewDestinationBindingCallback; import org.springframework.cloud.stream.config.BindingProperties; import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel; diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java deleted file mode 100644 index 3a65ab579..000000000 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/BinderAwareChannelResolverTests.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2013-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.binder; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; -import org.springframework.cloud.stream.binding.Bindable; -import org.springframework.cloud.stream.binding.BinderAwareChannelResolver; -import org.springframework.cloud.stream.binding.BindingService; -import org.springframework.cloud.stream.binding.DynamicDestinationsBindable; -import org.springframework.cloud.stream.binding.SubscribableChannelBindingTargetFactory; -import org.springframework.cloud.stream.config.BindingProperties; -import org.springframework.cloud.stream.config.BindingServiceProperties; -import org.springframework.cloud.stream.messaging.DirectWithAttributesChannel; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.support.ImmutableMessageChannelInterceptor; -import org.springframework.messaging.support.InterceptableChannel; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.matches; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Mark Fisher - * @author Gary Russell - * @author Ilayaperumal Gopinathan - * @author Oleg Zhurakousky - */ -public class BinderAwareChannelResolverTests { - - protected ConfigurableApplicationContext context; - - protected volatile BinderAwareChannelResolver resolver; - - protected volatile Binder binder; - - protected volatile SubscribableChannelBindingTargetFactory bindingTargetFactory; - - protected volatile BindingServiceProperties bindingServiceProperties; - - protected volatile DynamicDestinationsBindable dynamicDestinationsBindable; - - @SuppressWarnings("unchecked") - @Before - public void setupContext() throws Exception { - - this.context = new SpringApplicationBuilder( - TestChannelBinderConfiguration.getCompleteConfiguration( - BinderAwareChannelResolverTests.InterceptorConfiguration.class)) - .web(WebApplicationType.NONE).run(); - - this.resolver = this.context.getBean(BinderAwareChannelResolver.class); - this.binder = this.context.getBean(Binder.class); - this.bindingServiceProperties = this.context - .getBean(BindingServiceProperties.class); - this.bindingTargetFactory = this.context - .getBean(SubscribableChannelBindingTargetFactory.class); - } - - @Test - public void resolveChannel() { - Map bindables = this.context.getBeansOfType(Bindable.class); - assertThat(bindables).hasSize(1); - for (Bindable bindable : bindables.values()) { - assertThat(bindable.getInputs().size()).isEqualTo(0); // producer - assertThat(bindable.getOutputs().size()).isEqualTo(0); // consumer - } - MessageChannel registered = this.resolver.resolveDestination("foo"); - assertThat(((InterceptableChannel) registered).getInterceptors().size()) - .isEqualTo(2); - assertThat(((InterceptableChannel) registered).getInterceptors() - .get(1) instanceof ImmutableMessageChannelInterceptor).isTrue(); - - bindables = this.context.getBeansOfType(Bindable.class); - assertThat(bindables).hasSize(1); - for (Bindable bindable : bindables.values()) { - assertThat(bindable.getInputs().size()).isEqualTo(0); // producer - assertThat(bindable.getOutputs().size()).isEqualTo(1); // consumer - } - DirectChannel testChannel = new DirectChannel(); - testChannel.setComponentName("INPUT"); - final CountDownLatch latch = new CountDownLatch(1); - final List> received = new ArrayList<>(); - testChannel.subscribe(new MessageHandler() { - @Override - public void handleMessage(Message message) throws MessagingException { - received.add(message); - latch.countDown(); - } - }); - this.binder.bindConsumer("foo", null, testChannel, new ConsumerProperties()); - assertThat(received).hasSize(0); - registered.send(MessageBuilder.withPayload("hello").build()); - try { - assertThat(latch.await(1, TimeUnit.SECONDS)).describedAs("Latch timed out"); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - fail("interrupted while awaiting latch"); - } - assertThat(received).hasSize(1); - assertThat(new String((byte[]) received.get(0).getPayload())).isEqualTo("hello"); - this.context.close(); - for (Bindable bindable : bindables.values()) { - assertThat(bindable.getInputs().size()).isEqualTo(0); - assertThat(bindable.getOutputs().size()).isEqualTo(0); // Must not be bound" - } - } - - @Test - public void resolveNonRegisteredChannel() { - MessageChannel other = this.resolver.resolveDestination("other"); - assertThat(this.context.getBean("other")).isSameAs(other); - this.context.close(); - } - - @Test - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void propertyPassthrough() { - Map bindings = new HashMap<>(); - BindingProperties genericProperties = new BindingProperties(); - genericProperties.setContentType("text/plain"); - bindings.put("foo", genericProperties); - this.bindingServiceProperties.setBindings(bindings); - Binder binder = mock(Binder.class); - Binder binder2 = mock(Binder.class); - BinderFactory mockBinderFactory = Mockito.mock(BinderFactory.class); - Binding fooBinding = Mockito.mock(Binding.class); - Binding barBinding = Mockito.mock(Binding.class); - when(binder.bindProducer(matches("foo"), any(DirectChannel.class), - any(ProducerProperties.class))).thenReturn(fooBinding); - when(binder2.bindProducer(matches("bar"), any(DirectChannel.class), - any(ProducerProperties.class))).thenReturn(barBinding); - when(mockBinderFactory.getBinder(null, DirectWithAttributesChannel.class)) - .thenReturn(binder); - when(mockBinderFactory.getBinder("someTransport", - DirectWithAttributesChannel.class)).thenReturn(binder2); - BindingService bindingService = new BindingService(this.bindingServiceProperties, - mockBinderFactory, new ObjectMapper()); - BinderAwareChannelResolver resolver = new BinderAwareChannelResolver( - bindingService, this.bindingTargetFactory, - new DynamicDestinationsBindable()); - resolver.setBeanFactory(this.context.getBeanFactory()); - SubscribableChannel resolved = (SubscribableChannel) resolver - .resolveDestination("foo"); - verify(binder).bindProducer(eq("foo"), any(MessageChannel.class), - any(ProducerProperties.class)); - assertThat(resolved).isSameAs(this.context.getBean("foo")); - this.context.close(); - } - - @Configuration - public static class InterceptorConfiguration { - - @Bean - public GlobalChannelInterceptorWrapper testInterceptor() { - return new GlobalChannelInterceptorWrapper( - new ImmutableMessageChannelInterceptor()); - } - - } - -} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java deleted file mode 100644 index 01eb0d23b..000000000 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ExtendedPropertiesBinderAwareChannelResolverTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2013-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.binder; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.Test; - -import org.springframework.cloud.stream.binding.Bindable; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHandler; -import org.springframework.messaging.MessagingException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Mark Fisher - * @author Gary Russell - * @author Ilayaperumal Gopinathan - * @author Oleg Zhurakousky - */ -public class ExtendedPropertiesBinderAwareChannelResolverTests - extends BinderAwareChannelResolverTests { - - @Test - @Override - public void resolveChannel() { - Map bindables = this.context.getBeansOfType(Bindable.class); - assertThat(bindables).hasSize(1); - for (Bindable bindable : bindables.values()) { - assertThat(bindable.getInputs().size()).isEqualTo(0); // producer - assertThat(bindable.getOutputs().size()).isEqualTo(0); // consumer - } - MessageChannel registered = this.resolver.resolveDestination("foo"); - bindables = this.context.getBeansOfType(Bindable.class); - assertThat(bindables).hasSize(1); - for (Bindable bindable : bindables.values()) { - assertThat(bindable.getInputs().size()).isEqualTo(0); // producer - assertThat(bindable.getOutputs().size()).isEqualTo(1); // consumer - } - DirectChannel testChannel = new DirectChannel(); - final CountDownLatch latch = new CountDownLatch(1); - final List> received = new ArrayList<>(); - testChannel.subscribe(new MessageHandler() { - - @Override - public void handleMessage(Message message) throws MessagingException { - received.add(message); - latch.countDown(); - } - }); - this.binder.bindConsumer("foo", null, testChannel, - new ExtendedConsumerProperties( - new ConsumerProperties())); - assertThat(received).hasSize(0); - registered.send(MessageBuilder.withPayload("hello").build()); - try { - assertThat(latch.await(1, TimeUnit.SECONDS)).describedAs("latch timed out"); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - fail("interrupted while awaiting latch"); - } - assertThat(received).hasSize(1); - assertThat(new String((byte[]) received.get(0).getPayload())).isEqualTo("hello"); - this.context.close(); - for (Bindable bindable : bindables.values()) { - assertThat(bindable.getInputs().size()).isEqualTo(0); - assertThat(bindable.getOutputs().size()).isEqualTo(0); // Must not be bound" - } - } - -} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/BindingServiceTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/BindingServiceTests.java index 2f15bcf3c..4f4003495 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/BindingServiceTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/BindingServiceTests.java @@ -24,23 +24,16 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -58,14 +51,11 @@ import org.springframework.cloud.stream.binder.Binding; import org.springframework.cloud.stream.binder.ConsumerProperties; import org.springframework.cloud.stream.binder.DefaultBinderFactory; import org.springframework.cloud.stream.binder.DefaultBinderTypeRegistry; -import org.springframework.cloud.stream.binder.ExtendedProducerProperties; -import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder; import org.springframework.cloud.stream.binder.ProducerProperties; import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; import org.springframework.cloud.stream.config.BindingProperties; import org.springframework.cloud.stream.config.BindingServiceConfiguration; import org.springframework.cloud.stream.config.BindingServiceProperties; -import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory; import org.springframework.cloud.stream.messaging.Processor; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.cloud.stream.reflection.GenericsUtils; @@ -80,21 +70,16 @@ import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.core.DestinationResolutionException; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -326,93 +311,94 @@ public class BindingServiceTests { binderFactory.destroy(); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Test - public void checkDynamicBinding() { - BindingServiceProperties properties = new BindingServiceProperties(); - BindingProperties bindingProperties = new BindingProperties(); - bindingProperties.setProducer(new ProducerProperties()); - properties.setBindings(Collections.singletonMap("foo", bindingProperties)); - DefaultBinderFactory binderFactory = createMockBinderFactory(); - final ExtendedPropertiesBinder binder = mock(ExtendedPropertiesBinder.class); - Properties extendedProps = new Properties(); - when(binder.getExtendedProducerProperties(anyString())).thenReturn(extendedProps); - Binding mockBinding = Mockito.mock(Binding.class); - final AtomicReference dynamic = new AtomicReference<>(); - when(binder.bindProducer(matches("foo"), any(DirectChannel.class), - any(ProducerProperties.class))).thenReturn(mockBinding); - BindingService bindingService = new BindingService(properties, binderFactory, new ObjectMapper()) { - - @Override - protected Binder getBinder(String channelName, - Class bindableType) { - return binder; - } - - }; - SubscribableChannelBindingTargetFactory bindableSubscribableChannelFactory; - bindableSubscribableChannelFactory = new SubscribableChannelBindingTargetFactory( - new MessageConverterConfigurer(properties, - new CompositeMessageConverterFactory().getMessageConverterForAllRegistered())); - final AtomicBoolean callbackInvoked = new AtomicBoolean(); - BinderAwareChannelResolver resolver = new BinderAwareChannelResolver( - bindingService, bindableSubscribableChannelFactory, - new DynamicDestinationsBindable(), (name, channel, props, extended) -> { - callbackInvoked.set(true); - assertThat(name).isEqualTo("foo"); - assertThat(channel).isNotNull(); - assertThat(props).isNotNull(); - assertThat(extended).isSameAs(extendedProps); - props.setUseNativeEncoding(true); - extendedProps.setProperty("bar", "baz"); - }); - ConfigurableListableBeanFactory beanFactory = mock( - ConfigurableListableBeanFactory.class); - when(beanFactory.getBean("foo", MessageChannel.class)) - .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class)); - when(beanFactory.getBean("bar", MessageChannel.class)) - .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class)); - doAnswer(new Answer() { - - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - dynamic.set(invocation.getArgument(1)); - return null; - } - - }).when(beanFactory).registerSingleton(eq("foo"), any(MessageChannel.class)); - doAnswer(new Answer() { - - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - return dynamic.get(); - } - - }).when(beanFactory).initializeBean(any(MessageChannel.class), eq("foo")); - resolver.setBeanFactory(beanFactory); - MessageChannel resolved = resolver.resolveDestination("foo"); - assertThat(resolved).isSameAs(dynamic.get()); - ArgumentCaptor captor = ArgumentCaptor - .forClass(ProducerProperties.class); - verify(binder).bindProducer(eq("foo"), eq(dynamic.get()), captor.capture()); - assertThat(captor.getValue().isUseNativeEncoding()).isTrue(); - assertThat(captor.getValue()).isInstanceOf(ExtendedProducerProperties.class); - assertThat(((ExtendedProducerProperties) captor.getValue()).getExtension()) - .isSameAs(extendedProps); - doReturn(dynamic.get()).when(beanFactory).getBean("foo", MessageChannel.class); - properties.setDynamicDestinations(new String[] { "foo" }); - resolved = resolver.resolveDestination("foo"); - assertThat(resolved).isSameAs(dynamic.get()); - properties.setDynamicDestinations(new String[] { "test" }); - try { - resolver.resolveDestination("bar"); - fail("Should throw an exception"); - } - catch (DestinationResolutionException e) { - assertThat(e).hasMessageContaining( - "Failed to find MessageChannel bean with name 'bar'"); - } - } + //TODO: Need to re-write the following test. + //@SuppressWarnings({ "rawtypes", "unchecked" }) +// @Test +// public void checkDynamicBinding() { +// BindingServiceProperties properties = new BindingServiceProperties(); +// BindingProperties bindingProperties = new BindingProperties(); +// bindingProperties.setProducer(new ProducerProperties()); +// properties.setBindings(Collections.singletonMap("foo", bindingProperties)); +// DefaultBinderFactory binderFactory = createMockBinderFactory(); +// final ExtendedPropertiesBinder binder = mock(ExtendedPropertiesBinder.class); +// Properties extendedProps = new Properties(); +// when(binder.getExtendedProducerProperties(anyString())).thenReturn(extendedProps); +// Binding mockBinding = Mockito.mock(Binding.class); +// final AtomicReference dynamic = new AtomicReference<>(); +// when(binder.bindProducer(matches("foo"), any(DirectChannel.class), +// any(ProducerProperties.class))).thenReturn(mockBinding); +// BindingService bindingService = new BindingService(properties, binderFactory, new ObjectMapper()) { +// +// @Override +// protected Binder getBinder(String channelName, +// Class bindableType) { +// return binder; +// } +// +// }; +// SubscribableChannelBindingTargetFactory bindableSubscribableChannelFactory; +// bindableSubscribableChannelFactory = new SubscribableChannelBindingTargetFactory( +// new MessageConverterConfigurer(properties, +// new CompositeMessageConverterFactory().getMessageConverterForAllRegistered())); +// final AtomicBoolean callbackInvoked = new AtomicBoolean(); +// BinderAwareChannelResolver resolver = new BinderAwareChannelResolver( +// bindingService, bindableSubscribableChannelFactory, +// new DynamicDestinationsBindable(), (name, channel, props, extended) -> { +// callbackInvoked.set(true); +// assertThat(name).isEqualTo("foo"); +// assertThat(channel).isNotNull(); +// assertThat(props).isNotNull(); +// assertThat(extended).isSameAs(extendedProps); +// props.setUseNativeEncoding(true); +// extendedProps.setProperty("bar", "baz"); +// }); +// ConfigurableListableBeanFactory beanFactory = mock( +// ConfigurableListableBeanFactory.class); +// when(beanFactory.getBean("foo", MessageChannel.class)) +// .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class)); +// when(beanFactory.getBean("bar", MessageChannel.class)) +// .thenThrow(new NoSuchBeanDefinitionException(MessageChannel.class)); +// doAnswer(new Answer() { +// +// @Override +// public Void answer(InvocationOnMock invocation) throws Throwable { +// dynamic.set(invocation.getArgument(1)); +// return null; +// } +// +// }).when(beanFactory).registerSingleton(eq("foo"), any(MessageChannel.class)); +// doAnswer(new Answer() { +// +// @Override +// public Object answer(InvocationOnMock invocation) throws Throwable { +// return dynamic.get(); +// } +// +// }).when(beanFactory).initializeBean(any(MessageChannel.class), eq("foo")); +// resolver.setBeanFactory(beanFactory); +// MessageChannel resolved = resolver.resolveDestination("foo"); +// assertThat(resolved).isSameAs(dynamic.get()); +// ArgumentCaptor captor = ArgumentCaptor +// .forClass(ProducerProperties.class); +// verify(binder).bindProducer(eq("foo"), eq(dynamic.get()), captor.capture()); +// assertThat(captor.getValue().isUseNativeEncoding()).isTrue(); +// assertThat(captor.getValue()).isInstanceOf(ExtendedProducerProperties.class); +// assertThat(((ExtendedProducerProperties) captor.getValue()).getExtension()) +// .isSameAs(extendedProps); +// doReturn(dynamic.get()).when(beanFactory).getBean("foo", MessageChannel.class); +// properties.setDynamicDestinations(new String[] { "foo" }); +// resolved = resolver.resolveDestination("foo"); +// assertThat(resolved).isSameAs(dynamic.get()); +// properties.setDynamicDestinations(new String[] { "test" }); +// try { +// resolver.resolveDestination("bar"); +// fail("Should throw an exception"); +// } +// catch (DestinationResolutionException e) { +// assertThat(e).hasMessageContaining( +// "Failed to find MessageChannel bean with name 'bar'"); +// } +// } @Test public void testProducerPropertiesValidation() { diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/DynamicDestinationFunctionTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/DynamicDestinationFunctionTests.java index e9bc58f3d..9514a992d 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/DynamicDestinationFunctionTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/DynamicDestinationFunctionTests.java @@ -16,18 +16,15 @@ package org.springframework.cloud.stream.function; -import java.util.function.Consumer; - import org.junit.After; +import org.junit.Ignore; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy; import org.springframework.cloud.stream.binder.test.InputDestination; import org.springframework.cloud.stream.binder.test.OutputDestination; import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; -import org.springframework.cloud.stream.binding.BinderAwareChannelResolver; import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; @@ -40,6 +37,8 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Oleg Zhurakousky * @author David Turanski * + * + * TODO: Need to rewrite this test. */ public class DynamicDestinationFunctionTests { @@ -50,6 +49,7 @@ public class DynamicDestinationFunctionTests { } @Test + @Ignore public void testEmptyConfiguration() { TestChannelBinderConfiguration.applicationContextRunner(SampleConfiguration.class) .withPropertyValues( @@ -72,8 +72,8 @@ public class DynamicDestinationFunctionTests { @EnableAutoConfiguration public static class SampleConfiguration { - @Autowired - private BinderAwareChannelResolver resolver; +// @Autowired +// private BinderAwareChannelResolver resolver; @Bean public PartitionKeyExtractorStrategy keyExtractor() { @@ -86,12 +86,12 @@ public class DynamicDestinationFunctionTests { }; } - @Bean - public Consumer cons() { - return value -> { - resolver.resolveDestination(value).send(new GenericMessage(value)); - }; - } +// @Bean +// public Consumer cons() { +// return value -> { +// resolver.resolveDestination(value).send(new GenericMessage(value)); +// }; +// } } } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java index 78a3d77bc..b314e2a23 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java @@ -37,7 +37,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.cloud.stream.binder.test.OutputDestination; import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; -import org.springframework.cloud.stream.binding.BinderAwareChannelResolver.NewDestinationBindingCallback; +import org.springframework.cloud.stream.binding.NewDestinationBindingCallback; import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; From 0ee4b41b2f7f4fd39736c9ef986b1219aa07a48c Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Fri, 7 Jan 2022 16:13:22 -0500 Subject: [PATCH 17/27] Remove StreamMessageConverter StreamMessageConverter was deprecated in 3.0.x and now removed completely in 4.0.x. --- .../config/CustomMessageConverterTests.java | 5 -- .../annotation/StreamMessageConverter.java | 46 ------------------- 2 files changed, 51 deletions(-) delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamMessageConverter.java diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java index 515e4ebe1..5a6485eed 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomMessageConverterTests.java @@ -26,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamMessageConverter; import org.springframework.cloud.stream.binder.BinderFactory; import org.springframework.cloud.stream.messaging.Source; import org.springframework.cloud.stream.test.binder.TestSupportBinder; @@ -59,12 +58,10 @@ public class CustomMessageConverterTests { private BinderFactory binderFactory; @Autowired - @StreamMessageConverter private List customMessageConverters; @Test public void testCustomMessageConverter() throws Exception { - assertThat(this.customMessageConverters).hasSize(2); assertThat(this.customMessageConverters).extracting("class") .contains(FooConverter.class, BarConverter.class); this.testSource.output().send(MessageBuilder.withPayload(new Foo("hi")).build()); @@ -84,13 +81,11 @@ public class CustomMessageConverterTests { public static class TestSource { @Bean - @StreamMessageConverter public MessageConverter fooConverter() { return new FooConverter(); } @Bean - @StreamMessageConverter public MessageConverter barConverter() { return new BarConverter(); } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamMessageConverter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamMessageConverter.java deleted file mode 100644 index 443bc9fe9..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamMessageConverter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2017-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; - -/** - * Marker to tag {@link org.springframework.messaging.converter.MessageConverter} beans - * that will be added to the - * {@link org.springframework.cloud.stream.converter.CompositeMessageConverterFactory}. - * - * @author Vinicius Carvalho - * @author Arten Bilan - * - * @deprecated as of 3.0 and is not used by the framework anymore. - */ -@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Qualifier -@Bean -@Deprecated -public @interface StreamMessageConverter { - -} From 2fe7cf58c12fa084c1f1e4a078f542c696b60de6 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Fri, 7 Jan 2022 17:53:41 -0500 Subject: [PATCH 18/27] 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. --- .../stream/binder/AbstractBinderTests.java | 222 ---- ...ListenerAnnotatedMethodArgumentsTests.java | 192 --- ...notationBeanPostProcessorOverrideTest.java | 106 -- .../StreamListenerAsMetaAnnotationTests.java | 147 --- ...eamListenerContentTypeConversionTests.java | 77 -- .../StreamListenerDuplicateMappingTests.java | 127 -- .../StreamListenerHandlerBeanTests.java | 150 --- .../StreamListenerHandlerMethodTests.java | 623 --------- .../StreamListenerMessageArgumentTests.java | 125 -- ...mListenerMethodRegisteredOnlyOnceTest.java | 76 -- ...stenerMethodReturnWithConversionTests.java | 200 --- ...mListenerMethodSetupOrchestratorTests.java | 161 --- ...mListenerMethodWithReturnMessageTests.java | 125 -- ...eamListenerMethodWithReturnValueTests.java | 120 -- .../config/StreamListenerTestUtils.java | 105 -- ...enerWithAnnotatedInputOutputArgsTests.java | 185 --- .../StreamListenerWithConditionsTest.java | 144 -- .../config/TextPlainToJsonConversionTest.java | 123 -- .../TextPlainToJsonConversionTest.java.todo | 123 ++ .../config/contentType/ContentTypeTests.java | 272 ---- .../contentType/ContentTypeTests.java.todo | 272 ++++ .../stream/annotation/StreamListener.java | 180 --- ...spatchingStreamListenerMessageHandler.java | 149 --- ...ageChannelStreamListenerResultAdapter.java | 63 - ...amListenerAnnotationBeanPostProcessor.java | 571 -------- .../binding/StreamListenerErrorMessages.java | 133 -- .../binding/StreamListenerMessageHandler.java | 70 - .../binding/StreamListenerMethodUtils.java | 185 --- .../StreamListenerParameterAdapter.java | 55 - .../binding/StreamListenerResultAdapter.java | 52 - ...StreamListenerSetupMethodOrchestrator.java | 137 -- .../config/BindingServiceConfiguration.java | 13 - .../stream/binder/ErrorBindingTests.java | 43 +- .../binder/tck/ContentTypeTckTests.java | 1163 ----------------- .../binder/tck/ContentTypeTckTests.java.todo | 1163 +++++++++++++++++ .../stream/binder/tck/ErrorHandlingTests.java | 104 -- .../binder/tck/ErrorHandlingTests.java.todo | 104 ++ .../stream/binder/test/SampleStreamApp.java | 91 -- .../binder/test/SampleStreamApp.java.todo | 91 ++ .../ImplicitFunctionBindingTests.java | 33 +- 40 files changed, 1787 insertions(+), 6288 deletions(-) delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodRegisteredOnlyOnceTest.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodSetupOrchestratorTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithAnnotatedInputOutputArgsTests.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java create mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java.todo delete mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java create mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java.todo delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DispatchingStreamListenerMessageHandler.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMessageHandler.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java delete mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerSetupMethodOrchestrator.java delete mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java.todo delete mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java.todo delete mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java.todo diff --git a/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java b/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java index c3933d00a..c96bd78f1 100644 --- a/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java +++ b/spring-cloud-stream-binder-test/src/main/java/org/springframework/cloud/stream/binder/AbstractBinderTests.java @@ -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 producerBinding = binder.bindProducer( - String.format("bad%s0a", getDestinationNameDelimiter()), - moduleOutputChannel, producerBindingProperties.getProducer()); - - Binding 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 producerBinding = binder.bindProducer( - String.format("bad%s0d", getDestinationNameDelimiter()), - moduleOutputChannel, producerBindingProperties.getProducer()); - - Binding 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 reply = (Message) 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 producerBinding = binder.bindProducer( - String.format("bad%s0e", getDestinationNameDelimiter()), - moduleOutputChannel, producerBindingProperties.getProducer()); - - Binding 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 reply = (Message) 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 producerBinding = binder.bindProducer( - String.format("bad%s0f", getDestinationNameDelimiter()), - moduleOutputChannel, producerBindingProperties.getProducer()); - - Binding 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 reply = (Message) 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 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 = new ArrayList<>(); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java deleted file mode 100644 index ad3ed5df4..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotatedMethodArgumentsTests.java +++ /dev/null @@ -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) testPojoWithAnnotatedArguments.receivedArguments - .get(1)).containsEntry(MessageHeaders.CONTENT_TYPE, - MimeType.valueOf("application/json")); - assertThat((Map) 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 receivedArguments = new ArrayList<>(); - - @StreamListener(Processor.INPUT) - public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo, - @Headers Map 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 receivedArguments = new ArrayList<>(); - - @StreamListener - public void receive( - @Input(Processor.INPUT) @Payload StreamListenerTestUtils.FooPojo fooPojo, - @Headers Map 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 receivedArguments = new ArrayList<>(); - - @StreamListener(Processor.INPUT) - public void receive( - @Valid StreamListenerTestUtils.PojoWithValidation pojoWithValidation) { - this.receivedArguments.add(pojoWithValidation); - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java deleted file mode 100644 index ba0e89ac5..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAnnotationBeanPostProcessorOverrideTest.java +++ /dev/null @@ -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 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 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); - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java deleted file mode 100644 index a1edb0922..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerAsMetaAnnotationTests.java +++ /dev/null @@ -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 receivedFoo = new ArrayList<>(); - - List 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 receivedFoo = new ArrayList<>(); - - List receivedBar = new ArrayList<>(); - - @StreamListener(value = Sink.INPUT, condition = "headers['type']=='foo'") - public void receive(@Payload StreamListenerTestUtils.FooPojo fooPojo) { - this.receivedFoo.add(fooPojo); - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java deleted file mode 100644 index a827552c3..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerContentTypeConversionTests.java +++ /dev/null @@ -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 receivedArguments = new ArrayList<>(); - - CountDownLatch latch = new CountDownLatch(1); - - @StreamListener(Sink.INPUT) - public void receive(StreamListenerTestUtils.FooPojo fooPojo) { - this.receivedArguments.add(fooPojo); - this.latch.countDown(); - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java deleted file mode 100644 index c5cb84694..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerDuplicateMappingTests.java +++ /dev/null @@ -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 { - - 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 fooMessage) { - return null; - } - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public String receiveDuplicateMapping(Message fooMessage) { - return null; - } - - } - - @EnableBinding(Sink.class) - @EnableAutoConfiguration - public static class TestDuplicateMappingFromAbstractMethod - implements GenericSink { - - @Override - @StreamListener(Sink.INPUT) - public void testMethod(TestBase msg) { - } - - } - - public class TestBase implements Base { - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java deleted file mode 100644 index 160c9aefd..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerBeanTests.java +++ /dev/null @@ -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 message = (Message) 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 receivedPojos = new ArrayList<>(); - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java deleted file mode 100644 index 0be6c2147..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerHandlerMethodTests.java +++ /dev/null @@ -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 result = (Message) 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 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 result = (Message) 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 result = (Message) 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 result = (Message) 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 convertMessageBody(Message 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()); - } - }); - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java deleted file mode 100644 index 028e2222e..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMessageArgumentTests.java +++ /dev/null @@ -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 message = (Message) 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 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 fooMessage) { - this.receivedMessages.add(fooMessage); - StreamListenerTestUtils.BarPojo barPojo = new StreamListenerTestUtils.BarPojo(); - barPojo.setBar(fooMessage.getPayload()); - return barPojo; - } - - } - - public static class TestPojoWithMessageArgument { - - List> receivedMessages = new ArrayList<>(); - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodRegisteredOnlyOnceTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodRegisteredOnlyOnceTest.java deleted file mode 100644 index 7b475d835..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodRegisteredOnlyOnceTest.java +++ /dev/null @@ -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() { - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java deleted file mode 100644 index 5c6fe2eea..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodReturnWithConversionTests.java +++ /dev/null @@ -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 message = (Message) 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 message = (Message) 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 receivedPojos = new ArrayList<>(); - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodSetupOrchestratorTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodSetupOrchestratorTests.java deleted file mode 100644 index ba89ee697..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodSetupOrchestratorTests.java +++ /dev/null @@ -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 field1; - field1 = (LinkedHashSet) ReflectionUtils - .getField(field, this.streamListenerAnnotationBeanPostProcessor); - List 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 - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java deleted file mode 100644 index 03fa383e9..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnMessageTests.java +++ /dev/null @@ -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 message = (Message) 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 receivedPojos = new ArrayList<>(); - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java deleted file mode 100644 index 8829ff44e..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerMethodWithReturnValueTests.java +++ /dev/null @@ -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 message = (Message) 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 receivedPojos = new ArrayList<>(); - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java deleted file mode 100644 index 88dc892a6..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerTestUtils.java +++ /dev/null @@ -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; - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithAnnotatedInputOutputArgsTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithAnnotatedInputOutputArgsTests.java deleted file mode 100644 index 2e2662dc9..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithAnnotatedInputOutputArgsTests.java +++ /dev/null @@ -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 result = (Message) 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()); - } - }); - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java deleted file mode 100644 index eb3cd7f75..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/StreamListenerWithConditionsTest.java +++ /dev/null @@ -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 receivedFoo = new ArrayList<>(); - - List 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; - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java deleted file mode 100644 index 33c9094d8..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2017-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.config; - -import java.util.concurrent.TimeUnit; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.binder.BinderFactory; -import org.springframework.cloud.stream.messaging.Processor; -import org.springframework.cloud.stream.test.binder.TestSupportBinder; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Marius Bogoevici - * @author Vinicius Carvalho - * @author Oleg Zhurakousky - * @since 1.2 - */ -@RunWith(SpringJUnit4ClassRunner.class) -// @checkstyle:off -@SpringBootTest(classes = TextPlainToJsonConversionTest.FooProcessor.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) -// @checkstyle:on -public class TextPlainToJsonConversionTest { - - @Autowired - private Processor testProcessor; - - @Autowired - private BinderFactory binderFactory; - - private ObjectMapper mapper = new ObjectMapper(); - - @SuppressWarnings("unchecked") - @Test - public void testNoContentTypeToJsonConversionOnInput() throws Exception { - this.testProcessor.input() - .send(MessageBuilder.withPayload("{\"name\":\"Bar\"}").build()); - Message received = (Message) ((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 + "'}"; - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java.todo b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java.todo new file mode 100644 index 000000000..cf877e9ee --- /dev/null +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainToJsonConversionTest.java.todo @@ -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 received = (Message) ((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 + "'}"; +// } +// +// } +// +//} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java deleted file mode 100644 index 3c341fd0e..000000000 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java +++ /dev/null @@ -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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 headers = (Map) 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 headers = (Map) sourceApp.arguments.pop(); - sourceApp.arguments.pop(); - assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE)) - .includes(MimeTypeUtils.IMAGE_GIF)); - headers = (Map) 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 arguments = new LinkedList<>(); - - @StreamListener("POJO_INPUT") - public void receive(User user, @Headers Map 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 headers) { - this.arguments.push(data); - this.arguments.push(headers); - } - - } - -} diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java.todo b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java.todo new file mode 100644 index 000000000..06ed93ff8 --- /dev/null +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java.todo @@ -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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 message = (Message) 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 headers = (Map) 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 headers = (Map) sourceApp.arguments.pop(); +// sourceApp.arguments.pop(); +// assertThat(((MimeType) headers.get(MessageHeaders.CONTENT_TYPE)) +// .includes(MimeTypeUtils.IMAGE_GIF)); +// headers = (Map) 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 arguments = new LinkedList<>(); +// +// @StreamListener("POJO_INPUT") +// public void receive(User user, @Headers Map 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 headers) { +// this.arguments.push(data); +// this.arguments.push(headers); +// } +// +// } +// +//} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java deleted file mode 100644 index bad7b106a..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamListener.java +++ /dev/null @@ -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; - -/** - * 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 - *
- *
- * 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. - * - *

Declarative mode

- * - * 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. - * - *
    - *
  • 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.
  • - *
  • 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. - *
  • - *
- * - * An example of declarative method signature using the former idiom is as follows: - * - *
- * @StreamListener
- * public @Output("joined") Flux<String> join(
- *       @Input("input1") Flux<String> input1,
- *       @Input("input2") Flux<String> input2) {
- *   // ... join the two input streams via functional operators
- * }
- * 
- * - * An example of declarative method signature using the latter idiom is as follows: - * - *
- * @StreamListener(Processor.INPUT)
- * @SendTo(Processor.OUTPUT)
- * public Flux<String> convert(Flux<String> input) {
- *     return input.map(String::toUppercase);
- * }
- * 
- * - * Declarative methods are invoked only once, when the context is refreshed. - * - *

Individual message handler mode

- * - * 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: - *
    - *
  • A result of the type {@link org.springframework.messaging.Message} will be sent - * as-is
  • - *
  • All other results will become the payload of a - * {@link org.springframework.messaging.Message}
  • - *
- * - * The output binding target where the return message is sent is determined by consulting - * in the following order: - *
    - *
  • The {@link org.springframework.messaging.MessageHeaders} of the resulting - * message.
  • - *
  • The value set on the - * {@link org.springframework.messaging.handler.annotation.SendTo} annotation, if - * present
  • - *
- * - * An example of individual message handler signature is as follows: - * - *
- * @StreamListener(Processor.INPUT)
- * @SendTo(Processor.OUTPUT)
- * public String convert(String input) {
- * 		return input.toUppercase();
- * }
- * 
- * - * @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"; - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DispatchingStreamListenerMessageHandler.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DispatchingStreamListenerMessageHandler.java deleted file mode 100644 index 9efd38543..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/DispatchingStreamListenerMessageHandler.java +++ /dev/null @@ -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 handlerMethods; - - private final boolean evaluateExpressions; - - private final EvaluationContext evaluationContext; - - DispatchingStreamListenerMessageHandler( - Collection 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 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 findMatchingHandlers( - Message message) { - ArrayList 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; - } - - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java deleted file mode 100644 index 16ce41dff..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageChannelStreamListenerResultAdapter.java +++ /dev/null @@ -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 { - - @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 { - - } - - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java deleted file mode 100644 index b4d9b3e40..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerAnnotationBeanPostProcessor.java +++ /dev/null @@ -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 mappedListenerMethods = new LinkedMultiValueMap<>(); - - // @checkstyle:on - - private final Set streamListenerCallbacks = new HashSet<>(); - - // == dependencies that are injected in 'afterSingletonsInstantiated' to avoid early - // initialization - //private DestinationResolver binderAwareChannelResolver; - - private MessageHandlerMethodFactory messageHandlerMethodFactory; - - // == end dependencies - private SpringIntegrationProperties springIntegrationProperties; - - private ConfigurableApplicationContext applicationContext; - - private BeanExpressionResolver resolver; - - private BeanExpressionContext expressionContext; - - private Set 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> mappedBindingEntry : this.mappedListenerMethods - .entrySet()) { - ArrayList 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 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 streamListenerParameterAdapters = this.applicationContext - .getBeansOfType(StreamListenerParameterAdapter.class).values(); - Collection 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 streamListenerParameterAdapters; - - private final Collection streamListenerResultAdapters; - - private DefaultStreamListenerSetupMethodOrchestrator( - ConfigurableApplicationContext applicationContext, - Collection streamListenerParameterAdapters, - Collection 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.
- * Imperative - where handler method is invoked on each message by the handler - * infrastructure provided by the framework
- * Declarative - where handler is provided by the method itself.
- * 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; - } - - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java deleted file mode 100644 index cf1a19311..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerErrorMessages.java +++ /dev/null @@ -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."; - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMessageHandler.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMessageHandler.java deleted file mode 100644 index 4a311ce4a..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMessageHandler.java +++ /dev/null @@ -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); - } - } - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java deleted file mode 100644 index d61905941..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerMethodUtils.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java deleted file mode 100644 index 7d7ebf784..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerParameterAdapter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2016-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.stream.binding; - -import org.springframework.core.MethodParameter; - -/** - * Strategy for adapting a method argument type annotated with - * {@link org.springframework.cloud.stream.annotation.Input} or - * {@link org.springframework.cloud.stream.annotation.Output} from a binding type (e.g. - * {@link org.springframework.messaging.MessageChannel}) supported by an existing binder. - * - * This is a framework extension and is not primarily intended for use by end-users. - * - * @param adapter type - * @param binding result type - * @author Marius Bogoevici - */ -public interface StreamListenerParameterAdapter { - - /** - * 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); - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java deleted file mode 100644 index 6724a5263..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerResultAdapter.java +++ /dev/null @@ -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 stream listener result type - * @param binding target type - * @author Marius Bogoevici - */ -public interface StreamListenerResultAdapter { - - /** - * 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); - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerSetupMethodOrchestrator.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerSetupMethodOrchestrator.java deleted file mode 100644 index e1fe39af8..000000000 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/StreamListenerSetupMethodOrchestrator.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java index 8bb16c341..f6fb6ef7d 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceConfiguration.java @@ -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 diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java index 6846ff1fc..303ba97e9 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/ErrorBindingTests.java @@ -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 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 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 handle() { + return v -> { + this.counter++; + throw new RuntimeException("BOOM!"); + }; } @ServiceActivator(inputChannel = "input.anonymous.errors") diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java deleted file mode 100644 index d6644ea35..000000000 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java +++ /dev/null @@ -1,1163 +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.tck; - -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Ignore; -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.OutputDestination; -import org.springframework.cloud.stream.binder.test.TestChannelBinder; -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.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.integration.annotation.ServiceActivator; -import org.springframework.integration.channel.DirectChannel; -import org.springframework.lang.Nullable; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.MessagingException; -import org.springframework.messaging.converter.AbstractMessageConverter; -import org.springframework.messaging.converter.MessageConversionException; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.util.MimeType; -import org.springframework.util.MimeTypeUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Sort of a TCK test suite to validate payload conversion is done properly by interacting - * with binder's input/output destinations instead of its bridged channels. This means - * that all payloads (sent/received) must be expressed in the wire format (byte[]) - * - * @author Oleg Zhurakousky - * @author Gary Russell - * - */ -public class ContentTypeTckTests { - - @Test - public void stringToMapStreamListener() { - ApplicationContext context = new SpringApplicationBuilder( - StringToMapStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload())).isEqualTo("oleg"); - } - - @Test - public void stringToMapMessageStreamListener() { - ApplicationContext context = new SpringApplicationBuilder( - StringToMapMessageStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload())).isEqualTo("oleg"); - } - - @Test - public void withInternalPipeline() { - ApplicationContext context = new SpringApplicationBuilder(InternalPipeLine.class) - .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload())).isEqualTo("OLEG"); - } - - @Test - public void pojoToPojo() { - ApplicationContext context = new SpringApplicationBuilder( - PojoToPojoStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void pojoToString() { - ApplicationContext context = new SpringApplicationBuilder( - PojoToStringStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("oleg"); - } - - @Test - public void pojoToStringOutboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - PojoToStringStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.bindings.output.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.TEXT_PLAIN); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("oleg"); - } - - @Test - public void pojoToByteArray() { - ApplicationContext context = new SpringApplicationBuilder( - PojoToByteArrayStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("oleg"); - } - - @Test - public void pojoToByteArrayOutboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - PojoToByteArrayStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.bindings.output.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("oleg"); - } - - @Test - public void stringToPojoInboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - StringToPojoStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.bindings.input.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void typelessToPojoInboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessToPojoStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.bindings.input.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void typelessToPojoInboundContentTypeBindingJson() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessToPojoStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.bindings.input.contentType=application/json", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void typelessMessageToPojoInboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessMessageToPojoStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.cloud.stream.bindings.input.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void typelessMessageToPojoInboundContentTypeBindingJson() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessMessageToPojoStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.cloud.stream.bindings.input.contentType=application/json", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void typelessToPojoWithTextHeaderContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessToPojoStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) - .setHeader(MessageHeaders.CONTENT_TYPE, MimeType.valueOf("text/plain")) - .build()); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void typelessToPojoOutboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessToMessageStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.bindings.output.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) - .setHeader("contentType", new MimeType("text", "plain")).build()); - - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.TEXT_PLAIN); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void outboundMessageWithTextContentTypeOnly() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessToMessageTextOnlyContentTypeStreamListener.class) - .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) - .setHeader("contentType", new MimeType("text")).build()); - - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()) - .isEqualTo("text/plain"); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void stringToPojoInboundContentTypeHeader() { - ApplicationContext context = new SpringApplicationBuilder( - StringToPojoStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes(), - new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, - MimeTypeUtils.TEXT_PLAIN)))); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void byteArrayToPojoInboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - ByteArrayToPojoStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.bindings.input.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void byteArrayToPojoInboundContentTypeHeader() { - ApplicationContext context = new SpringApplicationBuilder( - StringToPojoStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes(), - new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, - MimeTypeUtils.TEXT_PLAIN)))); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void byteArrayToByteArray() { - ApplicationContext context = new SpringApplicationBuilder( - ByteArrayToByteArrayStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void byteArrayToByteArrayInboundOutboundContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - ByteArrayToByteArrayStreamListener.class).web(WebApplicationType.NONE) - .run("--spring.cloud.stream.bindings.input.contentType=text/plain", - "--spring.cloud.stream.bindings.output.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void pojoMessageToStringMessage() { - ApplicationContext context = new SpringApplicationBuilder( - PojoMessageToStringMessageStreamListener.class) - .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.TEXT_PLAIN); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("oleg"); - } - - @Test - public void pojoMessageToStringMessageServiceActivator() { - ApplicationContext context = new SpringApplicationBuilder( - PojoMessageToStringMessageServiceActivator.class) - .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.TEXT_PLAIN); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("oleg"); - } - - @Test - public void byteArrayMessageToStringJsonMessageStreamListener() { - ApplicationContext context = new SpringApplicationBuilder( - ByteArrayMessageToStringJsonMessageStreamListener.class) - .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("{\"name\":\"bob\"}"); - } - - @Test - public void byteArrayMessageToStringMessageStreamListener() { - ApplicationContext context = new SpringApplicationBuilder( - StringMessageToStringMessageStreamListener.class) - .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.TEXT_PLAIN); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("oleg"); - } - - @Test - public void customMessageConverter_defaultContentTypeBinding() { - ApplicationContext context = new SpringApplicationBuilder( - StringToStringStreamListener.class, CustomConverters.class) - .web(WebApplicationType.NONE) - .run("--spring.cloud.stream.default.contentType=foo/bar", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage).isNotNull(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo("FooBarMessageConverter"); - assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeType.valueOf("foo/bar")); - } - - // Failure tests - - @Test - public void _jsonToPojoWrongDefaultContentTypeProperty() { - ApplicationContext context = new SpringApplicationBuilder( - PojoToPojoStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.default.contentType=text/plain", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - TestChannelBinder binder = context.getBean(TestChannelBinder.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - assertThat(binder.getLastError().getPayload() instanceof MessagingException) - .isTrue(); - } - - @Test - @Ignore - public void _toStringDefaultContentTypePropertyUnknownContentType() { - ApplicationContext context = new SpringApplicationBuilder( - StringToStringStreamListener.class).web(WebApplicationType.NONE).run( - "--spring.cloud.stream.default.contentType=foo/bar", - "--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - TestChannelBinder binder = context.getBean(TestChannelBinder.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - assertThat( - binder.getLastError().getPayload() instanceof MessageConversionException) - .isTrue(); - } - - @Test - public void toCollectionWithParameterizedType() { - ApplicationContext context = new SpringApplicationBuilder( - CollectionWithParameterizedTypes.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "[{\"person\":{\"name\":\"jon\"},\"id\":123},{\"person\":{\"name\":\"jane\"},\"id\":456}]"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(outputMessage.getPayload()).isEqualTo(jsonPayload.getBytes()); - } - - // ====== - @Test - public void testWithMapInputParameter() { - ApplicationContext context = new SpringApplicationBuilder( - MapInputConfiguration.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void testWithMapPayloadParameter() { - ApplicationContext context = new SpringApplicationBuilder( - MapInputConfiguration.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void testWithListInputParameter() { - ApplicationContext context = new SpringApplicationBuilder( - ListInputConfiguration.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "[\"foo\",\"bar\"]"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void testWithMessageHeadersInputParameter() { - ApplicationContext context = new SpringApplicationBuilder( - MessageHeadersInputConfiguration.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "{\"name\":\"oleg\"}"; - source.send(new GenericMessage<>(jsonPayload.getBytes())); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isNotEqualTo(jsonPayload); - assertThat(outputMessage.getHeaders().containsKey(MessageHeaders.ID)).isTrue(); - assertThat(outputMessage.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)) - .isTrue(); - } - - @Test - public void testWithTypelessInputParameterAndOctetStream() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessPayloadConfiguration.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "[\"foo\",\"bar\"]"; - source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) - .setHeader(MessageHeaders.CONTENT_TYPE, - MimeTypeUtils.APPLICATION_OCTET_STREAM) - .build()); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void testWithTypelessInputParameterAndServiceActivator() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessPayloadConfigurationSA.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "[\"foo\",\"bar\"]"; - source.send(MessageBuilder.withPayload(jsonPayload.getBytes()).build()); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @Test - public void testWithTypelessMessageInputParameterAndServiceActivator() { - ApplicationContext context = new SpringApplicationBuilder( - TypelessMessageConfigurationSA.class).web(WebApplicationType.NONE) - .run("--spring.jmx.enabled=false"); - InputDestination source = context.getBean(InputDestination.class); - OutputDestination target = context.getBean(OutputDestination.class); - String jsonPayload = "[\"foo\",\"bar\"]"; - source.send(MessageBuilder.withPayload(jsonPayload.getBytes()).build()); - Message outputMessage = target.receive(); - assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) - .isEqualTo(jsonPayload); - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class CollectionWithParameterizedTypes { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public List> echo(List> value) { - assertThat(value.get(0) != null).isTrue(); - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TextInJsonOutListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Message echo(String value) { - return MessageBuilder.withPayload(value).setHeader( - MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class PojoToPojoStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Person echo(Person value) { - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class PojoToStringStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public String echo(Person value) { - return value.toString(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class PojoToByteArrayStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public byte[] echo(Person value) { - return value.toString().getBytes(StandardCharsets.UTF_8); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class ByteArrayToPojoStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Person echo(byte[] value) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(value, Person.class); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class StringToPojoStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Person echo(String value) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(value, Person.class); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TypelessToPojoStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Person echo(Object value) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - // assume it is string because CT is text/plain - return value instanceof byte[] - ? mapper.readValue((byte[]) value, Person.class) - : mapper.readValue((String) value, Person.class); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TypelessMessageToPojoStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Person echo(Message message) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - // assume it is string because CT is text/plain - return message.getPayload() instanceof byte[] - ? mapper.readValue((byte[]) message.getPayload(), Person.class) - : mapper.readValue((String) message.getPayload(), Person.class); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TypelessToMessageStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Message echo(Object value) throws Exception { - return MessageBuilder.withPayload(value.toString()) - .setHeader("contentType", new MimeType("text", "plain")).build(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TypelessToMessageTextOnlyContentTypeStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Message echo(Object value) throws Exception { - return MessageBuilder.withPayload(value.toString()) - .setHeader("contentType", new MimeType("text")).build(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class ByteArrayToByteArrayStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public byte[] echo(byte[] value) { - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class StringToStringStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public String echo(String value) { - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - /* - * Uncomment to test MBean name quoting for ":" in bean name component of ObjectName. - * Commented to avoid "InstanceAlreadyExistsException" in other tests. - */ - // @EnableIntegrationMBeanExport - public static class StringToMapStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public String echo(@Payload Map value) { - return (String) value.get("name"); - } - - @ServiceActivator(inputChannel = "input:foo.myGroup.errors") - public void error(Message message) { - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class StringToMapMessageStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public String echo(Message> value) { - assertThat(value.getPayload() instanceof Map).isTrue(); - return (String) value.getPayload().get("name"); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class PojoMessageToStringMessageStreamListener { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Message echo(Message value) { - return MessageBuilder.withPayload(value.getPayload().toString()) - .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) - .build(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class PojoMessageToStringMessageServiceActivator { - - @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) - public Message echo(Message value) { - return MessageBuilder.withPayload(value.getPayload().toString()) - .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) - .build(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class StringMessageToStringMessageStreamListener { - - @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) - public Message echo(Message value) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - Person person = mapper.readValue(value.getPayload(), Person.class); - return MessageBuilder.withPayload(person.toString()) - .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) - .build(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class ByteArrayMessageToStringJsonMessageStreamListener { - - @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) - public Message echo(Message value) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - Person person = mapper.readValue(value.getPayload(), Person.class); - person.setName("bob"); - String json = mapper.writeValueAsString(person); - return MessageBuilder.withPayload(json).build(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class InternalPipeLine { - - @StreamListener(Processor.INPUT) - @SendTo("internalChannel") - public String handleA(Person value) { - return "{\"name\":\"" + value.getName().toUpperCase() + "\"}"; - } - - @Bean - public MessageChannel internalChannel() { - return new DirectChannel(); - } - - @StreamListener("internalChannel") - @SendTo(Processor.OUTPUT) - public String handleB(Person value) { - return value.toString(); - } - - } - - public static class Employee

{ - - private P person; - - private int id; - - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - - public P getPerson() { - return this.person; - } - - public void setPerson(P person) { - this.person = person; - } - - } - - public static class Person { - - private String name; - - public Person() { - this(null); - } - - public Person(String name) { - this.name = name; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return this.name; - } - - } - - @Configuration - public static class CustomConverters { - - @Bean - public FooBarMessageConverter fooBarMessageConverter() { - return new FooBarMessageConverter(MimeType.valueOf("foo/bar")); - } - - @Bean - public AlwaysStringKryoMessageConverter kryoOverrideMessageConverter() { - return new AlwaysStringKryoMessageConverter( - MimeType.valueOf("application/x-java-object")); - } - - /** - * Even though this MessageConverter has nothing to do with Kryo it still shows - * how Kryo conversion can be customized/overriden since it simply overriding a - * converter for contentType 'application/x-java-object'. - * - */ - public static class AlwaysStringKryoMessageConverter - extends AbstractMessageConverter { - - public AlwaysStringKryoMessageConverter(MimeType supportedMimeType) { - super(supportedMimeType); - } - - @Override - protected boolean supports(Class clazz) { - return clazz == null || String.class.isAssignableFrom(clazz); - } - - @Override - protected Object convertFromInternal(Message message, Class targetClass, - @Nullable Object conversionHint) { - return this.getClass().getSimpleName(); - } - - @Override - protected Object convertToInternal(Object payload, - @Nullable MessageHeaders headers, @Nullable Object conversionHint) { - return ((String) payload).getBytes(StandardCharsets.UTF_8); - } - - } - - public static class FooBarMessageConverter extends AbstractMessageConverter { - - protected FooBarMessageConverter(MimeType supportedMimeType) { - super(supportedMimeType); - } - - @Override - protected boolean supports(Class clazz) { - return clazz != null && String.class.isAssignableFrom(clazz); - } - - @Override - protected Object convertFromInternal(Message message, Class targetClass, - @Nullable Object conversionHint) { - return this.getClass().getSimpleName(); - } - - @Override - protected Object convertToInternal(Object payload, - @Nullable MessageHeaders headers, @Nullable Object conversionHint) { - return ((String) payload).getBytes(StandardCharsets.UTF_8); - } - - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class MapInputConfiguration { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Map echo(Map value) throws Exception { - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class MapPayloadConfiguration { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Map echo(Message> value) throws Exception { - return value.getPayload(); - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class ListInputConfiguration { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public List echo(List value) throws Exception { - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class MessageHeadersInputConfiguration { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Map echo(MessageHeaders value) throws Exception { - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TypelessPayloadConfiguration { - - @StreamListener(Processor.INPUT) - @SendTo(Processor.OUTPUT) - public Object echo(Object value) throws Exception { - System.out.println(value); - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TypelessPayloadConfigurationSA { - - @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) - public Object echo(Object value) throws Exception { - System.out.println(value); - return value; - } - - } - - @EnableBinding(Processor.class) - @Import(TestChannelBinderConfiguration.class) - @EnableAutoConfiguration - public static class TypelessMessageConfigurationSA { - - @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) - public Object echo(Message value) throws Exception { - System.out.println(value.getPayload()); - return value.getPayload(); - } - - } - -} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java.todo b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java.todo new file mode 100644 index 000000000..2edf3f3f0 --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ContentTypeTckTests.java.todo @@ -0,0 +1,1163 @@ +///* +// * 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.tck; +// +//import java.nio.charset.StandardCharsets; +//import java.util.Collections; +//import java.util.List; +//import java.util.Map; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import org.junit.Ignore; +//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.OutputDestination; +//import org.springframework.cloud.stream.binder.test.TestChannelBinder; +//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.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.context.annotation.Import; +//import org.springframework.integration.annotation.ServiceActivator; +//import org.springframework.integration.channel.DirectChannel; +//import org.springframework.lang.Nullable; +//import org.springframework.messaging.Message; +//import org.springframework.messaging.MessageChannel; +//import org.springframework.messaging.MessageHeaders; +//import org.springframework.messaging.MessagingException; +//import org.springframework.messaging.converter.AbstractMessageConverter; +//import org.springframework.messaging.converter.MessageConversionException; +//import org.springframework.messaging.handler.annotation.Payload; +//import org.springframework.messaging.handler.annotation.SendTo; +//import org.springframework.messaging.support.GenericMessage; +//import org.springframework.messaging.support.MessageBuilder; +//import org.springframework.util.MimeType; +//import org.springframework.util.MimeTypeUtils; +// +//import static org.assertj.core.api.Assertions.assertThat; +// +///** +// * Sort of a TCK test suite to validate payload conversion is done properly by interacting +// * with binder's input/output destinations instead of its bridged channels. This means +// * that all payloads (sent/received) must be expressed in the wire format (byte[]) +// * +// * @author Oleg Zhurakousky +// * @author Gary Russell +// * +// */ +//public class ContentTypeTckTests { +// +// @Test +// public void stringToMapStreamListener() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringToMapStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload())).isEqualTo("oleg"); +// } +// +// @Test +// public void stringToMapMessageStreamListener() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringToMapMessageStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload())).isEqualTo("oleg"); +// } +// +// @Test +// public void withInternalPipeline() { +// ApplicationContext context = new SpringApplicationBuilder(InternalPipeLine.class) +// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload())).isEqualTo("OLEG"); +// } +// +// @Test +// public void pojoToPojo() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoToPojoStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void pojoToString() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoToStringStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("oleg"); +// } +// +// @Test +// public void pojoToStringOutboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoToStringStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.bindings.output.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.TEXT_PLAIN); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("oleg"); +// } +// +// @Test +// public void pojoToByteArray() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoToByteArrayStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("oleg"); +// } +// +// @Test +// public void pojoToByteArrayOutboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoToByteArrayStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.bindings.output.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("oleg"); +// } +// +// @Test +// public void stringToPojoInboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringToPojoStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.bindings.input.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void typelessToPojoInboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessToPojoStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.bindings.input.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void typelessToPojoInboundContentTypeBindingJson() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessToPojoStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.bindings.input.contentType=application/json", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void typelessMessageToPojoInboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessMessageToPojoStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.cloud.stream.bindings.input.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void typelessMessageToPojoInboundContentTypeBindingJson() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessMessageToPojoStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.cloud.stream.bindings.input.contentType=application/json", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void typelessToPojoWithTextHeaderContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessToPojoStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) +// .setHeader(MessageHeaders.CONTENT_TYPE, MimeType.valueOf("text/plain")) +// .build()); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void typelessToPojoOutboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessToMessageStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.bindings.output.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) +// .setHeader("contentType", new MimeType("text", "plain")).build()); +// +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.TEXT_PLAIN); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void outboundMessageWithTextContentTypeOnly() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessToMessageTextOnlyContentTypeStreamListener.class) +// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) +// .setHeader("contentType", new MimeType("text")).build()); +// +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()) +// .isEqualTo("text/plain"); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void stringToPojoInboundContentTypeHeader() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringToPojoStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes(), +// new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, +// MimeTypeUtils.TEXT_PLAIN)))); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void byteArrayToPojoInboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// ByteArrayToPojoStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.bindings.input.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void byteArrayToPojoInboundContentTypeHeader() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringToPojoStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes(), +// new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, +// MimeTypeUtils.TEXT_PLAIN)))); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void byteArrayToByteArray() { +// ApplicationContext context = new SpringApplicationBuilder( +// ByteArrayToByteArrayStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void byteArrayToByteArrayInboundOutboundContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// ByteArrayToByteArrayStreamListener.class).web(WebApplicationType.NONE) +// .run("--spring.cloud.stream.bindings.input.contentType=text/plain", +// "--spring.cloud.stream.bindings.output.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void pojoMessageToStringMessage() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoMessageToStringMessageStreamListener.class) +// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.TEXT_PLAIN); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("oleg"); +// } +// +// @Test +// public void pojoMessageToStringMessageServiceActivator() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoMessageToStringMessageServiceActivator.class) +// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.TEXT_PLAIN); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("oleg"); +// } +// +// @Test +// public void byteArrayMessageToStringJsonMessageStreamListener() { +// ApplicationContext context = new SpringApplicationBuilder( +// ByteArrayMessageToStringJsonMessageStreamListener.class) +// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.APPLICATION_JSON); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("{\"name\":\"bob\"}"); +// } +// +// @Test +// public void byteArrayMessageToStringMessageStreamListener() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringMessageToStringMessageStreamListener.class) +// .web(WebApplicationType.NONE).run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeTypeUtils.TEXT_PLAIN); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("oleg"); +// } +// +// @Test +// public void customMessageConverter_defaultContentTypeBinding() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringToStringStreamListener.class, CustomConverters.class) +// .web(WebApplicationType.NONE) +// .run("--spring.cloud.stream.default.contentType=foo/bar", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage).isNotNull(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo("FooBarMessageConverter"); +// assertThat(outputMessage.getHeaders().get(MessageHeaders.CONTENT_TYPE)) +// .isEqualTo(MimeType.valueOf("foo/bar")); +// } +// +// // Failure tests +// +// @Test +// public void _jsonToPojoWrongDefaultContentTypeProperty() { +// ApplicationContext context = new SpringApplicationBuilder( +// PojoToPojoStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.default.contentType=text/plain", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// TestChannelBinder binder = context.getBean(TestChannelBinder.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// assertThat(binder.getLastError().getPayload() instanceof MessagingException) +// .isTrue(); +// } +// +// @Test +// @Ignore +// public void _toStringDefaultContentTypePropertyUnknownContentType() { +// ApplicationContext context = new SpringApplicationBuilder( +// StringToStringStreamListener.class).web(WebApplicationType.NONE).run( +// "--spring.cloud.stream.default.contentType=foo/bar", +// "--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// TestChannelBinder binder = context.getBean(TestChannelBinder.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// assertThat( +// binder.getLastError().getPayload() instanceof MessageConversionException) +// .isTrue(); +// } +// +// @Test +// public void toCollectionWithParameterizedType() { +// ApplicationContext context = new SpringApplicationBuilder( +// CollectionWithParameterizedTypes.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "[{\"person\":{\"name\":\"jon\"},\"id\":123},{\"person\":{\"name\":\"jane\"},\"id\":456}]"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(outputMessage.getPayload()).isEqualTo(jsonPayload.getBytes()); +// } +// +// // ====== +// @Test +// public void testWithMapInputParameter() { +// ApplicationContext context = new SpringApplicationBuilder( +// MapInputConfiguration.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void testWithMapPayloadParameter() { +// ApplicationContext context = new SpringApplicationBuilder( +// MapInputConfiguration.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void testWithListInputParameter() { +// ApplicationContext context = new SpringApplicationBuilder( +// ListInputConfiguration.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "[\"foo\",\"bar\"]"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void testWithMessageHeadersInputParameter() { +// ApplicationContext context = new SpringApplicationBuilder( +// MessageHeadersInputConfiguration.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "{\"name\":\"oleg\"}"; +// source.send(new GenericMessage<>(jsonPayload.getBytes())); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isNotEqualTo(jsonPayload); +// assertThat(outputMessage.getHeaders().containsKey(MessageHeaders.ID)).isTrue(); +// assertThat(outputMessage.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)) +// .isTrue(); +// } +// +// @Test +// public void testWithTypelessInputParameterAndOctetStream() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessPayloadConfiguration.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "[\"foo\",\"bar\"]"; +// source.send(MessageBuilder.withPayload(jsonPayload.getBytes()) +// .setHeader(MessageHeaders.CONTENT_TYPE, +// MimeTypeUtils.APPLICATION_OCTET_STREAM) +// .build()); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void testWithTypelessInputParameterAndServiceActivator() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessPayloadConfigurationSA.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "[\"foo\",\"bar\"]"; +// source.send(MessageBuilder.withPayload(jsonPayload.getBytes()).build()); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @Test +// public void testWithTypelessMessageInputParameterAndServiceActivator() { +// ApplicationContext context = new SpringApplicationBuilder( +// TypelessMessageConfigurationSA.class).web(WebApplicationType.NONE) +// .run("--spring.jmx.enabled=false"); +// InputDestination source = context.getBean(InputDestination.class); +// OutputDestination target = context.getBean(OutputDestination.class); +// String jsonPayload = "[\"foo\",\"bar\"]"; +// source.send(MessageBuilder.withPayload(jsonPayload.getBytes()).build()); +// Message outputMessage = target.receive(); +// assertThat(new String(outputMessage.getPayload(), StandardCharsets.UTF_8)) +// .isEqualTo(jsonPayload); +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class CollectionWithParameterizedTypes { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public List> echo(List> value) { +// assertThat(value.get(0) != null).isTrue(); +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TextInJsonOutListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Message echo(String value) { +// return MessageBuilder.withPayload(value).setHeader( +// MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class PojoToPojoStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Person echo(Person value) { +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class PojoToStringStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public String echo(Person value) { +// return value.toString(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class PojoToByteArrayStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public byte[] echo(Person value) { +// return value.toString().getBytes(StandardCharsets.UTF_8); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class ByteArrayToPojoStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Person echo(byte[] value) throws Exception { +// ObjectMapper mapper = new ObjectMapper(); +// return mapper.readValue(value, Person.class); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class StringToPojoStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Person echo(String value) throws Exception { +// ObjectMapper mapper = new ObjectMapper(); +// return mapper.readValue(value, Person.class); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TypelessToPojoStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Person echo(Object value) throws Exception { +// ObjectMapper mapper = new ObjectMapper(); +// // assume it is string because CT is text/plain +// return value instanceof byte[] +// ? mapper.readValue((byte[]) value, Person.class) +// : mapper.readValue((String) value, Person.class); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TypelessMessageToPojoStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Person echo(Message message) throws Exception { +// ObjectMapper mapper = new ObjectMapper(); +// // assume it is string because CT is text/plain +// return message.getPayload() instanceof byte[] +// ? mapper.readValue((byte[]) message.getPayload(), Person.class) +// : mapper.readValue((String) message.getPayload(), Person.class); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TypelessToMessageStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Message echo(Object value) throws Exception { +// return MessageBuilder.withPayload(value.toString()) +// .setHeader("contentType", new MimeType("text", "plain")).build(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TypelessToMessageTextOnlyContentTypeStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Message echo(Object value) throws Exception { +// return MessageBuilder.withPayload(value.toString()) +// .setHeader("contentType", new MimeType("text")).build(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class ByteArrayToByteArrayStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public byte[] echo(byte[] value) { +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class StringToStringStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public String echo(String value) { +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// /* +// * Uncomment to test MBean name quoting for ":" in bean name component of ObjectName. +// * Commented to avoid "InstanceAlreadyExistsException" in other tests. +// */ +// // @EnableIntegrationMBeanExport +// public static class StringToMapStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public String echo(@Payload Map value) { +// return (String) value.get("name"); +// } +// +// @ServiceActivator(inputChannel = "input:foo.myGroup.errors") +// public void error(Message message) { +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class StringToMapMessageStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public String echo(Message> value) { +// assertThat(value.getPayload() instanceof Map).isTrue(); +// return (String) value.getPayload().get("name"); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class PojoMessageToStringMessageStreamListener { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Message echo(Message value) { +// return MessageBuilder.withPayload(value.getPayload().toString()) +// .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) +// .build(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class PojoMessageToStringMessageServiceActivator { +// +// @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) +// public Message echo(Message value) { +// return MessageBuilder.withPayload(value.getPayload().toString()) +// .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) +// .build(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class StringMessageToStringMessageStreamListener { +// +// @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) +// public Message echo(Message value) throws Exception { +// ObjectMapper mapper = new ObjectMapper(); +// Person person = mapper.readValue(value.getPayload(), Person.class); +// return MessageBuilder.withPayload(person.toString()) +// .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN) +// .build(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class ByteArrayMessageToStringJsonMessageStreamListener { +// +// @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) +// public Message echo(Message value) throws Exception { +// ObjectMapper mapper = new ObjectMapper(); +// Person person = mapper.readValue(value.getPayload(), Person.class); +// person.setName("bob"); +// String json = mapper.writeValueAsString(person); +// return MessageBuilder.withPayload(json).build(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class InternalPipeLine { +// +// @StreamListener(Processor.INPUT) +// @SendTo("internalChannel") +// public String handleA(Person value) { +// return "{\"name\":\"" + value.getName().toUpperCase() + "\"}"; +// } +// +// @Bean +// public MessageChannel internalChannel() { +// return new DirectChannel(); +// } +// +// @StreamListener("internalChannel") +// @SendTo(Processor.OUTPUT) +// public String handleB(Person value) { +// return value.toString(); +// } +// +// } +// +// public static class Employee

{ +// +// private P person; +// +// private int id; +// +// public int getId() { +// return this.id; +// } +// +// public void setId(int id) { +// this.id = id; +// } +// +// public P getPerson() { +// return this.person; +// } +// +// public void setPerson(P person) { +// this.person = person; +// } +// +// } +// +// public static class Person { +// +// private String name; +// +// public Person() { +// this(null); +// } +// +// public Person(String name) { +// this.name = name; +// } +// +// public String getName() { +// return this.name; +// } +// +// public void setName(String name) { +// this.name = name; +// } +// +// @Override +// public String toString() { +// return this.name; +// } +// +// } +// +// @Configuration +// public static class CustomConverters { +// +// @Bean +// public FooBarMessageConverter fooBarMessageConverter() { +// return new FooBarMessageConverter(MimeType.valueOf("foo/bar")); +// } +// +// @Bean +// public AlwaysStringKryoMessageConverter kryoOverrideMessageConverter() { +// return new AlwaysStringKryoMessageConverter( +// MimeType.valueOf("application/x-java-object")); +// } +// +// /** +// * Even though this MessageConverter has nothing to do with Kryo it still shows +// * how Kryo conversion can be customized/overriden since it simply overriding a +// * converter for contentType 'application/x-java-object'. +// * +// */ +// public static class AlwaysStringKryoMessageConverter +// extends AbstractMessageConverter { +// +// public AlwaysStringKryoMessageConverter(MimeType supportedMimeType) { +// super(supportedMimeType); +// } +// +// @Override +// protected boolean supports(Class clazz) { +// return clazz == null || String.class.isAssignableFrom(clazz); +// } +// +// @Override +// protected Object convertFromInternal(Message message, Class targetClass, +// @Nullable Object conversionHint) { +// return this.getClass().getSimpleName(); +// } +// +// @Override +// protected Object convertToInternal(Object payload, +// @Nullable MessageHeaders headers, @Nullable Object conversionHint) { +// return ((String) payload).getBytes(StandardCharsets.UTF_8); +// } +// +// } +// +// public static class FooBarMessageConverter extends AbstractMessageConverter { +// +// protected FooBarMessageConverter(MimeType supportedMimeType) { +// super(supportedMimeType); +// } +// +// @Override +// protected boolean supports(Class clazz) { +// return clazz != null && String.class.isAssignableFrom(clazz); +// } +// +// @Override +// protected Object convertFromInternal(Message message, Class targetClass, +// @Nullable Object conversionHint) { +// return this.getClass().getSimpleName(); +// } +// +// @Override +// protected Object convertToInternal(Object payload, +// @Nullable MessageHeaders headers, @Nullable Object conversionHint) { +// return ((String) payload).getBytes(StandardCharsets.UTF_8); +// } +// +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class MapInputConfiguration { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Map echo(Map value) throws Exception { +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class MapPayloadConfiguration { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Map echo(Message> value) throws Exception { +// return value.getPayload(); +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class ListInputConfiguration { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public List echo(List value) throws Exception { +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class MessageHeadersInputConfiguration { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Map echo(MessageHeaders value) throws Exception { +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TypelessPayloadConfiguration { +// +// @StreamListener(Processor.INPUT) +// @SendTo(Processor.OUTPUT) +// public Object echo(Object value) throws Exception { +// System.out.println(value); +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TypelessPayloadConfigurationSA { +// +// @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) +// public Object echo(Object value) throws Exception { +// System.out.println(value); +// return value; +// } +// +// } +// +// @EnableBinding(Processor.class) +// @Import(TestChannelBinderConfiguration.class) +// @EnableAutoConfiguration +// public static class TypelessMessageConfigurationSA { +// +// @ServiceActivator(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) +// public Object echo(Message value) throws Exception { +// System.out.println(value.getPayload()); +// return value.getPayload(); +// } +// +// } +// +//} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java deleted file mode 100644 index f7134c610..000000000 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java +++ /dev/null @@ -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; - } - - } - -} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java.todo b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java.todo new file mode 100644 index 000000000..848ec84ce --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/tck/ErrorHandlingTests.java.todo @@ -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; +// } +// +// } +// +//} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java deleted file mode 100644 index f6b0dfae3..000000000 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java +++ /dev/null @@ -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("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(); - - } - -} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java.todo b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java.todo new file mode 100644 index 000000000..0975cad92 --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binder/test/SampleStreamApp.java.todo @@ -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("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(); +// +// } +// +//} diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java index 523634436..7f225cc57 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/ImplicitFunctionBindingTests.java @@ -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 { From d7b4e07c4d9761d52228235271cbfcd5685fe1f8 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Fri, 7 Jan 2022 19:34:33 -0500 Subject: [PATCH 19/27] Docs cleanup for StreamListener --- README.adoc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.adoc b/README.adoc index 84d0b6668..d5b27d22c 100644 --- a/README.adoc +++ b/README.adoc @@ -191,18 +191,15 @@ compatibility you can still bring `spring-cloud-stream-reactive` from previous v [[spring-cloud-stream-preface-notable-deprecations]] -=== Notable Deprecations +=== Notable Deprecations and Removals -- Annotation-based programming model. Basically the @EnableBInding, @StreamListener and all related annotations are now deprecated in +- Annotation-based programming model is now fully removed. Basically the @EnableBInding, @StreamListener and all related annotations are now removed in favor of the functional programming model. See <> for more details. - _Reactive module_ (`spring-cloud-stream-reactive`) is discontinued and no longer distributed in favor of native support via spring-cloud-function. For backward compatibility you can still bring `spring-cloud-stream-reactive` from previous versions. - _Test support binder_ `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <> for more details. -- _@StreamMessageConverter_ - deprecated as it is no longer required. -- The `original-content-type` header references have been removed after it's been deprecated in v2.0. -This is primarily for function-based programming model. For StreamListener it would still be required and thus will stay until we deprecate and eventually discontinue StreamListener -and annotation-based programming model. +- _@StreamMessageConverter_ - Removed as it is no longer required. = Appendices [appendix] From a75a0d3e43611f728e76510792be035e804fa68e Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Mon, 10 Jan 2022 10:58:26 -0500 Subject: [PATCH 20/27] Ignore a test temporarily --- .../cloud/stream/function/SourceToFunctionsSupportTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java index c87c5de60..12b369cf8 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java @@ -100,7 +100,7 @@ public class SourceToFunctionsSupportTests { } @Test - @Disabled // fails intermittently + @Ignore // fails intermittently public void testFunctionsAreAppliedToExistingMessageSourceReactive() { try (ConfigurableApplicationContext context = new SpringApplicationBuilder( TestChannelBinderConfiguration.getCompleteConfiguration( From c755bfd9fdb0908a49aa93d161af40cb9a3ae811 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Mon, 10 Jan 2022 11:03:32 -0500 Subject: [PATCH 21/27] Checkstyle cleanup --- .../cloud/stream/function/SourceToFunctionsSupportTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java index 12b369cf8..e507f99b4 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/SourceToFunctionsSupportTests.java @@ -26,7 +26,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.rules.ExpectedException; import reactor.core.publisher.Flux; From 872edb2f24be8231badc84a62dfe6bb39dfbf4cd Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 14 Jan 2022 15:55:12 +0100 Subject: [PATCH 22/27] GH-2265 Add support for creating explicit bindings Resolves #2265 --- README.adoc | 30 ++++------ docs/src/main/asciidoc/_configprops.adoc | 4 +- .../main/asciidoc/spring-cloud-stream.adoc | 39 +++++++++++++ .../config/BindingServiceProperties.java | 40 ++++++++++++- .../function/FunctionConfiguration.java | 56 +++++++++++++----- .../stream/binding/ExplicitBindingTests.java | 58 +++++++++++++++++++ 6 files changed, 189 insertions(+), 38 deletions(-) create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java diff --git a/README.adoc b/README.adoc index d5b27d22c..ebcca497c 100644 --- a/README.adoc +++ b/README.adoc @@ -175,31 +175,21 @@ You can also build and package your application into a boot jar (by using `./mvn Now you have a working (albeit very basic) Spring Cloud Stream application. -== What's New in 3.x? +[[spel-and-streaming-data]] -[[spring-cloud-stream-preface-new-features]] -=== New Features and Enhancements +== Spring Expression Language (SpEL) in the context of Streaming data -- *Routing Function* - see <> for more details. -- *StreamBridge* - for dynamic destinations. See <> for more details. -- *Multiple bindings with functions* (multiple message handlers) - see <> for more details. -- *Functions with multiple inputs/outputs* (single function that can subscribe or target multiple destinations) - see <> for more details. -- *Native support for reactive programming* - since v3.0.0 we no longer distribute spring-cloud-stream-reactive modules and instead -relying on native reactive support provided by spring cloud function. For backward -compatibility you can still bring `spring-cloud-stream-reactive` from previous versions. +Throughout this reference manual you will encounter many features and examples where you can utilize Spring Expression Language (SpEL). It is important to understand certain limitations when it comes to using it. +SpEL gives you access to the current Message as well as the Application Context you are running in. +However it is important to understand what type of data SpEL can see especially in the context of the incoming Message. +From the broker, the message arrives in a form of a byte[]. It is then transformed to a `Message` by the binders where as you can see the payload of the message maintains its raw form. The headers of the message are ``, where values are typically another primitive or a collection/array of primitives, hence Object. +That is because binder does not know the required input type as it has no access to the user code (function). So effectively binder delivered an envelope with the payload and some readable meta-data in the form of message headers, just like the letter delivered by mail. +This means that while accessing payload of the message is possible you will only have access to it as raw data (i.e., byte[]). And while it may be very common for developers to ask for ability to have SpEL access to fields of a payload object as concrete type (e.g., Foo, Bar etc), you can see how difficult or even impossible would it be to achieve. +Here is one example to demonstrate the problem; Imagine you have a routing expression to route to different functions based on payload type. This requirement would imply payload conversion from byte[] to a specific type and then applying the SpEL. However, in order to perform such conversion we would need to know the actual type to pass to converter and that comes from function's signature which we don’t know which one. A better approach to solve this requirement would be to pass the type information as message headers (e.g., `application/json;type=foo.bar.Baz` ). You’ll get a clear readable String value that could be accessed and evaluated in a year and easy to read SpEL expression. -[[spring-cloud-stream-preface-notable-deprecations]] -=== Notable Deprecations and Removals - -- Annotation-based programming model is now fully removed. Basically the @EnableBInding, @StreamListener and all related annotations are now removed in -favor of the functional programming model. See <> for more details. -- _Reactive module_ (`spring-cloud-stream-reactive`) is discontinued and no longer distributed in favor of native support via spring-cloud-function. -For backward -compatibility you can still bring `spring-cloud-stream-reactive` from previous versions. -- _Test support binder_ `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <> for more details. -- _@StreamMessageConverter_ - Removed as it is no longer required. +Additionally it is considered very bad practice to use payload for routing decisions, since the payload is considered to be privileged data - data only to be read by its final recipient. Again, using the mail delivery analogy you would not want the mailman to open your envelope and read the contents of the letter to make some delivery decisions. The same concept applies here, especially when it is relatively easy to include such information when generating a Message. It enforces certain level of discipline related to the design of data to be transmitted over the network and which pieces of such data can be considered as public and which are privileged. = Appendices [appendix] diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 45e42323f..55f3103e3 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -9,6 +9,7 @@ |spring.cloud.stream.dynamic-destinations | `[]` | A list of destinations that can be bound dynamically. If set, only listed destinations can be bound. |spring.cloud.stream.function.batch-mode | `false` | |spring.cloud.stream.function.bindings | | +|spring.cloud.stream.input-bindings | | A semi-colon delimited string to explicitly define input bindings (specifically for cases when there is no implicit trigger to create such bindings such as Function, Supplier or Consumer). |spring.cloud.stream.instance-count | `1` | The number of deployed instances of an application. Default: 1. NOTE: Could also be managed per individual binding "spring.cloud.stream.bindings.foo.consumer.instance-count" where 'foo' is the name of the binding. |spring.cloud.stream.instance-index | `0` | The instance id of the application: a number from 0 to instanceCount-1. Used for partitioning and with Kafka. NOTE: Could also be managed per individual binding "spring.cloud.stream.bindings.foo.consumer.instance-index" where 'foo' is the name of the binding. |spring.cloud.stream.instance-index-list | | A list of instance id's from 0 to instanceCount-1. Used for partitioning and with Kafka. NOTE: Could also be managed per individual binding "spring.cloud.stream.bindings.foo.consumer.instance-index-list" where 'foo' is the name of the binding. This setting will override the one set in 'spring.cloud.stream.instance-index' @@ -18,9 +19,10 @@ |spring.cloud.stream.metrics.meter-filter | | Pattern to control the 'meters' one wants to capture. By default all 'meters' will be captured. For example, 'spring.integration.*' will only capture metric information for meters whose name starts with 'spring.integration'. |spring.cloud.stream.metrics.properties | | Application properties that should be added to the metrics payload For example: `spring.application**`. |spring.cloud.stream.metrics.schedule-interval | `60s` | Interval expressed as Duration for scheduling metrics snapshots publishing. Defaults to 60 seconds +|spring.cloud.stream.output-bindings | | A semi-colon delimited string to explicitly define output bindings (specifically for cases when there is no implicit trigger to create such bindings such as Function, Supplier or Consumer). |spring.cloud.stream.override-cloud-connectors | `false` | This property is only applicable when the cloud profile is active and Spring Cloud Connectors are provided with the application. If the property is false (the default), the binder detects a suitable bound service (for example, a RabbitMQ service bound in Cloud Foundry for the RabbitMQ binder) and uses it for creating connections (usually through Spring Cloud Connectors). When set to true, this property instructs binders to completely ignore the bound services and rely on Spring Boot properties (for example, relying on the spring.rabbitmq.* properties provided in the environment for the RabbitMQ binder). The typical usage of this property is to be nested in a customized environment when connecting to multiple systems. |spring.cloud.stream.pollable-source | `none` | A semi-colon delimited list of binding names of pollable sources. Binding names follow the same naming convention as functions. For example, name '...pollable-source=foobar' will be accessible as 'foobar-iin-0'' binding |spring.cloud.stream.sendto.destination | `none` | The name of the header used to determine the name of the output destination -|spring.cloud.stream.source | | A colon delimited string representing the names of the sources based on which source bindings will be created. This is primarily to support cases where source binding may be required without providing a corresponding Supplier. (e.g., for cases where the actual source of data is outside of scope of spring-cloud-stream - HTTP -> Stream) +|spring.cloud.stream.source | | A semi-colon delimited string representing the names of the sources based on which source bindings will be created. This is primarily to support cases where source binding may be required without providing a corresponding Supplier. (e.g., for cases where the actual source of data is outside of scope of spring-cloud-stream - HTTP -> Stream) @deprecated use {@link #outputBindings} |=== \ No newline at end of file diff --git a/docs/src/main/asciidoc/spring-cloud-stream.adoc b/docs/src/main/asciidoc/spring-cloud-stream.adoc index 678563d14..99a632239 100644 --- a/docs/src/main/asciidoc/spring-cloud-stream.adoc +++ b/docs/src/main/asciidoc/spring-cloud-stream.adoc @@ -336,6 +336,45 @@ where you are clearly correlating the input of `uppercase` function to `sample-t For more on properties and other configuration options please see <> section. [[spring-cloud-stream-overview-producing-consuming-messages]] + +===== Explicit binding creation + +In the previous section we explained how bindings are created implicitly driven by Function, Supplier or Consumer provided by your application. +However, there are times when you may need to create binding explicitly where bindings are not tied to any function. This is typically done to +support integrations with other frameworks (e.g., Spring Integration framework) where you may need direct access to the underlying `MessageChannel`. + +Spring Cloud Stream allows you to define input and output bindings explicitly via `spring.cloud.stream.input-bindings` and `spring.cloud.stream.output-bindings` +properties. Noticed the plural in the property names allowing you to define multiple bindings by simply using `;` as a delimiter. +Just look at the following test case as an example: + +---- +@Test +public void testExplicitBindings() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(EmptyConfiguration.class)) + .web(WebApplicationType.NONE) + .run("--spring.jmx.enabled=false", + "--spring.cloud.stream.input-bindings=fooin;barin", + "--spring.cloud.stream.output-bindings=fooout;barout")) { + + assertThat(context.getBean("fooin-in-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("barin-in-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("fooout-out-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("barout-out-0", MessageChannel.class)).isNotNull(); + } +} + +@EnableAutoConfiguration +@Configuration +public static class EmptyConfiguration { +} +---- + +As you can see we have declared two input bindings and two output bindings while our configuration had no functions defined, yet we were able to successfully create these bindings and access their corresponding channels. + +The rest of the binding rules that apply to implicit bindings apply here as well (for example, you can see that `fooin` turned into `fooin-in-0` binding/channel etc). + + === Producing and Consuming Messages You can write a Spring Cloud Stream application by simply writing functions and exposing them as `@Bean`s. diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java index fcebda21f..015d5dea1 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java @@ -61,12 +61,26 @@ public class BindingServiceProperties private static final int DEFAULT_BINDING_RETRY_INTERVAL = 30; /** - * A colon delimited string representing the names of the sources based on which source bindings will be created. + * A semi-colon delimited string representing the names of the sources based on which source bindings will be created. * This is primarily to support cases where source binding may be required without providing a corresponding Supplier. * (e.g., for cases where the actual source of data is outside of scope of spring-cloud-stream - HTTP -> Stream) + * @deprecated use {@link #outputBindings} */ + @Deprecated private String source; + /** + * A semi-colon delimited string to explicitly define input bindings (specifically for cases when there + * is no implicit trigger to create such bindings such as Function, Supplier or Consumer). + */ + private String inputBindings; + + /** + * A semi-colon delimited string to explicitly define output bindings (specifically for cases when there + * is no implicit trigger to create such bindings such as Function, Supplier or Consumer). + */ + private String outputBindings; + /** * The instance id of the application: a number from 0 to instanceCount-1. Used for * partitioning and with Kafka. NOTE: Could also be managed per individual binding @@ -302,10 +316,18 @@ public class BindingServiceProperties this.bindingRetryInterval = bindingRetryInterval; } + /** + * @deprecated in favor of {@link #getOutputBindings()} + */ + @Deprecated public String getSource() { return source; } + /** + * @deprecated in favor of {@link #setOutputBindings()} + */ + @Deprecated public void setSource(String source) { this.source = source; } @@ -325,6 +347,22 @@ public class BindingServiceProperties this.dynamicDestinationCacheSize = dynamicDestinationCacheSize; } + public String getInputBindings() { + return inputBindings; + } + + public void setInputBindings(String inputBindings) { + this.inputBindings = inputBindings; + } + + public String getOutputBindings() { + return outputBindings; + } + + public void setOutputBindings(String outputBindings) { + this.outputBindings = outputBindings; + } + /* * The "necessary" implies the scenario where only defaults are defined. */ diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java index fd0b7cd1f..0ab3784af 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java @@ -129,6 +129,8 @@ public class FunctionConfiguration { private final static String SOURCE_PROPERY = "spring.cloud.stream.source"; +// private final static String OUT_BINDINGS = "spring.cloud.stream.output-bindings"; + @Bean public StreamBridge streamBridgeUtils(FunctionCatalog functionCatalog, FunctionRegistry functionRegistry, BindingServiceProperties bindingServiceProperties, ConfigurableApplicationContext applicationContext, @@ -847,29 +849,51 @@ public class FunctionConfiguration { } } - if (StringUtils.hasText(this.environment.getProperty(SOURCE_PROPERY))) { - String[] sourceNames = this.environment.getProperty(SOURCE_PROPERY).split(";"); + this.createStandAloneBindingsIfNecessary(registry, applicationContext.getBean(BindingServiceProperties.class)); - for (String sourceName : sourceNames) { - FunctionInvocationWrapper sourceFunc = functionCatalog.lookup(sourceName); - - if (sourceFunc == null || //see https://github.com/spring-cloud/spring-cloud-stream/issues/2229 - (!sourceFunc.getFunctionDefinition().equals(sourceName) && applicationContext.containsBean(sourceName))) { - RootBeanDefinition functionBindableProxyDefinition = new RootBeanDefinition(BindableFunctionProxyFactory.class); - functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(sourceName); - functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(0); - functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(1); - functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.streamFunctionProperties); - registry.registerBeanDefinition(sourceName + "_binding", functionBindableProxyDefinition); - } - } - } } else { logger.info("Functional binding is disabled due to the presense of @EnableBinding annotation in your configuration"); } } + private void createStandAloneBindingsIfNecessary(BeanDefinitionRegistry registry, BindingServiceProperties bindingProperties) { + String[] inputBindings = StringUtils.hasText(bindingProperties.getInputBindings()) + ? bindingProperties.getInputBindings().split(";") : new String[0]; + + String[] outputBindings = StringUtils.hasText(bindingProperties.getSource()) ? bindingProperties.getSource().split(";") : ( + StringUtils.hasText(bindingProperties.getOutputBindings()) ? bindingProperties.getOutputBindings().split(";") : new String[0] + ); + for (String inputBindingName : inputBindings) { + FunctionInvocationWrapper sourceFunc = functionCatalog.lookup(inputBindingName); + + if (sourceFunc == null || //see https://github.com/spring-cloud/spring-cloud-stream/issues/2229 + (!sourceFunc.getFunctionDefinition().equals(inputBindingName) && applicationContext.containsBean(inputBindingName))) { + RootBeanDefinition functionBindableProxyDefinition = new RootBeanDefinition(BindableFunctionProxyFactory.class); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(inputBindingName); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(1); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(0); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.streamFunctionProperties); + registry.registerBeanDefinition(inputBindingName + "_binding", functionBindableProxyDefinition); + } + } + + for (String outputBindingName : outputBindings) { + FunctionInvocationWrapper sourceFunc = functionCatalog.lookup(outputBindingName); + + if (sourceFunc == null || //see https://github.com/spring-cloud/spring-cloud-stream/issues/2229 + (!sourceFunc.getFunctionDefinition().equals(outputBindingName) && applicationContext.containsBean(outputBindingName))) { + RootBeanDefinition functionBindableProxyDefinition = new RootBeanDefinition(BindableFunctionProxyFactory.class); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(outputBindingName); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(0); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(1); + functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.streamFunctionProperties); + registry.registerBeanDefinition(outputBindingName + "_binding", functionBindableProxyDefinition); + } + } + + } + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = (ConfigurableApplicationContext) applicationContext; diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java new file mode 100644 index 000000000..5ab5f9bd5 --- /dev/null +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.binding; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.MessageChannel; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * + */ +public class ExplicitBindingTests { + + @Test + public void testExplicitBindings() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(EmptyConfiguration.class)) + .web(WebApplicationType.NONE) + .run("--spring.jmx.enabled=false", + "--spring.cloud.stream.input-bindings=fooin;barin", + "--spring.cloud.stream.output-bindings=fooout;barout")) { + + assertThat(context.getBean("fooin-in-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("barin-in-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("fooout-out-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("barout-out-0", MessageChannel.class)).isNotNull(); + } + } + + @EnableAutoConfiguration + @Configuration + public static class EmptyConfiguration { + + } +} From d3e5459c2cf30ba930b03143b6fa268d985a3b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NUSSBAUMER?= Date: Mon, 17 Jan 2022 08:36:08 +0100 Subject: [PATCH 23/27] GH-2266 Add test to ensure GlobalChannelInterceptor is not added multiple times to the MessageChannels --- .../stream/function/StreamBridgeTests.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java index b314e2a23..67ff492d9 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java @@ -41,6 +41,7 @@ import org.springframework.cloud.stream.binding.NewDestinationBindingCallback; import org.springframework.cloud.stream.config.BindingServiceProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.integration.channel.AbstractMessageChannel; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.config.GlobalChannelInterceptor; import org.springframework.integration.dsl.IntegrationFlow; @@ -185,6 +186,27 @@ public class StreamBridgeTests { } } + @Test + public void testInterceptorIsNotAddedMultipleTimesToTheMessageChannel() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration + .getCompleteConfiguration(InterceptorConfiguration.class)) + .web(WebApplicationType.NONE).run( + "--spring.jmx.enabled=false", + "--spring.cloud.stream.dynamic-destination-cache-size=1", + "--spring.cloud.stream.source=outputA;outputB", + "--spring.cloud.stream.bindings.outputA-out-0.destination=outputA", + "--spring.cloud.stream.bindings.outputB-out-0.destination=outputB")) { + StreamBridge bridge = context.getBean(StreamBridge.class); + + bridge.send("outputA-out-0", "hello foo"); + bridge.send("outputA-out-0", "hello foo"); + + AbstractMessageChannel messageChannel = context.getBean("outputA-out-0", AbstractMessageChannel.class); + + assertThat(messageChannel.getInterceptors()).hasSize(1); + } + } + @Test public void testWithInterceptorsRegisteredOnlyOnOutputChannel() throws InterruptedException { try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration From 19de19d8bcd318d6f30ce4205f2221bca4ad0a11 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 18 Jan 2022 10:32:12 +0100 Subject: [PATCH 24/27] GH-2266 Fix channel interceptor application for dynamic destinations Resolves #2266 Resolves #2269 --- .../cloud/stream/binding/MessageConverterConfigurer.java | 4 +++- .../cloud/stream/config/BindingServiceProperties.java | 5 +++++ .../cloud/stream/function/FunctionConfiguration.java | 2 -- .../springframework/cloud/stream/function/StreamBridge.java | 1 - .../cloud/stream/function/StreamBridgeTests.java | 4 +++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java index d9355de8f..15c3f481f 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binding/MessageConverterConfigurer.java @@ -147,7 +147,9 @@ public class MessageConverterConfigurer ProducerProperties producerProperties = bindingProperties.getProducer(); boolean partitioned = !inbound && producerProperties != null && producerProperties.isPartitioned(); boolean functional = streamFunctionProperties != null - && (StringUtils.hasText(streamFunctionProperties.getDefinition()) || StringUtils.hasText(bindingServiceProperties.getSource())); + && (StringUtils.hasText(streamFunctionProperties.getDefinition()) + || StringUtils.hasText(bindingServiceProperties.getInputBindings()) + || StringUtils.hasText(bindingServiceProperties.getOutputBindings())); if (partitioned) { if (inbound || !functional) { diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java index 015d5dea1..a4698fabc 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/BindingServiceProperties.java @@ -44,6 +44,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.integration.support.utils.IntegrationUtils; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * @author Dave Syer @@ -330,6 +331,7 @@ public class BindingServiceProperties @Deprecated public void setSource(String source) { this.source = source; + this.outputBindings = source; } public void updateProducerProperties(String bindingName, @@ -356,10 +358,13 @@ public class BindingServiceProperties } public String getOutputBindings() { + return outputBindings; } public void setOutputBindings(String outputBindings) { + Assert.state(!StringUtils.hasText(this.source), "Setting 'source' and 'output-binding' is not allowed " + + "because 'source' is deprecated in favor of 'output-binding'."); this.outputBindings = outputBindings; } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java index 0ab3784af..12ccab77d 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java @@ -129,8 +129,6 @@ public class FunctionConfiguration { private final static String SOURCE_PROPERY = "spring.cloud.stream.source"; -// private final static String OUT_BINDINGS = "spring.cloud.stream.output-bindings"; - @Bean public StreamBridge streamBridgeUtils(FunctionCatalog functionCatalog, FunctionRegistry functionRegistry, BindingServiceProperties bindingServiceProperties, ConfigurableApplicationContext applicationContext, diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java index 155c02724..9ea88d556 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java @@ -256,7 +256,6 @@ public final class StreamBridge implements SmartInitializingSingleton { SubscribableChannel messageChannel = this.channelCache.get(destinationName); if (messageChannel == null && this.applicationContext.containsBean(destinationName)) { messageChannel = this.applicationContext.getBean(destinationName, SubscribableChannel.class); - this.addInterceptors((AbstractMessageChannel) messageChannel, destinationName); } if (messageChannel == null) { messageChannel = new DirectWithAttributesChannel(); diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java index 67ff492d9..546538440 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java @@ -193,11 +193,13 @@ public class StreamBridgeTests { .web(WebApplicationType.NONE).run( "--spring.jmx.enabled=false", "--spring.cloud.stream.dynamic-destination-cache-size=1", - "--spring.cloud.stream.source=outputA;outputB", + "--spring.cloud.stream.output-bindings=outputA;outputB", "--spring.cloud.stream.bindings.outputA-out-0.destination=outputA", "--spring.cloud.stream.bindings.outputB-out-0.destination=outputB")) { StreamBridge bridge = context.getBean(StreamBridge.class); + bridge.send("outputA-out-0", "hello foo"); + bridge.send("outputA-out-0", "hello foo"); bridge.send("outputA-out-0", "hello foo"); bridge.send("outputA-out-0", "hello foo"); From 72820c1544e32b90813864e58c97244cf384828e Mon Sep 17 00:00:00 2001 From: Mandy Neumann Date: Tue, 18 Jan 2022 09:51:33 +0100 Subject: [PATCH 25/27] Fix some spelling errors --- docs/src/main/asciidoc/spring-cloud-stream.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-stream.adoc b/docs/src/main/asciidoc/spring-cloud-stream.adoc index 99a632239..8cd76835e 100644 --- a/docs/src/main/asciidoc/spring-cloud-stream.adoc +++ b/docs/src/main/asciidoc/spring-cloud-stream.adoc @@ -704,13 +704,13 @@ public class WebSourceApplication { @RequestMapping @ResponseStatus(HttpStatus.ACCEPTED) public void delegateToSupplier(@RequestBody String body) { - streamBridge.send("myBinidng", body); + streamBridge.send("myBinding", body); } } ---- -As you can see inside of `delegateToSupplier` method we're using StreamBridge to send data to `myBinidng` binding. And here you're also benefiting from -the dynamic features of `StreamBridge` where if `myBinidng` doesn't exist it will be created automatically and cached, otherwise existing binding will be used. +As you can see inside of `delegateToSupplier` method we're using StreamBridge to send data to `myBinding` binding. And here you're also benefiting from +the dynamic features of `StreamBridge` where if `myBinding` doesn't exist it will be created automatically and cached, otherwise existing binding will be used. NOTE: Caching dynamic destinations (bindings) could result in memory leaks in the event there are many dynamic destinations. To have some level of control we provide a self-evicting caching mechanism for output bindings with default cache size of 10. This means that if your dynamic destination size goes above that number, there is a possibility that an existing binding will be evicted and thus would need to be recreated which could cause minor performance degradation. You can increase the cache size via `spring.cloud.stream.dynamic-destination-cache-size` property setting it to the desired value. From b8a70edc21603cac138c0b0e6bde93c4a37c40e1 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 18 Jan 2022 11:51:22 +0100 Subject: [PATCH 26/27] GH-2268 Ensure StreamBridge works with nullChannel Resolves #2268 --- .../stream/function/FunctionConfiguration.java | 2 +- .../cloud/stream/function/StreamBridge.java | 16 ++++++++-------- .../cloud/stream/function/StreamBridgeTests.java | 12 ++++++++++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java index 12ccab77d..04178e058 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java @@ -601,7 +601,7 @@ public class FunctionConfiguration { private void doSendMessage(Object result, Message requestMessage) { if (result instanceof Message && ((Message) result).getHeaders().get("spring.cloud.stream.sendto.destination") != null) { String destinationName = (String) ((Message) result).getHeaders().get("spring.cloud.stream.sendto.destination"); - SubscribableChannel outputChannel = streamBridge.resolveDestination(destinationName, producerProperties, null); + MessageChannel outputChannel = streamBridge.resolveDestination(destinationName, producerProperties, null); if (logger.isInfoEnabled()) { logger.info("Output message is sent to '" + destinationName + "' destination"); } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java index 9ea88d556..2d259e07b 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/StreamBridge.java @@ -47,7 +47,7 @@ import org.springframework.integration.config.GlobalChannelInterceptorProcessor; import org.springframework.integration.support.MessageBuilder; import org.springframework.lang.Nullable; import org.springframework.messaging.Message; -import org.springframework.messaging.SubscribableChannel; +import org.springframework.messaging.MessageChannel; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.StringUtils; @@ -76,7 +76,7 @@ public final class StreamBridge implements SmartInitializingSingleton { private final Log logger = LogFactory.getLog(getClass()); - private final Map channelCache; + private final Map channelCache; private final FunctionCatalog functionCatalog; @@ -111,9 +111,9 @@ public final class StreamBridge implements SmartInitializingSingleton { this.applicationContext = applicationContext; this.bindingServiceProperties = bindingServiceProperties; this.destinationBindingCallback = destinationBindingCallback; - this.channelCache = new LinkedHashMap() { + this.channelCache = new LinkedHashMap() { @Override - protected boolean removeEldestEntry(Map.Entry eldest) { + protected boolean removeEldestEntry(Map.Entry eldest) { boolean remove = size() > bindingServiceProperties.getDynamicDestinationCacheSize(); if (remove && logger.isDebugEnabled()) { logger.debug("Removing message channel from cache " + eldest.getKey()); @@ -207,7 +207,7 @@ public final class StreamBridge implements SmartInitializingSingleton { data = MessageBuilder.withPayload(data).build(); } ProducerProperties producerProperties = this.bindingServiceProperties.getProducerProperties(bindingName); - SubscribableChannel messageChannel = this.resolveDestination(bindingName, producerProperties, binderName); + MessageChannel messageChannel = this.resolveDestination(bindingName, producerProperties, binderName); Function functionToInvoke = this.getStreamBridgeFunction(outputContentType.toString(), producerProperties); @@ -252,10 +252,10 @@ public final class StreamBridge implements SmartInitializingSingleton { } @SuppressWarnings({ "unchecked", "rawtypes"}) - synchronized SubscribableChannel resolveDestination(String destinationName, ProducerProperties producerProperties, String binderName) { - SubscribableChannel messageChannel = this.channelCache.get(destinationName); + synchronized MessageChannel resolveDestination(String destinationName, ProducerProperties producerProperties, String binderName) { + MessageChannel messageChannel = this.channelCache.get(destinationName); if (messageChannel == null && this.applicationContext.containsBean(destinationName)) { - messageChannel = this.applicationContext.getBean(destinationName, SubscribableChannel.class); + messageChannel = this.applicationContext.getBean(destinationName, MessageChannel.class); } if (messageChannel == null) { messageChannel = new DirectWithAttributesChannel(); diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java index 546538440..47d70d09f 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/function/StreamBridgeTests.java @@ -186,6 +186,18 @@ public class StreamBridgeTests { } } + @Test // validate that there is no exception thrown when sending to null channel + public void test_2268() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration + .getCompleteConfiguration(InterceptorConfiguration.class)) + .web(WebApplicationType.NONE).run( + "--spring.jmx.enabled=false")) { + StreamBridge bridge = context.getBean(StreamBridge.class); + + bridge.send("nullChannel", "blah"); + } + } + @Test public void testInterceptorIsNotAddedMultipleTimesToTheMessageChannel() { try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration From 19b71fb6ca6f5d4ee24103dc55d6a315d0d6b33e Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 18 Jan 2022 14:07:50 +0100 Subject: [PATCH 27/27] GH-2265 Fix support for creating explicit bindings This commit ensures that in the event there is a Consumer or Supplier the output-bidning or input-binding is still created Resolves #2265 --- .../function/FunctionConfiguration.java | 21 +++++--- .../stream/binding/ExplicitBindingTests.java | 50 +++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java index 04178e058..e5205d9a3 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/function/FunctionConfiguration.java @@ -185,6 +185,9 @@ public class FunctionConfiguration { if (functionWrapper != null && functionWrapper.isSupplier()) { // gather output content types List contentTypes = new ArrayList(); + if (proxyFactory.getOutputs().size() == 0) { + return; + } Assert.isTrue(proxyFactory.getOutputs().size() == 1, "Supplier with multiple outputs is not supported at the moment."); String outputName = proxyFactory.getOutputs().iterator().next(); @@ -552,11 +555,13 @@ public class FunctionConfiguration { } else { String outputDestinationName = this.determineOutputDestinationName(0, bindableProxyFactory, functionType); - String inputDestinationName = inputBindingNames.iterator().next(); - Object inputDestination = this.applicationContext.getBean(inputDestinationName); - if (inputDestination != null && inputDestination instanceof SubscribableChannel) { - AbstractMessageHandler handler = createFunctionHandler(function, inputDestinationName, outputDestinationName); - ((SubscribableChannel) inputDestination).subscribe(handler); + if (!ObjectUtils.isEmpty(inputBindingNames)) { + String inputDestinationName = inputBindingNames.iterator().next(); + Object inputDestination = this.applicationContext.getBean(inputDestinationName); + if (inputDestination != null && inputDestination instanceof SubscribableChannel) { + AbstractMessageHandler handler = createFunctionHandler(function, inputDestinationName, outputDestinationName); + ((SubscribableChannel) inputDestination).subscribe(handler); + } } } } @@ -866,13 +871,14 @@ public class FunctionConfiguration { FunctionInvocationWrapper sourceFunc = functionCatalog.lookup(inputBindingName); if (sourceFunc == null || //see https://github.com/spring-cloud/spring-cloud-stream/issues/2229 + sourceFunc.isSupplier() || (!sourceFunc.getFunctionDefinition().equals(inputBindingName) && applicationContext.containsBean(inputBindingName))) { RootBeanDefinition functionBindableProxyDefinition = new RootBeanDefinition(BindableFunctionProxyFactory.class); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(inputBindingName); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(1); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(0); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.streamFunctionProperties); - registry.registerBeanDefinition(inputBindingName + "_binding", functionBindableProxyDefinition); + registry.registerBeanDefinition(inputBindingName + "_binding_in", functionBindableProxyDefinition); } } @@ -880,13 +886,14 @@ public class FunctionConfiguration { FunctionInvocationWrapper sourceFunc = functionCatalog.lookup(outputBindingName); if (sourceFunc == null || //see https://github.com/spring-cloud/spring-cloud-stream/issues/2229 + sourceFunc.isConsumer() || (!sourceFunc.getFunctionDefinition().equals(outputBindingName) && applicationContext.containsBean(outputBindingName))) { RootBeanDefinition functionBindableProxyDefinition = new RootBeanDefinition(BindableFunctionProxyFactory.class); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(outputBindingName); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(0); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(1); functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.streamFunctionProperties); - registry.registerBeanDefinition(outputBindingName + "_binding", functionBindableProxyDefinition); + registry.registerBeanDefinition(outputBindingName + "_binding_out", functionBindableProxyDefinition); } } diff --git a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java index 5ab5f9bd5..8d3788844 100644 --- a/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java +++ b/spring-cloud-stream/src/test/java/org/springframework/cloud/stream/binding/ExplicitBindingTests.java @@ -16,6 +16,9 @@ package org.springframework.cloud.stream.binding; +import java.util.function.Consumer; +import java.util.function.Supplier; + import org.junit.jupiter.api.Test; import org.springframework.boot.WebApplicationType; @@ -23,6 +26,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.MessageChannel; @@ -50,9 +54,55 @@ public class ExplicitBindingTests { } } + @Test + public void testExplicitBindingsWithExistingConsumer() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(ConsumerConfiguration.class)) + .web(WebApplicationType.NONE) + .run("--spring.jmx.enabled=false", + "--spring.cloud.stream.output-bindings=consume")) { + + assertThat(context.getBean("consume-in-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("consume-out-0", MessageChannel.class)).isNotNull(); + } + } + + @Test + public void testExplicitBindingsWithExistingSupplier() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(SupplierConfiguration.class)) + .web(WebApplicationType.NONE) + .run("--spring.jmx.enabled=false", + "--spring.cloud.stream.input-bindings=supply")) { + + assertThat(context.getBean("supply-in-0", MessageChannel.class)).isNotNull(); + assertThat(context.getBean("supply-out-0", MessageChannel.class)).isNotNull(); + } + } + @EnableAutoConfiguration @Configuration public static class EmptyConfiguration { } + + @EnableAutoConfiguration + @Configuration + public static class ConsumerConfiguration { + + @Bean + public Consumer consume() { + return System.out::println; + } + } + + @EnableAutoConfiguration + @Configuration + public static class SupplierConfiguration { + + @Bean + public Supplier supply() { + return () -> "hello"; + } + } }