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
This commit is contained in:
Vinicius Carvalho
2017-09-05 13:45:21 -04:00
committed by Soby Chacko
parent 24cf992301
commit 171f034a8c
74 changed files with 1745 additions and 1066 deletions

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<version>2.0.0.M2</version>
<relativePath/>
</parent>
<scm>
@@ -110,7 +110,6 @@
<modules>
<module>spring-cloud-stream</module>
<module>spring-cloud-stream-binder-test</module>
<module>spring-cloud-stream-codec</module>
<module>spring-cloud-stream-rxjava</module>
<module>spring-cloud-stream-test-support</module>
<module>spring-cloud-stream-test-support-internal</module>

View File

@@ -79,7 +79,8 @@ public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends
*/
protected Message<?> 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<B extends AbstractTestBinder<? extends
@Test
public void testClean() throws Exception {
Binder binder = getBinder();
Binding<MessageChannel> foo0ProducerBinding = binder.bindProducer("foo.0", new DirectChannel(),
createProducerProperties());
Binding<MessageChannel> foo0ConsumerBinding = binder.bindConsumer("foo.0", "testClean", new DirectChannel(),
createConsumerProperties());
Binding<MessageChannel> foo1ProducerBinding = binder.bindProducer("foo.1", new DirectChannel(),
createProducerProperties());
Binding<MessageChannel> foo1ConsumerBinding = binder.bindConsumer("foo.1", "testClean", new DirectChannel(),
createConsumerProperties());
Binding<MessageChannel> foo2ProducerBinding = binder.bindProducer("foo.2", new DirectChannel(),
createProducerProperties());
Binding<MessageChannel> foo0ProducerBinding = binder.bindProducer("foo.0",
new DirectChannel(), createProducerProperties());
Binding<MessageChannel> foo0ConsumerBinding = binder.bindConsumer("foo.0",
"testClean", new DirectChannel(), createConsumerProperties());
Binding<MessageChannel> foo1ProducerBinding = binder.bindProducer("foo.1",
new DirectChannel(), createProducerProperties());
Binding<MessageChannel> foo1ConsumerBinding = binder.bindConsumer("foo.1",
"testClean", new DirectChannel(), createConsumerProperties());
Binding<MessageChannel> 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<MessageChannel> producerBinding = binder.bindProducer("foo.0", moduleOutputChannel,
outputBindingProperties.getProducer());
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.0", "testSendAndReceive", moduleInputChannel,
createConsumerProperties());
Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar")
Binding<MessageChannel> producerBinding = binder.bindProducer("foo.0",
moduleOutputChannel, outputBindingProperties.getProducer());
Binding<MessageChannel> 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<B extends AbstractTestBinder<? extends
QueueChannel moduleInputChannel = new QueueChannel();
Binding<MessageChannel> producerBinding1 = binder.bindProducer("foo.x", moduleOutputChannel1,
createProducerProperties());
Binding<MessageChannel> producerBinding2 = binder.bindProducer("foo.y", moduleOutputChannel2,
createProducerProperties());
Binding<MessageChannel> producerBinding1 = binder.bindProducer("foo.x",
moduleOutputChannel1, createProducerProperties());
Binding<MessageChannel> producerBinding2 = binder.bindProducer("foo.y",
moduleOutputChannel2, createProducerProperties());
Binding<MessageChannel> consumerBinding1 = binder.bindConsumer("foo.x", "testSendAndReceiveMultipleTopics", moduleInputChannel,
Binding<MessageChannel> consumerBinding1 = binder.bindConsumer("foo.x",
"testSendAndReceiveMultipleTopics", moduleInputChannel,
createConsumerProperties());
Binding<MessageChannel> consumerBinding2 = binder.bindConsumer("foo.y", "testSendAndReceiveMultipleTopics", moduleInputChannel,
Binding<MessageChannel> 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<B extends AbstractTestBinder<? extends
assertThat(messages[0]).isNotNull();
assertThat(messages[1]).isNotNull();
assertThat(messages).extracting("payload").containsExactlyInAnyOrder(testPayload1.getBytes(),
testPayload2.getBytes());
assertThat(messages).extracting("payload").containsExactlyInAnyOrder(
testPayload1.getBytes(), testPayload2.getBytes());
producerBinding1.unbind();
producerBinding2.unbind();
@@ -190,22 +209,26 @@ public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends
public void testSendAndReceiveNoOriginalContentType() throws Exception {
Binder binder = getBinder();
BindingProperties producerBindingProperties = createProducerBindingProperties(createProducerProperties());
DirectChannel moduleOutputChannel = createBindableChannel("output", producerBindingProperties);
BindingProperties producerBindingProperties = createProducerBindingProperties(
createProducerProperties());
DirectChannel moduleOutputChannel = createBindableChannel("output",
producerBindingProperties);
QueueChannel moduleInputChannel = new QueueChannel();
Binding<MessageChannel> producerBinding = binder.bindProducer("bar.0", moduleOutputChannel,
producerBindingProperties.getProducer());
Binding<MessageChannel> consumerBinding = binder.bindConsumer("bar.0", "testSendAndReceiveNoOriginalContentType", moduleInputChannel,
Binding<MessageChannel> producerBinding = binder.bindProducer("bar.0",
moduleOutputChannel, producerBindingProperties.getProducer());
Binding<MessageChannel> 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<B extends AbstractTestBinder<? extends
protected abstract PP createProducerProperties();
protected final BindingProperties createConsumerBindingProperties(CP consumerProperties) {
protected final BindingProperties createConsumerBindingProperties(
CP consumerProperties) {
BindingProperties bindingProperties = new BindingProperties();
bindingProperties.setConsumer(consumerProperties);
return bindingProperties;
@@ -228,8 +252,8 @@ public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends
return bindingProperties;
}
protected DirectChannel createBindableChannel(String channelName, BindingProperties bindingProperties)
throws Exception {
protected DirectChannel createBindableChannel(String channelName,
BindingProperties bindingProperties) throws Exception {
BindingServiceProperties bindingServiceProperties = new BindingServiceProperties();
bindingServiceProperties.getBindings().put(channelName, bindingProperties);
ConfigurableApplicationContext applicationContext = new GenericApplicationContext();

View File

@@ -33,7 +33,8 @@ import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Gary Russell
* @author Mark Fisher
* @author Marius Bogoevici
* @author Vinicius Carvalho
*/
public abstract class PartitionCapableBinderTests<B extends AbstractTestBinder<? extends AbstractBinder<MessageChannel, CP, PP>, CP, PP>, CP extends ConsumerProperties, PP extends ProducerProperties>
extends AbstractBinderTests<B, CP, PP> {
@@ -67,7 +69,7 @@ public abstract class PartitionCapableBinderTests<B extends AbstractTestBinder<?
createConsumerProperties());
String testPayload1 = "foo-" + UUID.randomUUID().toString();
output.send(new GenericMessage<>(testPayload1.getBytes()));
output.send(MessageBuilder.withPayload(testPayload1).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN).build());
Message<byte[]> receivedMessage1 = (Message<byte[]>) receive(input1);
assertThat(receivedMessage1).isNotNull();
@@ -80,11 +82,11 @@ public abstract class PartitionCapableBinderTests<B extends AbstractTestBinder<?
binding2.unbind();
String testPayload2 = "foo-" + UUID.randomUUID().toString();
output.send(new GenericMessage<>(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<byte[]>) receive(input1);
assertThat(receivedMessage1).isNotNull();
@@ -114,7 +116,7 @@ public abstract class PartitionCapableBinderTests<B extends AbstractTestBinder<?
Binding<MessageChannel> 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<MessageChannel> consumerBinding = binder.bindConsumer(testDestination, "test1", inbound1,
@@ -141,7 +143,7 @@ public abstract class PartitionCapableBinderTests<B extends AbstractTestBinder<?
Binding<MessageChannel> 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<MessageChannel> consumerBinding1 = binder.bindConsumer(testDestination, "test1", inbound1,
@@ -198,13 +200,14 @@ public abstract class PartitionCapableBinderTests<B extends AbstractTestBinder<?
catch (UnsupportedOperationException ignored) {
}
Message<Integer> message2 = MessageBuilder.withPayload(2)
Message<String> 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<B extends AbstractTestBinder<?
};
if (usesExplicitRouting()) {
assertThat(receive0.getPayload()).isEqualTo(0);
assertThat(receive1.getPayload()).isEqualTo(1);
assertThat(receive2.getPayload()).isEqualTo(2);
assertThat(receive0.getPayload()).isEqualTo("0".getBytes());
assertThat(receive1.getPayload()).isEqualTo("1".getBytes());
assertThat(receive2.getPayload()).isEqualTo("2".getBytes());
assertThat(receive2).has(correlationHeadersForPayload2);
}
else {
List<Message<?>> 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<Message<?>> payloadIs2 = new Condition<Message<?>>() {
@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<B extends AbstractTestBinder<?
+ "-' + headers['" + BinderHeaders.PARTITION_HEADER + "']");
}
output.send(new GenericMessage<>(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<B extends AbstractTestBinder<?
assertThat(receive2).isNotNull();
if (usesExplicitRouting()) {
assertThat(receive0.getPayload()).isEqualTo(0);
assertThat(receive1.getPayload()).isEqualTo(1);
assertThat(receive2.getPayload()).isEqualTo(2);
assertThat(receive0.getPayload()).isEqualTo("0".getBytes());
assertThat(receive1.getPayload()).isEqualTo("1".getBytes());
assertThat(receive2.getPayload()).isEqualTo("2".getBytes());
}
else {
List<Message<?>> 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();

View File

@@ -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<byte[]> 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<byte[]> 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("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><test></test>")
.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("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><test></test>");
assertThat(reconstructed.get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.TEXT_XML.toString());
}
@Test
public void testContentTypePreservedForJson() throws IOException {
Message<String> 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<String> 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<MessageChannel, ConsumerProperties, ProducerProperties> {
@Override
protected Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel channel,
ConsumerProperties properties) {
return null;
}
@Override
public Binding<MessageChannel> doBindProducer(String name, MessageChannel channel,
ProducerProperties properties) {
return null;
}
}
}

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-stream-codec</artifactId>
<packaging>jar</packaging>
<name>spring-cloud-stream-codec</name>
<description>Serialization library used by transport</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-parent</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo-shaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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<String, KryoRegistrar> kryoRegistrarMap = applicationContext.getBeansOfType(KryoRegistrar.class);
return new PojoCodec(new ArrayList<>(kryoRegistrarMap.values()), kryoCodecProperties.isReferences());
}
@Bean
@ConditionalOnMissingBean(KryoRegistrar.class)
public KryoRegistrar fileRegistrar() {
return new FileKryoRegistrar();
}
}

View File

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

View File

@@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration

View File

@@ -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.<channelName>.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.<channelName>.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<T>` 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<T>`.
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<byte[]>` 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=<Class being serialized>
|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.<channel>.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[]>`, `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();
}

View File

@@ -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<String> received = (Message<String>) ((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)

View File

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

View File

@@ -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<MessageConverter> 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<String> received = (Message<String>) ((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();
}

View File

@@ -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<String> consume(String data) {
return MessageBuilder.withPayload(data).setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String> message = (Message<String>) collector.forChannel(
Message<byte[]> message = (Message<byte[]>) 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();

View File

@@ -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<byte[]> result = (Message<byte[]>) 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<byte[]> 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<byte[]> result = (Message<byte[]>) 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<byte[]> result = (Message<byte[]>) 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<byte[]> result = (Message<byte[]>) 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

View File

@@ -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<StreamListenerTestUtils.BarPojo> message = (Message<StreamListenerTestUtils.BarPojo>) collector
Message<byte[]> message = (Message<byte[]>) 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();
}

View File

@@ -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<String> message = (Message<String>) collector.forChannel(processor.output()).poll(1,
Assertions.assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
Assertions.assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<byte[]> message = (Message<byte[]>) 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<StreamListenerTestUtils.BarPojo> message = (Message<StreamListenerTestUtils.BarPojo>) collector
Assertions.assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
Assertions.assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<byte[]> message = (Message<byte[]>) 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();
}
}

View File

@@ -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<StreamListenerTestUtils.BarPojo> message = (Message<StreamListenerTestUtils.BarPojo>) collector
Assertions.assertThat(testPojoWithMessageReturn.receivedPojos).hasSize(1);
Assertions.assertThat(testPojoWithMessageReturn.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<byte[]> message = (Message<byte[]>) 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();
}

View File

@@ -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<String> message = (Message<String>) collector
Message<byte[]> message = (Message<byte[]>) 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();
}

View File

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

View File

@@ -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<byte[]> result = (Message<byte[]>) 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();
}

View File

@@ -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<byte[]> received = (Message<byte[]>) ((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<byte[]> received = (Message<byte[]>) ((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<byte[]> received = (Message<byte[]>) ((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)

View File

@@ -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<byte[]> received = (Message<byte[]>) ((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());

View File

@@ -49,9 +49,10 @@ public class AggregateApplicationTests {
TestSupportBinder testSupportBinder = (TestSupportBinder) context.getBean(BinderFactory.class).getBinder(null,
MessageChannel.class);
MessageChannel processorOutput = testSupportBinder.getChannelForName("output");
Message<String> received = (Message<String>) (testSupportBinder.messageCollector().forChannel(processorOutput)
Message<byte[]> received = (Message<byte[]>) (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"));
}
}

View File

@@ -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<String> process(String message) {
return MessageBuilder.withPayload(message + " processed").setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build();
}
}

View File

@@ -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<String>() {
@Override
public Message<String> 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();
}
};
}

View File

@@ -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<byte[]> message = (Message<byte[]>) 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<byte[]> message = (Message<byte[]>) 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<byte[]> message = (Message<byte[]>) 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<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.APPLICATION_OCTET_STREAM));
assertThat(message.getPayload()).isEqualTo(data);
}
}
@Test
public void testSendBinaryDataWithContentType() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.output.contentType=image/jpeg")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
byte[] data = new byte[] { 0, 1, 2, 3 };
source.output().send(MessageBuilder.withPayload(data)
.build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(message.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<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.IMAGE_JPEG));
assertThat(message.getPayload()).isEqualTo(data);
}
}
@Test
public void 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<byte[]> message = (Message<byte[]>) 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<byte[]> message = (Message<byte[]>) 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<byte[]> message = (Message<byte[]>) 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<byte[]> message = (Message<byte[]>) 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<String,Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
User received = (User)sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeTypeUtils.APPLICATION_JSON));
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@Test
public void testReceiveRawWithDifferentContentTypes() 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<String,Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeTypeUtils.IMAGE_GIF));
headers = (Map<String, Object>) sourceApp.arguments.pop();
sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeTypeUtils.IMAGE_JPEG));
}
}
@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<String,Object> headers = (Map<String, Object>) 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<String,Object> headers = (Map<String, Object>) 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<String,Object> headers = (Map<String, Object>) 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<String, Object> 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<String, Object> 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();
}
}
}

View File

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

View File

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

View File

@@ -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<String> messages = new ArrayList<>();
List<byte[]> 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<String> messages = new ArrayList<>();
List<byte[]> 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<String> messages = new ArrayList<>();
List<byte[]> 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<String> messages) throws InterruptedException {
private static void assertMessages(MessageChannel channel, MessageCollector messageCollector, List<byte[]> 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<String> messages) throws InterruptedException {
private static void assertMessagesX(MessageChannel channel, MessageCollector messageCollector, List<byte[]> 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<String> messages) throws InterruptedException {
private static void assertMessagesY(MessageChannel channel, MessageCollector messageCollector, List<byte[]> 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);
}
}

View File

@@ -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<byte[]> result = (Message<byte[]>) 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<A> 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<A> {
@StreamListener(Processor.INPUT)
public void receive(Flux<A> input,
@Output(Processor.OUTPUT) FluxSender output) {
output.send(input.map(m -> MessageBuilder.withPayload((A) m.toString().toUpperCase()).build()));
public void receive(Flux<A> input, @Output(Processor.OUTPUT) FluxSender output) {
output.send(input.map(m -> MessageBuilder
.withPayload((A) m.toString().toUpperCase()).build()));
}
}
}

View File

@@ -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<byte[]> result = (Message<byte[]>) 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();
}

View File

@@ -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<byte[]> result = (Message<byte[]>) 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();
}

View File

@@ -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<byte[]> result = (Message<byte[]>) 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);

View File

@@ -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<byte[]> result = (Message<byte[]>) 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);

View File

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

View File

@@ -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<byte[]> result = (Message<byte[]>) 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);

View File

@@ -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<byte[]> result = (Message<byte[]>) 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);

View File

@@ -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<byte[]> result = (Message<byte[]>) 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();
}

View File

@@ -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<byte[]> result = (Message<byte[]>) 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;
}

View File

@@ -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<byte[]> result = (Message<byte[]>) 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();
}

View File

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

View File

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

View File

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

View File

@@ -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<byte[]> receivedMessage = (Message<byte[]>) 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

View File

@@ -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<byte[]> receivedMessage = (Message<byte[]>) 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();
}

View File

@@ -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<byte[]> response = (Message<byte[]>) 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)

View File

@@ -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<String> message = new GenericMessage<>("hello");
this.processor.input().send(message);
Message<String> received = (Message<String>) this.messageCollector.forChannel(this.processor.output()).poll();
assertThat(received.getPayload()).isEqualTo("hello world");
Message<byte[]> received = (Message<byte[]>) this.messageCollector.forChannel(this.processor.output()).poll();
assertThat(received.getPayload()).isEqualTo("hello world".getBytes());
}
@SpringBootApplication

View File

@@ -99,14 +99,15 @@
<property name="illegalPkgs" value="org.slf4j"/>
</module>
<module name="RedundantImport"/>
<module name="ReturnCount">
<property name="max" value="0"/>
<property name="tokens" value="CTOR_DEF"/>
</module>
<module name="ReturnCount">
<property name="max" value="1"/>
<property name="tokens" value="LAMBDA"/>
</module>
<module name="ReturnCount">
<property name="max" value="1"/>
<property name="tokens" value="CTOR_DEF"/>
</module>
<!--
<module name="ReturnCount">
<property name="max" value="3"/>

View File

@@ -0,0 +1,36 @@
/*
* Copyright 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Marker to tag {@link org.springframework.messaging.converter.MessageConverter} beans that will be added to the {@link org.springframework.cloud.stream.converter.CompositeMessageConverterFactory}
* @author Vinicius Carvalho
*/
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface StreamMessageConverter {
}

View File

@@ -26,7 +26,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.integration.codec.Codec;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.messaging.Message;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
@@ -44,6 +43,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Marius Bogoevici
* @author Soby Chacko
* @author Vinicius Carvalho
*/
public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends ProducerProperties>
implements ApplicationContextAware, InitializingBean, Binder<T, C, P> {
@@ -60,7 +60,6 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
private volatile AbstractApplicationContext applicationContext;
private volatile Codec codec;
private volatile EvaluationContext evaluationContext;
@@ -98,10 +97,6 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
return this.applicationContext.getBeanFactory();
}
public void setCodec(Codec codec) {
this.codec = codec;
}
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
}
@@ -151,22 +146,22 @@ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends
return name + GROUP_INDEX_DELIMITER + (StringUtils.hasText(group) ? group : "default");
}
@Deprecated
protected final MessageValues serializePayloadIfNecessary(Message<?> message) {
return MessageSerializationUtils.serializePayload(message, this.codec);
}
protected final byte[] serializePayloadIfNecessary(Object originalPayload) {
return MessageSerializationUtils.serializePayload(originalPayload, this.codec);
return MessageSerializationUtils.serializePayload(message);
}
@Deprecated
protected final MessageValues deserializePayloadIfNecessary(Message<?> message) {
return MessageSerializationUtils.deserializePayload(new MessageValues(message), this.contentTypeResolver, this.codec);
return MessageSerializationUtils.deserializePayload(new MessageValues(message), this.contentTypeResolver);
}
@Deprecated
protected final MessageValues deserializePayloadIfNecessary(MessageValues messageValues) {
return MessageSerializationUtils.deserializePayload(messageValues, this.contentTypeResolver, this.codec);
return MessageSerializationUtils.deserializePayload(messageValues, this.contentTypeResolver);
}
@Deprecated
protected String buildPartitionRoutingExpression(String expressionRoot) {
return "'" + expressionRoot + "-' + headers['" + BinderHeaders.PARTITION_HEADER + "']";
}

View File

@@ -525,6 +525,7 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
return destination.getName() + ".errors";
}
@Deprecated
private final class ReceivingHandler extends AbstractReplyProducingMessageHandler {
private final boolean extractEmbeddedHeaders;
@@ -574,6 +575,7 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
}
}
@Deprecated
private final class SendingHandler extends AbstractMessageHandler implements Lifecycle {
private final boolean embedHeaders;
@@ -582,7 +584,6 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
private final MessageHandler delegate;
private final boolean useNativeEncoding;
private SendingHandler(MessageHandler delegate, boolean embedHeaders,
String[] headersToEmbed, boolean useNativeEncoding) {
@@ -593,6 +594,8 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
this.useNativeEncoding = useNativeEncoding;
}
private final boolean useNativeEncoding;
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
Message<?> messageToSend = (this.useNativeEncoding) ? message
@@ -600,6 +603,7 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
this.delegate.handleMessage(messageToSend);
}
private Message<?> serializeAndEmbedHeadersIfApplicable(Message<?> message) throws Exception {
MessageValues transformed = serializePayloadIfNecessary(message);
byte[] payload;

View File

@@ -16,26 +16,19 @@
package org.springframework.cloud.stream.binder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.serializer.support.SerializationFailedException;
import org.springframework.integration.codec.Codec;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.ContentTypeResolver;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.ObjectUtils;
/**
* Utility class for serializing and de-serializing the message payload.
*
* @author Soby Chacko
* @author Vinicius Carvalho
*/
public abstract class MessageSerializationUtils {
@@ -45,122 +38,36 @@ public abstract class MessageSerializationUtils {
* Serialize the message payload unless it is a byte array.
*
* @param message the message with the payload to serialize
* @param codec the codec used for serialization
* @return the Message with teh serialized payload
*/
public static MessageValues serializePayload(Message<?> message, Codec codec) {
public static MessageValues serializePayload(Message<?> message) {
Object originalPayload = message.getPayload();
Object originalContentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
// Pass content type as String since some transport adapters will exclude
// CONTENT_TYPE Header otherwise
Object contentType = JavaClassMimeTypeUtils
.mimeTypeFromObject(originalPayload, ObjectUtils.nullSafeToString(originalContentType)).toString();
Object payload = serializePayload(originalPayload, codec);
MessageValues messageValues = new MessageValues(message);
messageValues.setPayload(payload);
messageValues.put(MessageHeaders.CONTENT_TYPE, contentType);
if (originalContentType != null && !originalContentType.toString().equals(contentType.toString())) {
messageValues.put(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE, originalContentType.toString());
}
messageValues.setPayload(originalPayload);
messageValues.put(MessageHeaders.CONTENT_TYPE, originalContentType);
return messageValues;
}
/**
* Serialize the payload object if it is not a byte array.
*
* @param originalPayload the payload to serialize
* @param codec the codec used for serialization
* @return the serialized byte array or the original payload if it is already a byte array
* @throws SerializationFailedException thrown when serialization failed
*/
public static byte[] serializePayload(Object originalPayload, Codec codec) {
if (originalPayload instanceof byte[]) {
return (byte[]) originalPayload;
}
else {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
if (originalPayload instanceof String) {
return ((String) originalPayload).getBytes("UTF-8");
}
codec.encode(originalPayload, bos);
return bos.toByteArray();
}
catch (IOException e) {
throw new SerializationFailedException(
"unable to serialize payload [" + originalPayload.getClass().getName() + "]", e);
}
}
}
/**
* De-serialize the message payload if necessary.
*
* @param messageValues message with the payload to deserialize
* @param contentTypeResolver used for resolving the mime type.
* @param codec used for deserialization
* @return Deserialized Message.
*/
public static MessageValues deserializePayload(MessageValues messageValues, ContentTypeResolver contentTypeResolver,
Codec codec) {
Object originalPayload = messageValues.getPayload();
public static MessageValues deserializePayload(MessageValues messageValues, ContentTypeResolver contentTypeResolver) {
Object payload = messageValues.getPayload();
MimeType contentType = contentTypeResolver.resolve(new MessageHeaders(messageValues.getHeaders()));
Object payload = deserializePayload(originalPayload, contentType, codec);
if (payload != null) {
messageValues.setPayload(payload);
Object originalContentType = messageValues.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE);
// Reset content-type only if the original content type is not null (when
// receiving messages from
// non-SCSt applications).
if (originalContentType != null) {
messageValues.put(MessageHeaders.CONTENT_TYPE, originalContentType);
messageValues.remove(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE);
}
messageValues.put(MessageHeaders.CONTENT_TYPE, contentType);
}
return messageValues;
}
private static Object deserializePayload(Object payload, MimeType contentType, Codec codec) {
if (payload instanceof byte[]) {
if (contentType == null || MimeTypeUtils.APPLICATION_OCTET_STREAM.equals(contentType)) {
return payload;
}
else {
return deserializePayload((byte[]) payload, contentType, payloadTypeCache, codec);
}
}
return payload;
}
private static Object deserializePayload(byte[] bytes, MimeType contentType,
Map<String, Class<?>> payloadTypeCache, Codec codec) {
if ("text".equalsIgnoreCase(contentType.getType()) || MimeTypeUtils.APPLICATION_JSON.equals(contentType)) {
try {
return new String(bytes, "UTF-8");
}
catch (UnsupportedEncodingException e) {
String errorMessage = "unable to deserialize [java.lang.String]. Encoding not supported. "
+ e.getMessage();
throw new SerializationFailedException(errorMessage, e);
}
}
else {
String className = JavaClassMimeTypeUtils.classNameFromMimeType(contentType);
try {
// Cache types to avoid unnecessary ClassUtils.forName calls.
Class<?> targetType = payloadTypeCache.get(className);
if (targetType == null) {
targetType = ClassUtils.forName(className, null);
payloadTypeCache.put(className, targetType);
}
return codec.decode(bytes, targetType);
} // catch all exceptions that could occur during de-serialization
catch (Exception e) {
String errorMessage = "Unable to deserialize [" + className + "] using the contentType [" + contentType
+ "] " + e.getMessage();
throw new SerializationFailedException(errorMessage, e);
}
}
}
}

View File

@@ -43,7 +43,7 @@ import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.tuple.Tuple;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
@@ -61,7 +61,8 @@ import org.springframework.util.StringUtils;
* @author Maxim Kirilov
* @author Gary Russell
*/
public class MessageConverterConfigurer implements MessageChannelConfigurer, BeanFactoryAware, InitializingBean {
public class MessageConverterConfigurer
implements MessageChannelConfigurer, BeanFactoryAware, InitializingBean {
private final MessageBuilderFactory messageBuilderFactory = new MutableMessageBuilderFactory();
@@ -73,7 +74,8 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
public MessageConverterConfigurer(BindingServiceProperties bindingServiceProperties,
CompositeMessageConverterFactory compositeMessageConverterFactory) {
Assert.notNull(compositeMessageConverterFactory, "The message converter factory cannot be null");
Assert.notNull(compositeMessageConverterFactory,
"The message converter factory cannot be null");
this.bindingServiceProperties = bindingServiceProperties;
this.compositeMessageConverterFactory = compositeMessageConverterFactory;
}
@@ -94,7 +96,8 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
}
@Override
public void configureOutputChannel(MessageChannel messageChannel, String channelName) {
public void configureOutputChannel(MessageChannel messageChannel,
String channelName) {
configureMessageChannel(messageChannel, channelName, false);
}
@@ -104,11 +107,12 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
* @param channel message channel to set the data-type and message converters
* @param channelName the channel name
*/
private void configureMessageChannel(MessageChannel channel, String channelName, boolean input) {
private void configureMessageChannel(MessageChannel channel, String channelName,
boolean input) {
Assert.isAssignable(AbstractMessageChannel.class, channel.getClass());
AbstractMessageChannel messageChannel = (AbstractMessageChannel) channel;
final BindingProperties bindingProperties = this.bindingServiceProperties.getBindingProperties(
channelName);
final BindingProperties bindingProperties = this.bindingServiceProperties
.getBindingProperties(channelName);
final String contentType = bindingProperties.getContentType();
ProducerProperties producerProperties = bindingProperties.getProducer();
if (!input && producerProperties != null && producerProperties.isPartitioned()) {
@@ -116,24 +120,26 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
getPartitionKeyExtractorStrategy(producerProperties),
getPartitionSelectorStrategy(producerProperties)));
}
// TODO: Set all interceptors in the correct order for input/output channels
if (StringUtils.hasText(contentType)) {
messageChannel.addInterceptor(new ContentTypeConvertingInterceptor(contentType, input));
messageChannel.addInterceptor(
new ContentTypeConvertingInterceptor(contentType, input));
}
}
private PartitionKeyExtractorStrategy getPartitionKeyExtractorStrategy(ProducerProperties producerProperties) {
private PartitionKeyExtractorStrategy getPartitionKeyExtractorStrategy(
ProducerProperties producerProperties) {
if (producerProperties.getPartitionKeyExtractorClass() != null) {
return getBean(
producerProperties.getPartitionKeyExtractorClass().getName(),
return getBean(producerProperties.getPartitionKeyExtractorClass().getName(),
PartitionKeyExtractorStrategy.class);
}
return null;
}
private PartitionSelectorStrategy getPartitionSelectorStrategy(ProducerProperties producerProperties) {
private PartitionSelectorStrategy getPartitionSelectorStrategy(
ProducerProperties producerProperties) {
if (producerProperties.getPartitionSelectorClass() != null) {
return getBean(
producerProperties.getPartitionSelectorClass().getName(),
return getBean(producerProperties.getPartitionSelectorClass().getName(),
PartitionSelectorStrategy.class);
}
return new DefaultPartitionSelector();
@@ -149,7 +155,8 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
T bean;
Class<?> clazz;
try {
clazz = ClassUtils.forName(className, this.beanFactory.getBeanClassLoader());
clazz = ClassUtils.forName(className,
this.beanFactory.getBeanClassLoader());
}
catch (Exception e) {
throw new BinderException("Failed to load class: " + className, e);
@@ -161,7 +168,8 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
this.beanFactory.initializeBean(bean, className);
}
catch (Exception e) {
throw new BinderException("Failed to instantiate class: " + className, e);
throw new BinderException("Failed to instantiate class: " + className,
e);
}
return bean;
}
@@ -185,116 +193,69 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
}
private final class ContentTypeConvertingInterceptor extends ChannelInterceptorAdapter {
private final String contentType;
private final class ContentTypeConvertingInterceptor
extends ChannelInterceptorAdapter {
private final MimeType mimeType;
private final boolean input;
private final Class<?> klazz;
private final MessageConverter messageConverter;
private final boolean provideHint;
private ContentTypeConvertingInterceptor(String contentType, boolean input) {
this.contentType = contentType;
this.mimeType = MessageConverterUtils.getMimeType(contentType);
this.input = input;
if (MessageConverterUtils.X_JAVA_OBJECT.includes(this.mimeType)) {
this.klazz = MessageConverterUtils
.getJavaTypeForJavaObjectContentType(this.mimeType);
}
else if (this.mimeType.equals(MessageConverterUtils.X_SPRING_TUPLE)) {
this.klazz = Tuple.class;
}
else if (this.mimeType.getType().equals("text") || this.mimeType.getSubtype().equals(
"json") || this.mimeType.getSubtype().equals("xml")) {
this.klazz = String.class;
}
else {
this.klazz = byte[].class;
}
this.messageConverter = MessageConverterConfigurer.this.compositeMessageConverterFactory
.getMessageConverterForType(this.mimeType);
.getMessageConverterForAllRegistered();
this.provideHint = this.messageConverter instanceof AbstractMessageConverter;
}
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
// bypass conversion for ErrorMessges
if (message instanceof ErrorMessage) {
return message;
}
Message<?> sentMessage = null;
if (this.klazz.isAssignableFrom(message.getPayload().getClass())) {
Object contentTypeFromMessage = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
if (contentTypeFromMessage == null) {
sentMessage = MessageConverterConfigurer.this.messageBuilderFactory
.fromMessage(message)
.setHeader(MessageHeaders.CONTENT_TYPE, this.contentType)
.build();
}
else {
sentMessage = message;
}
Object converted;
// bypass conversion for raw bytes or input channels
if (this.input || message.getPayload() instanceof byte[]) {
return MessageConverterConfigurer.this.messageBuilderFactory
.withPayload(message.getPayload())
.copyHeaders(message.getHeaders())
.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, this.mimeType)
.build();
}
else {
Object converted;
if (this.input) {
if (this.provideHint) {
converted = ((AbstractMessageConverter) this.messageConverter).fromMessage(message, this.klazz,
this.mimeType);
if (converted == null && message.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)) {
converted = ((AbstractMessageConverter) this.messageConverter).fromMessage(
MessageConverterConfigurer.this.messageBuilderFactory.fromMessage(message)
.removeHeader(MessageHeaders.CONTENT_TYPE)
.build(), this.klazz, this.mimeType);
}
}
else {
converted = this.messageConverter.fromMessage(message, this.klazz);
if (converted == null && message.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)) {
converted = this.messageConverter.fromMessage(
MessageConverterConfigurer.this.messageBuilderFactory.fromMessage(message)
.removeHeader(MessageHeaders.CONTENT_TYPE)
.build(), this.klazz);
}
}
MutableMessageHeaders headers = new MutableMessageHeaders(
message.getHeaders());
if (!headers.containsKey(MessageHeaders.CONTENT_TYPE)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.mimeType);
}
converted = this.messageConverter.toMessage(message.getPayload(),
headers);
}
if (converted != null) {
if (converted instanceof Message) {
sentMessage = (Message<?>) converted;
}
else {
MutableMessageHeaders headers = new MutableMessageHeaders(message.getHeaders());
if (this.provideHint) {
converted = ((AbstractMessageConverter) this.messageConverter).toMessage(message.getPayload(),
headers, this.mimeType);
if (converted == null && message.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)) {
headers.remove(MessageHeaders.CONTENT_TYPE);
converted = ((AbstractMessageConverter) this.messageConverter).toMessage(message.getPayload(),
headers, this.mimeType);
}
}
else {
converted = this.messageConverter.toMessage(message.getPayload(), headers);
if (converted == null && message.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE)) {
headers.remove(MessageHeaders.CONTENT_TYPE);
converted = this.messageConverter.toMessage(message.getPayload(), headers);
}
}
}
if (converted != null) {
if (converted instanceof Message) {
sentMessage = (Message<?>) converted;
}
else {
sentMessage = MessageConverterConfigurer.this.messageBuilderFactory.withPayload(converted)
.copyHeaders(message.getHeaders()).setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE,
this.mimeType)
.build();
}
sentMessage = MessageConverterConfigurer.this.messageBuilderFactory
.withPayload(converted).copyHeaders(message.getHeaders())
.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, this.mimeType)
.build();
}
}
if (sentMessage == null) {
throw new MessageConversionException(message, this.messageConverter.getClass().toString()
+ " could not convert '" + message + "' to the configured output type: '"
+ this.contentType + "'");
throw new MessageConversionException(message,
this.messageConverter.getClass().toString()
+ " could not convert '" + message
+ "' to the configured output type: '" + this.mimeType
+ "'");
}
return sentMessage;
@@ -312,8 +273,10 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
PartitionSelectorStrategy partitionSelectorStrategy) {
this.bindingProperties = bindingProperties;
this.partitionHandler = new PartitionHandler(
ExpressionUtils.createStandardEvaluationContext(MessageConverterConfigurer.this.beanFactory),
this.bindingProperties.getProducer(), partitionKeyExtractorStrategy, partitionSelectorStrategy);
ExpressionUtils.createStandardEvaluationContext(
MessageConverterConfigurer.this.beanFactory),
this.bindingProperties.getProducer(), partitionKeyExtractorStrategy,
partitionSelectorStrategy);
}
@Override
@@ -322,16 +285,15 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
int partition = this.partitionHandler.determinePartition(message);
return MessageConverterConfigurer.this.messageBuilderFactory
.fromMessage(message)
.setHeader(BinderHeaders.PARTITION_HEADER, partition)
.build();
.setHeader(BinderHeaders.PARTITION_HEADER, partition).build();
}
else {
return MessageConverterConfigurer.this.messageBuilderFactory
.fromMessage(message)
.setHeader(BinderHeaders.PARTITION_HEADER,
message.getHeaders().get(BinderHeaders.PARTITION_OVERRIDE))
.removeHeader(BinderHeaders.PARTITION_OVERRIDE)
.build();
message.getHeaders()
.get(BinderHeaders.PARTITION_OVERRIDE))
.removeHeader(BinderHeaders.PARTITION_OVERRIDE).build();
}
}
}

View File

@@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.binder.ProducerProperties;
import org.springframework.util.MimeTypeUtils;
import org.springframework.validation.annotation.Validated;
/**
@@ -54,7 +55,7 @@ public class BindingProperties {
// Properties for both inbound/outbound
private String contentType;
private String contentType = MimeTypeUtils.APPLICATION_JSON_VALUE;
private String binder;

View File

@@ -17,17 +17,14 @@
package org.springframework.cloud.stream.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -53,6 +50,7 @@ import org.springframework.cloud.stream.converter.CompositeMessageConverterFacto
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.expression.PropertyAccessor;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.PublishSubscribeChannel;
@@ -63,13 +61,11 @@ import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.json.JsonPropertyAccessor;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.core.DestinationResolutionException;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
import org.springframework.tuple.spel.TuplePropertyAccessor;
import org.springframework.util.CollectionUtils;
/**
* Configuration class that provides necessary beans for {@link MessageChannel} binding.
@@ -79,9 +75,11 @@ import org.springframework.util.CollectionUtils;
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Gary Russell
* @author Vinicius Carvalho
*/
@Configuration
@EnableConfigurationProperties({ BindingServiceProperties.class, SpringIntegrationProperties.class })
@Import(ContentTypeConfiguration.class)
public class BindingServiceConfiguration {
public static final String STREAM_LISTENER_ANNOTATION_BEAN_POST_PROCESSOR_NAME = "streamListenerAnnotationBeanPostProcessor";
@@ -90,15 +88,6 @@ public class BindingServiceConfiguration {
private static final String ERROR_KEY_NAME = "error";
@Autowired(required = false)
private ObjectMapper objectMapper;
/**
* User defined custom message converters
*/
@Autowired(required = false)
private List<MessageConverter> customMessageConverters;
@Bean
public static MessageHandlerMethodFactory messageHandlerMethodFactory(
CompositeMessageConverterFactory compositeMessageConverterFactory) {
@@ -193,14 +182,6 @@ public class BindingServiceConfiguration {
return new DynamicDestinationsBindable();
}
@Bean
public CompositeMessageConverterFactory compositeMessageConverterFactory() {
List<MessageConverter> messageConverters = new ArrayList<>();
if (!CollectionUtils.isEmpty(this.customMessageConverters)) {
messageConverters.addAll(Collections.unmodifiableCollection(this.customMessageConverters));
}
return new CompositeMessageConverterFactory(messageConverters, this.objectMapper);
}
@Bean
// provided for backwards compatibility scenarios

View File

@@ -0,0 +1,64 @@
/*
* Copyright 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamMessageConverter;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.CollectionUtils;
/**
* @author Vinicius Carvalho
*/
@Configuration
public class ContentTypeConfiguration {
@Autowired(required = false)
private ObjectMapper objectMapper;
/**
* User defined custom message converters
*/
@Autowired(required = false)
@StreamMessageConverter
private List<MessageConverter> customMessageConverters;
@Bean
public CompositeMessageConverterFactory compositeMessageConverterFactory() {
List<MessageConverter> messageConverters = new ArrayList<>();
if (!CollectionUtils.isEmpty(this.customMessageConverters)) {
messageConverters.addAll(Collections.unmodifiableCollection(this.customMessageConverters));
}
return new CompositeMessageConverterFactory(messageConverters, this.objectMapper);
}
@Bean(name = IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME)
public ConfigurableCompositeMessageConverter configurableCompositeMessageConverter(CompositeMessageConverterFactory factory){
return new ConfigurableCompositeMessageConverter(factory.getMessageConverterForAllRegistered().getConverters());
}
}

View File

@@ -27,7 +27,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.ByteArrayMessageConverter;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
@@ -38,6 +37,7 @@ import org.springframework.util.MimeType;
* @author David Turanski
* @author Ilayaperumal Gopinathan
* @author Marius Bogoevici
* @author Vinicius Carvalho
*/
public class CompositeMessageConverterFactory {
@@ -69,8 +69,7 @@ public class CompositeMessageConverterFactory {
private void initDefaultConverters() {
this.converters.add(new TupleJsonMessageConverter(this.objectMapper));
MappingJackson2MessageConverter jsonMessageConverter = new MappingJackson2MessageConverter();
jsonMessageConverter.setSerializedPayloadClass(String.class);
CustomJackson2MappingMessageConverter jsonMessageConverter = new CustomJackson2MappingMessageConverter();
if (this.objectMapper != null) {
jsonMessageConverter.setObjectMapper(this.objectMapper);
}
@@ -79,6 +78,7 @@ public class CompositeMessageConverterFactory {
this.converters.add(new ByteArrayMessageConverter());
this.converters.add(new ObjectStringMessageConverter());
this.converters.add(new JavaSerializationMessageConverter());
this.converters.add(new KryoMessageConverter(null,true));
this.converters.add(new JsonUnmarshallingConverter(this.objectMapper));
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 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.converter;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.converter.MessageConversionException;
/**
* Custom implementation of {@link org.springframework.messaging.converter.MappingJackson2MessageConverter} that handles special String cases.
* If the target of conversion is a String, it tries to read it from a quoted json string, to properly remove the quotes, if it fails, it then just
* returns the original string as it is just a raw json string needed for the target.
*
* @author Vinicius Carvalho
*/
public class CustomJackson2MappingMessageConverter extends MappingJackson2MessageConverter{
public CustomJackson2MappingMessageConverter() {
super();
setSerializedPayloadClass(byte[].class);
setStrictContentTypeMatch(true);
}
@Override
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
try{
return super.convertFromInternal(message, targetClass, conversionHint);
}catch (MessageConversionException me){
//Strings need special treatment
if(targetClass.isAssignableFrom(String.class)){
return message.getPayload();
}
throw me;
}
}
}

View File

@@ -24,8 +24,6 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* Message converter providing backwards compatibility for applications using an Java type
@@ -39,33 +37,13 @@ public class JsonUnmarshallingConverter extends AbstractMessageConverter {
protected JsonUnmarshallingConverter(ObjectMapper objectMapper) {
super(MessageConverterUtils.X_JAVA_OBJECT);
setStrictContentTypeMatch(true);
this.objectMapper = objectMapper != null ? objectMapper : new ObjectMapper();
}
@Override
protected boolean supports(Class<?> aClass) {
return true;
}
@Override
protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
if ((message.getPayload() instanceof String) || (message.getPayload() instanceof byte[])) {
return true;
}
return canConvertFromBasedOnContentTypeHeader(message);
}
private boolean canConvertFromBasedOnContentTypeHeader(Message<?> message) {
Object contentTypeHeader = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
if (contentTypeHeader instanceof String) {
return MimeTypeUtils.APPLICATION_JSON.includes(MimeTypeUtils.parseMimeType((String) contentTypeHeader));
}
else if (contentTypeHeader instanceof MimeType) {
return MimeTypeUtils.APPLICATION_JSON.includes((MimeType) contentTypeHeader);
}
else {
return contentTypeHeader == null;
}
return String.class.isAssignableFrom(aClass) || byte[].class.isAssignableFrom(aClass) ;
}
@Override

View File

@@ -0,0 +1,249 @@
/*
* Copyright 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.converter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.pool.KryoFactory;
import com.esotericsoftware.kryo.pool.KryoPool;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.integration.codec.kryo.CompositeKryoRegistrar;
import org.springframework.integration.codec.kryo.KryoRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.ContentTypeResolver;
import org.springframework.messaging.converter.DefaultContentTypeResolver;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.converter.SmartMessageConverter;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
/**
* @author Vinicius Carvalho
*/
public class KryoMessageConverter implements SmartMessageConverter {
protected final KryoPool pool;
private final CompositeKryoRegistrar kryoRegistrar;
private final boolean useReferences;
private ConcurrentMap<String, MimeType> mimeTypesCache = new ConcurrentHashMap<>();
private final List<MimeType> supportedMimeTypes;
public static final String KRYO_MIME_TYPE = "application/x-java-object";
private ContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver();
public KryoMessageConverter(List<KryoRegistrar> kryoRegistrars, boolean useReferences) {
this.useReferences = useReferences;
this.kryoRegistrar = CollectionUtils.isEmpty(kryoRegistrars) ? null :
new CompositeKryoRegistrar(kryoRegistrars);
KryoFactory factory = () -> {
Kryo kryo = new Kryo();
configureKryoInstance(kryo);
return kryo;
};
this.pool = new KryoPool.Builder(factory).softReferences().build();
this.supportedMimeTypes = Collections.singletonList(MimeType.valueOf(KRYO_MIME_TYPE));
}
@Nullable
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
if (!canConvertFrom(message, targetClass)) {
return null;
}
if(!message.getPayload().getClass().isAssignableFrom(byte[].class)){
throw new MessageConversionException("This converter can only convert messages with byte[] payload");
}
byte[] payload = (byte[])message.getPayload();
try {
return deserialize(payload, targetClass);
}
catch (IOException e) {
throw new MessageConversionException("Could not deserialize payload",e);
}
}
@Nullable
@Override
public Message<?> toMessage(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) {
if (!canConvertTo(payload, headers)) {
return null;
}
byte[] payloadToUse = serialize(payload);
MimeType mimeType = getDefaultContentType(payload);
if (headers != null) {
MessageHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(headers, MessageHeaderAccessor.class);
if (accessor != null && accessor.isMutable()) {
if (mimeType != null) {
accessor.setHeader(MessageHeaders.CONTENT_TYPE, mimeType);
}
return MessageBuilder.createMessage(payloadToUse, accessor.getMessageHeaders());
}
}
MessageBuilder<?> builder = MessageBuilder.withPayload(payloadToUse);
if (headers != null) {
builder.copyHeaders(headers);
}
if (mimeType != null) {
builder.setHeader(MessageHeaders.CONTENT_TYPE, mimeType);
}
return builder.build();
}
private boolean canConvertTo(Object payload, MessageHeaders headers) {
return (supports(payload.getClass()) && supportsMimeType(headers));
}
@Nullable
protected MimeType getDefaultContentType(Object payload) {
return mimeTypeFromObject(payload);
}
protected byte[] serialize(Object payload) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final Output output = new Output(baos);
this.pool.run(kryo -> {
kryo.writeObject(output,payload);
return Void.TYPE;
});
output.close();
return baos.toByteArray();
}
protected <T> T deserialize(byte[] bytes, Class<T> type) throws IOException {
Assert.notNull(bytes, "'bytes' cannot be null");
final Input input = new Input(bytes);
try {
return deserialize(input, type);
}
finally {
input.close();
}
}
protected <T> T deserialize(InputStream inputStream, final Class<T> type) throws IOException {
Assert.notNull(inputStream, "'inputStream' cannot be null");
Assert.notNull(type, "'type' cannot be null");
final Input input = (inputStream instanceof Input ? (Input) inputStream : new Input(inputStream));
T result = null;
try {
result = this.pool.run(kryo -> kryo.readObject(input,type));
}
finally {
input.close();
}
return result;
}
protected void configureKryoInstance(Kryo kryo) {
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
if (this.kryoRegistrar != null) {
this.kryoRegistrar.registerTypes(kryo);
}
kryo.setReferences(this.useReferences);
}
protected MimeType mimeTypeFromObject(Object payload) {
Assert.notNull(payload, "payload object cannot be null.");
String className = payload.getClass().getName();
MimeType mimeType = mimeTypesCache.get(className);
if (mimeType == null) {
String modifiedClassName = className;
if (payload.getClass().isArray()) {
// Need to remove trailing ';' for an object array, e.g.
// "[Ljava.lang.String;" or multi-dimensional
// "[[[Ljava.lang.String;"
if (modifiedClassName.endsWith(";")) {
modifiedClassName = modifiedClassName.substring(0, modifiedClassName.length() - 1);
}
// Wrap in quotes to handle the illegal '[' character
modifiedClassName = "\"" + modifiedClassName + "\"";
}
mimeType = MimeType.valueOf(KRYO_MIME_TYPE+";type=" + modifiedClassName);
mimeTypesCache.put(className, mimeType);
}
return mimeType;
}
protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
return (supports(targetClass) && supportsMimeType(message.getHeaders()));
}
protected boolean supportsMimeType(@Nullable MessageHeaders headers) {
if (getSupportedMimeTypes().isEmpty()) {
return true;
}
MimeType mimeType = getMimeType(headers);
if (mimeType == null) {
return false;
}
for (MimeType current : getSupportedMimeTypes()) {
if (current.getType().equals(mimeType.getType()) && current.getSubtype().equals(mimeType.getSubtype())) {
return true;
}
}
return false;
}
@Nullable
protected MimeType getMimeType(@Nullable MessageHeaders headers) {
return (headers != null && this.contentTypeResolver != null ? this.contentTypeResolver.resolve(headers) : null);
}
private boolean supports(Class<?> targetClass) {
return true;
}
@Nullable
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass) {
return fromMessage(message, targetClass, null);
}
@Nullable
@Override
public Message<?> toMessage(Object payload, @Nullable MessageHeaders headers) {
return toMessage(payload,headers,null);
}
public List<MimeType> getSupportedMimeTypes() {
return supportedMimeTypes;
}
}

View File

@@ -38,6 +38,7 @@ public class ObjectStringMessageConverter extends AbstractMessageConverter {
public ObjectStringMessageConverter() {
super(new MimeType("text", "plain", Charset.forName("UTF-8")));
setStrictContentTypeMatch(true);
}
protected boolean supports(Class<?> clazz) {
@@ -65,10 +66,10 @@ public class ObjectStringMessageConverter extends AbstractMessageConverter {
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
if (payload != null) {
if ((payload instanceof byte[])) {
return new String((byte[]) payload, Charset.forName("UTF-8"));
return payload;
}
else {
return payload.toString();
return payload.toString().getBytes();
}
}
else {

View File

@@ -32,10 +32,11 @@ import org.springframework.util.MimeTypeUtils;
/**
* A {@link org.springframework.messaging.converter.MessageConverter} to convert a
* {@link Tuple} to a JSON String
* {@link Tuple} to JSON bytes
* @author David Turanski
* @author Ilayaperumal Gopinathan
* @author Marius Bogoevici
* @author Vinicius Carvalho
*/
public class TupleJsonMessageConverter extends AbstractMessageConverter {
@@ -75,7 +76,7 @@ public class TupleJsonMessageConverter extends AbstractMessageConverter {
else {
json = t.toString();
}
return json;
return json.getBytes();
}
@Override

View File

@@ -153,7 +153,7 @@ public class BinderAwareChannelResolverTests {
fail("interrupted while awaiting latch");
}
assertThat(received).hasSize(1);
assertThat(received.get(0).getPayload()).isEqualTo("hello");
assertThat(new String((byte[])received.get(0).getPayload())).isEqualTo("hello");
context.close();
assertThat(producerBindings).hasSize(1);
assertThat(binding.isBound()).isFalse().describedAs("Must not be bound");

View File

@@ -102,7 +102,7 @@ public class ErrorBindingTests {
((SubscribableChannel)errorBridgeChannel).subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat(message.getPayload()).isEqualTo("{\"foo\":\"bar\"}");
assertThat(new String((byte[])message.getPayload())).isEqualTo("{\"foo\":\"bar\"}");
received.set(true);
}
});
@@ -131,7 +131,7 @@ public class ErrorBindingTests {
((SubscribableChannel)errorBridgeChannel).subscribe(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
String payload = (String) message.getPayload();
String payload = new String((byte[]) message.getPayload());
assertThat(payload.contains("cause")).isTrue();
assertThat(payload.contains("stackTrace")).isTrue();
assertThat(payload.contains("throwing exception")).isTrue();

View File

@@ -135,7 +135,7 @@ public class ExtendedPropertiesBinderAwareChannelResolverTests extends BinderAwa
fail("interrupted while awaiting latch");
}
assertThat(received).hasSize(1);
assertThat(received.get(0).getPayload()).isEqualTo("hello");
assertThat(new String((byte[])received.get(0).getPayload())).isEqualTo("hello");
context.close();
assertThat(producerBindings).hasSize(1);
assertThat(binding.isBound()).isFalse().describedAs("Must not be bound");

View File

@@ -42,7 +42,10 @@ import static org.junit.Assert.fail;
*/
public class MessageConverterConfigurerTests {
@Test
/**
* @since 2.0 bad contentType will result in MessageConversionException
*/
@Test(expected = MessageConversionException.class)
public void testConfigureOutputChannelWithBadContentType() {
BindingServiceProperties props = new BindingServiceProperties();
BindingProperties bindingProps = new BindingProperties();

View File

@@ -0,0 +1,63 @@
/*
* Copyright 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.converter;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Vinicius Carvalho
*/
public class CustomMappingJackson2MessageConverterTests {
@Test
public void convertFromStructuredJsonIntoPojo() throws Exception {
String payload = "{\"id\":1}";
Message message = MessageBuilder.withPayload(payload.getBytes()).setHeader(MessageHeaders.CONTENT_TYPE,"application/json").build();
CustomJackson2MappingMessageConverter converter = new CustomJackson2MappingMessageConverter();
Object converted = converter.convertFromInternal(message, Map.class,null);
assertThat(((Map)converted).get("id")).isNotNull();
}
@Test
public void convertFromStructuredJsonIntoString() throws Exception {
String payload = "{\"id\":1}";
Message message = MessageBuilder.withPayload(payload.getBytes()).setHeader(MessageHeaders.CONTENT_TYPE,"application/json").build();
CustomJackson2MappingMessageConverter converter = new CustomJackson2MappingMessageConverter();
Object converted = converter.convertFromInternal(message, String.class,null);
assertThat(converted).isNotNull();
assertThat(payload).isEqualTo(new String((byte[])converted));
}
@Test
public void convertFromJsonString() throws Exception {
ObjectMapper mapper = new ObjectMapper();
String payload = mapper.writeValueAsString("foo");
Message message = MessageBuilder.withPayload(payload.getBytes()).setHeader(MessageHeaders.CONTENT_TYPE,"application/json").build();
CustomJackson2MappingMessageConverter converter = new CustomJackson2MappingMessageConverter();
Object converted = converter.convertFromInternal(message, String.class,null);
assertThat(converted).isNotNull();
assertThat("foo").isEqualTo((String)converted);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 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.converter;
import java.io.ByteArrayOutputStream;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConversionException;
/**
* @author Vinicius Carvalho
*/
public class KryoMessageConverterTests {
@Test
public void convertStringType() throws Exception {
KryoMessageConverter kryoMessageConverter = new KryoMessageConverter(null,true);
Message<?> message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,"application/x-java-object").build();
Message converted = kryoMessageConverter.toMessage(message.getPayload(),message.getHeaders());
Assert.assertNotNull(converted);
Assert.assertEquals("application/x-java-object;type=java.lang.String",converted.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString());
}
@Test
public void readStringType() throws Exception {
KryoMessageConverter kryoMessageConverter = new KryoMessageConverter(null,true);
Kryo kryo = new Kryo();
String foo = "foo";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryo.writeObject(output,foo);
output.close();
Message message = MessageBuilder.withPayload(baos.toByteArray()).setHeader(MessageHeaders.CONTENT_TYPE,KryoMessageConverter.KRYO_MIME_TYPE+";type=java.lang.String").build();
Object result = kryoMessageConverter.fromMessage(message,String.class);
Assert.assertEquals(foo,result);
}
@Test
public void testMissingHeaders() throws Exception {
KryoMessageConverter kryoMessageConverter = new KryoMessageConverter(null,true);
Kryo kryo = new Kryo();
String foo = "foo";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryo.writeObject(output,foo);
output.close();
Message message = MessageBuilder.withPayload(baos.toByteArray()).build();
Object result = kryoMessageConverter.fromMessage(message,String.class);
Assert.assertNull(result);
}
@Test(expected = MessageConversionException.class)
public void readWithWrongPayloadType() throws Exception{
KryoMessageConverter kryoMessageConverter = new KryoMessageConverter(null,true);
Message message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,KryoMessageConverter.KRYO_MIME_TYPE+";type=java.lang.String").build();
kryoMessageConverter.fromMessage(message,String.class);
}
@Test(expected = MessageConversionException.class)
public void readWithWrongPayloadFormat() throws Exception{
KryoMessageConverter kryoMessageConverter = new KryoMessageConverter(null,true);
Message message = MessageBuilder.withPayload("foo").setHeader(MessageHeaders.CONTENT_TYPE,KryoMessageConverter.KRYO_MIME_TYPE+";type=java.lang.String").build();
kryoMessageConverter.fromMessage(message,String.class);
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.stream.interceptor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -31,9 +32,11 @@ import org.springframework.context.annotation.Import;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.MimeTypeUtils;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -48,7 +51,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
@SpringBootTest(classes = BoundChannelsInterceptedTest.Foo.class)
public class BoundChannelsInterceptedTest {
public static final Message<?> TEST_MESSAGE = MessageBuilder.withPayload("bar").build();
public static final Message<?> TEST_MESSAGE = MessageBuilder.withPayload("bar").setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();
@Autowired
@Bindings(BoundChannelsInterceptedTest.Foo.class)
@@ -60,7 +63,7 @@ public class BoundChannelsInterceptedTest {
@Test
public void testBoundChannelsIntercepted() {
this.fooSink.input().send(TEST_MESSAGE);
verify(this.channelInterceptor).preSend(TEST_MESSAGE, this.fooSink.input());
verify(this.channelInterceptor).preSend(Mockito.any(), Mockito.eq(this.fooSink.input()));
verifyNoMoreInteractions(this.channelInterceptor);
}