Content type redesign

Fixes #992, #1050, #1051, #1052

Adding custom jackson converter with some tests
Adds kryo message converter to replace codec
Checkstyle changes

Removing codec support
- Removed codec dependency from AbstractBinder
- MessageSerializationUtils is almost an empty shell for now, just to
  keep code compiling until we get EmbeddedHeaders interceptors
- Updated Kryo tests

Removing codec module from build
Added a new Annotation for custom converters '@StreamConverter'
Fixed some tests with new expected behavior
Moved broken tests to a temporary package to keep track of progress
Fixed KryoConverter to fail based on headers
Fixed a couple of more tests

Making converters strict to only convert their corresponding contentType
Bypassing conversion for ErrorMessages

* Configuring SI ConfigurableCompositeMessageConverter
 - Moved ContentType related beans into separate configuration
 - Configured SI ConfigurableCompositeMessageConverter to use same
   converters as Stream does (for ServiceActivator)
- TupleConverter should return byte[] as all other converters
- Fixed tests

* Fixes tests
 - Revert to Boot 2.0.0.M3. Snapshots breaking actuator
 - Checkstyle fixes
 - Disable JsonUnmarshalling as a catch all converter

Fixing Schema tests
Fixing Metrics tests
Fixing reactive tests
applying checkstyle fixes

 * Adding new content type tests
 - Fixed ContentTypeInterceptor misusage of default mimeType

Changing contentType doc section
Improving doc section
Last minute polish
Fixing BinderTests to use bytes to compare messages
Applied changes to Base Binders test to use the new contentType handling mechanism
PR review fixes

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

View File

@@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Ilayaperumal Gopinathan
* @author Vinicius Carvalho
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { ContentTypeOutboundSourceTests.TestSource.class })
@@ -53,12 +54,15 @@ public class ContentTypeOutboundSourceTests {
@Test
@SuppressWarnings("unchecked")
public void testMessageHeaderWhenNoExplicitContentTypeOnMessage() throws Exception {
testSource.output().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}").build());
Message<String> received = (Message<String>) ((TestSupportBinder) binderFactory.getBinder(null,
testSource.output().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}").setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build());
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null,
MessageChannel.class))
.messageCollector().forChannel(testSource.output()).poll();
assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()).isEqualTo("application/json");
assertThat(received).hasFieldOrPropertyWithValue("payload", "{\"message\":\"Hi\"}");
assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE).toString()).contains("text/plain");
Object payload = received.getPayload();
assertThat(payload.getClass().isAssignableFrom(byte[].class)).isTrue();
byte[] contents = (byte[])payload;
assertThat("{\"message\":\"Hi\"}").isEqualTo(new String(contents));
}
@EnableBinding(Source.class)

View File

@@ -53,6 +53,11 @@ public class CustomHeaderPropagationTests {
private BinderFactory binderFactory;
@Test
/**
* @since 2.0 The behavior of content type handling has changed.
* All input/output channels have a default content type of application/json
* When a processor or a source returns a String, and if the content type is json it will be quoted
*/
public void testCustomHeaderPropagation() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload("{'name':'foo'}")
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json")
@@ -65,8 +70,8 @@ public class CustomHeaderPropagationTests {
assertThat(received).isNotNull();
assertThat(received.getHeaders()).containsEntry("foo", "fooValue");
assertThat(received.getHeaders()).doesNotContainKey("bar");
assertThat(received.getHeaders()).doesNotContainKey(MessageHeaders.CONTENT_TYPE);
assertThat(received.getPayload()).isEqualTo("{'name':'foo'}");
assertThat(received.getHeaders()).containsKeys(MessageHeaders.CONTENT_TYPE);
assertThat(new String((byte[])received.getPayload())).isEqualTo("{'name':'foo'}");
}
@EnableBinding(Processor.class)
@@ -74,8 +79,9 @@ public class CustomHeaderPropagationTests {
public static class HeaderPropagationProcessor {
@ServiceActivator(inputChannel = "input", outputChannel = "output")
public String consume(String data) {
return data;
public Message<String> consume(String data) {
//if we don't force content to be String, it will be quoted on the outbound channel
return MessageBuilder.withPayload(data).setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build();
}
}

View File

@@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.stream.annotation.Bindings;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamMessageConverter;
import org.springframework.cloud.stream.binder.BinderFactory;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.cloud.stream.test.binder.TestSupportBinder;
@@ -35,8 +36,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.converter.ConfigurableCompositeMessageConverter;
import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
@@ -64,14 +63,14 @@ public class CustomMessageConverterTests {
private BinderFactory binderFactory;
@Autowired
@StreamMessageConverter
private List<MessageConverter> customMessageConverters;
@Test
public void testCustomMessageConverter() throws Exception {
assertThat(customMessageConverters).hasSize(4);
assertThat(customMessageConverters).hasSize(2);
assertThat(customMessageConverters).extracting("class").contains(FooConverter.class,
BarConverter.class, DefaultDatatypeChannelMessageConverter.class,
ConfigurableCompositeMessageConverter.class);
BarConverter.class);
testSource.output().send(MessageBuilder.withPayload(new Foo("hi")).build());
@SuppressWarnings("unchecked")
Message<String> received = (Message<String>) ((TestSupportBinder) binderFactory.getBinder(null,
@@ -88,11 +87,13 @@ public class CustomMessageConverterTests {
public static class TestSource {
@Bean
@StreamMessageConverter
public MessageConverter fooConverter() {
return new FooConverter();
}
@Bean
@StreamMessageConverter
public MessageConverter barConverter() {
return new BarConverter();
}

View File

@@ -64,8 +64,8 @@ public class DefaultHeaderPropagationTests {
assertThat(received).isNotNull();
assertThat(received.getHeaders()).containsEntry("foo", "fooValue");
assertThat(received.getHeaders()).containsEntry("bar", "barValue");
assertThat(received.getHeaders()).doesNotContainKey(MessageHeaders.CONTENT_TYPE);
assertThat(received.getPayload()).isEqualTo("{'name':'foo'}");
assertThat(received.getHeaders()).containsKeys(MessageHeaders.CONTENT_TYPE);
assertThat(received.getPayload()).isEqualTo("{'name':'foo'}".getBytes());
}
@EnableBinding(Processor.class)
@@ -73,8 +73,8 @@ public class DefaultHeaderPropagationTests {
public static class HeaderPropagationProcessor {
@ServiceActivator(inputChannel = "input", outputChannel = "output")
public String consume(String data) {
return data;
public Message<String> consume(String data) {
return MessageBuilder.withPayload(data).setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build();
}
}

View File

@@ -33,9 +33,9 @@ import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
@@ -51,8 +51,8 @@ public class DefaultHeaderPropagationWithApplicationProvidedHeaderTests {
@Autowired
private BinderFactory binderFactory;
@Test
public void testHeaderPropagationIfSetByApplication() throws Exception {
@Test(expected = MessageConversionException.class)
public void testFailedonCustomContentTypeWithoutConverter() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload("{'name':'foo'}")
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json")
.setHeader("foo", "fooValue")
@@ -61,11 +61,7 @@ public class DefaultHeaderPropagationWithApplicationProvidedHeaderTests {
@SuppressWarnings("unchecked")
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received.getHeaders()).containsEntry("foo", "fooValue");
assertThat(received.getHeaders()).containsEntry("bar", "barValue");
assertThat(received.getHeaders()).containsEntry(MessageHeaders.CONTENT_TYPE, "custom/header");
assertThat(received).isNotNull();
assertThat(received.getPayload()).isEqualTo("{'name':'foo'}");
}
@EnableBinding(Processor.class)

View File

@@ -64,8 +64,8 @@ public class DeserializeJSONToJavaTypeTests {
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received).isNotNull();
assertThat(received.getPayload()).isInstanceOf(Foo.class);
assertThat((Foo) received.getPayload()).hasFieldOrPropertyWithValue("name", "Bar");
assertThat(received.getPayload()).isInstanceOf(byte[].class);
assertThat((byte[]) received.getPayload()).isEqualTo("{\"name\":\"Bar\"}".getBytes());
}
@EnableBinding(Processor.class)

View File

@@ -33,7 +33,6 @@ import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.tuple.Tuple;
import org.springframework.tuple.TupleBuilder;
@@ -56,12 +55,13 @@ public class InboundJsonToTupleConversionTest {
@Test
public void testInboundJsonTupleConversion() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload("{'name':'foo'}")
.setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build());
.build());
@SuppressWarnings("unchecked")
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received).isNotNull();
assertThat(received.getPayload()).isEqualTo(TupleBuilder.tuple().of("name", "foo"));
assertThat(TupleBuilder.fromString(new String((byte[])received.getPayload()))).isEqualTo(TupleBuilder.tuple().of("name", "foo"));
}
@EnableBinding(Processor.class)

View File

@@ -42,7 +42,6 @@ import org.springframework.messaging.MessagingException;
import org.springframework.messaging.converter.CompositeMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.tuple.Tuple;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -78,14 +77,13 @@ public class MessageChannelConfigurerTests {
MessageHandler messageHandler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat(message.getPayload()).isInstanceOf(Tuple.class);
assertThat(((Tuple) message.getPayload()).getFieldNames().get(0)).isEqualTo("message");
assertThat(((Tuple) message.getPayload()).getValue(0)).isEqualTo("Hi");
assertThat(message.getPayload()).isInstanceOf(byte[].class);
assertThat(message.getPayload()).isEqualTo("{\"message\":\"Hi\"}".getBytes());
latch.countDown();
}
};
testSink.input().subscribe(messageHandler);
testSink.input().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}").build());
testSink.input().send(MessageBuilder.withPayload("{\"message\":\"Hi\"}".getBytes()).build());
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
testSink.input().unsubscribe(messageHandler);
}

View File

@@ -23,6 +23,7 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -48,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Vinicius Carvalho
*/
@RunWith(Parameterized.class)
public class StreamListenerHandlerBeanTests {
@@ -76,13 +78,13 @@ public class StreamListenerHandlerBeanTests {
MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
.setHeader("contentType", "application/json").build());
HandlerBean handlerBean = context.getBean(HandlerBean.class);
assertThat(handlerBean.receivedPojos).hasSize(1);
assertThat(handlerBean.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo",
Assertions.assertThat(handlerBean.receivedPojos).hasSize(1);
Assertions.assertThat(handlerBean.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo",
"barbar" + id);
Message<String> message = (Message<String>) collector.forChannel(
Message<byte[]> message = (Message<byte[]>) collector.forChannel(
processor.output()).poll(1, TimeUnit.SECONDS);
assertThat(message).isNotNull();
assertThat(message.getPayload()).isEqualTo("{\"bar\":\"barbar" + id + "\"}");
assertThat(new String(message.getPayload())).isEqualTo("{\"bar\":\"barbar" + id + "\"}");
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.APPLICATION_JSON));
context.close();

View File

@@ -64,13 +64,15 @@ import static org.springframework.cloud.stream.binding.StreamListenerErrorMessag
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Gary Russell
* @author Vinicius Carvalho
*/
public class StreamListenerHandlerMethodTests {
@Test
public void testInvalidInputOnMethod() throws Exception {
try {
SpringApplication.run(TestInvalidInputOnMethod.class, "--server.port=0");
SpringApplication.run(TestInvalidInputOnMethod.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected: " + INPUT_AT_STREAM_LISTENER);
}
catch (BeanCreationException e) {
@@ -81,30 +83,41 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodWithObjectAsMethodArgument() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(TestMethodWithObjectAsMethodArgument.class,
"--server.port=0");
"--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.input.contentType=text/plain",
"--spring.cloud.stream.bindings.output.contentType=text/plain");
Processor processor = context.getBean(Processor.class);
final String testMessage = "testing";
processor.input().send(MessageBuilder.withPayload(testMessage).build());
MessageCollector messageCollector = context.getBean(MessageCollector.class);
Message<?> result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
Message<byte[]> result = (Message<byte[]>) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
assertThat(result).isNotNull();
assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase());
assertThat(new String(result.getPayload())).isEqualTo(testMessage.toUpperCase());
context.close();
}
@Test
/**
* @since 2.0 : This test is an example of the new behavior of 2.0 when it comes to contentType handling.
* The default contentType being JSON in order to be able to check a message without quotes the user needs to set the input/output contentType accordingly
* Also, received messages are always of Message<byte[]> now.
*/
public void testMethodHeadersPropagatged() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(TestMethodHeadersPropagated.class,
"--server.port=0");
"--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.input.contentType=text/plain",
"--spring.cloud.stream.bindings.output.contentType=text/plain");
Processor processor = context.getBean(Processor.class);
final String testMessage = "testing";
processor.input().send(MessageBuilder.withPayload(testMessage)
.setHeader("foo", "bar")
.build());
MessageCollector messageCollector = context.getBean(MessageCollector.class);
Message<?> result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
Message<byte[]> result = (Message<byte[]>) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
assertThat(result).isNotNull();
assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase());
assertThat(new String(result.getPayload())).isEqualTo(testMessage.toUpperCase());
assertThat(result.getHeaders().get("foo")).isEqualTo("bar");
context.close();
}
@@ -112,41 +125,49 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodHeadersNotPropagatged() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(TestMethodHeadersNotPropagated.class,
"--server.port=0");
"--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.input.contentType=text/plain",
"--spring.cloud.stream.bindings.output.contentType=text/plain");
Processor processor = context.getBean(Processor.class);
final String testMessage = "testing";
processor.input().send(MessageBuilder.withPayload(testMessage)
.setHeader("foo", "bar")
.build());
MessageCollector messageCollector = context.getBean(MessageCollector.class);
Message<?> result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
Message<byte[]> result = (Message<byte[]>) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
assertThat(result).isNotNull();
assertThat(result.getPayload()).isEqualTo(testMessage.toUpperCase());
assertThat(new String(result.getPayload())).isEqualTo(testMessage.toUpperCase());
assertThat(result.getHeaders().get("foo")).isNull();
context.close();
}
@Test
//TODO: Handle dynamic destinations and contentType
public void testStreamListenerMethodWithTargetBeanFromOutside() throws Exception {
ConfigurableApplicationContext context = SpringApplication
.run(TestStreamListenerMethodWithTargetBeanFromOutside.class, "--server.port=0");
.run(TestStreamListenerMethodWithTargetBeanFromOutside.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.input.contentType=text/plain",
"--spring.cloud.stream.bindings.output.contentType=text/plain");
Sink sink = context.getBean(Sink.class);
final String testMessageToSend = "testing";
sink.input().send(MessageBuilder.withPayload(testMessageToSend).build());
DirectChannel directChannel = (DirectChannel) context.getBean(testMessageToSend.toUpperCase(),
MessageChannel.class);
MessageCollector messageCollector = context.getBean(MessageCollector.class);
Message<?> result = messageCollector.forChannel(directChannel).poll(1000, TimeUnit.MILLISECONDS);
Message<byte[]> result = (Message<byte[]>) messageCollector.forChannel(directChannel).poll(1000, TimeUnit.MILLISECONDS);
sink.input().send(MessageBuilder.withPayload(testMessageToSend).build());
assertThat(result).isNotNull();
assertThat(result.getPayload()).isEqualTo(testMessageToSend.toUpperCase());
assertThat(new String(result.getPayload())).isEqualTo(testMessageToSend.toUpperCase());
context.close();
}
@Test
public void testInvalidReturnTypeWithSendToAndOutput() throws Exception {
try {
SpringApplication.run(TestReturnTypeWithMultipleOutput.class, "--server.port=0");
SpringApplication.run(TestReturnTypeWithMultipleOutput.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected: " + RETURN_TYPE_MULTIPLE_OUTBOUND_SPECIFIED);
}
catch (BeanCreationException e) {
@@ -157,7 +178,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testInvalidReturnTypeWithNoOutput() throws Exception {
try {
SpringApplication.run(TestInvalidReturnTypeWithNoOutput.class, "--server.port=0");
SpringApplication.run(TestInvalidReturnTypeWithNoOutput.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected: " + RETURN_TYPE_NO_OUTBOUND_SPECIFIED);
}
catch (BeanCreationException e) {
@@ -168,7 +190,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testInvalidInputAnnotationWithNoValue() throws Exception {
try {
SpringApplication.run(TestInvalidInputAnnotationWithNoValue.class, "--server.port=0");
SpringApplication.run(TestInvalidInputAnnotationWithNoValue.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected: " + INVALID_INBOUND_NAME);
}
catch (BeanCreationException e) {
@@ -179,7 +202,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testInvalidOutputAnnotationWithNoValue() throws Exception {
try {
SpringApplication.run(TestInvalidOutputAnnotationWithNoValue.class, "--server.port=0");
SpringApplication.run(TestInvalidOutputAnnotationWithNoValue.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected: " + INVALID_OUTBOUND_NAME);
}
catch (BeanCreationException e) {
@@ -190,7 +214,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodInvalidInboundName() throws Exception {
try {
SpringApplication.run(TestMethodInvalidInboundName.class, "--server.port=0");
SpringApplication.run(TestMethodInvalidInboundName.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected on using invalid inbound name");
}
catch (BeanCreationException e) {
@@ -203,7 +228,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodInvalidOutboundName() throws Exception {
try {
SpringApplication.run(TestMethodInvalidOutboundName.class, "--server.port=0");
SpringApplication.run(TestMethodInvalidOutboundName.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected on using invalid outbound name");
}
catch (BeanCreationException e) {
@@ -215,7 +241,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testAmbiguousMethodArguments1() throws Exception {
try {
SpringApplication.run(TestAmbiguousMethodArguments1.class, "--server.port=0");
SpringApplication.run(TestAmbiguousMethodArguments1.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected: " + AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS);
}
catch (BeanCreationException e) {
@@ -226,7 +253,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testAmbiguousMethodArguments2() throws Exception {
try {
SpringApplication.run(TestAmbiguousMethodArguments2.class, "--server.port=0");
SpringApplication.run(TestAmbiguousMethodArguments2.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected:" + AMBIGUOUS_MESSAGE_HANDLER_METHOD_ARGUMENTS);
}
catch (BeanCreationException e) {
@@ -237,7 +265,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodWithInputAsMethodAndParameter() throws Exception {
try {
SpringApplication.run(TestMethodWithInputAsMethodAndParameter.class, "--server.port=0");
SpringApplication.run(TestMethodWithInputAsMethodAndParameter.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected: " + INVALID_DECLARATIVE_METHOD_PARAMETERS);
}
catch (BeanCreationException e) {
@@ -248,7 +277,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodWithOutputAsMethodAndParameter() throws Exception {
try {
SpringApplication.run(TestMethodWithOutputAsMethodAndParameter.class, "--server.port=0");
SpringApplication.run(TestMethodWithOutputAsMethodAndParameter.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected:" + INVALID_OUTPUT_VALUES);
}
catch (BeanCreationException e) {
@@ -259,7 +289,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodWithoutInput() throws Exception {
try {
SpringApplication.run(TestMethodWithoutInput.class, "--server.port=0");
SpringApplication.run(TestMethodWithoutInput.class, "--server.port=0",
"--spring.jmx.enabled=false");
fail("Exception expected when inbound target is not set");
}
catch (BeanCreationException e) {
@@ -270,7 +301,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodWithMultipleInputParameters() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(TestMethodWithMultipleInputParameters.class,
"--server.port=0");
"--server.port=0",
"--spring.jmx.enabled=false");
Processor processor = context.getBean(Processor.class);
StreamListenerTestUtils.FooInboundChannel1 inboundChannel2 = context
.getBean(StreamListenerTestUtils.FooInboundChannel1.class);
@@ -294,7 +326,8 @@ public class StreamListenerHandlerMethodTests {
@Test
public void testMethodWithMultipleOutputParameters() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(TestMethodWithMultipleOutputParameters.class,
"--server.port=0");
"--server.port=0",
"--spring.jmx.enabled=false");
Processor processor = context.getBean(Processor.class);
String id = UUID.randomUUID().toString();
StreamListenerTestUtils.FooOutboundChannel1 source2 = context

View File

@@ -44,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Vinicius Carvalho
*/
@RunWith(Parameterized.class)
public class StreamListenerMessageArgumentTests {
@@ -63,7 +64,7 @@ public class StreamListenerMessageArgumentTests {
@SuppressWarnings("unchecked")
public void testMessageArgument() throws Exception {
ConfigurableApplicationContext context = SpringApplication
.run(this.configClass, "--server.port=0");
.run(this.configClass, "--server.port=0", "--spring.cloud.stream.bindings.output.contentType=text/plain","--spring.jmx.enabled=false");
MessageCollector collector = context.getBean(MessageCollector.class);
Processor processor = context.getBean(Processor.class);
String id = UUID.randomUUID().toString();
@@ -73,10 +74,10 @@ public class StreamListenerMessageArgumentTests {
.getBean(TestPojoWithMessageArgument.class);
assertThat(testPojoWithMessageArgument.receivedMessages).hasSize(1);
assertThat(testPojoWithMessageArgument.receivedMessages.get(0).getPayload()).isEqualTo("barbar" + id);
Message<StreamListenerTestUtils.BarPojo> message = (Message<StreamListenerTestUtils.BarPojo>) collector
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
assertThat(message).isNotNull();
assertThat(message.getPayload().getBar()).isEqualTo("barbar" + id);
assertThat(new String(message.getPayload())).contains("barbar" + id);
context.close();
}

View File

@@ -23,6 +23,8 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -50,6 +52,8 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Vinicius Carvalho
*
*/
@RunWith(StreamListenerMethodReturnWithConversionTests.class)
@Suite.SuiteClasses({ StreamListenerMethodReturnWithConversionTests.TestReturnConversion.class,
@@ -79,19 +83,19 @@ public class StreamListenerMethodReturnWithConversionTests extends Suite {
@SuppressWarnings("unchecked")
public void testReturnConversion() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(this.configClass,
"--spring.cloud.stream.bindings.output.contentType=application/json", "--server.port=0");
"--spring.cloud.stream.bindings.output.contentType=application/json", "--server.port=0","--spring.jmx.enabled=false");
MessageCollector collector = context.getBean(MessageCollector.class);
Processor processor = context.getBean(Processor.class);
String id = UUID.randomUUID().toString();
processor.input().send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
.setHeader("contentType", "application/json").build());
TestPojoWithMimeType testPojoWithMimeType = context.getBean(TestPojoWithMimeType.class);
assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<String> message = (Message<String>) collector.forChannel(processor.output()).poll(1,
Assertions.assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
Assertions.assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<byte[]> message = (Message<byte[]>) collector.forChannel(processor.output()).poll(1,
TimeUnit.SECONDS);
assertThat(message).isNotNull();
assertThat(message.getPayload()).isEqualTo("{\"bar\":\"barbar" + id + "\"}");
assertThat(new String(message.getPayload())).isEqualTo("{\"bar\":\"barbar" + id + "\"}");
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.APPLICATION_JSON));
context.close();
@@ -103,6 +107,8 @@ public class StreamListenerMethodReturnWithConversionTests extends Suite {
private Class<?> configClass;
private ObjectMapper mapper = new ObjectMapper();
public TestReturnNoConversion(Class<?> configClass) {
this.configClass = configClass;
}
@@ -115,21 +121,22 @@ public class StreamListenerMethodReturnWithConversionTests extends Suite {
@Test
@SuppressWarnings("unchecked")
public void testReturnNoConversion() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0");
ConfigurableApplicationContext context = SpringApplication.run(this.configClass, "--server.port=0","--spring.jmx.enabled=false");
MessageCollector collector = context.getBean(MessageCollector.class);
Processor processor = context.getBean(Processor.class);
String id = UUID.randomUUID().toString();
processor.input().send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
.setHeader("contentType", "application/json").build());
TestPojoWithMimeType testPojoWithMimeType = context.getBean(TestPojoWithMimeType.class);
assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<StreamListenerTestUtils.BarPojo> message = (Message<StreamListenerTestUtils.BarPojo>) collector
Assertions.assertThat(testPojoWithMimeType.receivedPojos).hasSize(1);
Assertions.assertThat(testPojoWithMimeType.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(processor.output()).poll(1,
TimeUnit.SECONDS);
assertThat(message).isNotNull();
assertThat(message.getPayload().getBar()).isEqualTo("barbar" + id);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) == null);
StreamListenerTestUtils.BarPojo barPojo = mapper.readValue(message.getPayload(),StreamListenerTestUtils.BarPojo.class);
assertThat(barPojo.getBar()).isEqualTo("barbar" + id);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class) != null);
context.close();
}
}

View File

@@ -23,6 +23,7 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -44,6 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Vinicius Carvalho
*/
@RunWith(Parameterized.class)
public class StreamListenerMethodWithReturnMessageTests {
@@ -63,7 +65,7 @@ public class StreamListenerMethodWithReturnMessageTests {
@SuppressWarnings("unchecked")
public void testReturnMessage() throws Exception {
ConfigurableApplicationContext context = SpringApplication
.run(this.configClass, "--server.port=0");
.run(this.configClass, "--server.port=0","--spring.jmx.enabled=false");
MessageCollector collector = context.getBean(MessageCollector.class);
Processor processor = context.getBean(Processor.class);
String id = UUID.randomUUID().toString();
@@ -72,12 +74,12 @@ public class StreamListenerMethodWithReturnMessageTests {
.setHeader("contentType", "application/json").build());
TestPojoWithMessageReturn testPojoWithMessageReturn = context
.getBean(TestPojoWithMessageReturn.class);
assertThat(testPojoWithMessageReturn.receivedPojos).hasSize(1);
assertThat(testPojoWithMessageReturn.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<StreamListenerTestUtils.BarPojo> message = (Message<StreamListenerTestUtils.BarPojo>) collector
Assertions.assertThat(testPojoWithMessageReturn.receivedPojos).hasSize(1);
Assertions.assertThat(testPojoWithMessageReturn.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
assertThat(message).isNotNull();
assertThat(message.getPayload().getBar()).isEqualTo("barbar" + id);
assertThat(new String(message.getPayload())).contains("barbar" + id);
context.close();
}

View File

@@ -23,6 +23,7 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -63,21 +64,21 @@ public class StreamListenerMethodWithReturnValueTests {
@SuppressWarnings("unchecked")
public void testReturn() throws Exception {
ConfigurableApplicationContext context = SpringApplication
.run(this.configClass, "--server.port=0");
.run(this.configClass, "--server.port=0","--spring.jmx.enabled=false");
MessageCollector collector = context.getBean(MessageCollector.class);
Processor processor = context.getBean(Processor.class);
String id = UUID.randomUUID().toString();
processor.input()
.send(MessageBuilder.withPayload("{\"foo\":\"barbar" + id + "\"}")
.setHeader("contentType", "application/json").build());
Message<String> message = (Message<String>) collector
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
TestStringProcessor testStringProcessor = context
.getBean(TestStringProcessor.class);
assertThat(testStringProcessor.receivedPojos).hasSize(1);
assertThat(testStringProcessor.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
Assertions.assertThat(testStringProcessor.receivedPojos).hasSize(1);
Assertions.assertThat(testStringProcessor.receivedPojos.get(0)).hasFieldOrPropertyWithValue("foo", "barbar" + id);
assertThat(message).isNotNull();
assertThat(message.getPayload()).isEqualTo("barbar" + id);
assertThat(new String(message.getPayload())).contains("barbar" + id);
context.close();
}

View File

@@ -55,6 +55,14 @@ public class StreamListenerTestUtils {
public void setFoo(String foo) {
this.foo = foo;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("FooPojo{");
sb.append("foo='").append(foo).append('\'');
sb.append('}');
return sb.toString();
}
}
public static class BarPojo {
@@ -68,5 +76,13 @@ public class StreamListenerTestUtils {
public void setBar(String bar) {
this.bar = bar;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("BarPojo{");
sb.append("bar='").append(bar).append('\'');
sb.append('}');
return sb.toString();
}
}
}

View File

@@ -45,12 +45,13 @@ import static org.springframework.cloud.stream.binding.StreamListenerErrorMessag
/**
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
* @author Vinicius Carvalho
*/
public class StreamListenerWithAnnotatedInputOutputArgsTests {
@Test
public void testInputOutputArgs() throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(TestInputOutputArgs.class, "--server.port=0");
ConfigurableApplicationContext context = SpringApplication.run(TestInputOutputArgs.class, "--server.port=0", "--spring.cloud.stream.bindings.output.contentType=text/plain", "--spring.jmx.enabled=false");
sendMessageAndValidate(context);
}
@@ -68,7 +69,7 @@ public class StreamListenerWithAnnotatedInputOutputArgsTests {
@Test
public void testInputOutputArgsWithInvalidBindableTarget() {
try {
SpringApplication.run(TestInputOutputArgsWithInvalidBindableTarget.class, "--server.port=0");
SpringApplication.run(TestInputOutputArgsWithInvalidBindableTarget.class, "--server.port=0","--spring.jmx.enabled=false");
fail("Exception expected on using invalid bindable target as method parameter");
}
catch (BeanCreationException e) {
@@ -81,7 +82,7 @@ public class StreamListenerWithAnnotatedInputOutputArgsTests {
@Test
public void testInputOutputArgsWithParameterOrderChanged() throws Exception {
ConfigurableApplicationContext context = SpringApplication
.run(TestInputOutputArgsWithParameterOrderChanged.class, "--server.port=0");
.run(TestInputOutputArgsWithParameterOrderChanged.class, "--server.port=0", "--spring.cloud.stream.bindings.output.contentType=text/plain","--spring.jmx.enabled=false");
sendMessageAndValidate(context);
}
@@ -90,9 +91,9 @@ public class StreamListenerWithAnnotatedInputOutputArgsTests {
Processor processor = context.getBean(Processor.class);
processor.input().send(MessageBuilder.withPayload("hello").setHeader("contentType", "text/plain").build());
MessageCollector messageCollector = context.getBean(MessageCollector.class);
Message<?> result = messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
Message<byte[]> result = (Message<byte[]>) messageCollector.forChannel(processor.output()).poll(1000, TimeUnit.MILLISECONDS);
assertThat(result).isNotNull();
assertThat(result.getPayload()).isEqualTo("HELLO");
assertThat(new String(result.getPayload())).isEqualTo("HELLO");
context.close();
}

View File

@@ -56,30 +56,30 @@ public class TextPlainConversionTest {
public void testTextPlainConversionOnOutput() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload("Bar").build());
@SuppressWarnings("unchecked")
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
Message<byte[]> received = (Message<byte[]>) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received).isNotNull();
assertThat(received.getPayload()).isEqualTo("Foo{name='Bar'}");
assertThat(new String(received.getPayload())).isEqualTo("Foo{name='Bar'}");
}
@Test
public void testByteArrayConversionOnOutput() throws Exception {
testProcessor.output().send(MessageBuilder.withPayload("Bar".getBytes()).build());
@SuppressWarnings("unchecked")
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
Message<byte[]> received = (Message<byte[]>) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received).isNotNull();
assertThat(received.getPayload()).isEqualTo("Bar");
assertThat(new String(received.getPayload())).isEqualTo("Bar");
}
@Test
public void testTextPlainConversionOnInputAndOutput() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload(new Foo("Bar")).build());
@SuppressWarnings("unchecked")
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
Message<byte[]> received = (Message<byte[]>) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received).isNotNull();
assertThat(received.getPayload()).isEqualTo("Foo{name='Foo{name='Bar'}'}");
assertThat(new String(received.getPayload())).isEqualTo("Foo{name='Foo{name='Bar'}'}");
}
@EnableBinding(Processor.class)

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.stream.config;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +34,7 @@ import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConversionException;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -40,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marius Bogoevici
* @author Vinicius Carvalho
* @since 1.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -52,16 +55,23 @@ public class TextPlainToJsonConversionTest {
@Autowired
private BinderFactory binderFactory;
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testNoContentTypeToJsonConversionOnInput() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}").build());
Message<?> received = ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
Message<byte[]> received = (Message<byte[]>) ((TestSupportBinder) binderFactory.getBinder(null, MessageChannel.class))
.messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS);
assertThat(received).isNotNull();
assertThat(((Foo) received.getPayload()).getName()).isEqualTo("transformed-Bar");
Foo foo = mapper.readValue(received.getPayload(),Foo.class);
assertThat(foo.getName()).isEqualTo("transformed-Bar");
}
@Test
/**
* @since 2.0: Conversion from text/plain -> json is no longer supported. Strict contentType only.
* @throws Exception
*/
@Test(expected = MessageConversionException.class)
public void testTextPlainToJsonConversionOnInput() throws Exception {
testProcessor.input().send(MessageBuilder.withPayload("{\"name\":\"Bar\"}")
.setHeader(MessageHeaders.CONTENT_TYPE, "text/plain").build());

View File

@@ -49,9 +49,10 @@ public class AggregateApplicationTests {
TestSupportBinder testSupportBinder = (TestSupportBinder) context.getBean(BinderFactory.class).getBinder(null,
MessageChannel.class);
MessageChannel processorOutput = testSupportBinder.getChannelForName("output");
Message<String> received = (Message<String>) (testSupportBinder.messageCollector().forChannel(processorOutput)
Message<byte[]> received = (Message<byte[]>) (testSupportBinder.messageCollector().forChannel(processorOutput)
.poll(5, TimeUnit.SECONDS));
Assert.assertThat(received, notNullValue());
Assert.assertTrue(received.getPayload().endsWith("processed"));
String payload = new String(received.getPayload());
Assert.assertTrue(payload.endsWith("processed"));
}
}

View File

@@ -21,7 +21,10 @@ import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.support.MessageBuilder;
/**
* @author Ilayaperumal Gopinathan
@@ -33,7 +36,7 @@ public class TestProcessor {
@StreamListener(Processor.INPUT)
@SendTo(Processor.OUTPUT)
public String process(String message) {
return message + " processed";
public Message<String> process(String message) {
return MessageBuilder.withPayload(message + " processed").setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build();
}
}

View File

@@ -27,7 +27,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.core.MessageSource;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
/**
* @author Ilayaperumal Gopinathan
@@ -43,7 +44,7 @@ public class TestSource {
return new MessageSource<String>() {
@Override
public Message<String> receive() {
return new GenericMessage<>(new SimpleDateFormat("DDMMMYYYY").format(new Date()));
return MessageBuilder.withPayload(new SimpleDateFormat("DDMMMYYYY").format(new Date())).setHeader(MessageHeaders.CONTENT_TYPE,"text/plain").build();
}
};
}

View File

@@ -0,0 +1,445 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.config.contentType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.converter.KryoMessageConverter;
import org.springframework.cloud.stream.converter.MessageConverterUtils;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.cloud.stream.test.binder.MessageCollector;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.tuple.Tuple;
import org.springframework.tuple.TupleBuilder;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Vinicius Carvalho
*/
public class ContentTypeTests {
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testSendWithDefaultContentType() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
User user = new User("Alice");
source.output().send(MessageBuilder.withPayload(user).build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
User received = mapper.readValue(message.getPayload(), User.class);
assertThat(
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.APPLICATION_JSON));
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@Test
public void testSendJsonAsString() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
User user = new User("Alice");
String json = mapper.writeValueAsString(user);
source.output().send(MessageBuilder.withPayload(user).build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.APPLICATION_JSON));
assertThat(json.getBytes()).isEqualTo(message.getPayload());
}
}
@Test
public void testSendJsonString() throws Exception{
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
source.output().send(MessageBuilder.withPayload("foo").build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.APPLICATION_JSON));
assertThat("\"foo\"".getBytes()).isEqualTo(message.getPayload());
}
}
@Test
public void testSendBynaryDataWithoutContentType() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
byte[] data = new byte[] { 0, 1, 2, 3 };
source.output().send(MessageBuilder.withPayload(data).build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(
message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.APPLICATION_OCTET_STREAM));
assertThat(message.getPayload()).isEqualTo(data);
}
}
@Test
public void testSendBinaryDataWithContentType() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.output.contentType=image/jpeg")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
byte[] data = new byte[] { 0, 1, 2, 3 };
source.output().send(MessageBuilder.withPayload(data)
.build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.IMAGE_JPEG));
assertThat(message.getPayload()).isEqualTo(data);
}
}
@Test
public void testSendBinaryDataWithContentTypeUsingHeaders() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
byte[] data = new byte[] { 0, 1, 2, 3 };
source.output().send(MessageBuilder.withPayload(data)
.setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.IMAGE_JPEG)
.build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.IMAGE_JPEG));
assertThat(message.getPayload()).isEqualTo(data);
}
}
@Test
public void testSendJavaSerializable() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.output.contentType=application/x-java-serialized-object")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
User user = new User("Alice");
source.output().send(MessageBuilder.withPayload(user).build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MessageConverterUtils.X_JAVA_SERIALIZED_OBJECT));
ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) (message.getPayload()));
User received = (User) new ObjectInputStream(bis).readObject();
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@Test
public void testSendKryoSerialized() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.output.contentType=application/x-java-object")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Kryo kryo = new Kryo();
Source source = context.getBean(Source.class);
User user = new User("Alice");
source.output().send(MessageBuilder.withPayload(user).build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
com.esotericsoftware.kryo.io.Input input = new com.esotericsoftware.kryo.io.Input(new ByteArrayInputStream(message.getPayload()));
User received = kryo.readObject(input,User.class);
input.close();
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE)));
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@Test
public void testSendStringType() throws Exception{
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.output.contentType=text/plain")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
User user = new User("Alice");
source.output().send(MessageBuilder.withPayload(user).build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MimeTypeUtils.TEXT_PLAIN));
assertThat(message.getPayload()).isEqualTo(user.toString().getBytes());
}
}
@Test
public void testSendTuple() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SourceApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.output.contentType=application/x-spring-tuple")) {
MessageCollector collector = context.getBean(MessageCollector.class);
Source source = context.getBean(Source.class);
Tuple tuple = TupleBuilder.tuple().of("foo","bar");
source.output().send(MessageBuilder.withPayload(tuple).build());
Message<byte[]> message = (Message<byte[]>) collector
.forChannel(source.output()).poll(1, TimeUnit.SECONDS);
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class)
.includes(MessageConverterUtils.X_SPRING_TUPLE));
assertThat(TupleBuilder.fromString(new String(message.getPayload()))).isEqualTo(tuple);
}
}
@Test
public void testReceiveWithDefaults() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SinkApplication.class, "--server.port=0",
"--spring.jmx.enabled=false")) {
TestSink testSink = context.getBean(TestSink.class);
SinkApplication sourceApp = context.getBean(SinkApplication.class);
User user = new User("Alice");
testSink.pojo().send(MessageBuilder.withPayload(mapper.writeValueAsBytes(user)).build());
Map<String,Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
User received = (User)sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeTypeUtils.APPLICATION_JSON));
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@Test
public void testReceiveRawWithDifferentContentTypes() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SinkApplication.class, "--server.port=0",
"--spring.jmx.enabled=false")) {
TestSink testSink = context.getBean(TestSink.class);
SinkApplication sourceApp = context.getBean(SinkApplication.class);
testSink.raw().send(MessageBuilder.withPayload(new byte[4])
.setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.IMAGE_JPEG)
.build());
testSink.raw().send(MessageBuilder.withPayload(new byte[4])
.setHeader(MessageHeaders.CONTENT_TYPE,MimeTypeUtils.IMAGE_GIF)
.build());
Map<String,Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeTypeUtils.IMAGE_GIF));
headers = (Map<String, Object>) sourceApp.arguments.pop();
sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeTypeUtils.IMAGE_JPEG));
}
}
@Test
public void testReceiveKryoPayload() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SinkApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.pojo_input.contentType=application/x-java-object"
)) {
TestSink testSink = context.getBean(TestSink.class);
SinkApplication sourceApp = context.getBean(SinkApplication.class);
Kryo kryo = new Kryo();
User user = new User("Alice");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryo.writeObject(output,user);
output.close();
testSink.pojo().send(MessageBuilder.withPayload(baos.toByteArray()).build());
Map<String,Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
User received = (User)sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE)));
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@Test
public void testReceiveKryoWithHeadersOverridingDefault() throws Exception{
try (ConfigurableApplicationContext context = SpringApplication.run(
SinkApplication.class, "--server.port=0",
"--spring.jmx.enabled=false"
)) {
TestSink testSink = context.getBean(TestSink.class);
SinkApplication sourceApp = context.getBean(SinkApplication.class);
Kryo kryo = new Kryo();
User user = new User("Alice");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryo.writeObject(output,user);
output.close();
testSink.pojo().send(MessageBuilder.withPayload(baos.toByteArray())
.setHeader(MessageHeaders.CONTENT_TYPE, MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE))
.build());
Map<String,Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
User received = (User)sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MimeType.valueOf(KryoMessageConverter.KRYO_MIME_TYPE)));
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@Test
public void testReceiveJavaSerializable() throws Exception {
try (ConfigurableApplicationContext context = SpringApplication.run(
SinkApplication.class, "--server.port=0",
"--spring.jmx.enabled=false",
"--spring.cloud.stream.bindings.pojo_input.contentType=application/x-java-serialized-object"
)) {
TestSink testSink = context.getBean(TestSink.class);
SinkApplication sourceApp = context.getBean(SinkApplication.class);
User user = new User("Alice");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ObjectOutputStream(baos).writeObject(user);
testSink.pojo().send(MessageBuilder.withPayload(baos.toByteArray()).build());
Map<String,Object> headers = (Map<String, Object>) sourceApp.arguments.pop();
User received = (User)sourceApp.arguments.pop();
assertThat(((MimeType)headers.get(MessageHeaders.CONTENT_TYPE))
.includes(MessageConverterUtils.X_JAVA_SERIALIZED_OBJECT));
assertThat(user.getName()).isEqualTo(received.getName());
}
}
@EnableBinding(Source.class)
@SpringBootApplication
public static class SourceApplication {
}
@EnableBinding(TestSink.class)
@SpringBootApplication
public static class SinkApplication {
public LinkedList arguments = new LinkedList();
@StreamListener("POJO_INPUT")
public void receive(User user, @Headers Map<String, Object> headers){
arguments.push(user);
arguments.push(headers);
}
@StreamListener("TUPLE_INPUT")
public void receive(Tuple tuple){
}
@StreamListener("STRING_INPUT")
public void receive(String string){
}
@StreamListener("RAW_INPUT")
public void receive(byte[] data, @Headers Map<String, Object> headers){
arguments.push(data);
arguments.push(headers);
}
}
public interface TestSink {
@Input("POJO_INPUT")
SubscribableChannel pojo();
@Input("STRING_INPUT")
SubscribableChannel string();
@Input("TUPLE_INPUT")
SubscribableChannel tuple();
@Input("RAW_INPUT")
SubscribableChannel raw();
}
public static class User implements Serializable {
private String name;
public User(){}
@JsonCreator
public User(@JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("User{");
sb.append("name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
}

View File

@@ -1,3 +1,4 @@
spring.cloud.stream.bindings.output.destination=partOut
spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload
spring.cloud.stream.bindings.output.producer.partitionCount=3