From f65dcbe975e802b3838a5a07f33a9f3329802749 Mon Sep 17 00:00:00 2001 From: Marius Bogoevici Date: Thu, 9 Feb 2017 01:33:02 -0500 Subject: [PATCH] Add converter for Object->String conversion Support `text/plain` outbound content type for generic objects Fix #774 --- .../config/TextPlainConversionTest.java | 111 ++++++++++++++++++ .../config/textplain/text-plain.properties | 2 + .../CompositeMessageConverterFactory.java | 6 +- .../ObjectStringMessageConverter.java | 68 +++++++++++ 4 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java create mode 100644 spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/textplain/text-plain.properties create mode 100644 spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ObjectStringMessageConverter.java diff --git a/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java new file mode 100644 index 000000000..8d945fe54 --- /dev/null +++ b/spring-cloud-stream-integration-tests/src/test/java/org/springframework/cloud/stream/config/TextPlainConversionTest.java @@ -0,0 +1,111 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.binder.BinderFactory; +import org.springframework.cloud.stream.messaging.Processor; +import org.springframework.cloud.stream.test.binder.TestSupportBinder; +import org.springframework.context.annotation.PropertySource; +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.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Marius Bogoevici + * + * @since 1.2 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = TextPlainConversionTest.FooProcessor.class) +public class TextPlainConversionTest { + + @Autowired + private Processor testProcessor; + + @Autowired + private BinderFactory binderFactory; + + @Test + public void testTextPlainConversionOnOutput() throws Exception { + testProcessor.input().send(MessageBuilder.withPayload("Bar").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("Foo{name='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)) + .messageCollector().forChannel(testProcessor.output()).poll(1, TimeUnit.SECONDS); + assertThat(received).isNotNull(); + assertThat(received.getPayload()).isEqualTo("Foo{name='Foo{name='Bar'}'}"); + } + + + @EnableBinding(Processor.class) + @EnableAutoConfiguration + @PropertySource("classpath:/org/springframework/cloud/stream/config/textplain/text-plain.properties") + public static class FooProcessor { + + @ServiceActivator(inputChannel = "input", outputChannel = "output") + public Foo consume(String foo) { + return new Foo(foo); + } + + } + + public static class Foo { + + private String name; + + public Foo(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Foo{name='" + name + "'}"; + } + + } + +} diff --git a/spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/textplain/text-plain.properties b/spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/textplain/text-plain.properties new file mode 100644 index 000000000..794d41aa5 --- /dev/null +++ b/spring-cloud-stream-integration-tests/src/test/resources/org/springframework/cloud/stream/config/textplain/text-plain.properties @@ -0,0 +1,2 @@ +spring.cloud.stream.bindings.input.content-type=text/plain +spring.cloud.stream.bindings.output.content-type=text/plain diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/CompositeMessageConverterFactory.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/CompositeMessageConverterFactory.java index d7055b1bb..375ee1928 100644 --- a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/CompositeMessageConverterFactory.java +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/CompositeMessageConverterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * 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. @@ -29,7 +29,6 @@ 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.messaging.converter.StringMessageConverter; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; @@ -79,8 +78,7 @@ public class CompositeMessageConverterFactory { this.converters.add(jsonMessageConverter); this.converters.add(new ByteArrayMessageConverter()); - - this.converters.add(new StringMessageConverter()); + this.converters.add(new ObjectStringMessageConverter()); this.converters.add(new JavaSerializationMessageConverter()); this.converters.add(new JsonUnmarshallingConverter(this.objectMapper)); } diff --git a/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ObjectStringMessageConverter.java b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ObjectStringMessageConverter.java new file mode 100644 index 000000000..67bc064a9 --- /dev/null +++ b/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/converter/ObjectStringMessageConverter.java @@ -0,0 +1,68 @@ +/* + * 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.nio.charset.Charset; + +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.AbstractMessageConverter; +import org.springframework.util.MimeType; + +/** + * A {@link org.springframework.messaging.converter.MessageConverter} to convert a + * non-String objects to a String, when expected content type is "text/plain". + * + * It only performs conversions to internal format and is a wrapper + * around {@link Object#toString()}. + * + * @author Marius Bogoevici + * + * @since 1.2 + */ +public class ObjectStringMessageConverter extends AbstractMessageConverter { + + public ObjectStringMessageConverter() { + super(new MimeType("text", "plain", Charset.forName("UTF-8"))); + } + + protected boolean supports(Class clazz) { + return true; + } + + protected Object convertFromInternal(Message message, Class targetClass, Object conversionHint) { + if (message.getPayload() != null) { + if (message.getPayload() instanceof byte[]) { + return new String((byte[]) message.getPayload(), Charset.forName("UTF-8")); + } + else { + return message.getPayload().toString(); + } + } + return null; + } + + protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) { + if (payload != null) { + return payload.toString(); + } + else { + return null; + } + } + +}