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:
committed by
Soby Chacko
parent
24cf992301
commit
171f034a8c
3
pom.xml
3
pom.xml
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
|
||||
org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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 + "']";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user