From 171f034a8c2dd7c89123170ffcd9cb5012c3a2f9 Mon Sep 17 00:00:00 2001 From: Vinicius Carvalho Date: Tue, 5 Sep 2017 13:45:21 -0400 Subject: [PATCH] Content type redesign Fixes #992, #1050, #1051, #1052 Adding custom jackson converter with some tests Adds kryo message converter to replace codec Checkstyle changes Removing codec support - Removed codec dependency from AbstractBinder - MessageSerializationUtils is almost an empty shell for now, just to keep code compiling until we get EmbeddedHeaders interceptors - Updated Kryo tests Removing codec module from build Added a new Annotation for custom converters '@StreamConverter' Fixed some tests with new expected behavior Moved broken tests to a temporary package to keep track of progress Fixed KryoConverter to fail based on headers Fixed a couple of more tests Making converters strict to only convert their corresponding contentType Bypassing conversion for ErrorMessages * Configuring SI ConfigurableCompositeMessageConverter - Moved ContentType related beans into separate configuration - Configured SI ConfigurableCompositeMessageConverter to use same converters as Stream does (for ServiceActivator) - TupleConverter should return byte[] as all other converters - Fixed tests * Fixes tests - Revert to Boot 2.0.0.M3. Snapshots breaking actuator - Checkstyle fixes - Disable JsonUnmarshalling as a catch all converter Fixing Schema tests Fixing Metrics tests Fixing reactive tests applying checkstyle fixes * Adding new content type tests - Fixed ContentTypeInterceptor misusage of default mimeType Changing contentType doc section Improving doc section Last minute polish Fixing BinderTests to use bytes to compare messages Applied changes to Base Binders test to use the new contentType handling mechanism PR review fixes Renaming StreamConverter -> StreamMessageConverter --- pom.xml | 3 +- .../stream/binder/AbstractBinderTests.java | 130 ++--- .../binder/PartitionCapableBinderTests.java | 45 +- .../MessageChannelBinderSupportTests.java | 265 ----------- spring-cloud-stream-codec/pom.xml | 41 -- .../kryo/KryoCodecAutoConfiguration.java | 64 --- .../codec/kryo/KryoCodecProperties.java | 39 -- .../main/resources/META-INF/spring.factories | 2 - .../spring-cloud-stream-overview.adoc | 234 +++++---- .../ContentTypeOutboundSourceTests.java | 12 +- .../config/CustomHeaderPropagationTests.java | 14 +- .../config/CustomMessageConverterTests.java | 11 +- .../config/DefaultHeaderPropagationTests.java | 8 +- ...ionWithApplicationProvidedHeaderTests.java | 12 +- .../DeserializeJSONToJavaTypeTests.java | 4 +- .../InboundJsonToTupleConversionTest.java | 6 +- .../config/MessageChannelConfigurerTests.java | 8 +- .../StreamListenerHandlerBeanTests.java | 10 +- .../StreamListenerHandlerMethodTests.java | 87 ++-- .../StreamListenerMessageArgumentTests.java | 7 +- ...stenerMethodReturnWithConversionTests.java | 29 +- ...mListenerMethodWithReturnMessageTests.java | 12 +- ...eamListenerMethodWithReturnValueTests.java | 11 +- .../config/StreamListenerTestUtils.java | 16 + ...enerWithAnnotatedInputOutputArgsTests.java | 11 +- .../config/TextPlainConversionTest.java | 12 +- .../config/TextPlainToJsonConversionTest.java | 16 +- .../aggregate/AggregateApplicationTests.java | 5 +- .../aggregate/processor/TestProcessor.java | 7 +- .../config/aggregate/source/TestSource.java | 5 +- .../config/contentType/ContentTypeTests.java | 445 ++++++++++++++++++ .../partitioned-configurers.properties | 1 + .../ApplicationMetricsExporterTests.java | 18 +- .../reactive/StreamEmitterBasicTests.java | 87 ++-- ...icFluxInputOutputArgsWithMessageTests.java | 37 +- ...mListenerReactiveInputOutputArgsTests.java | 9 +- ...activeInputOutputArgsWithMessageTests.java | 9 +- ...utOutputArgsWithSenderAndFailureTests.java | 9 +- ...eactiveInputOutputArgsWithSenderTests.java | 9 +- .../StreamListenerReactiveMethodTests.java | 10 +- ...enerReactiveMethodWithReturnTypeTests.java | 9 +- ...istenerReactiveReturnWithFailureTests.java | 9 +- ...istenerReactiveReturnWithMessageTests.java | 9 +- ...amListenerReactiveReturnWithPojoTests.java | 18 +- ...rdFluxInputOutputArgsWithMessageTests.java | 6 +- ...AvroMessageConverterAutoConfiguration.java | 3 + .../avro/AvroSchemaMessageConverterTests.java | 3 + ...maRegistryClientMessageConverterTests.java | 2 + .../aggregate/bean/AggregateWithBeanTest.java | 6 +- .../aggregate/main/AggregateWithMainTest.java | 6 +- .../AutoconfigurationDisabledTest.java | 8 +- .../stream/test/example/ExampleTest.java | 6 +- .../src/main/resources/checkstyle.xml | 9 +- .../annotation/StreamMessageConverter.java | 36 ++ .../cloud/stream/binder/AbstractBinder.java | 21 +- .../binder/AbstractMessageChannelBinder.java | 6 +- .../binder/MessageSerializationUtils.java | 111 +---- .../binding/MessageConverterConfigurer.java | 176 +++---- .../stream/config/BindingProperties.java | 3 +- .../config/BindingServiceConfiguration.java | 25 +- .../config/ContentTypeConfiguration.java | 64 +++ .../CompositeMessageConverterFactory.java | 6 +- ...CustomJackson2MappingMessageConverter.java | 54 +++ .../converter/JsonUnmarshallingConverter.java | 26 +- .../converter/KryoMessageConverter.java | 249 ++++++++++ .../ObjectStringMessageConverter.java | 5 +- .../converter/TupleJsonMessageConverter.java | 5 +- .../BinderAwareChannelResolverTests.java | 2 +- .../stream/binder/ErrorBindingTests.java | 4 +- ...ertiesBinderAwareChannelResolverTests.java | 2 +- .../MessageConverterConfigurerTests.java | 5 +- ...mMappingJackson2MessageConverterTests.java | 63 +++ .../converter/KryoMessageConverterTests.java | 87 ++++ .../BoundChannelsInterceptedTest.java | 7 +- 74 files changed, 1745 insertions(+), 1066 deletions(-) delete mode 100644 spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java delete mode 100644 spring-cloud-stream-codec/pom.xml delete mode 100644 spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecAutoConfiguration.java delete mode 100644 spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecProperties.java delete mode 100644 spring-cloud-stream-codec/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/annotation/StreamMessageConverter.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/ContentTypeConfiguration.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/CustomJackson2MappingMessageConverter.java create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/KryoMessageConverter.java create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/converter/CustomMappingJackson2MessageConverterTests.java create mode 100644 spring-cloud-stream/src/test/java/org/springframework/cloud/stream/converter/KryoMessageConverterTests.java diff --git a/pom.xml b/pom.xml index 9e74055a9..1fdf6565b 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.springframework.cloud spring-cloud-build - 2.0.0.BUILD-SNAPSHOT + 2.0.0.M2 @@ -110,7 +110,6 @@ spring-cloud-stream spring-cloud-stream-binder-test - spring-cloud-stream-codec spring-cloud-stream-rxjava spring-cloud-stream-test-support spring-cloud-stream-test-support-internal 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 389c3f826..f68361aa8 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 @@ -79,7 +79,8 @@ public abstract class AbstractBinderTests receive(PollableChannel channel, int additionalMultiplier) { long startTime = System.currentTimeMillis(); - Message receive = channel.receive((int) (1000 * timeoutMultiplier * additionalMultiplier)); + Message receive = channel + .receive((int) (1000 * timeoutMultiplier * additionalMultiplier)); long elapsed = System.currentTimeMillis() - startTime; logger.debug("receive() took " + elapsed / 1000 + " seconds"); return receive; @@ -88,53 +89,63 @@ public abstract class AbstractBinderTests foo0ProducerBinding = binder.bindProducer("foo.0", new DirectChannel(), - createProducerProperties()); - Binding foo0ConsumerBinding = binder.bindConsumer("foo.0", "testClean", new DirectChannel(), - createConsumerProperties()); - Binding foo1ProducerBinding = binder.bindProducer("foo.1", new DirectChannel(), - createProducerProperties()); - Binding foo1ConsumerBinding = binder.bindConsumer("foo.1", "testClean", new DirectChannel(), - createConsumerProperties()); - Binding foo2ProducerBinding = binder.bindProducer("foo.2", new DirectChannel(), - createProducerProperties()); + Binding foo0ProducerBinding = binder.bindProducer("foo.0", + new DirectChannel(), createProducerProperties()); + Binding foo0ConsumerBinding = binder.bindConsumer("foo.0", + "testClean", new DirectChannel(), createConsumerProperties()); + Binding foo1ProducerBinding = binder.bindProducer("foo.1", + new DirectChannel(), createProducerProperties()); + Binding foo1ConsumerBinding = binder.bindConsumer("foo.1", + "testClean", new DirectChannel(), createConsumerProperties()); + Binding foo2ProducerBinding = binder.bindProducer("foo.2", + new DirectChannel(), createProducerProperties()); foo0ProducerBinding.unbind(); - assertThat(TestUtils.getPropertyValue(foo0ProducerBinding, "lifecycle", Lifecycle.class).isRunning()) - .isFalse(); + assertThat(TestUtils + .getPropertyValue(foo0ProducerBinding, "lifecycle", Lifecycle.class) + .isRunning()).isFalse(); foo0ConsumerBinding.unbind(); foo1ProducerBinding.unbind(); - assertThat(TestUtils.getPropertyValue(foo0ConsumerBinding, "lifecycle", Lifecycle.class).isRunning()) - .isFalse(); - assertThat(TestUtils.getPropertyValue(foo1ProducerBinding, "lifecycle", Lifecycle.class).isRunning()) - .isFalse(); + assertThat(TestUtils + .getPropertyValue(foo0ConsumerBinding, "lifecycle", Lifecycle.class) + .isRunning()).isFalse(); + assertThat(TestUtils + .getPropertyValue(foo1ProducerBinding, "lifecycle", Lifecycle.class) + .isRunning()).isFalse(); foo1ConsumerBinding.unbind(); foo2ProducerBinding.unbind(); - assertThat(TestUtils.getPropertyValue(foo1ConsumerBinding, "lifecycle", Lifecycle.class).isRunning()) - .isFalse(); - assertThat(TestUtils.getPropertyValue(foo2ProducerBinding, "lifecycle", Lifecycle.class).isRunning()) - .isFalse(); + assertThat(TestUtils + .getPropertyValue(foo1ConsumerBinding, "lifecycle", Lifecycle.class) + .isRunning()).isFalse(); + assertThat(TestUtils + .getPropertyValue(foo2ProducerBinding, "lifecycle", Lifecycle.class) + .isRunning()).isFalse(); } @Test public void testSendAndReceive() throws Exception { Binder binder = getBinder(); - BindingProperties outputBindingProperties = createProducerBindingProperties(createProducerProperties()); - DirectChannel moduleOutputChannel = createBindableChannel("output", outputBindingProperties); + BindingProperties outputBindingProperties = createProducerBindingProperties( + createProducerProperties()); + DirectChannel moduleOutputChannel = createBindableChannel("output", + outputBindingProperties); QueueChannel moduleInputChannel = new QueueChannel(); - Binding producerBinding = binder.bindProducer("foo.0", moduleOutputChannel, - outputBindingProperties.getProducer()); - Binding consumerBinding = binder.bindConsumer("foo.0", "testSendAndReceive", moduleInputChannel, - createConsumerProperties()); - Message message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar") + Binding producerBinding = binder.bindProducer("foo.0", + moduleOutputChannel, outputBindingProperties.getProducer()); + Binding consumerBinding = binder.bindConsumer("foo.0", + "testSendAndReceive", moduleInputChannel, createConsumerProperties()); + // Bypass conversion we are only testing sendReceive + Message message = MessageBuilder.withPayload("foo".getBytes()) + .setHeader(MessageHeaders.CONTENT_TYPE, + MimeTypeUtils.APPLICATION_OCTET_STREAM) .build(); // Let the consumer actually bind to the producer before sending a msg binderBindUnbindLatency(); moduleOutputChannel.send(message); Message inbound = receive(moduleInputChannel); assertThat(inbound).isNotNull(); - assertThat(inbound.getPayload()).isEqualTo("foo"); - assertThat(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - assertThat(inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo("foo/bar"); + assertThat(inbound.getPayload()).isEqualTo("foo".getBytes()); + assertThat(inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE)) + .isEqualTo(MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE); producerBinding.unbind(); consumerBinding.unbind(); } @@ -150,20 +161,28 @@ public abstract class AbstractBinderTests producerBinding1 = binder.bindProducer("foo.x", moduleOutputChannel1, - createProducerProperties()); - Binding producerBinding2 = binder.bindProducer("foo.y", moduleOutputChannel2, - createProducerProperties()); + Binding producerBinding1 = binder.bindProducer("foo.x", + moduleOutputChannel1, createProducerProperties()); + Binding producerBinding2 = binder.bindProducer("foo.y", + moduleOutputChannel2, createProducerProperties()); - Binding consumerBinding1 = binder.bindConsumer("foo.x", "testSendAndReceiveMultipleTopics", moduleInputChannel, + Binding consumerBinding1 = binder.bindConsumer("foo.x", + "testSendAndReceiveMultipleTopics", moduleInputChannel, createConsumerProperties()); - Binding consumerBinding2 = binder.bindConsumer("foo.y", "testSendAndReceiveMultipleTopics", moduleInputChannel, + Binding consumerBinding2 = binder.bindConsumer("foo.y", + "testSendAndReceiveMultipleTopics", moduleInputChannel, createConsumerProperties()); String testPayload1 = "foo" + UUID.randomUUID().toString(); - Message message1 = MessageBuilder.withPayload(testPayload1.getBytes()).build(); + Message message1 = MessageBuilder.withPayload(testPayload1.getBytes()) + .setHeader(MessageHeaders.CONTENT_TYPE, + MimeTypeUtils.APPLICATION_OCTET_STREAM) + .build(); String testPayload2 = "foo" + UUID.randomUUID().toString(); - Message message2 = MessageBuilder.withPayload(testPayload2.getBytes()).build(); + Message message2 = MessageBuilder.withPayload(testPayload2.getBytes()) + .setHeader(MessageHeaders.CONTENT_TYPE, + MimeTypeUtils.APPLICATION_OCTET_STREAM) + .build(); // Let the consumer actually bind to the producer before sending a msg binderBindUnbindLatency(); @@ -176,8 +195,8 @@ public abstract class AbstractBinderTests producerBinding = binder.bindProducer("bar.0", moduleOutputChannel, - producerBindingProperties.getProducer()); - Binding consumerBinding = binder.bindConsumer("bar.0", "testSendAndReceiveNoOriginalContentType", moduleInputChannel, + Binding producerBinding = binder.bindProducer("bar.0", + moduleOutputChannel, producerBindingProperties.getProducer()); + Binding consumerBinding = binder.bindConsumer("bar.0", + "testSendAndReceiveNoOriginalContentType", moduleInputChannel, createConsumerProperties()); binderBindUnbindLatency(); - Message message = MessageBuilder.withPayload("foo").build(); + Message message = MessageBuilder.withPayload("foo") + .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build(); moduleOutputChannel.send(message); Message inbound = receive(moduleInputChannel); assertThat(inbound).isNotNull(); - assertThat(inbound.getPayload()).isEqualTo("foo"); - assertThat(inbound.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - assertThat(inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.TEXT_PLAIN_VALUE); + assertThat(inbound.getPayload()).isEqualTo("foo".getBytes()); + assertThat(inbound.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()) + .isEqualTo(MimeTypeUtils.TEXT_PLAIN_VALUE); producerBinding.unbind(); consumerBinding.unbind(); } @@ -216,7 +239,8 @@ public abstract class AbstractBinderTests, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties> extends AbstractBinderTests { @@ -67,7 +69,7 @@ public abstract class PartitionCapableBinderTests(testPayload1.getBytes())); + output.send(MessageBuilder.withPayload(testPayload1).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build()); Message receivedMessage1 = (Message) receive(input1); assertThat(receivedMessage1).isNotNull(); @@ -80,11 +82,11 @@ public abstract class PartitionCapableBinderTests(testPayload2.getBytes())); + output.send(MessageBuilder.withPayload(testPayload2).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build()); binding2 = binder.bindConsumer("defaultGroup.0", null, input2, createConsumerProperties()); String testPayload3 = "foo-" + UUID.randomUUID().toString(); - output.send(new GenericMessage<>(testPayload3.getBytes())); + output.send(MessageBuilder.withPayload(testPayload3).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build()); receivedMessage1 = (Message) receive(input1); assertThat(receivedMessage1).isNotNull(); @@ -114,7 +116,7 @@ public abstract class PartitionCapableBinderTests producerBinding = binder.bindProducer(testDestination, output, producerProperties); String testPayload = "foo-" + UUID.randomUUID().toString(); - output.send(new GenericMessage<>(testPayload.getBytes())); + output.send(MessageBuilder.withPayload(testPayload).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build()); QueueChannel inbound1 = new QueueChannel(); Binding consumerBinding = binder.bindConsumer(testDestination, "test1", inbound1, @@ -141,7 +143,7 @@ public abstract class PartitionCapableBinderTests producerBinding = binder.bindProducer(testDestination, output, producerProperties); String testPayload = "foo-" + UUID.randomUUID().toString(); - output.send(new GenericMessage<>(testPayload.getBytes())); + output.send(MessageBuilder.withPayload(testPayload).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build()); QueueChannel inbound1 = new QueueChannel(); Binding consumerBinding1 = binder.bindConsumer(testDestination, "test1", inbound1, @@ -198,13 +200,14 @@ public abstract class PartitionCapableBinderTests message2 = MessageBuilder.withPayload(2) + Message message2 = MessageBuilder.withPayload("2") .setHeader(IntegrationMessageHeaderAccessor.CORRELATION_ID, "foo") + .setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.TEXT_PLAIN) .setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, 42) .setHeader(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, 43).build(); output.send(message2); - output.send(new GenericMessage<>(1)); - output.send(new GenericMessage<>(0)); + output.send(MessageBuilder.withPayload("1").setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.TEXT_PLAIN).build()); + output.send(MessageBuilder.withPayload("0").setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.TEXT_PLAIN).build()); Message receive0 = receive(input0); assertThat(receive0).isNotNull(); @@ -223,19 +226,19 @@ public abstract class PartitionCapableBinderTests> receivedMessages = Arrays.asList(receive0, receive1, receive2); - assertThat(receivedMessages).extracting("payload").containsExactlyInAnyOrder(0, 1, 2); + assertThat(receivedMessages).extracting("payload").containsExactlyInAnyOrder("0".getBytes(), "1".getBytes(), "2".getBytes()); Condition> payloadIs2 = new Condition>() { @Override public boolean matches(Message value) { - return value.getPayload().equals(2); + return value.getPayload().equals("2".getBytes()); } }; assertThat(receivedMessages).filteredOn(payloadIs2).areExactly(1, correlationHeadersForPayload2); @@ -286,9 +289,9 @@ public abstract class PartitionCapableBinderTests(2)); - output.send(new GenericMessage<>(1)); - output.send(new GenericMessage<>(0)); + output.send(MessageBuilder.withPayload("2").setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.TEXT_PLAIN).build()); + output.send(MessageBuilder.withPayload("1").setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.TEXT_PLAIN).build()); + output.send(MessageBuilder.withPayload("0").setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.TEXT_PLAIN).build()); Message receive0 = receive(input0); assertThat(receive0).isNotNull(); @@ -298,13 +301,13 @@ public abstract class PartitionCapableBinderTests> receivedMessages = Arrays.asList(receive0, receive1, receive2); - assertThat(receivedMessages).extracting("payload").containsExactlyInAnyOrder(0, 1, 2); + assertThat(receivedMessages).extracting("payload").containsExactlyInAnyOrder("0".getBytes(), "1".getBytes(), "2".getBytes()); } input0Binding.unbind(); diff --git a/spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java b/spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java deleted file mode 100644 index c722521b6..000000000 --- a/spring-cloud-stream-binder-test/src/test/java/org/springframework/cloud/stream/binder/MessageChannelBinderSupportTests.java +++ /dev/null @@ -1,265 +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 - * - * http://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.io.IOException; -import java.util.Collections; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.integration.codec.kryo.PojoCodec; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.tuple.TupleKryoRegistrar; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.converter.ContentTypeResolver; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.tuple.DefaultTuple; -import org.springframework.tuple.Tuple; -import org.springframework.tuple.TupleBuilder; -import org.springframework.util.MimeType; -import org.springframework.util.MimeTypeUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Gary Russell - * @author David Turanski - * @author Ilayaperumal Gopinathan - */ -public class MessageChannelBinderSupportTests { - - private final ContentTypeResolver contentTypeResolver = new StringConvertingContentTypeResolver(); - - private final TestMessageChannelBinder binder = new TestMessageChannelBinder(); - - @Before - public void setUp() { - binder.setCodec(new PojoCodec(new TupleKryoRegistrar())); - } - - @Test - public void testBytesPassThru() { - byte[] payload = "foo".getBytes(); - Message message = MessageBuilder.withPayload(payload).build(); - MessageValues converted = binder.serializePayloadIfNecessary(message); - assertThat(converted.getPayload()).isSameAs(payload); - Message convertedMessage = converted.toMessage(); - assertThat(convertedMessage.getPayload()).isSameAs(payload); - assertThat(contentTypeResolver.resolve(convertedMessage.getHeaders())) - .isEqualTo(MimeTypeUtils.APPLICATION_OCTET_STREAM); - MessageValues reconstructed = binder.deserializePayloadIfNecessary(convertedMessage); - payload = (byte[]) reconstructed.getPayload(); - assertThat(converted.getPayload()).isSameAs(payload); - assertThat(reconstructed.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - } - - @Test - public void testBytesPassThruContentType() { - byte[] payload = "foo".getBytes(); - Message message = MessageBuilder.withPayload(payload) - .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE).build(); - MessageValues messageValues = binder.serializePayloadIfNecessary(message); - Message converted = messageValues.toMessage(); - assertThat(converted.getPayload()).isSameAs(payload); - assertThat(contentTypeResolver.resolve(converted.getHeaders())) - .isEqualTo(MimeTypeUtils.APPLICATION_OCTET_STREAM); - MessageValues reconstructed = binder.deserializePayloadIfNecessary(converted); - payload = (byte[]) reconstructed.getPayload(); - assertThat(converted.getPayload()).isSameAs(payload); - assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo(MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE); - assertThat(reconstructed.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - } - - @Test - public void testString() throws IOException { - MessageValues convertedValues = binder.serializePayloadIfNecessary(new GenericMessage<>("foo")); - Message converted = convertedValues.toMessage(); - assertThat(contentTypeResolver.resolve(converted.getHeaders())).isEqualTo(MimeTypeUtils.TEXT_PLAIN); - MessageValues reconstructed = binder.deserializePayloadIfNecessary(converted); - assertThat(reconstructed.getPayload()).isEqualTo("foo"); - assertThat(reconstructed.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.TEXT_PLAIN_VALUE); - } - - @Test - public void testStringXML() throws IOException { - Message message = MessageBuilder - .withPayload("") - .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_XML).build(); - Message converted = binder.serializePayloadIfNecessary(message).toMessage(); - assertThat(contentTypeResolver.resolve(converted.getHeaders())).isEqualTo(MimeTypeUtils.TEXT_PLAIN); - MessageValues reconstructed = binder.deserializePayloadIfNecessary(converted); - assertThat(reconstructed.getPayload()) - .isEqualTo(""); - assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.TEXT_XML.toString()); - } - - @Test - public void testContentTypePreservedForJson() throws IOException { - Message inbound = MessageBuilder.withPayload("{\"foo\":\"foo\"}") - .copyHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)) - .build(); - MessageValues convertedValues = binder.serializePayloadIfNecessary(inbound); - Message converted = convertedValues.toMessage(); - assertThat(contentTypeResolver.resolve(converted.getHeaders())).isEqualTo(MimeTypeUtils.APPLICATION_JSON); - assertThat(converted.getHeaders().get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - MessageValues reconstructed = binder.deserializePayloadIfNecessary(converted); - assertThat(reconstructed.getPayload()).isEqualTo("{\"foo\":\"foo\"}"); - assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.APPLICATION_JSON_VALUE); - } - - @Test - public void testContentTypePreservedForNonSCStApp() { - Message inbound = MessageBuilder.withPayload("{\"foo\":\"bar\"}") - .copyHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)) - .build(); - MessageValues reconstructed = binder.deserializePayloadIfNecessary(inbound); - assertThat(reconstructed.getPayload()).isEqualTo("{\"foo\":\"bar\"}"); - assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.APPLICATION_JSON); - } - - @Test - public void testPojoSerialization() { - MessageValues convertedValues = binder.serializePayloadIfNecessary(new GenericMessage<>(new Foo("bar"))); - Message converted = convertedValues.toMessage(); - MimeType mimeType = contentTypeResolver.resolve(converted.getHeaders()); - assertThat(mimeType.getType()).isEqualTo("application"); - assertThat(mimeType.getSubtype()).isEqualTo("x-java-object"); - assertThat(mimeType.getParameter("type")).isEqualTo(Foo.class.getName()); - - MessageValues reconstructed = binder.deserializePayloadIfNecessary(converted); - assertThat(((Foo) reconstructed.getPayload()).getBar()).isEqualTo("bar"); - assertThat(reconstructed.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)).isEqualTo( - "application/x-java-object;type=org.springframework.cloud.stream.binder.MessageChannelBinderSupportTests$Foo"); - } - - @Test - public void testTupleSerialization() { - Tuple payload = TupleBuilder.tuple().of("foo", "bar"); - MessageValues convertedValues = binder.serializePayloadIfNecessary(new GenericMessage<>(payload)); - Message converted = convertedValues.toMessage(); - MimeType mimeType = contentTypeResolver.resolve(converted.getHeaders()); - assertThat(mimeType.getType()).isEqualTo("application"); - assertThat(mimeType.getSubtype()).isEqualTo("x-java-object"); - assertThat(mimeType.getParameter("type")).isEqualTo(DefaultTuple.class.getName()); - - MessageValues reconstructed = binder.deserializePayloadIfNecessary(converted); - assertThat(((Tuple) reconstructed.getPayload()).getString("foo")).isEqualTo("bar"); - assertThat(reconstructed.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE)).isNull(); - assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)) - .isEqualTo("application/x-java-object;type=org.springframework.tuple.DefaultTuple"); - } - - @Test - public void mimeTypeIsSimpleObject() throws ClassNotFoundException { - MimeType mt = JavaClassMimeTypeUtils.mimeTypeFromObject(new Object(), null); - String className = JavaClassMimeTypeUtils.classNameFromMimeType(mt); - assertThat(Class.forName(className)).isEqualTo(Object.class); - } - - @Test - public void mimeTypeIsObjectArray() throws ClassNotFoundException { - MimeType mt = JavaClassMimeTypeUtils.mimeTypeFromObject(new String[0], null); - String className = JavaClassMimeTypeUtils.classNameFromMimeType(mt); - assertThat(Class.forName(className)).isEqualTo(String[].class); - } - - @Test - public void mimeTypeIsMultiDimensionalObjectArray() throws ClassNotFoundException { - MimeType mt = JavaClassMimeTypeUtils.mimeTypeFromObject(new String[0][0][0], null); - String className = JavaClassMimeTypeUtils.classNameFromMimeType(mt); - assertThat(Class.forName(className)).isEqualTo(String[][][].class); - } - - @Test - public void mimeTypeIsPrimitiveArray() throws ClassNotFoundException { - MimeType mt = JavaClassMimeTypeUtils.mimeTypeFromObject(new int[0], null); - String className = JavaClassMimeTypeUtils.classNameFromMimeType(mt); - assertThat(Class.forName(className)).isEqualTo(int[].class); - } - - @Test - public void mimeTypeIsMultiDimensionalPrimitiveArray() throws ClassNotFoundException { - MimeType mt = JavaClassMimeTypeUtils.mimeTypeFromObject(new int[0][0][0], null); - String className = JavaClassMimeTypeUtils.classNameFromMimeType(mt); - assertThat(Class.forName(className)).isEqualTo(int[][][].class); - } - - public static class Foo { - - private String bar; - - public Foo() { - } - - public Foo(String bar) { - this.bar = bar; - } - - public String getBar() { - return bar; - } - - public void setBar(String bar) { - this.bar = bar; - } - - } - - public static class Bar { - - private String foo; - - public Bar() { - } - - public Bar(String foo) { - this.foo = foo; - } - - public String getFoo() { - return foo; - } - - public void setFoo(String foo) { - this.foo = foo; - } - - } - - public class TestMessageChannelBinder - extends AbstractBinder { - - @Override - protected Binding doBindConsumer(String name, String group, MessageChannel channel, - ConsumerProperties properties) { - return null; - } - - @Override - public Binding doBindProducer(String name, MessageChannel channel, - ProducerProperties properties) { - return null; - } - } - -} diff --git a/spring-cloud-stream-codec/pom.xml b/spring-cloud-stream-codec/pom.xml deleted file mode 100644 index 54bd4ad24..000000000 --- a/spring-cloud-stream-codec/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - 4.0.0 - - spring-cloud-stream-codec - jar - spring-cloud-stream-codec - Serialization library used by transport - - - org.springframework.cloud - spring-cloud-stream-parent - 2.0.0.BUILD-SNAPSHOT - - - - - com.esotericsoftware - kryo-shaded - - - org.springframework.integration - spring-integration-core - true - - - org.springframework.boot - spring-boot-starter-logging - true - - - org.springframework.boot - spring-boot-autoconfigure - true - - - com.fasterxml.jackson.core - jackson-annotations - - - diff --git a/spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecAutoConfiguration.java b/spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecAutoConfiguration.java deleted file mode 100644 index dfe8f09de..000000000 --- a/spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecAutoConfiguration.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015-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 - * - * http://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.codec.kryo; - -import java.util.ArrayList; -import java.util.Map; - -import com.esotericsoftware.kryo.Kryo; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.integration.codec.Codec; -import org.springframework.integration.codec.kryo.FileKryoRegistrar; -import org.springframework.integration.codec.kryo.KryoRegistrar; -import org.springframework.integration.codec.kryo.PojoCodec; - -/** - * Auto configures {@link PojoCodec} if Kryo is on the class path. - * @author David Turanski - */ -@Configuration -@ConditionalOnClass(Kryo.class) -@EnableConfigurationProperties(KryoCodecProperties.class) -@ConditionalOnMissingBean(Codec.class) -public class KryoCodecAutoConfiguration { - - @Autowired - ApplicationContext applicationContext; - - @Autowired - KryoCodecProperties kryoCodecProperties; - - @Bean - @ConditionalOnMissingBean(PojoCodec.class) - public PojoCodec codec() { - Map kryoRegistrarMap = applicationContext.getBeansOfType(KryoRegistrar.class); - return new PojoCodec(new ArrayList<>(kryoRegistrarMap.values()), kryoCodecProperties.isReferences()); - } - - @Bean - @ConditionalOnMissingBean(KryoRegistrar.class) - public KryoRegistrar fileRegistrar() { - return new FileKryoRegistrar(); - } -} diff --git a/spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecProperties.java b/spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecProperties.java deleted file mode 100644 index 3d9ab9681..000000000 --- a/spring-cloud-stream-codec/src/main/java/org/springframework/cloud/stream/config/codec/kryo/KryoCodecProperties.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2015 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 - * - * http://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.codec.kryo; - -import com.fasterxml.jackson.annotation.JsonInclude; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author David Turanski - */ -@ConfigurationProperties("spring.cloud.codec.kryo") -@JsonInclude(JsonInclude.Include.NON_DEFAULT) -public class KryoCodecProperties { - private boolean references = true; - - public boolean isReferences() { - return references; - } - - public void setReferences(boolean references) { - this.references = references; - } - -} diff --git a/spring-cloud-stream-codec/src/main/resources/META-INF/spring.factories b/spring-cloud-stream-codec/src/main/resources/META-INF/spring.factories deleted file mode 100644 index bb62d5a5d..000000000 --- a/spring-cloud-stream-codec/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration:\ -org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration diff --git a/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc b/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc index 82ee1c198..a1d14a4d8 100644 --- a/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc +++ b/spring-cloud-stream-core-docs/src/main/asciidoc/spring-cloud-stream-overview.adoc @@ -1361,122 +1361,187 @@ To allow you to propagate information about the content type of produced message For middleware that does not directly support headers, Spring Cloud Stream provides its own mechanism of automatically wrapping outbound messages in an envelope of its own. For middleware that does support headers, Spring Cloud Stream applications may receive messages with a given content type from non-Spring Cloud Stream applications. -Spring Cloud Stream can handle messages based on this information in two ways: +The content type resolution process have been redesigned for Spring Cloud Stream 2.0. -* Through its `contentType` settings on inbound and outbound channels -* Through its argument mapping performed for methods annotated with `@StreamListener` +Please read the migrating from 1.3 section to understand the changes when interacting with applications using versions of the framework. + +The framework depends on a `contentType` to be present as a header in order to know how serialize/deserialize a payload. Spring Cloud Stream allows you to declaratively configure type conversion for inputs and outputs using the `spring.cloud.stream.bindings..content-type` property of a binding. Note that general type conversion may also be accomplished easily by using a transformer inside your application. -Currently, Spring Cloud Stream natively supports the following type conversions commonly used in streams: -* *JSON* to/from *POJO* -* *JSON* to/from https://github.com/spring-projects/spring-tuple/blob/master/spring-tuple/src/main/java/org/springframework/tuple/Tuple.java[org.springframework.tuple.Tuple] -* *Object* to/from *byte[]* : Either the raw bytes serialized for remote transport, bytes emitted by an application, or converted to bytes using Java serialization(requires the object to be Serializable) -* *String* to/from *byte[]* -* *Object* to *plain text* (invokes the object's _toString()_ method) +[NOTE] +==== +For both input and output channel, setting a contentType via a property or via annotation only triggers the `default` converter if a message header with value `contentType` is not present. +This is useful for cases where you just want to send a _POJO_ without sending any header information, or to consume messages that do not have a `contentType` header present. +The framework will always override any default settings with the value found on the message headers. +==== -Where _JSON_ represents either a byte array or String payload containing JSON. -Currently, Objects may be converted from a JSON byte array or String. -Converting to JSON always produces a String. - -If no `content-type` property is set on an outbound channel, Spring Cloud Stream will serialize the payload using a serializer based on the https://github.com/EsotericSoftware/kryo[Kryo] serialization framework. -Deserializing messages at the destination requires the payload class to be present on the receiver's classpath. +[TIP] +==== +Although contentType became a required property, the framework will set a default value of `application/json` for all input/output channels if one is not +provided by the user. +==== [[mime-types]] === MIME types -`content-type` values are parsed as media types, e.g., `application/json` or `text/plain;charset=UTF-8`. +The `content-type` values are parsed as media types, e.g., `application/json` or `text/plain;charset=UTF-8`. + MIME types are especially useful for indicating how to convert to String or byte[] content. Spring Cloud Stream also uses MIME type format to represent Java types, using the general type `application/x-java-object` with a `type` parameter. For example, `application/x-java-object;type=java.util.Map` or `application/x-java-object;type=com.bar.Foo` can be set as the `content-type` property of an input binding. In addition, Spring Cloud Stream provides custom MIME types, notably, `application/x-spring-tuple` to specify a Tuple. [[mime-types-and-java-types]] -=== MIME types and Java types -The type conversions Spring Cloud Stream provides out of the box are summarized in the following table: -'Source Payload' means the payload before conversion and 'Target Payload' means the 'payload' after conversion. -The type conversion can occur either on the 'producer' side (output) or at the 'consumer' side (input). +=== Channel contentType and Message Headers -|=== -|Source Payload |Target Payload | `content-type` header (source message) | `content-type` header (after conversion) | Comments +You can configure a message channel content type using `spring.cloud.stream.bindings..content-type` property, or using the `@Input` and `@Output` annotations. +By doing so, even if you send a POJO with no `contentType` information, the framework will set the MessageHeader `contentType` to the specified value set for the channel. -|POJO -|JSON String -|ignored -|application/json -| +However, if you send a `Message` and sets the `contentType` manually, that takes precedence over the configured property value. +This is valid for both input and output channels. The `MessageHeader` will always take precedence over the default configured `contentType` for the channel. -|Tuple -|JSON String -|ignored -|application/json -|JSON is tailored for Tuple +=== ContentType handling for output channels -|POJO -|String (toString()) -|ignored -|text/plain, java.lang.String -| +Starting with version 2.0, the framework will no longer try to infer a contentType based on the payload `T` of a `Message`. +It will instead use the contentType header (or the default provided by the framework) to configure the right `MessageConverter` to serialize the payload into `byte[]`. -|POJO -|byte[] (java.io serialized) -|ignored -|application/x-java-serialized-object -| +The `contentType` you set is a hint to activate the corresponding `MessageConverter`. The converter can then modify the contentType to augment the information, such as the case with `Kryo` and `Avro` conveters. -|JSON byte[] or String -|POJO -|application/json (or none) -|application/x-java-object -| +For outbound messages, if your payload is of typ `byte[]`, the framework will skip the conversion logic, and just write those bytes to the wire. +In this case, if `contentType` of the message is absent, it will set the default value specified to channel. -|byte[] or String -|Serializable -|application/x-java-serialized-object -|application/x-java-object -| - -|JSON byte[] or String -|Tuple -|application/json (or none) -|application/x-spring-tuple -| - -|byte[] -|String -|any -|text/plain, java.lang.String -|will apply any Charset specified in the content-type header - -|String -|byte[] -|any -|application/octet-stream -|will apply any Charset specified in the content-type header - -|=== - -[NOTE] -==== -Conversion applies to payloads that require type conversion. -For example, if an application produces an XML string with outputType=application/json, the payload will not be converted from XML to JSON. -This is because the payload send to the outbound channel is already a String so no conversion will be applied at runtime. -It is also important to note that when using the default serialization mechanism, the payload class must be shared between the sending and receiving application, and compatible with the binary content. -This can create issues when application code changes independently in the two applications, as the binary format and code may become incompatible. -==== [TIP] ==== -While conversion is supported for both inbound and outbound channels, it is especially recommended to be used for the conversion of outbound messages. -For the conversion of inbound messages, especially when the target is a POJO, the `@StreamListener` support will perform the conversion automatically. +If you intend to bypass conversion, just make sure you set the appropriate `contentType` header, otherwise you could be sending some arbitrary binary data, and the framework may set the header as `application/json` (default). ==== +The following snippet shows how you can bypass conversion and set the correct contentType header. + +```java + +@Autowired +private Source source; + + public void sendImageData(File f) throws Exception{ + byte[] data = Files.readAllBytes(f.toPath()); + MimeType mimeType = (f.getName().endsWith("gif")) ? MimeTypeUtils.IMAGE_GIF : MimeTypeUtils.IMAGE_JPEG; + source.output().send(MessageBuilder.withPayload(data) + .setHeader(MessageHeaders.CONTENT_TYPE, mimeType) + .build()); + } + + +``` + +Regardless of contentType used, the result is always a `Message` with a header `contentType` set. This is what gets passed to the binder to be sent over the wire. + +|=== +|`content-type` header | MessageConverter | `content-type` augmented |Supported types | Comments + +|application/json +|CustomMappingJackson2MessageConverter +|application/json +| POJO, primitives and Strings that represent JSON data +| It's the default converter if none is specified. Note that if you send a raw String it will be quoted + +|text/plain +|ObjectStringMessageConverter +|text/plain +|Invokes `toString()` of the object +| + +|application/x-spring-tuple +|TupleJsonMessageConverter +|application/x-spring-tuple +|org.springframework.tuple.Tuple +| + +|application/x-java-serialized-object +|JavaSerializationMessageConverter +|application/x-java-serialized-object +|Any Java type that implements `Serializable` +|This converter uses java native serialization. Receivers of this data must have the same class on the classpath. + +|application/x-java-object +|KryoMessageConverter +|application/x-java-object;type= +|Any Java type that can be serialized using Kryo +|Receivers of this data must have the same class on the classpath. + +|application/avro +|AvroMessageConverter +|application/avro +|A Generic or SpecificRecord from Avro types, a POJO if reflection is used +|Avro needs an associated schema to write/read data. Please refer to the section on the docs on how to use it properly + +|=== + +=== ContentType handling for input channels + +For input channels, Spring Cloud Stream uses `@StreamListener` and `@ServiceActivator` content handling to support the conversion. +It does so by checking either the channel `content-type` set via `@Input(contentType="text/plain")` annotation or via `spring.cloud.stream.bindings..contentType` property, or the presense of a header `contentType`. + +The framework will check the contentType set for the Message, select the appropriate `MessageConverter` and apply conversion passing the argument as the target type. + +If the converter does not support the target type it will return `null`, if *all* configured converters return `null`, a `MessageConversionException` is thrown. + +Just like output channels, if your method payload argument is of type `Message`, `byte[]` or `Message` conversion is skipped and you get the raw bytes from the wire, plus the corresponding headers. + +[TIP] +==== +Remember, the MessageHeader always takes precedence over the annotation or property configuration. +==== + +|=== +|`content-type` header | MessageConverter | Supported target type | Comments + +|applicaiton/json +|CustomMappingJackson2MessageConverter +| POJO or String +| + +|text/plain +|ObjectStringMessageConverter +|String +| + +|application/x-spring-tuple +|TupleJsonMessageConverter +|org.springframework.tuple.Tuple +| + +|application/x-java-serialized-object +|JavaSerializationMessageConverter +|Any Java type that implements `Serializable` +| + +|application/x-java-object +|KryoMessageConverter +|Any Java type that can be serialized using Kryo +| + +|application/avro +|AvroMessageConverter +|A Generic or SpecificRecord from Avro types, a POJO if reflection is used +|Avro needs an associated schema to write/read data. Please refer to the section on the docs on how to use it properly + +|=== + + === Customizing message conversion Besides the conversions that it supports out of the box, Spring Cloud Stream also supports registering your own message conversion implementations. This allows you to send and receive data in a variety of custom formats, including binary, and associate them with specific `contentTypes`. -Spring Cloud Stream registers all the beans of type `org.springframework.messaging.converter.MessageConverter` as custom message converters along with the out of the box message converters. + +Spring Cloud Stream registers all the beans of type `org.springframework.messaging.converter.MessageConverter` that are qualifeied using `@StreamConverter` annotation, as custom message converters along with the out of the box message converters. + +[NOTE] +==== +The framework requires the `@StreamConverter` qualifier annotation to avoid picking up other converters that may be present on the `ApplicationContext` and could overlap with the default ones. +==== If your message converter needs to work with a specific `content-type` and target class (for both input and output), then the message converter needs to extend `org.springframework.messaging.converter.AbstractMessageConverter`. For conversion when using `@StreamListener`, a message converter that implements `org.springframework.messaging.converter.MessageConverter` would suffice. @@ -1492,6 +1557,7 @@ public static class SinkApplication { ... @Bean + @StreamConverter public MessageConverter customMessageConverter() { return new MyCustomMessageConverter(); } diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ContentTypeOutboundSourceTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ContentTypeOutboundSourceTests.java index 4ea6ef9d2..1f9e6b683 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ContentTypeOutboundSourceTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/ContentTypeOutboundSourceTests.java @@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Ilayaperumal Gopinathan + * @author Vinicius Carvalho */ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { ContentTypeOutboundSourceTests.TestSource.class }) @@ -53,12 +54,15 @@ public class ContentTypeOutboundSourceTests { @Test @SuppressWarnings("unchecked") public void testMessageHeaderWhenNoExplicitContentTypeOnMessage() throws Exception { - testSource.output().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}").build()); - Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, + testSource.output().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}").setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build()); + Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testSource.output()).poll(); - assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()).isEqualTo("application/json"); - assertThat(received).hasFieldOrPropertyWithValue("payload", "{\"message\":\"Hi\"}"); + assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()).contains("text/plain"); + Object payload = received.getPayload(); + assertThat(payload.getClass().isAssignableFrom(byte[].class)).isTrue(); + byte[] contents = (byte[])payload; + assertThat("{\"message\":\"Hi\"}").isEqualTo(new String(contents)); } @EnableBinding(Source.class) diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomHeaderPropagationTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomHeaderPropagationTests.java index f18ab599f..050e5b83a 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomHeaderPropagationTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/CustomHeaderPropagationTests.java @@ -53,6 +53,11 @@ public class CustomHeaderPropagationTests { private BinderFactory binderFactory; @Test + /** + * @since 2.0 The behavior of content type handling has changed. + * All input/output channels have a default content type of application/json + * When a processor or a source returns a String, and if the content type is json it will be quoted + */ public void testCustomHeaderPropagation() throws Exception { testProcessor.input().send(MessageBuilder.withPayload("{'name':'foo'}") .setHeader(MessageHeaders.CONTENT_TYPE, "application/json") @@ -65,8 +70,8 @@ public class CustomHeaderPropagationTests { assertThat(received).isNotNull(); assertThat(received.getHeaders()).containsEntry("foo", "fooValue"); assertThat(received.getHeaders()).doesNotContainKey("bar"); - assertThat(received.getHeaders()).doesNotContainKey(MessageHeaders.CONTENT_TYPE); - assertThat(received.getPayload()).isEqualTo("{'name':'foo'}"); + assertThat(received.getHeaders()).containsKeys(MessageHeaders.CONTENT_TYPE); + assertThat(new String((byte[])received.getPayload())).isEqualTo("{'name':'foo'}"); } @EnableBinding(Processor.class) @@ -74,8 +79,9 @@ public class CustomHeaderPropagationTests { public static class HeaderPropagationProcessor { @ServiceActivator(inputChannel = "input", outputChannel = "output") - public String consume(String data) { - return data; + public Message consume(String data) { + //if we don't force content to be String, it will be quoted on the outbound channel + return MessageBuilder.withPayload(data).setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build(); } } 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 2a589fcae..c50096920 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 @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.stream.annotation.Bindings; 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; @@ -35,8 +36,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.integration.support.MessageBuilder; -import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter; -import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; @@ -64,14 +63,14 @@ public class CustomMessageConverterTests { private BinderFactory binderFactory; @Autowired + @StreamMessageConverter private List customMessageConverters; @Test public void testCustomMessageConverter() throws Exception { - assertThat(customMessageConverters).hasSize(4); + assertThat(customMessageConverters).hasSize(2); assertThat(customMessageConverters).extracting("class").contains(FooConverter.class, - BarConverter.class, DefaultDatatypeChannelMessageConverter.class, - ConfigurableCompositeMessageConverter.class); + BarConverter.class); testSource.output().send(MessageBuilder.withPayload(new Foo("hi")).build()); @SuppressWarnings("unchecked") Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, @@ -88,11 +87,13 @@ 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-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationTests.java index 6b18552ae..049b81ebb 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationTests.java @@ -64,8 +64,8 @@ public class DefaultHeaderPropagationTests { assertThat(received).isNotNull(); assertThat(received.getHeaders()).containsEntry("foo", "fooValue"); assertThat(received.getHeaders()).containsEntry("bar", "barValue"); - assertThat(received.getHeaders()).doesNotContainKey(MessageHeaders.CONTENT_TYPE); - assertThat(received.getPayload()).isEqualTo("{'name':'foo'}"); + assertThat(received.getHeaders()).containsKeys(MessageHeaders.CONTENT_TYPE); + assertThat(received.getPayload()).isEqualTo("{'name':'foo'}".getBytes()); } @EnableBinding(Processor.class) @@ -73,8 +73,8 @@ public class DefaultHeaderPropagationTests { public static class HeaderPropagationProcessor { @ServiceActivator(inputChannel = "input", outputChannel = "output") - public String consume(String data) { - return data; + public Message consume(String data) { + return MessageBuilder.withPayload(data).setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build(); } } diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationWithApplicationProvidedHeaderTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationWithApplicationProvidedHeaderTests.java index afb7b8c09..43b5fe925 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationWithApplicationProvidedHeaderTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DefaultHeaderPropagationWithApplicationProvidedHeaderTests.java @@ -33,9 +33,9 @@ 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.converter.MessageConversionException; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static org.assertj.core.api.Assertions.assertThat; /** * @author Marius Bogoevici @@ -51,8 +51,8 @@ public class DefaultHeaderPropagationWithApplicationProvidedHeaderTests { @Autowired private BinderFactory binderFactory; - @Test - public void testHeaderPropagationIfSetByApplication() throws Exception { + @Test(expected = MessageConversionException.class) + public void testFailedonCustomContentTypeWithoutConverter() throws Exception { testProcessor.input().send(MessageBuilder.withPayload("{'name':'foo'}") .setHeader(MessageHeaders.CONTENT_TYPE, "application/json") .setHeader("foo", "fooValue") @@ -61,11 +61,7 @@ public class DefaultHeaderPropagationWithApplicationProvidedHeaderTests { @SuppressWarnings("unchecked") Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); - assertThat(received.getHeaders()).containsEntry("foo", "fooValue"); - assertThat(received.getHeaders()).containsEntry("bar", "barValue"); - assertThat(received.getHeaders()).containsEntry(MessageHeaders.CONTENT_TYPE, "custom/header"); - assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo("{'name':'foo'}"); + } @EnableBinding(Processor.class) diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java index af3a37858..5d7d8123a 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/DeserializeJSONToJavaTypeTests.java @@ -64,8 +64,8 @@ public class DeserializeJSONToJavaTypeTests { Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(received).isNotNull(); - assertThat(received.getPayload()).isInstanceOf(Foo.class); - assertThat((Foo) received.getPayload()).hasFieldOrPropertyWithValue("name", "Bar"); + assertThat(received.getPayload()).isInstanceOf(byte[].class); + assertThat((byte[]) received.getPayload()).isEqualTo("{\"name\":\"Bar\"}".getBytes()); } @EnableBinding(Processor.class) diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/InboundJsonToTupleConversionTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/InboundJsonToTupleConversionTest.java index 6d1bd0a4c..d5e9d3d33 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/InboundJsonToTupleConversionTest.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/InboundJsonToTupleConversionTest.java @@ -33,7 +33,6 @@ import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageHeaders; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.tuple.Tuple; import org.springframework.tuple.TupleBuilder; @@ -56,12 +55,13 @@ public class InboundJsonToTupleConversionTest { @Test public void testInboundJsonTupleConversion() throws Exception { testProcessor.input().send(MessageBuilder.withPayload("{'name':'foo'}") - .setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build()); + .build()); @SuppressWarnings("unchecked") Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo(TupleBuilder.tuple().of("name", "foo")); + + assertThat(TupleBuilder.fromString(new String((byte[])received.getPayload()))).isEqualTo(TupleBuilder.tuple().of("name", "foo")); } @EnableBinding(Processor.class) diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java index 302f0bf83..99842618f 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/MessageChannelConfigurerTests.java @@ -42,7 +42,6 @@ import org.springframework.messaging.MessagingException; import org.springframework.messaging.converter.CompositeMessageConverter; import org.springframework.messaging.converter.MessageConverter; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.tuple.Tuple; import org.springframework.util.MimeTypeUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -78,14 +77,13 @@ public class MessageChannelConfigurerTests { MessageHandler messageHandler = new MessageHandler() { @Override public void handleMessage(Message message) throws MessagingException { - assertThat(message.getPayload()).isInstanceOf(Tuple.class); - assertThat(((Tuple) message.getPayload()).getFieldNames().get(0)).isEqualTo("message"); - assertThat(((Tuple) message.getPayload()).getValue(0)).isEqualTo("Hi"); + assertThat(message.getPayload()).isInstanceOf(byte[].class); + assertThat(message.getPayload()).isEqualTo("{\"message\":\"Hi\"}".getBytes()); latch.countDown(); } }; testSink.input().subscribe(messageHandler); - testSink.input().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}").build()); + testSink.input().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}".getBytes()).build()); assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); testSink.input().unsubscribe(messageHandler); } 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 index 95fea05e0..ba9c823eb 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -48,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan + * @author Vinicius Carvalho */ @RunWith(Parameterized.class) public class StreamListenerHandlerBeanTests { @@ -76,13 +78,13 @@ public class StreamListenerHandlerBeanTests { MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}") .setHeader("contentType", "application/json").build()); HandlerBean handlerBean = context.getBean(HandlerBean.class); - assertThat(handlerBean.receivedPojos).hasSize(1); - assertThat(handlerBean.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", + Assertions.assertThat(handlerBean.receivedPojos).hasSize(1); + Assertions.assertThat(handlerBean.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id); - Message message = (Message) collector.forChannel( + Message message = (Message) collector.forChannel( processor.output()).poll(1, TimeUnit.SECONDS); assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo("{\"bar\":\"barbar" + id + "\"}"); + assertThat(new String(message.getPayload())).isEqualTo("{\"bar\":\"barbar" + id + "\"}"); assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) .includes(MimeTypeUtils.APPLICATION_JSON)); context.close(); 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 index abe449fc2..c3ba673d9 100644 --- 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 @@ -64,13 +64,15 @@ import static org.springframework.cloud.stream.binding.StreamListenerErrorMessag * @author Marius Bogoevici * @author Ilayaperumal Gopinathan * @author Gary Russell + * @author Vinicius Carvalho */ public class StreamListenerHandlerMethodTests { @Test public void testInvalidInputOnMethod() throws Exception { try { - SpringApplication.run(TestInvalidInputOnMethod.class, "--server.port=0"); + SpringApplication.run(TestInvalidInputOnMethod.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected: " + INPUT_AT_STREAM_LISTENER); } catch (BeanCreationException e) { @@ -81,30 +83,41 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodWithObjectAsMethodArgument() throws Exception { ConfigurableApplicationContext context = SpringApplication.run(TestMethodWithObjectAsMethodArgument.class, - "--server.port=0"); + "--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 = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase()); + assertThat(new String(result.getPayload())).isEqualTo(testMessage.toUpperCase()); context.close(); } @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"); + "--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 = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase()); + assertThat(new String(result.getPayload())).isEqualTo(testMessage.toUpperCase()); assertThat(result.getHeaders().get("foo")).isEqualTo("bar"); context.close(); } @@ -112,41 +125,49 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodHeadersNotPropagatged() throws Exception { ConfigurableApplicationContext context = SpringApplication.run(TestMethodHeadersNotPropagated.class, - "--server.port=0"); + "--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 = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase()); + assertThat(new String(result.getPayload())).isEqualTo(testMessage.toUpperCase()); assertThat(result.getHeaders().get("foo")).isNull(); context.close(); } - @Test + + //TODO: Handle dynamic destinations and contentType public void testStreamListenerMethodWithTargetBeanFromOutside() throws Exception { ConfigurableApplicationContext context = SpringApplication - .run(TestStreamListenerMethodWithTargetBeanFromOutside.class, "--server.port=0"); + .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 = messageCollector.forChannel(directChannel).poll(1000, TimeUnit.MILLISECONDS); + 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()); + assertThat(new String(result.getPayload())).isEqualTo(testMessageToSend.toUpperCase()); context.close(); } @Test public void testInvalidReturnTypeWithSendToAndOutput() throws Exception { try { - SpringApplication.run(TestReturnTypeWithMultipleOutput.class, "--server.port=0"); + SpringApplication.run(TestReturnTypeWithMultipleOutput.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected: " + RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED); } catch (BeanCreationException e) { @@ -157,7 +178,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testInvalidReturnTypeWithNoOutput() throws Exception { try { - SpringApplication.run(TestInvalidReturnTypeWithNoOutput.class, "--server.port=0"); + SpringApplication.run(TestInvalidReturnTypeWithNoOutput.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected: " + RETURN_TYPE_NO_OUTBOUND_SPECIFIED); } catch (BeanCreationException e) { @@ -168,7 +190,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testInvalidInputAnnotationWithNoValue() throws Exception { try { - SpringApplication.run(TestInvalidInputAnnotationWithNoValue.class, "--server.port=0"); + SpringApplication.run(TestInvalidInputAnnotationWithNoValue.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected: " + INVALID_INBOUND_NAME); } catch (BeanCreationException e) { @@ -179,7 +202,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testInvalidOutputAnnotationWithNoValue() throws Exception { try { - SpringApplication.run(TestInvalidOutputAnnotationWithNoValue.class, "--server.port=0"); + SpringApplication.run(TestInvalidOutputAnnotationWithNoValue.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected: " + INVALID_OUTBOUND_NAME); } catch (BeanCreationException e) { @@ -190,7 +214,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodInvalidInboundName() throws Exception { try { - SpringApplication.run(TestMethodInvalidInboundName.class, "--server.port=0"); + SpringApplication.run(TestMethodInvalidInboundName.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected on using invalid inbound name"); } catch (BeanCreationException e) { @@ -203,7 +228,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodInvalidOutboundName() throws Exception { try { - SpringApplication.run(TestMethodInvalidOutboundName.class, "--server.port=0"); + SpringApplication.run(TestMethodInvalidOutboundName.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected on using invalid outbound name"); } catch (BeanCreationException e) { @@ -215,7 +241,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testAmbiguousMethodArguments1() throws Exception { try { - SpringApplication.run(TestAmbiguousMethodArguments1.class, "--server.port=0"); + SpringApplication.run(TestAmbiguousMethodArguments1.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected: " + AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS); } catch (BeanCreationException e) { @@ -226,7 +253,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testAmbiguousMethodArguments2() throws Exception { try { - SpringApplication.run(TestAmbiguousMethodArguments2.class, "--server.port=0"); + SpringApplication.run(TestAmbiguousMethodArguments2.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected:" + AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS); } catch (BeanCreationException e) { @@ -237,7 +265,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodWithInputAsMethodAndParameter() throws Exception { try { - SpringApplication.run(TestMethodWithInputAsMethodAndParameter.class, "--server.port=0"); + SpringApplication.run(TestMethodWithInputAsMethodAndParameter.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected: " + INVALID_DECLARATIVE_METHOD_PARAMETERS); } catch (BeanCreationException e) { @@ -248,7 +277,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodWithOutputAsMethodAndParameter() throws Exception { try { - SpringApplication.run(TestMethodWithOutputAsMethodAndParameter.class, "--server.port=0"); + SpringApplication.run(TestMethodWithOutputAsMethodAndParameter.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected:" + INVALID_OUTPUT_VALUES); } catch (BeanCreationException e) { @@ -259,7 +289,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodWithoutInput() throws Exception { try { - SpringApplication.run(TestMethodWithoutInput.class, "--server.port=0"); + SpringApplication.run(TestMethodWithoutInput.class, "--server.port=0", + "--spring.jmx.enabled=false"); fail("Exception expected when inbound target is not set"); } catch (BeanCreationException e) { @@ -270,7 +301,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodWithMultipleInputParameters() throws Exception { ConfigurableApplicationContext context = SpringApplication.run(TestMethodWithMultipleInputParameters.class, - "--server.port=0"); + "--server.port=0", + "--spring.jmx.enabled=false"); Processor processor = context.getBean(Processor.class); StreamListenerTestUtils.FooInboundChannel1 inboundChannel2 = context .getBean(StreamListenerTestUtils.FooInboundChannel1.class); @@ -294,7 +326,8 @@ public class StreamListenerHandlerMethodTests { @Test public void testMethodWithMultipleOutputParameters() throws Exception { ConfigurableApplicationContext context = SpringApplication.run(TestMethodWithMultipleOutputParameters.class, - "--server.port=0"); + "--server.port=0", + "--spring.jmx.enabled=false"); Processor processor = context.getBean(Processor.class); String id = UUID.randomUUID().toString(); StreamListenerTestUtils.FooOutboundChannel1 source2 = context 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 index cdf2d0e85..a43756cd2 100644 --- 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 @@ -44,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan + * @author Vinicius Carvalho */ @RunWith(Parameterized.class) public class StreamListenerMessageArgumentTests { @@ -63,7 +64,7 @@ public class StreamListenerMessageArgumentTests { @SuppressWarnings("unchecked") public void testMessageArgument() throws Exception { ConfigurableApplicationContext context = SpringApplication - .run(this.configClass, "--server.port=0"); + .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(); @@ -73,10 +74,10 @@ public class StreamListenerMessageArgumentTests { .getBean(TestPojoWithMessageArgument.class); assertThat(testPojoWithMessageArgument.receivedMessages).hasSize(1); assertThat(testPojoWithMessageArgument.receivedMessages.get(0).getPayload()).isEqualTo("barbar" + id); - Message message = (Message) collector + Message message = (Message) collector .forChannel(processor.output()).poll(1, TimeUnit.SECONDS); assertThat(message).isNotNull(); - assertThat(message.getPayload().getBar()).isEqualTo("barbar" + id); + assertThat(new String(message.getPayload())).contains("barbar" + id); context.close(); } 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 index b3e8ada39..d345dad6f 100644 --- 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 @@ -23,6 +23,8 @@ 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; @@ -50,6 +52,8 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan + * @author Vinicius Carvalho + * */ @RunWith(StreamListenerMethodReturnWithConversionTests.class) @Suite.SuiteClasses({ StreamListenerMethodReturnWithConversionTests.TestReturnConversion.class, @@ -79,19 +83,19 @@ public class StreamListenerMethodReturnWithConversionTests extends Suite { @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.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); - assertThat(testPojoWithMimeType.receivedPojos).hasSize(1); - assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id); - Message message = (Message) collector.forChannel(processor.output()).poll(1, + 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(message.getPayload()).isEqualTo("{\"bar\":\"barbar" + id + "\"}"); + assertThat(new String(message.getPayload())).isEqualTo("{\"bar\":\"barbar" + id + "\"}"); assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) .includes(MimeTypeUtils.APPLICATION_JSON)); context.close(); @@ -103,6 +107,8 @@ public class StreamListenerMethodReturnWithConversionTests extends Suite { private Class configClass; + private ObjectMapper mapper = new ObjectMapper(); + public TestReturnNoConversion(Class configClass) { this.configClass = configClass; } @@ -115,21 +121,22 @@ public class StreamListenerMethodReturnWithConversionTests extends Suite { @Test @SuppressWarnings("unchecked") public void testReturnNoConversion() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + 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); - assertThat(testPojoWithMimeType.receivedPojos).hasSize(1); - assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id); - Message message = (Message) collector + 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(message.getPayload().getBar()).isEqualTo("barbar" + id); - assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) == null); + StreamListenerTestUtils.BarPojo barPojo = 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(); } } 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 index 9d23dc99d..3b24535c1 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -44,6 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan + * @author Vinicius Carvalho */ @RunWith(Parameterized.class) public class StreamListenerMethodWithReturnMessageTests { @@ -63,7 +65,7 @@ public class StreamListenerMethodWithReturnMessageTests { @SuppressWarnings("unchecked") public void testReturnMessage() throws Exception { ConfigurableApplicationContext context = SpringApplication - .run(this.configClass, "--server.port=0"); + .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(); @@ -72,12 +74,12 @@ public class StreamListenerMethodWithReturnMessageTests { .setHeader("contentType", "application/json").build()); TestPojoWithMessageReturn testPojoWithMessageReturn = context .getBean(TestPojoWithMessageReturn.class); - assertThat(testPojoWithMessageReturn.receivedPojos).hasSize(1); - assertThat(testPojoWithMessageReturn.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id); - Message message = (Message) collector + 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().getBar()).isEqualTo("barbar" + id); + assertThat(new String(message.getPayload())).contains("barbar" + id); context.close(); } 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 index af819fc06..476d3480f 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -63,21 +64,21 @@ public class StreamListenerMethodWithReturnValueTests { @SuppressWarnings("unchecked") public void testReturn() throws Exception { ConfigurableApplicationContext context = SpringApplication - .run(this.configClass, "--server.port=0"); + .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 + Message message = (Message) collector .forChannel(processor.output()).poll(1, TimeUnit.SECONDS); TestStringProcessor testStringProcessor = context .getBean(TestStringProcessor.class); - assertThat(testStringProcessor.receivedPojos).hasSize(1); - assertThat(testStringProcessor.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id); + Assertions.assertThat(testStringProcessor.receivedPojos).hasSize(1); + Assertions.assertThat(testStringProcessor.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id); assertThat(message).isNotNull(); - assertThat(message.getPayload()).isEqualTo("barbar" + id); + assertThat(new String(message.getPayload())).contains("barbar" + id); context.close(); } 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 243d39921..9858b30c3 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 @@ -55,6 +55,14 @@ public class StreamListenerTestUtils { public void setFoo(String foo) { this.foo = foo; } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("FooPojo{"); + sb.append("foo='").append(foo).append('\''); + sb.append('}'); + return sb.toString(); + } } public static class BarPojo { @@ -68,5 +76,13 @@ public class StreamListenerTestUtils { public void setBar(String bar) { this.bar = bar; } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("BarPojo{"); + sb.append("bar='").append(bar).append('\''); + sb.append('}'); + return sb.toString(); + } } } 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 index 69b9e9621..d2f5626be 100644 --- 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 @@ -45,12 +45,13 @@ import static org.springframework.cloud.stream.binding.StreamListenerErrorMessag /** * @author Marius Bogoevici * @author Ilayaperumal Gopinathan + * @author Vinicius Carvalho */ public class StreamListenerWithAnnotatedInputOutputArgsTests { @Test public void testInputOutputArgs() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(TestInputOutputArgs.class, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(TestInputOutputArgs.class, "--server.port=0", "--spring.cloud.stream.bindings.output.contentType=text/plain", "--spring.jmx.enabled=false"); sendMessageAndValidate(context); } @@ -68,7 +69,7 @@ public class StreamListenerWithAnnotatedInputOutputArgsTests { @Test public void testInputOutputArgsWithInvalidBindableTarget() { try { - SpringApplication.run(TestInputOutputArgsWithInvalidBindableTarget.class, "--server.port=0"); + SpringApplication.run(TestInputOutputArgsWithInvalidBindableTarget.class, "--server.port=0","--spring.jmx.enabled=false"); fail("Exception expected on using invalid bindable target as method parameter"); } catch (BeanCreationException e) { @@ -81,7 +82,7 @@ public class StreamListenerWithAnnotatedInputOutputArgsTests { @Test public void testInputOutputArgsWithParameterOrderChanged() throws Exception { ConfigurableApplicationContext context = SpringApplication - .run(TestInputOutputArgsWithParameterOrderChanged.class, "--server.port=0"); + .run(TestInputOutputArgsWithParameterOrderChanged.class, "--server.port=0", "--spring.cloud.stream.bindings.output.contentType=text/plain","--spring.jmx.enabled=false"); sendMessageAndValidate(context); } @@ -90,9 +91,9 @@ public class StreamListenerWithAnnotatedInputOutputArgsTests { 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 = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo("HELLO"); + assertThat(new String(result.getPayload())).isEqualTo("HELLO"); context.close(); } diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java index 1c4f71d9f..0f8abc8fe 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java @@ -56,30 +56,30 @@ public class TextPlainConversionTest { public void testTextPlainConversionOnOutput() throws Exception { testProcessor.input().send(MessageBuilder.withPayload("Bar").build()); @SuppressWarnings("unchecked") - Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) + Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo("Foo{name='Bar'}"); + assertThat(new String(received.getPayload())).isEqualTo("Foo{name='Bar'}"); } @Test public void testByteArrayConversionOnOutput() throws Exception { testProcessor.output().send(MessageBuilder.withPayload("Bar".getBytes()).build()); @SuppressWarnings("unchecked") - Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) + Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo("Bar"); + assertThat(new String(received.getPayload())).isEqualTo("Bar"); } @Test public void testTextPlainConversionOnInputAndOutput() throws Exception { testProcessor.input().send(MessageBuilder.withPayload(new Foo("Bar")).build()); @SuppressWarnings("unchecked") - Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) + Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(received).isNotNull(); - assertThat(received.getPayload()).isEqualTo("Foo{name='Foo{name='Bar'}'}"); + assertThat(new String(received.getPayload())).isEqualTo("Foo{name='Foo{name='Bar'}'}"); } @EnableBinding(Processor.class) 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 index c24467f53..1df9ca403 100644 --- 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 @@ -18,6 +18,7 @@ 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; @@ -33,6 +34,7 @@ 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.converter.MessageConversionException; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -40,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Marius Bogoevici + * @author Vinicius Carvalho * @since 1.2 */ @RunWith(SpringJUnit4ClassRunner.class) @@ -52,16 +55,23 @@ public class TextPlainToJsonConversionTest { @Autowired private BinderFactory binderFactory; + private ObjectMapper mapper = new ObjectMapper(); + @Test public void testNoContentTypeToJsonConversionOnInput() throws Exception { testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}").build()); - Message received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) + Message received = (Message) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class)) .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(received).isNotNull(); - assertThat(((Foo) received.getPayload()).getName()).isEqualTo("transformed-Bar"); + Foo foo = mapper.readValue(received.getPayload(),Foo.class); + assertThat(foo.getName()).isEqualTo("transformed-Bar"); } - @Test + /** + * @since 2.0: Conversion from text/plain -> json is no longer supported. Strict contentType only. + * @throws Exception + */ + @Test(expected = MessageConversionException.class) public void testTextPlainToJsonConversionOnInput() throws Exception { testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}") .setHeader(MessageHeaders.CONTENT_TYPE, "text/plain").build()); diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/AggregateApplicationTests.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/AggregateApplicationTests.java index 79ba8ef00..d54d51dfd 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/AggregateApplicationTests.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/AggregateApplicationTests.java @@ -49,9 +49,10 @@ public class AggregateApplicationTests { TestSupportBinder testSupportBinder = (TestSupportBinder) context.getBean(BinderFactory.class).getBinder(null, MessageChannel.class); MessageChannel processorOutput = testSupportBinder.getChannelForName("output"); - Message received = (Message) (testSupportBinder.messageCollector().forChannel(processorOutput) + Message received = (Message) (testSupportBinder.messageCollector().forChannel(processorOutput) .poll(5, TimeUnit.SECONDS)); Assert.assertThat(received, notNullValue()); - Assert.assertTrue(received.getPayload().endsWith("processed")); + String payload = new String(received.getPayload()); + Assert.assertTrue(payload.endsWith("processed")); } } diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/processor/TestProcessor.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/processor/TestProcessor.java index e7b76b367..fbf9af5cf 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/processor/TestProcessor.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/processor/TestProcessor.java @@ -21,7 +21,10 @@ import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Processor; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.support.MessageBuilder; /** * @author Ilayaperumal Gopinathan @@ -33,7 +36,7 @@ public class TestProcessor { @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) - public String process(String message) { - return message + " processed"; + public Message process(String message) { + return MessageBuilder.withPayload(message + " processed").setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build(); } } diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/source/TestSource.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/source/TestSource.java index e6421af15..bd1cda8eb 100644 --- a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/source/TestSource.java +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/aggregate/source/TestSource.java @@ -27,7 +27,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.InboundChannelAdapter; import org.springframework.integration.core.MessageSource; import org.springframework.messaging.Message; -import org.springframework.messaging.support.GenericMessage; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.MessageBuilder; /** * @author Ilayaperumal Gopinathan @@ -43,7 +44,7 @@ public class TestSource { return new MessageSource() { @Override public Message receive() { - return new GenericMessage<>(new SimpleDateFormat("DDMMMYYYY").format(new Date())); + return MessageBuilder.withPayload(new SimpleDateFormat("DDMMMYYYY").format(new Date())).setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build(); } }; } 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 new file mode 100644 index 000000000..aa43e6561 --- /dev/null +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/contentType/ContentTypeTests.java @@ -0,0 +1,445 @@ +/* + * Copyright 2016 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 + * + * http://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.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Output; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +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.converter.KryoMessageConverter; +import org.springframework.cloud.stream.converter.MessageConverterUtils; +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.tuple.Tuple; +import org.springframework.tuple.TupleBuilder; +import org.springframework.util.MimeType; +import org.springframework.util.MimeTypeUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Vinicius Carvalho + */ +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 = 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 = 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.getBytes()).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\"".getBytes()).isEqualTo(message.getPayload()); + } + } + + @Test + public void testSendBynaryDataWithoutContentType() 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).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.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) + .includes(MimeTypeUtils.IMAGE_JPEG)); + 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 testSendJavaSerializable() throws Exception { + try (ConfigurableApplicationContext context = SpringApplication.run( + SourceApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.output.contentType=application/x-java-serialized-object")) { + 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(MessageConverterUtils.X_JAVA_SERIALIZED_OBJECT)); + ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) (message.getPayload())); + User received = (User) new ObjectInputStream(bis).readObject(); + assertThat(user.getName()).isEqualTo(received.getName()); + } + } + + @Test + public void testSendKryoSerialized() throws Exception { + try (ConfigurableApplicationContext context = SpringApplication.run( + SourceApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.output.contentType=application/x-java-object")) { + MessageCollector collector = context.getBean(MessageCollector.class); + Kryo kryo = new Kryo(); + 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); + com.esotericsoftware.kryo.io.Input input = new com.esotericsoftware.kryo.io.Input(new ByteArrayInputStream(message.getPayload())); + User received = kryo.readObject(input,User.class); + input.close(); + assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) + .includes(MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE))); + assertThat(user.getName()).isEqualTo(received.getName()); + + } + } + + @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().getBytes()); + } + } + + @Test + public void testSendTuple() throws Exception { + try (ConfigurableApplicationContext context = SpringApplication.run( + SourceApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.output.contentType=application/x-spring-tuple")) { + MessageCollector collector = context.getBean(MessageCollector.class); + Source source = context.getBean(Source.class); + Tuple tuple = TupleBuilder.tuple().of("foo","bar"); + source.output().send(MessageBuilder.withPayload(tuple).build()); + Message message = (Message) collector + .forChannel(source.output()).poll(1, TimeUnit.SECONDS); + assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) + .includes(MessageConverterUtils.X_SPRING_TUPLE)); + assertThat(TupleBuilder.fromString(new String(message.getPayload()))).isEqualTo(tuple); + } + } + + @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(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() 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); + 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)); + } + } + + @Test + public void testReceiveKryoPayload() throws Exception { + try (ConfigurableApplicationContext context = SpringApplication.run( + SinkApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.pojo_input.contentType=application/x-java-object" + )) { + TestSink testSink = context.getBean(TestSink.class); + SinkApplication sourceApp = context.getBean(SinkApplication.class); + Kryo kryo = new Kryo(); + User user = new User("Alice"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Output output = new Output(baos); + kryo.writeObject(output,user); + output.close(); + testSink.pojo().send(MessageBuilder.withPayload(baos.toByteArray()).build()); + Map headers = (Map) sourceApp.arguments.pop(); + User received = (User)sourceApp.arguments.pop(); + assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE)) + .includes(MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE))); + assertThat(user.getName()).isEqualTo(received.getName()); + } + } + + @Test + public void testReceiveKryoWithHeadersOverridingDefault() 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); + Kryo kryo = new Kryo(); + User user = new User("Alice"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Output output = new Output(baos); + kryo.writeObject(output,user); + output.close(); + testSink.pojo().send(MessageBuilder.withPayload(baos.toByteArray()) + .setHeader(MessageHeaders.CONTENT_TYPE, MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE)) + .build()); + Map headers = (Map) sourceApp.arguments.pop(); + User received = (User)sourceApp.arguments.pop(); + assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE)) + .includes(MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE))); + assertThat(user.getName()).isEqualTo(received.getName()); + } + } + + @Test + public void testReceiveJavaSerializable() throws Exception { + try (ConfigurableApplicationContext context = SpringApplication.run( + SinkApplication.class, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.pojo_input.contentType=application/x-java-serialized-object" + )) { + TestSink testSink = context.getBean(TestSink.class); + SinkApplication sourceApp = context.getBean(SinkApplication.class); + User user = new User("Alice"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new ObjectOutputStream(baos).writeObject(user); + testSink.pojo().send(MessageBuilder.withPayload(baos.toByteArray()).build()); + Map headers = (Map) sourceApp.arguments.pop(); + User received = (User)sourceApp.arguments.pop(); + assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE)) + .includes(MessageConverterUtils.X_JAVA_SERIALIZED_OBJECT)); + assertThat(user.getName()).isEqualTo(received.getName()); + + } + } + + @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){ + arguments.push(user); + arguments.push(headers); + } + + @StreamListener("TUPLE_INPUT") + public void receive(Tuple tuple){ + } + + @StreamListener("STRING_INPUT") + public void receive(String string){ + } + + @StreamListener("RAW_INPUT") + public void receive(byte[] data, @Headers Map headers){ + arguments.push(data); + arguments.push(headers); + } + + } + + public interface TestSink { + + @Input("POJO_INPUT") + SubscribableChannel pojo(); + + @Input("STRING_INPUT") + SubscribableChannel string(); + + @Input("TUPLE_INPUT") + SubscribableChannel tuple(); + + @Input("RAW_INPUT") + SubscribableChannel raw(); + + } + + public static class User implements Serializable { + + private String name; + + public User(){} + + @JsonCreator + public User(@JsonProperty("name") String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("User{"); + sb.append("name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } + } + +} diff --git a/spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/channel/partitioned-configurers.properties b/spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/channel/partitioned-configurers.properties index 9b7e4bd95..aee346650 100644 --- a/spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/channel/partitioned-configurers.properties +++ b/spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/channel/partitioned-configurers.properties @@ -1,3 +1,4 @@ spring.cloud.stream.bindings.output.destination=partOut spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload spring.cloud.stream.bindings.output.producer.partitionCount=3 + diff --git a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java index aea651909..48fcc9871 100644 --- a/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java +++ b/spring-cloud-stream-metrics/src/test/java/org/springframework/cloud/stream/metrics/ApplicationMetricsExporterTests.java @@ -81,7 +81,7 @@ public class ApplicationMetricsExporterTests { Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); ApplicationMetrics applicationMetrics = mapper - .readValue((String) message.getPayload(), ApplicationMetrics.class); + .readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); Assert.assertEquals("application", applicationMetrics.getName()); @@ -102,7 +102,7 @@ public class ApplicationMetricsExporterTests { .poll(10, TimeUnit.SECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), + ApplicationMetrics applicationMetrics = mapper.readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); @@ -126,7 +126,7 @@ public class ApplicationMetricsExporterTests { Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); ApplicationMetrics applicationMetrics = mapper - .readValue((String) message.getPayload(), ApplicationMetrics.class); + .readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); Assert.assertTrue(contains("mem", applicationMetrics.getMetrics())); @@ -150,7 +150,7 @@ public class ApplicationMetricsExporterTests { Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); ApplicationMetrics applicationMetrics = mapper - .readValue((String) message.getPayload(), ApplicationMetrics.class); + .readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertFalse(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); Assert.assertTrue(contains("mem", applicationMetrics.getMetrics())); @@ -173,7 +173,7 @@ public class ApplicationMetricsExporterTests { .poll(10, TimeUnit.SECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), + ApplicationMetrics applicationMetrics = mapper.readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertFalse(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); @@ -196,7 +196,7 @@ public class ApplicationMetricsExporterTests { .poll(10, TimeUnit.SECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), + ApplicationMetrics applicationMetrics = mapper.readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertFalse(contains("mem", applicationMetrics.getMetrics())); Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", @@ -229,7 +229,7 @@ public class ApplicationMetricsExporterTests { .poll(10, TimeUnit.SECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), + ApplicationMetrics applicationMetrics = mapper.readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); @@ -265,7 +265,7 @@ public class ApplicationMetricsExporterTests { .poll(10, TimeUnit.SECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), + ApplicationMetrics applicationMetrics = mapper.readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); @@ -292,7 +292,7 @@ public class ApplicationMetricsExporterTests { .poll(10, TimeUnit.SECONDS); Assert.assertNotNull(message); ObjectMapper mapper = applicationContext.getBean(ObjectMapper.class); - ApplicationMetrics applicationMetrics = mapper.readValue((String) message.getPayload(), + ApplicationMetrics applicationMetrics = mapper.readValue((byte[]) message.getPayload(), ApplicationMetrics.class); Assert.assertTrue(contains("integration.channel.errorChannel.errorRate.mean", applicationMetrics.getMetrics())); diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamEmitterBasicTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamEmitterBasicTests.java index 14005480c..aac728dcc 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamEmitterBasicTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamEmitterBasicTests.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; +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; @@ -34,7 +35,6 @@ import org.springframework.cloud.stream.messaging.Processor; import org.springframework.cloud.stream.messaging.Source; import org.springframework.cloud.stream.test.binder.MessageCollector; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.messaging.Message; @@ -52,54 +52,75 @@ public class StreamEmitterBasicTests { @Test public void testFluxReturnAndOutputMethodLevel() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(TestFluxReturnAndOutputMethodLevel.class); - context.refresh(); + ConfigurableApplicationContext context = SpringApplication.run(TestFluxReturnAndOutputMethodLevel.class, + "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); receiveAndValidate(context); context.close(); } @Test public void testVoidReturnAndOutputMethodParameter() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(TestVoidReturnAndOutputMethodParameter.class); - context.refresh(); + ConfigurableApplicationContext context = SpringApplication.run(TestVoidReturnAndOutputMethodParameter.class, + "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); receiveAndValidate(context); context.close(); } @Test public void testVoidReturnAndOutputAtMethodLevel() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(TestVoidReturnAndOutputAtMethodLevel.class); - context.refresh(); + ConfigurableApplicationContext context = SpringApplication.run(TestVoidReturnAndOutputAtMethodLevel.class, + "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); receiveAndValidate(context); context.close(); } @Test public void testVoidReturnAndMultipleOutputMethodParameters() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(TestVoidReturnAndMultipleOutputMethodParameters.class); - context.refresh(); + ConfigurableApplicationContext context = SpringApplication.run(TestVoidReturnAndMultipleOutputMethodParameters.class, + "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain", + "--spring.cloud.stream.bindings.output1.contentType=text/plain", + "--spring.cloud.stream.bindings.output2.contentType=text/plain", + "--spring.cloud.stream.bindings.output3.contentType=text/plain"); receiveAndValidateMultipleOutputs(context); context.close(); } @Test public void testMultipleStreamEmitterMethods() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(TestMultipleStreamEmitterMethods.class); - context.refresh(); + ConfigurableApplicationContext context = SpringApplication.run(TestMultipleStreamEmitterMethods.class, + "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain", + "--spring.cloud.stream.bindings.output1.contentType=text/plain", + "--spring.cloud.stream.bindings.output2.contentType=text/plain", + "--spring.cloud.stream.bindings.output3.contentType=text/plain"); receiveAndValidateMultipleOutputs(context); context.close(); } @Test public void testSameAppContextWithMultipleStreamEmitters() throws Exception { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(TestSameAppContextWithMultipleStreamEmitters.class); - context.refresh(); + ConfigurableApplicationContext context = SpringApplication.run(TestSameAppContextWithMultipleStreamEmitters.class, + "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain", + "--spring.cloud.stream.bindings.output1.contentType=text/plain", + "--spring.cloud.stream.bindings.output2.contentType=text/plain", + "--spring.cloud.stream.bindings.output3.contentType=text/plain"); receiveAndValidateMultiStreamEmittersInSameContext(context); context.close(); } @@ -108,12 +129,12 @@ public class StreamEmitterBasicTests { private static void receiveAndValidate(ConfigurableApplicationContext context) throws InterruptedException { Source source = context.getBean(Source.class); MessageCollector messageCollector = context.getBean(MessageCollector.class); - List messages = new ArrayList<>(); + List messages = new ArrayList<>(); for (int i = 0; i < 1000; i++) { - messages.add((String) messageCollector.forChannel(source.output()).poll(5000, TimeUnit.MILLISECONDS).getPayload()); + messages.add((byte[]) messageCollector.forChannel(source.output()).poll(5000, TimeUnit.MILLISECONDS).getPayload()); } for (int i = 0; i < 1000; i++) { - assertThat(messages.get(i)).isEqualTo("HELLO WORLD!!" + i); + assertThat(new String(messages.get(i))).isEqualTo("HELLO WORLD!!" + i); } } @@ -121,7 +142,7 @@ public class StreamEmitterBasicTests { private static void receiveAndValidateMultipleOutputs(ConfigurableApplicationContext context) throws InterruptedException { TestMultiOutboundChannels source = context.getBean(TestMultiOutboundChannels.class); MessageCollector messageCollector = context.getBean(MessageCollector.class); - List messages = new ArrayList<>(); + List messages = new ArrayList<>(); assertMessages(source.output1(), messageCollector, messages); messages.clear(); assertMessages(source.output2(), messageCollector, messages); @@ -135,37 +156,37 @@ public class StreamEmitterBasicTests { TestMultiOutboundChannels source1 = context1.getBean(TestMultiOutboundChannels.class); MessageCollector messageCollector = context1.getBean(MessageCollector.class); - List messages = new ArrayList<>(); + List messages = new ArrayList<>(); assertMessagesX(source1.output1(), messageCollector, messages); messages.clear(); assertMessagesY(source1.output2(), messageCollector, messages); messages.clear(); } - private static void assertMessages(MessageChannel channel, MessageCollector messageCollector, List messages) throws InterruptedException { + private static void assertMessages(MessageChannel channel, MessageCollector messageCollector, List messages) throws InterruptedException { for (int i = 0; i < 1000; i++) { - messages.add((String) messageCollector.forChannel(channel).poll(5000, TimeUnit.MILLISECONDS).getPayload()); + messages.add((byte[]) messageCollector.forChannel(channel).poll(5000, TimeUnit.MILLISECONDS).getPayload()); } for (int i = 0; i < 1000; i++) { - assertThat(messages.get(i)).isEqualTo("Hello World!!" + i); + assertThat(new String(messages.get(i))).isEqualTo("Hello World!!" + i); } } - private static void assertMessagesX(MessageChannel channel, MessageCollector messageCollector, List messages) throws InterruptedException { + private static void assertMessagesX(MessageChannel channel, MessageCollector messageCollector, List messages) throws InterruptedException { for (int i = 0; i < 1000; i++) { - messages.add((String) messageCollector.forChannel(channel).poll(5000, TimeUnit.MILLISECONDS).getPayload()); + messages.add((byte[]) messageCollector.forChannel(channel).poll(5000, TimeUnit.MILLISECONDS).getPayload()); } for (int i = 0; i < 1000; i++) { - assertThat(messages.get(i)).isEqualTo("Hello World!!" + i); + assertThat(new String(messages.get(i))).isEqualTo("Hello World!!" + i); } } - private static void assertMessagesY(MessageChannel channel, MessageCollector messageCollector, List messages) throws InterruptedException { + private static void assertMessagesY(MessageChannel channel, MessageCollector messageCollector, List messages) throws InterruptedException { for (int i = 0; i < 1000; i++) { - messages.add((String) messageCollector.forChannel(channel).poll(5000, TimeUnit.MILLISECONDS).getPayload()); + messages.add((byte[]) messageCollector.forChannel(channel).poll(5000, TimeUnit.MILLISECONDS).getPayload()); } for (int i = 0; i < 1000; i++) { - assertThat(messages.get(i)).isEqualTo("Hello FooBar!!" + i); + assertThat(new String(messages.get(i))).isEqualTo("Hello FooBar!!" + i); } } diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerGenericFluxInputOutputArgsWithMessageTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerGenericFluxInputOutputArgsWithMessageTests.java index d8c8e9aa4..fd3e3826d 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerGenericFluxInputOutputArgsWithMessageTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerGenericFluxInputOutputArgsWithMessageTests.java @@ -40,25 +40,32 @@ import static org.springframework.cloud.stream.binding.StreamListenerErrorMessag /** * @author Ilayaperumal Gopinathan + * @author Vinicius Carvalho */ @SuppressWarnings("unchecked") public class StreamListenerGenericFluxInputOutputArgsWithMessageTests { @SuppressWarnings("unchecked") - private static void sendMessageAndValidate(ConfigurableApplicationContext context) throws InterruptedException { + private static void sendMessageAndValidate(ConfigurableApplicationContext context) + throws InterruptedException { Processor processor = context.getBean(Processor.class); String sentPayload = "hello " + UUID.randomUUID().toString(); - processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); + processor.input().send(MessageBuilder.withPayload(sentPayload) + .setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, + TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } @Test public void testGenericFluxInputOutputArgsWithMessage() throws Exception { - ConfigurableApplicationContext context = SpringApplication - .run(TestGenericStringFluxInputOutputArgsWithMessageImpl1.class, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run( + TestGenericStringFluxInputOutputArgsWithMessageImpl1.class, + "--server.port=0", "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); context.close(); } @@ -66,11 +73,16 @@ public class StreamListenerGenericFluxInputOutputArgsWithMessageTests { @Test public void testInvalidInputValueWithOutputMethodParameters() { try { - SpringApplication.run(TestGenericStringFluxInputOutputArgsWithMessageImpl2.class, "--server.port=0"); + SpringApplication.run( + TestGenericStringFluxInputOutputArgsWithMessageImpl2.class, + "--server.port=0", "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); fail("Expected exception: " + INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM); } catch (Exception e) { - assertThat(e.getMessage()).contains(INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM); + assertThat(e.getMessage()) + .contains(INVALID_INPUT_VALUE_WITH_OUTPUT_METHOD_PARAM); } } @@ -89,7 +101,8 @@ public class StreamListenerGenericFluxInputOutputArgsWithMessageTests { @StreamListener public void receive(@Input(Processor.INPUT) Flux input, @Output(Processor.OUTPUT) FluxSender output) { - output.send(input.map(m -> MessageBuilder.withPayload((A) m.toString().toUpperCase()).build())); + output.send(input.map(m -> MessageBuilder + .withPayload((A) m.toString().toUpperCase()).build())); } } @@ -98,9 +111,9 @@ public class StreamListenerGenericFluxInputOutputArgsWithMessageTests { public static class TestGenericFluxInputOutputArgsWithMessage2 { @StreamListener(Processor.INPUT) - public void receive(Flux input, - @Output(Processor.OUTPUT) FluxSender output) { - output.send(input.map(m -> MessageBuilder.withPayload((A) m.toString().toUpperCase()).build())); + public void receive(Flux input, @Output(Processor.OUTPUT) FluxSender output) { + output.send(input.map(m -> MessageBuilder + .withPayload((A) m.toString().toUpperCase()).build())); } } } diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsTests.java index 7ff1702ca..de8de36a1 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsTests.java @@ -65,14 +65,17 @@ public class StreamListenerReactiveInputOutputArgsTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } @Test public void testInputOutputArgs() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); context.close(); } diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithMessageTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithMessageTests.java index 5e81d664d..0beda9bcf 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithMessageTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithMessageTests.java @@ -66,14 +66,17 @@ public class StreamListenerReactiveInputOutputArgsWithMessageTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } @Test public void testInputOutputArgs() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); context.close(); } diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderAndFailureTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderAndFailureTests.java index ac4c94a62..79642c890 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderAndFailureTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderAndFailureTests.java @@ -66,9 +66,9 @@ public class StreamListenerReactiveInputOutputArgsWithSenderAndFailureTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } private static void sendFailingMessage(ConfigurableApplicationContext context) throws InterruptedException { @@ -79,7 +79,10 @@ public class StreamListenerReactiveInputOutputArgsWithSenderAndFailureTests { @Test public void testInputOutputArgsWithFluxSenderAndFailure() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); sendFailingMessage(context); sendMessageAndValidate(context); diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderTests.java index ed2500d47..aa7ce88e0 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveInputOutputArgsWithSenderTests.java @@ -66,15 +66,18 @@ public class StreamListenerReactiveInputOutputArgsWithSenderTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } @Test public void testInputOutputArgsWithFluxSender() throws Exception { ConfigurableApplicationContext context = SpringApplication.run(this.configClass, - "--server.port=0"); + "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); // send multiple message sendMessageAndValidate(context); sendMessageAndValidate(context); diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java index b22ceeb02..8f84d4c7c 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodTests.java @@ -52,7 +52,10 @@ public class StreamListenerReactiveMethodTests { @Test public void testRxJava1InvalidInputValueWithOutputMethodParameters() { try { - SpringApplication.run(RxJava1TestInputOutputArgs.class, "--server.port=0"); + SpringApplication.run(RxJava1TestInputOutputArgs.class, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); fail("IllegalArgumentException should have been thrown"); } catch (Exception e) { @@ -63,7 +66,10 @@ public class StreamListenerReactiveMethodTests { @Test public void testMethodReturnTypeWithNoOutboundSpecified() { try { - SpringApplication.run(ReactorTestReturn5.class, "--server.port=0"); + SpringApplication.run(ReactorTestReturn5.class, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); fail("Exception expected: " + RETURN_TYPE_NO_OUTBOUND_SPECIFIED); } catch (Exception e) { diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodWithReturnTypeTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodWithReturnTypeTests.java index 1cd496f2d..ac8127e95 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodWithReturnTypeTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveMethodWithReturnTypeTests.java @@ -69,14 +69,17 @@ public class StreamListenerReactiveMethodWithReturnTypeTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } @Test public void testReturn() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); sendMessageAndValidate(context); sendMessageAndValidate(context); diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java index 211bd6c05..369c683f4 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithFailureTests.java @@ -70,9 +70,9 @@ public class StreamListenerReactiveReturnWithFailureTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } private static void sendFailingMessage(ConfigurableApplicationContext context) throws InterruptedException { @@ -83,7 +83,10 @@ public class StreamListenerReactiveReturnWithFailureTests { @Test public void testReturnWithFailure() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); sendFailingMessage(context); sendMessageAndValidate(context); diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java index 3e0820652..eb4796ad0 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithMessageTests.java @@ -70,14 +70,17 @@ public class StreamListenerReactiveReturnWithMessageTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } @Test public void testReturnWithMessage() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0", + "--spring.jmx.enabled=false", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); context.close(); } diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java index f56c141a7..ea2635c3b 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerReactiveReturnWithPojoTests.java @@ -20,6 +20,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.concurrent.TimeUnit; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -50,6 +53,8 @@ public class StreamListenerReactiveReturnWithPojoTests { private Class configClass; + private ObjectMapper mapper = new ObjectMapper(); + public StreamListenerReactiveReturnWithPojoTests(Class configClass) { this.configClass = configClass; } @@ -63,16 +68,18 @@ public class StreamListenerReactiveReturnWithPojoTests { @Test public void testReturnWithPojo() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0"); + ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0", + "--spring.jmx.enabled=false"); @SuppressWarnings("unchecked") Processor processor = context.getBean(Processor.class); processor.input().send(MessageBuilder.withPayload("{\"message\":\"helloPojo\"}") .setHeader("contentType", "application/json").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isInstanceOf(BarPojo.class); - assertThat(((BarPojo) result.getPayload()).getBarMessage()).isEqualTo("helloPojo"); + assertThat(result.getPayload()).isInstanceOf(byte[].class); + BarPojo barPojo = mapper.readValue(result.getPayload(),BarPojo.class); + assertThat(barPojo.getBarMessage()).isEqualTo("helloPojo"); context.close(); } @@ -175,7 +182,8 @@ public class StreamListenerReactiveReturnWithPojoTests { private String barMessage; - public BarPojo(String barMessage) { + @JsonCreator + public BarPojo(@JsonProperty("barMessage") String barMessage) { this.barMessage = barMessage; } diff --git a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerWildCardFluxInputOutputArgsWithMessageTests.java b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerWildCardFluxInputOutputArgsWithMessageTests.java index 0d6d304ca..1d86c3f47 100644 --- a/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerWildCardFluxInputOutputArgsWithMessageTests.java +++ b/spring-cloud-stream-reactive/src/test/java/org/springframework/cloud/stream/reactive/StreamListenerWildCardFluxInputOutputArgsWithMessageTests.java @@ -51,15 +51,15 @@ public class StreamListenerWildCardFluxInputOutputArgsWithMessageTests { String sentPayload = "hello " + UUID.randomUUID().toString(); processor.input().send(MessageBuilder.withPayload(sentPayload).setHeader("contentType", "text/plain").build()); MessageCollector messageCollector = context.getBean(MessageCollector.class); - Message result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message result = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(result).isNotNull(); - assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase()); + assertThat(result.getPayload()).isEqualTo(sentPayload.toUpperCase().getBytes()); } @Test public void testWildCardFluxInputOutputArgsWithMessage() throws Exception { ConfigurableApplicationContext context = SpringApplication - .run(TestWildCardFluxInputOutputArgsWithMessage1.class, "--server.port=0"); + .run(TestWildCardFluxInputOutputArgsWithMessage1.class, "--server.port=0","--spring.cloud.stream.bindings.output.contentType=text/plain"); sendMessageAndValidate(context); context.close(); } diff --git a/spring-cloud-stream-schema/src/main/java/org/springframework/cloud/stream/schema/avro/AvroMessageConverterAutoConfiguration.java b/spring-cloud-stream-schema/src/main/java/org/springframework/cloud/stream/schema/avro/AvroMessageConverterAutoConfiguration.java index 30acd316d..d8adbddc9 100644 --- a/spring-cloud-stream-schema/src/main/java/org/springframework/cloud/stream/schema/avro/AvroMessageConverterAutoConfiguration.java +++ b/spring-cloud-stream-schema/src/main/java/org/springframework/cloud/stream/schema/avro/AvroMessageConverterAutoConfiguration.java @@ -24,6 +24,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.cloud.stream.annotation.StreamMessageConverter; +import org.springframework.cloud.stream.binder.StringConvertingContentTypeResolver; import org.springframework.cloud.stream.schema.client.SchemaRegistryClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -45,6 +47,7 @@ public class AvroMessageConverterAutoConfiguration { @Bean @ConditionalOnMissingBean(AvroSchemaRegistryClientMessageConverter.class) + @StreamMessageConverter public AvroSchemaRegistryClientMessageConverter avroSchemaMessageConverter( SchemaRegistryClient schemaRegistryClient) { AvroSchemaRegistryClientMessageConverter avroSchemaRegistryClientMessageConverter = new AvroSchemaRegistryClientMessageConverter( diff --git a/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaMessageConverterTests.java b/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaMessageConverterTests.java index 5ef37d995..d04aac0c0 100644 --- a/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaMessageConverterTests.java +++ b/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaMessageConverterTests.java @@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; +import org.springframework.cloud.stream.annotation.StreamMessageConverter; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.cloud.stream.messaging.Source; import org.springframework.cloud.stream.schema.avro.AvroSchemaMessageConverter; @@ -203,6 +204,7 @@ public class AvroSchemaMessageConverterTests { } @Bean + @StreamMessageConverter public MessageConverter userMessageConverter() throws IOException { AvroSchemaMessageConverter avroSchemaMessageConverter = new AvroSchemaMessageConverter( MimeType.valueOf("avro/bytes")); @@ -232,6 +234,7 @@ public class AvroSchemaMessageConverterTests { } @Bean + @StreamMessageConverter public MessageConverter userMessageConverter() throws IOException { AvroSchemaMessageConverter avroSchemaMessageConverter = new AvroSchemaMessageConverter( MimeType.valueOf("avro/bytes")); diff --git a/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaRegistryClientMessageConverterTests.java b/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaRegistryClientMessageConverterTests.java index 92aba20e9..e64537b28 100644 --- a/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaRegistryClientMessageConverterTests.java +++ b/spring-cloud-stream-schema/src/test/java/org/springframework/cloud/schema/avro/AvroSchemaRegistryClientMessageConverterTests.java @@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cache.support.NoOpCacheManager; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; +import org.springframework.cloud.stream.annotation.StreamMessageConverter; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.cloud.stream.messaging.Source; import org.springframework.cloud.stream.schema.avro.AvroSchemaRegistryClientMessageConverter; @@ -160,6 +161,7 @@ public class AvroSchemaRegistryClientMessageConverterTests { public static class NoCacheConfiguration { @SuppressWarnings("deprecation") @Bean + @StreamMessageConverter AvroSchemaRegistryClientMessageConverter avroSchemaRegistryClientMessageConverter() { return new AvroSchemaRegistryClientMessageConverter(new DefaultSchemaRegistryClient()); } diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/bean/AggregateWithBeanTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/bean/AggregateWithBeanTest.java index d26d38402..2e4c619cd 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/bean/AggregateWithBeanTest.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/bean/AggregateWithBeanTest.java @@ -42,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Marius Bogoevici */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = AggregateWithBeanTest.ChainedProcessors.class, properties = { "server.port=-1" }) +@SpringBootTest(classes = AggregateWithBeanTest.ChainedProcessors.class, properties = { "server.port=-1","--spring.cloud.stream.bindings.input.contentType=text/plain","--spring.cloud.stream.bindings.output.contentType=text/plain" }) public class AggregateWithBeanTest { @Autowired @@ -56,9 +56,9 @@ public class AggregateWithBeanTest { Processor uppercaseProcessor = aggregateApplication.getBinding(Processor.class, "upper"); Processor suffixProcessor = aggregateApplication.getBinding(Processor.class, "suffix"); uppercaseProcessor.input().send(MessageBuilder.withPayload("Hello").build()); - Message receivedMessage = messageCollector.forChannel(suffixProcessor.output()).poll(1, TimeUnit.SECONDS); + Message receivedMessage = (Message) messageCollector.forChannel(suffixProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(receivedMessage).isNotNull(); - assertThat(receivedMessage.getPayload()).isEqualTo("HELLO WORLD!"); + assertThat(receivedMessage.getPayload()).isEqualTo("HELLO WORLD!".getBytes()); } @SpringBootApplication diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/main/AggregateWithMainTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/main/AggregateWithMainTest.java index 4eb43d46a..6cddc9fdf 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/main/AggregateWithMainTest.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/aggregate/main/AggregateWithMainTest.java @@ -48,16 +48,16 @@ public class AggregateWithMainTest { ConfigurableApplicationContext context = new AggregateApplicationBuilder(MainConfiguration.class) .from(UppercaseProcessor.class).namespace("upper") .to(SuffixProcessor.class).namespace("suffix") - .run(); + .run("--spring.cloud.stream.bindings.input.contentType=text/plain","--spring.cloud.stream.bindings.output.contentType=text/plain"); AggregateApplication aggregateAccessor = context.getBean(AggregateApplication.class); MessageCollector messageCollector = context.getBean(MessageCollector.class); Processor uppercaseProcessor = aggregateAccessor.getBinding(Processor.class, "upper"); Processor suffixProcessor = aggregateAccessor.getBinding(Processor.class, "suffix"); uppercaseProcessor.input().send(MessageBuilder.withPayload("Hello").build()); - Message receivedMessage = messageCollector.forChannel(suffixProcessor.output()).poll(1, TimeUnit.SECONDS); + Message receivedMessage = (Message) messageCollector.forChannel(suffixProcessor.output()).poll(1, TimeUnit.SECONDS); assertThat(receivedMessage).isNotNull(); - assertThat(receivedMessage.getPayload()).isEqualTo("HELLO WORLD!"); + assertThat(receivedMessage.getPayload()).isEqualTo("HELLO WORLD!".getBytes()); context.close(); } diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/disable/AutoconfigurationDisabledTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/disable/AutoconfigurationDisabledTest.java index c1f9b768d..7d12e3a44 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/disable/AutoconfigurationDisabledTest.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/disable/AutoconfigurationDisabledTest.java @@ -42,7 +42,9 @@ import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = AutoconfigurationDisabledTest.MyProcessor.class, properties = { "server.port=-1", - "spring.cloud.stream.defaultBinder=test" + "spring.cloud.stream.defaultBinder=test", + "--spring.cloud.stream.bindings.input.contentType=text/plain", + "--spring.cloud.stream.bindings.output.contentType=text/plain" }) @DirtiesContext public class AutoconfigurationDisabledTest { @@ -57,9 +59,9 @@ public class AutoconfigurationDisabledTest { public void testAutoconfigurationDisabled() throws Exception { processor.input().send(MessageBuilder.withPayload("Hello").build()); // Since the interaction is synchronous, the result should be immediate - Message response = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); + Message response = (Message) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS); assertThat(response).isNotNull(); - assertThat(response.getPayload()).isEqualTo("Hello world"); + assertThat(response.getPayload()).isEqualTo("Hello world".getBytes()); } @SpringBootApplication(exclude = TestSupportBinderAutoConfiguration.class) diff --git a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java index 70f8c92d7..693d9afd3 100644 --- a/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java +++ b/spring-cloud-stream-test-support/src/test/java/org/springframework/cloud/stream/test/example/ExampleTest.java @@ -41,7 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; * correctly. */ @RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = ExampleTest.MyProcessor.class, properties = { "server.port=-1" }) +@SpringBootTest(classes = ExampleTest.MyProcessor.class, properties = { "server.port=-1", "--spring.cloud.stream.bindings.input.contentType=text/plain", "--spring.cloud.stream.bindings.output.contentType=text/plain" }) @DirtiesContext public class ExampleTest { @@ -60,8 +60,8 @@ public class ExampleTest { public void testWiring() { Message message = new GenericMessage<>("hello"); this.processor.input().send(message); - Message received = (Message) this.messageCollector.forChannel(this.processor.output()).poll(); - assertThat(received.getPayload()).isEqualTo("hello world"); + Message received = (Message) this.messageCollector.forChannel(this.processor.output()).poll(); + assertThat(received.getPayload()).isEqualTo("hello world".getBytes()); } @SpringBootApplication diff --git a/spring-cloud-stream-tools/src/main/resources/checkstyle.xml b/spring-cloud-stream-tools/src/main/resources/checkstyle.xml index 283e4e2c9..8fb3b1841 100644 --- a/spring-cloud-stream-tools/src/main/resources/checkstyle.xml +++ b/spring-cloud-stream-tools/src/main/resources/checkstyle.xml @@ -99,14 +99,15 @@ - - - - + + + + +