Rework the converter system

Fixes #505

The goals are twofold: to simplify the registration of new converters and
to add some consistency to the conversion process, and align them with the
way converters are used in generic Spring Messaging listeners, by describing
two possible transformations: inbound, message (including contentType)->targetClass,
and outbound payload+headers -> message. The idea is for the two
transformations to match the input and output directions of the bound channels.

List of changes:

- Create and configure input/output channels distinctly - reflected
  in the definitions of `BindableChannelFactory` and `MessageChannelConfigurer`;
- Replace AbstractFromMessageConverter with bidirectional converters;
- Use the channel direction (input/output) to determine whether
  `toMessage` or `fromMessage` will be invoked;
- Use contentType support from `AbstractMessageConverter` to map
  converters to mime types instead;

Making converters more robust

Addressing comments

Convert content type headers to String before serializing

Addressing further PR comments
This commit is contained in:
Marius Bogoevici
2016-07-19 15:28:42 -04:00
committed by Mark Fisher
parent bd92af4784
commit 55b9aa75dd
24 changed files with 282 additions and 934 deletions

View File

@@ -239,7 +239,7 @@ public abstract class AbstractBinderTests<B extends AbstractTestBinder<? extends
new CompositeMessageConverterFactory(null, null));
messageConverterConfigurer.setBeanFactory(applicationContext.getBeanFactory());
messageConverterConfigurer.afterPropertiesSet();
messageConverterConfigurer.configureMessageChannel(channel, channelName);
messageConverterConfigurer.configureOutputChannel(channel, channelName);
return channel;
}

View File

@@ -29,15 +29,17 @@ 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.binder.BinderFactory;
import org.springframework.cloud.stream.converter.AbstractFromMessageConverter;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.cloud.stream.test.binder.TestSupportBinder;
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.DefaultDatatypeChannelMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.MimeType;
@@ -59,19 +61,19 @@ public class CustomMessageConverterTests {
private BinderFactory binderFactory;
@Autowired
private List<AbstractFromMessageConverter> customMessageConverters;
private List<MessageConverter> customMessageConverters;
@Test
public void testCustomMessageConverter() throws Exception {
assertThat(customMessageConverters).hasSize(2);
assertThat(customMessageConverters).extracting("class").contains(FooToBarConverter.class,
BarToFooConverter.class);
assertThat(customMessageConverters).hasSize(3);
assertThat(customMessageConverters).extracting("class").contains(FooConverter.class,
BarConverter.class, DefaultDatatypeChannelMessageConverter.class);
testSource.output().send(MessageBuilder.withPayload(new Foo("hi")).build());
@SuppressWarnings("unchecked")
Message<String> received = (Message<String>) ((TestSupportBinder) binderFactory.getBinder(null))
.messageCollector().forChannel(testSource.output()).poll(1, TimeUnit.SECONDS);
Assert.assertThat(received, notNullValue());
assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo("test/bar");
assertThat(received.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeType.valueOf("test/foo"));
}
@EnableBinding(Source.class)
@@ -81,39 +83,34 @@ public class CustomMessageConverterTests {
public static class TestSource {
@Bean
public AbstractFromMessageConverter fooConverter() {
return new FooToBarConverter();
public MessageConverter fooConverter() {
return new FooConverter();
}
@Bean
public AbstractFromMessageConverter barConverter() {
return new BarToFooConverter();
public MessageConverter barConverter() {
return new BarConverter();
}
}
public static class FooToBarConverter extends AbstractFromMessageConverter {
public static class FooConverter extends AbstractMessageConverter {
public FooToBarConverter() {
super(MimeType.valueOf("test/bar"));
public FooConverter() {
super(MimeType.valueOf("test/foo"));
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class[] {Bar.class};
protected boolean supports(Class<?> clazz) {
return clazz.equals(Foo.class);
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] {Foo.class};
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
Object result = null;
try {
if (message.getPayload() instanceof Foo) {
Foo fooPayload = (Foo) message.getPayload();
result = new Bar(fooPayload.test);
if (payload instanceof Foo) {
Foo fooPayload = (Foo) payload;
result = fooPayload.test.getBytes();
}
}
catch (Exception e) {
@@ -124,29 +121,25 @@ public class CustomMessageConverterTests {
}
}
public static class BarToFooConverter extends AbstractFromMessageConverter {
public static class BarConverter extends AbstractMessageConverter {
public BarToFooConverter() {
super(MimeType.valueOf("test/foo"));
public BarConverter() {
super(MimeType.valueOf("test/bar"));
}
@Override
protected boolean supports(Class<?> clazz) {
return clazz.equals(Bar.class);
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class[] {Foo.class};
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] {Bar.class};
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
Object result = null;
try {
if (message.getPayload() instanceof Bar) {
Bar barPayload = (Bar) message.getPayload();
result = new Foo(barPayload.testing);
if (payload instanceof Bar) {
Bar barPayload = (Bar) payload;
result = barPayload.testing.getBytes();
}
}
catch (Exception e) {

View File

@@ -42,6 +42,8 @@ import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
@@ -129,7 +131,7 @@ public class StreamListenerTests {
Message<String> message = (Message<String>) collector.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
assertThat(message).isNotNull();
assertThat(message.getPayload()).isEqualTo("{\"qux\":\"barbar" + id + "\"}");
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo("application/json");
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class).includes(MimeTypeUtils.APPLICATION_JSON));
context.close();
}
@@ -224,7 +226,7 @@ public class StreamListenerTests {
Message<String> message = (Message<String>) collector.forChannel(processor.output()).poll(1, TimeUnit.SECONDS);
assertThat(message).isNotNull();
assertThat(message.getPayload()).isEqualTo("{\"qux\":\"barbar" + id + "\"}");
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE)).isEqualTo("application/json");
assertThat(message.getHeaders().get(MessageHeaders.CONTENT_TYPE, MimeType.class).includes(MimeTypeUtils.APPLICATION_JSON));
context.close();
}

View File

@@ -1,2 +1,2 @@
spring.cloud.stream.bindings.output.destination=configure1
spring.cloud.stream.bindings.output.contentType=test/bar
spring.cloud.stream.bindings.output.contentType=test/foo

View File

@@ -31,6 +31,7 @@ import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
/**
* {@link AbstractBinder} that serves as base class for {@link MessageChannel}
@@ -119,7 +120,7 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
/**
* Creates target destinations for outbound channels. The implementation
* is middleware-specific.
* @param name the name of the producer destination
* @param name the name of the producer destination
* @param properties producer properties
*/
protected abstract void createProducerDestinationIfNecessary(String name, P properties);
@@ -132,8 +133,8 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
* In order to be fully compliant, the {@link MessageHandler} of the binder
* must observe the following headers:
* <ul>
* <li>{@link BinderHeaders#PARTITION_HEADER} - indicates the target
* partition where the message must be sent</li>
* <li>{@link BinderHeaders#PARTITION_HEADER} - indicates the target
* partition where the message must be sent</li>
* </ul>
* <p>
* @param destination the name of the target destination
@@ -210,8 +211,8 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
/**
* Creates the middleware destination the consumer will start to consume data from.
* @param name the name of the destination
* @param group the consumer group
* @param name the name of the destination
* @param group the consumer group
* @param properties consumer properties
* @return reference to the consumer destination
*/
@@ -298,6 +299,15 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
MessageValues transformed = serializePayloadIfNecessary(message);
byte[] payload;
if (this.embedHeaders) {
Object contentType = transformed.get(MessageHeaders.CONTENT_TYPE);
// transform content type headers to String, so that they can be properly embedded in JSON
if (contentType instanceof MimeType) {
transformed.put(MessageHeaders.CONTENT_TYPE, contentType.toString());
}
Object originalContentType = transformed.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE);
if (originalContentType instanceof MimeType) {
transformed.put(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE, originalContentType.toString());
}
payload = AbstractMessageChannelBinder.this.embeddedHeadersMessageConverter.embedHeaders(transformed,
this.embeddedHeaders);
}
@@ -306,7 +316,7 @@ public abstract class AbstractMessageChannelBinder<C extends ConsumerProperties,
}
if (!this.embedHeaders && !AbstractMessageChannelBinder.this.supportsHeadersNatively) {
Object contentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
if (contentType != null && !contentType.equals(MediaType.APPLICATION_OCTET_STREAM_VALUE)) {
if (contentType != null && !contentType.toString().equals(MediaType.APPLICATION_OCTET_STREAM_VALUE)) {
this.logger.error(
"Raw mode supports only " + MediaType.APPLICATION_OCTET_STREAM_VALUE + " content type"
+ message.getPayload().getClass());

View File

@@ -21,18 +21,24 @@ import org.springframework.messaging.SubscribableChannel;
/**
* Defines methods to create/configure the {@link org.springframework.messaging.MessageChannel}s defined
* in {@link org.springframework.cloud.stream.annotation.EnableBinding}.
*
* @author Ilayaperumal Gopinathan
*/
public interface BindableChannelFactory {
/**
* Create a {@link SubscribableChannel} that will be bound via the message channel
* {@link org.springframework.cloud.stream.binder.Binder}.
*
* Create an input {@link SubscribableChannel} that will be bound via
* the message channel {@link org.springframework.cloud.stream.binder.Binder}.
* @param name name of the message channel
* @return subscribable message channel
*/
SubscribableChannel createSubscribableChannel(String name);
SubscribableChannel createInputChannel(String name);
/**
* Create an output {@link SubscribableChannel} that will be bound via
* the message channel {@link org.springframework.cloud.stream.binder.Binder}.
* @param name name of the message channel
* @return subscribable message channel
*/
SubscribableChannel createOutputChannel(String name);
}

View File

@@ -102,25 +102,26 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
@Override
public void afterPropertiesSet() throws Exception {
ReflectionUtils.doWithMethods(type, new ReflectionUtils.MethodCallback() {
Assert.notNull(BindableProxyFactory.this.channelFactory, "Channel Factory cannot be null");
ReflectionUtils.doWithMethods(this.type, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException {
Assert.notNull(channelFactory, "Channel Factory cannot be null");
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(input, method);
validateChannelType(method.getReturnType());
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
inputHolders.put(name, new ChannelHolder(channelFactory.createSubscribableChannel(name), true));
BindableProxyFactory.this.inputHolders.put(name, new ChannelHolder(
BindableProxyFactory.this.channelFactory.createInputChannel(name), true));
}
else {
inputHolders.put(name, new ChannelHolder(sharedChannel, false));
BindableProxyFactory.this.inputHolders.put(name, new ChannelHolder(sharedChannel, false));
}
}
}
});
ReflectionUtils.doWithMethods(type, new ReflectionUtils.MethodCallback() {
ReflectionUtils.doWithMethods(this.type, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException {
Output output = AnnotationUtils.findAnnotation(method, Output.class);
@@ -129,10 +130,11 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
validateChannelType(method.getReturnType());
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
outputHolders.put(name, new ChannelHolder(channelFactory.createSubscribableChannel(name), true));
BindableProxyFactory.this.outputHolders.put(name, new ChannelHolder(
BindableProxyFactory.this.channelFactory.createOutputChannel(name), true));
}
else {
outputHolders.put(name, new ChannelHolder(sharedChannel, false));
BindableProxyFactory.this.outputHolders.put(name, new ChannelHolder(sharedChannel, false));
}
}
}

View File

@@ -84,7 +84,7 @@ public class BinderAwareChannelResolver extends BeanFactoryMessageChannelDestina
boolean dynamicAllowed = ObjectUtils.isEmpty(dynamicDestinations)
|| ObjectUtils.containsElement(dynamicDestinations, channelName);
if (dynamicAllowed) {
channel = this.bindableChannelFactory.createSubscribableChannel(channelName);
channel = this.bindableChannelFactory.createOutputChannel(channelName);
this.beanFactory.registerSingleton(channelName, channel);
channel = (MessageChannel) this.beanFactory.initializeBean(channel, channelName);
Binding<MessageChannel> binding = this.channelBindingService.bindProducer(channel, channelName);

View File

@@ -33,9 +33,17 @@ public class CompositeMessageChannelConfigurer implements MessageChannelConfigur
}
@Override
public void configureMessageChannel(MessageChannel messageChannel, String channelName) {
for (MessageChannelConfigurer messageChannelConfigurer : messageChannelConfigurers) {
messageChannelConfigurer.configureMessageChannel(messageChannel, channelName);
public void configureInputChannel(MessageChannel messageChannel, String channelName) {
for (MessageChannelConfigurer messageChannelConfigurer : this.messageChannelConfigurers) {
messageChannelConfigurer.configureInputChannel(messageChannel, channelName);
}
}
@Override
public void configureOutputChannel(MessageChannel messageChannel, String channelName) {
for (MessageChannelConfigurer messageChannelConfigurer : this.messageChannelConfigurers) {
messageChannelConfigurer.configureOutputChannel(messageChannel, channelName);
}
}
}

View File

@@ -35,9 +35,17 @@ public class DefaultBindableChannelFactory implements BindableChannelFactory {
}
@Override
public SubscribableChannel createSubscribableChannel(String name) {
public SubscribableChannel createInputChannel(String name) {
SubscribableChannel subscribableChannel = new DirectChannel();
messageChannelConfigurer.configureMessageChannel(subscribableChannel, name);
this.messageChannelConfigurer.configureInputChannel(subscribableChannel, name);
return subscribableChannel;
}
@Override
public SubscribableChannel createOutputChannel(String name) {
SubscribableChannel subscribableChannel = new DirectChannel();
this.messageChannelConfigurer.configureOutputChannel(subscribableChannel, name);
return subscribableChannel;
}
}

View File

@@ -25,9 +25,16 @@ import org.springframework.messaging.MessageChannel;
public interface MessageChannelConfigurer {
/**
* Configure the given message channel.
* Configure the given input message channel.
* @param messageChannel the message channel
* @param channelName name of the message channel
*/
void configureMessageChannel(MessageChannel messageChannel, String channelName);
void configureInputChannel(MessageChannel messageChannel, String channelName);
/**
* Configure the given output message channel.
* @param messageChannel the message channel
* @param channelName name of the message channel
*/
void configureOutputChannel(MessageChannel messageChannel, String channelName);
}

View File

@@ -17,8 +17,6 @@
package org.springframework.cloud.stream.binding;
import java.util.Collections;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -39,8 +37,9 @@ 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.converter.SmartMessageConverter;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.tuple.Tuple;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.util.StringUtils;
@@ -81,92 +80,35 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
Assert.notNull(this.beanFactory, "Bean factory cannot be empty");
}
@Override
public void configureInputChannel(MessageChannel messageChannel, String channelName) {
configureMessageChannel(messageChannel, channelName, true);
}
@Override
public void configureOutputChannel(MessageChannel messageChannel, String channelName) {
configureMessageChannel(messageChannel, channelName, false);
}
/**
* Setup data-type and message converters for the given message channel.
* @param channel message channel to set the data-type and message converters
* @param channelName the channel name
*/
@Override
public void configureMessageChannel(MessageChannel channel, String channelName) {
private void configureMessageChannel(MessageChannel channel, String channelName, boolean input) {
Assert.isAssignable(AbstractMessageChannel.class, channel.getClass());
AbstractMessageChannel messageChannel = (AbstractMessageChannel) channel;
final BindingProperties bindingProperties = this.channelBindingServiceProperties.getBindingProperties(
channelName);
final String contentType = bindingProperties.getContentType();
if (bindingProperties.getProducer() != null && bindingProperties.getProducer().isPartitioned()) {
if (!input && bindingProperties.getProducer() != null && bindingProperties.getProducer().isPartitioned()) {
messageChannel.addInterceptor(new PartitioningInterceptor(bindingProperties));
}
if (StringUtils.hasText(contentType)) {
messageChannel.addInterceptor(new ContentTypeConvertingInterceptor(contentType));
messageChannel.addInterceptor(new ContentTypeConvertingInterceptor(contentType, input));
}
}
/**
* A {@link SmartMessageConverter} that delegates to another {@link SmartMessageConverter} for conversion.
*
* Will wrap the returning result of the conversion into a {@link Message} if it is not a {@link Message}
* instance already.
*/
private final class MessageWrappingMessageConverter implements SmartMessageConverter {
private final MimeType contentType;
private final SmartMessageConverter delegate;
private MessageWrappingMessageConverter(SmartMessageConverter delegate, MimeType contentType) {
Assert.notNull(delegate, "Delegate converter cannot be null");
Assert.notNull(contentType, "Content type cannot be null");
this.delegate = delegate;
this.contentType = contentType;
}
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass) {
Object converted = this.delegate.fromMessage(message, targetClass);
if (converted instanceof Message) {
return converted;
}
else {
return build(converted, message.getHeaders());
}
}
@Override
public Object fromMessage(Message<?> message, Class<?> targetClass, Object conversionHint) {
Object converted = this.delegate.fromMessage(message, targetClass, conversionHint);
if (converted == null || converted instanceof Message) {
return converted;
}
else {
return build(converted, message.getHeaders());
}
}
@Override
public Message<?> toMessage(Object payload, MessageHeaders headers) {
return this.delegate.toMessage(payload, headers);
}
@Override
public Message<?> toMessage(Object payload, MessageHeaders headers, Object conversionHint) {
return this.delegate.toMessage(payload, headers, conversionHint);
}
/**
* Convenience method to construct a converted message
* @param payload the converted payload
* @param headers the existing message headers
* @return the converted message
*/
protected Object build(Object payload, MessageHeaders headers) {
MimeType messageContentType = MessageConverterUtils.X_JAVA_OBJECT.equals(this.contentType) ?
MessageConverterUtils.javaObjectMimeType(payload.getClass()) : this.contentType;
return MessageConverterConfigurer.this.messageBuilderFactory.withPayload(payload).copyHeaders(headers)
.copyHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE,
messageContentType.toString()))
.build();
}
}
private final class ContentTypeConvertingInterceptor extends ChannelInterceptorAdapter {
@@ -174,41 +116,68 @@ public class MessageConverterConfigurer implements MessageChannelConfigurer, Bea
private final MimeType mimeType;
private ContentTypeConvertingInterceptor(String contentType) {
private final boolean input;
private final Class<?> klazz;
private final MessageConverter messageConverter;
private ContentTypeConvertingInterceptor(String contentType, boolean input) {
this.contentType = contentType;
this.mimeType = MessageConverterUtils.getMimeType(contentType);
this.input = input;
if (MessageConverterUtils.X_JAVA_OBJECT.equals(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);
}
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
Class<?>[] classes =
MessageConverterConfigurer.this.compositeMessageConverterFactory.supportedDataTypes(
this.mimeType);
MessageWrappingMessageConverter messageConverter =
new MessageWrappingMessageConverter(
MessageConverterConfigurer.this.compositeMessageConverterFactory
.getMessageConverterForType(this.mimeType), this.mimeType);
for (Class<?> aClass : classes) {
if (aClass.isAssignableFrom(message.getPayload().getClass())) {
Object contentTypeFromMessage = message.getHeaders().get(MessageHeaders.CONTENT_TYPE);
if (contentTypeFromMessage == null) {
return MessageConverterConfigurer.this.messageBuilderFactory
.fromMessage(message)
.setHeader(MessageHeaders.CONTENT_TYPE, this.contentType)
.build();
}
else {
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 {
Object converted = messageConverter.fromMessage(message, aClass);
if (converted != null) {
return (Message<?>) converted;
}
sentMessage = message;
}
}
throw new MessageConversionException("Cannot convert " + message + " to " + this.contentType);
else {
Object converted = this.input ? this.messageConverter.fromMessage(message, this.klazz)
: this.messageConverter.toMessage(message.getPayload(), message.getHeaders());
if (converted instanceof Message) {
sentMessage = (Message<?>) converted;
}
else {
sentMessage = MessageConverterConfigurer.this.messageBuilderFactory.withPayload(converted)
.copyHeaders(message.getHeaders()).setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE,
this.mimeType).build();
}
}
if (sentMessage == null) {
throw new MessageConversionException("Cannot convert " + message + " to " + this.contentType);
}
return sentMessage;
}
}

View File

@@ -48,7 +48,6 @@ import org.springframework.cloud.stream.binding.MessageConverterConfigurer;
import org.springframework.cloud.stream.binding.OutputBindingLifecycle;
import org.springframework.cloud.stream.binding.SingleChannelBindable;
import org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor;
import org.springframework.cloud.stream.converter.AbstractFromMessageConverter;
import org.springframework.cloud.stream.converter.CompositeMessageConverterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -60,6 +59,7 @@ import org.springframework.integration.config.IntegrationEvaluationContextFactor
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.json.JsonPropertyAccessor;
import org.springframework.messaging.MessageChannel;
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;
@@ -88,7 +88,7 @@ public class ChannelBindingServiceConfiguration {
* User defined custom message converters
*/
@Autowired(required = false)
private List<AbstractFromMessageConverter> customMessageConverters;
private List<MessageConverter> customMessageConverters;
@Bean
// This conditional is intentionally not in an autoconfig (usually a bad idea) because
@@ -160,7 +160,7 @@ public class ChannelBindingServiceConfiguration {
@Bean
public CompositeMessageConverterFactory compositeMessageConverterFactory() {
List<AbstractFromMessageConverter> messageConverters = new ArrayList<>();
List<MessageConverter> messageConverters = new ArrayList<>();
if (!CollectionUtils.isEmpty(this.customMessageConverters)) {
messageConverters.addAll(Collections.unmodifiableCollection(this.customMessageConverters));
}

View File

@@ -1,171 +0,0 @@
/*
* Copyright 2015-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.converter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
/**
* Base class for converters applied via Spring Integration 4.x data type channels.
*
* Extend this class to implement {@link org.springframework.messaging.converter.MessageConverter MessageConverters}
* used with custom Message conversion. Only {@link #fromMessage} is supported.
* @author David Turanski
* @author Ilayaperumal Gopinathan
* @author Marius Bogoevici
*/
public abstract class AbstractFromMessageConverter extends AbstractMessageConverter {
protected final Log logger = LogFactory.getLog(getClass());
protected final List<MimeType> targetMimeTypes;
/**
* Creates a converter that ignores content-type message headers
* @param targetMimeType the required target type
*/
protected AbstractFromMessageConverter(MimeType targetMimeType) {
this(new ArrayList<MimeType>(), targetMimeType);
}
protected AbstractFromMessageConverter(Collection<MimeType> targetMimeTypes) {
this(new ArrayList<MimeType>(), targetMimeTypes);
}
/**
* Creates a converter that handles one or more content-type message headers
* @param supportedSourceMimeTypes list of {@link MimeType} that may present in
* content-type header
* @param targetMimeType the required target type
*/
protected AbstractFromMessageConverter(Collection<MimeType> supportedSourceMimeTypes, MimeType targetMimeType) {
super(supportedSourceMimeTypes);
Assert.notNull(targetMimeType, "'targetMimeType' cannot be null");
this.targetMimeTypes = Collections.singletonList(targetMimeType);
}
/**
* Creates a converter that handles one or more content-type message headers and one
* or more target MIME types
* @param supportedSourceMimeTypes a list of supported content types
* @param targetMimeTypes a list of supported target types
*/
protected AbstractFromMessageConverter(Collection<MimeType> supportedSourceMimeTypes,
Collection<MimeType> targetMimeTypes) {
super(supportedSourceMimeTypes);
Assert.notNull(targetMimeTypes, "'targetMimeTypes' cannot be null");
this.targetMimeTypes = new ArrayList<>(targetMimeTypes);
}
/**
* Creates a converter that requires a specific content-type message header
* @param supportedSourceMimeType {@link MimeType} that must be present in
* content-type header
* @param targetMimeType the required target type
*/
protected AbstractFromMessageConverter(MimeType supportedSourceMimeType, MimeType targetMimeType) {
this(Collections.singletonList(supportedSourceMimeType), targetMimeType);
}
/**
* Creates a converter that requires a specific content-type message header and
* supports multiple target MIME types.
* @param supportedSourceMimeType {@link MimeType} that must be present in
* content-type header
* @param targetMimeTypes a list of supported target types
*/
protected AbstractFromMessageConverter(MimeType supportedSourceMimeType, Collection<MimeType> targetMimeTypes) {
this(Collections.singletonList(supportedSourceMimeType), targetMimeTypes);
}
/**
* Subclasses implement this to specify supported target types
* @return an array of supported classes or null if any target type is supported
*/
protected abstract Class<?>[] supportedTargetTypes();
/**
* Subclasses implement this to specify supported payload types
* @return an array of supported classes or null if any target type is supported
*/
protected abstract Class<?>[] supportedPayloadTypes();
protected boolean supportsPayloadType(Class<?> clazz) {
return supportsType(clazz, supportedPayloadTypes());
}
@Override
protected boolean supports(Class<?> clazz) {
return supportsType(clazz, supportedTargetTypes());
}
private boolean supportsType(Class<?> clazz, Class<?>[] supportedTypes) {
if (supportedTypes != null) {
for (Class<?> targetType : supportedTypes) {
if (ClassUtils.isAssignable(targetType, clazz)) {
return true;
}
}
return false;
}
return true;
}
@Override
protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
return super.canConvertFrom(message, targetClass) && supportsPayloadType(message.getPayload().getClass());
}
public boolean supportsTargetMimeType(MimeType mimeType) {
for (MimeType targetMimeType : targetMimeTypes) {
if (targetMimeType.includes(mimeType)) {
return true;
}
}
return false;
}
/**
* Not supported by default
*/
@Override
protected boolean canConvertTo(Object payload, MessageHeaders headers) {
return false;
}
/**
* Not supported by default
*/
@Override
public Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
throw new UnsupportedOperationException("'convertTo' not supported");
}
}

View File

@@ -1,85 +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.converter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.messaging.Message;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to convert from byte[] to String applying the Charset provided in
* the content-type header if any.
*
* @author David Turanski
* @author Ilayaperumal Gopinathan
*/
public class ByteArrayToStringMessageConverter extends AbstractFromMessageConverter {
private final static List<MimeType> targetMimeTypes = new ArrayList<MimeType>();
static {
targetMimeTypes.add(MessageConverterUtils.X_JAVA_OBJECT);
targetMimeTypes.add(MimeTypeUtils.TEXT_PLAIN);
}
public ByteArrayToStringMessageConverter() {
super(Arrays.asList(new MimeType[] { MimeTypeUtils.APPLICATION_OCTET_STREAM, MimeTypeUtils.TEXT_PLAIN }),
targetMimeTypes);
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] { String.class };
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] { byte[].class };
}
/**
* Don't need to manipulate message headers. Just return payload
*/
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
MimeType mimeType = getContentTypeResolver().resolve(message.getHeaders());
String converted = null;
if (mimeType == null || mimeType.getParameter("Charset") == null) {
converted = new String((byte[]) message.getPayload());
}
else {
String encoding = mimeType.getParameter("Charset");
if (encoding != null) {
try {
converted = new String((byte[]) message.getPayload(), encoding);
}
catch (UnsupportedEncodingException e) {
logger.error(e.getMessage(), e);
}
}
}
return converted;
}
}

View File

@@ -17,19 +17,21 @@
package org.springframework.cloud.stream.converter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
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.messaging.converter.StringMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
import org.springframework.util.ObjectUtils;
/**
@@ -40,18 +42,20 @@ import org.springframework.util.ObjectUtils;
*/
public class CompositeMessageConverterFactory {
private final Log log = LogFactory.getLog(CompositeMessageConverterFactory.class);
private final ObjectMapper objectMapper;
private final List<AbstractFromMessageConverter> converters;
private final List<MessageConverter> converters;
public CompositeMessageConverterFactory() {
this(Collections.<AbstractFromMessageConverter>emptyList(), new ObjectMapper());
this(Collections.<MessageConverter>emptyList(), new ObjectMapper());
}
/**
* @param customConverters a list of {@link AbstractFromMessageConverter}
* @param customConverters a list of {@link AbstractMessageConverter}
*/
public CompositeMessageConverterFactory(List<? extends AbstractFromMessageConverter> customConverters,
public CompositeMessageConverterFactory(List<? extends MessageConverter> customConverters,
ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
if (!CollectionUtils.isEmpty(customConverters)) {
@@ -65,56 +69,52 @@ public class CompositeMessageConverterFactory {
private void initDefaultConverters() {
this.converters.add(new JsonToTupleMessageConverter());
this.converters.add(new TupleToJsonMessageConverter(objectMapper));
this.converters.add(new JsonToPojoMessageConverter(objectMapper));
this.converters.add(new PojoToJsonMessageConverter(objectMapper));
this.converters.add(new ByteArrayToStringMessageConverter());
this.converters.add(new StringToByteArrayMessageConverter());
this.converters.add(new PojoToStringMessageConverter());
this.converters.add(new JavaToSerializedMessageConverter());
this.converters.add(new SerializedToJavaMessageConverter());
this.converters.add(new TupleJsonMessageConverter(this.objectMapper));
MappingJackson2MessageConverter jsonMessageConverter = new MappingJackson2MessageConverter();
jsonMessageConverter.setSerializedPayloadClass(String.class);
if (this.objectMapper != null) {
jsonMessageConverter.setObjectMapper(this.objectMapper);
}
this.converters.add(jsonMessageConverter);
this.converters.add(new ByteArrayMessageConverter());
this.converters.add(new StringMessageConverter());
this.converters.add(new JavaSerializationMessageConverter());
}
/**
* Creation method.
* @param targetMimeType the target MIME type
* @param mimeType the target MIME type
* @return a converter for the target MIME type
*/
public CompositeMessageConverter getMessageConverterForType(MimeType targetMimeType) {
List<MessageConverter> targetMimeTypeConverters = new ArrayList<MessageConverter>();
for (AbstractFromMessageConverter converter : converters) {
if (converter.supportsTargetMimeType(targetMimeType)) {
targetMimeTypeConverters.add(converter);
}
}
if (CollectionUtils.isEmpty(targetMimeTypeConverters)) {
throw new ConversionException("No message converter is registered for "
+ targetMimeType.toString());
}
return new CompositeMessageConverter(targetMimeTypeConverters);
}
public CompositeMessageConverter getMessageConverterForAllRegistered() {
return new CompositeMessageConverter(new ArrayList<MessageConverter>(converters));
}
public Class<?>[] supportedDataTypes(MimeType targetMimeType) {
Set<Class<?>> supportedDataTypes = new HashSet<>();
// Make sure to check if the target type is of explicit java object type.
if (MessageConverterUtils.X_JAVA_OBJECT.includes(targetMimeType)) {
supportedDataTypes.add(MessageConverterUtils.getJavaTypeForJavaObjectContentType(targetMimeType));
}
else {
for (AbstractFromMessageConverter converter : converters) {
if (converter.supportsTargetMimeType(targetMimeType)) {
Class<?>[] targetTypes = converter.supportedTargetTypes();
if (!ObjectUtils.isEmpty(targetTypes)) {
supportedDataTypes.addAll(Arrays.asList(targetTypes));
public CompositeMessageConverter getMessageConverterForType(MimeType mimeType) {
List<MessageConverter> converters = new ArrayList<>();
for (MessageConverter converter : this.converters) {
if (converter instanceof AbstractMessageConverter) {
for (MimeType type : ((AbstractMessageConverter) converter).getSupportedMimeTypes()) {
if (type.includes(mimeType)) {
converters.add(converter);
}
}
}
else {
if (this.log.isDebugEnabled()) {
this.log.debug("Ommitted " + converter + " of type " + converter.getClass().toString() +
" for '" + mimeType.toString() + "' as it is not an AbstractMessageConverter");
}
}
}
return supportedDataTypes.toArray(new Class<?>[supportedDataTypes.size()]);
if (CollectionUtils.isEmpty(converters)) {
throw new ConversionException("No message converter is registered for "
+ mimeType.toString());
}
return new CompositeMessageConverter(converters);
}
public CompositeMessageConverter getMessageConverterForAllRegistered() {
return new CompositeMessageConverter(new ArrayList<>(this.converters));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* 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.
@@ -16,44 +16,55 @@
package org.springframework.cloud.stream.converter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to convert from a POJO to byte[] with Java.io serialization if any.
*
* @author David Turanski
* @author Marius Bogoevici
*/
public class JavaToSerializedMessageConverter extends AbstractFromMessageConverter {
public class JavaSerializationMessageConverter extends AbstractMessageConverter {
public JavaToSerializedMessageConverter() {
super(MessageConverterUtils.X_JAVA_OBJECT, MessageConverterUtils.X_JAVA_SERIALIZED_OBJECT);
public JavaSerializationMessageConverter() {
super(Arrays.asList(MessageConverterUtils.X_JAVA_SERIALIZED_OBJECT));
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] { byte[].class };
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] { Serializable.class };
protected boolean supports(Class<?> clazz) {
return Serializable.class.isAssignableFrom(clazz);
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
if (!(message.getPayload() instanceof byte[])) {
return null;
}
try {
ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) (message.getPayload()));
return new ObjectInputStream(bis).readObject();
}
catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
return null;
}
@Override
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
new ObjectOutputStream(bos).writeObject(message.getPayload());
new ObjectOutputStream(bos).writeObject(payload);
}
catch (IOException e) {
logger.error(e.getMessage(), e);
this.logger.error(e.getMessage(), e);
return null;
}
return bos.toByteArray();

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2015-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.converter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.messaging.Message;
import org.springframework.util.MimeTypeUtils;
/**
*
* @author David Turanski
* @author Ilayaperumal Gopinathan
*/
public class JsonToPojoMessageConverter extends AbstractFromMessageConverter {
private final ObjectMapper objectMapper;
public JsonToPojoMessageConverter(ObjectMapper objectMapper) {
super(MimeTypeUtils.APPLICATION_JSON, MessageConverterUtils.X_JAVA_OBJECT);
this.objectMapper = (objectMapper != null) ? objectMapper : new ObjectMapper();
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] {String.class, byte[].class};
}
@Override
protected Class<?>[] supportedTargetTypes() {
return null; // any type
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
Object result = null;
try {
Object payload = message.getPayload();
if (payload instanceof byte[]) {
result = objectMapper.readValue((byte[]) payload, targetClass);
}
else if (payload instanceof String) {
result = objectMapper.readValue((String) payload, targetClass);
}
}
catch (Exception e) {
logger.error(e.getMessage(), e);
return null;
}
return result;
}
}

View File

@@ -1,69 +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.converter;
import java.util.ArrayList;
import java.util.List;
import org.springframework.messaging.Message;
import org.springframework.tuple.Tuple;
import org.springframework.tuple.TupleBuilder;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to convert from a JSON (byte[] or String) to a {@link Tuple}.
*
* @author David Turanski
*/
public class JsonToTupleMessageConverter extends AbstractFromMessageConverter {
private final static List<MimeType> targetMimeTypes = new ArrayList<MimeType>();
static {
targetMimeTypes.add(MessageConverterUtils.X_SPRING_TUPLE);
targetMimeTypes.add(MessageConverterUtils.X_JAVA_OBJECT);
}
public JsonToTupleMessageConverter() {
super(MimeTypeUtils.APPLICATION_JSON, targetMimeTypes);
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] { Tuple.class };
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] { byte[].class, String.class };
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
String source = null;
if (message.getPayload() instanceof byte[]) {
source = new String((byte[]) message.getPayload());
}
else {
source = (String) message.getPayload();
}
return TupleBuilder.fromString(source);
}
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2015-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.converter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.util.MimeTypeUtils;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to convert a Java object to a JSON String
*
* @author David Turanski
* @author David Liu
* @author Ilayaperumal Gopinathan
*/
public class PojoToJsonMessageConverter extends AbstractFromMessageConverter {
private final ObjectMapper objectMapper;
@Value("${typeconversion.json.prettyPrint:false}")
private volatile boolean prettyPrint;
public PojoToJsonMessageConverter(ObjectMapper objectMapper) {
super(MimeTypeUtils.APPLICATION_JSON);
this.objectMapper = (objectMapper != null) ? objectMapper : new ObjectMapper();
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] {String.class};
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return null;
}
public void setPrettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
Object result;
try {
if (prettyPrint) {
result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(message.getPayload());
}
else {
result = objectMapper.writeValueAsString(message.getPayload());
}
}
catch (JsonProcessingException e) {
logger.error(e.getMessage(), e);
return null;
}
return result;
}
}

View File

@@ -1,62 +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.converter;
import org.springframework.messaging.Message;
import org.springframework.tuple.Tuple;
import org.springframework.tuple.TupleBuilder;
import org.springframework.util.MimeTypeUtils;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to convert a Java object to a String using toString()
*
* @author David Turanski
* @author Ilayaperumal Gopinathan
*/
public class PojoToStringMessageConverter extends AbstractFromMessageConverter {
public PojoToStringMessageConverter() {
super(MimeTypeUtils.TEXT_PLAIN);
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] { String.class };
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return null;
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
String payloadString = null;
if (message.getPayload() instanceof Tuple) {
TupleBuilder builder = TupleBuilder.tuple();
builder.putAll((Tuple) message.getPayload());
payloadString = builder.build().toString();
}
else {
payloadString = message.getPayload().toString();
}
return payloadString;
}
}

View File

@@ -1,62 +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.converter;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import org.springframework.messaging.Message;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to deserialize {@link Serializable} Java objects.
*
* @author David Turanski
*/
public class SerializedToJavaMessageConverter extends AbstractFromMessageConverter {
public SerializedToJavaMessageConverter() {
super(MessageConverterUtils.X_JAVA_SERIALIZED_OBJECT, MessageConverterUtils.X_JAVA_OBJECT);
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] { Serializable.class };
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] { byte[].class };
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) (message.getPayload()));
Object result = null;
try {
result = new ObjectInputStream(bis).readObject();
}
catch (Exception e) {
logger.error(e.getMessage(), e);
return null;
}
return result;
}
}

View File

@@ -1,81 +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.converter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.messaging.Message;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to convert a String to a byte[], applying the provided Charset in
* the content-type header if any.
*
* @author David Turanski
* @author Ilayaperumal Gopinathan
*/
public class StringToByteArrayMessageConverter extends AbstractFromMessageConverter {
private final static List<MimeType> targetMimeTypes = new ArrayList<MimeType>();
static {
targetMimeTypes.add(MimeTypeUtils.APPLICATION_OCTET_STREAM);
}
public StringToByteArrayMessageConverter() {
super(targetMimeTypes);
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] { byte[].class };
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] { String.class };
}
/**
* Don't need to manipulate message headers. Just return the payload
*/
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
MimeType mimeType = getContentTypeResolver().resolve(message.getHeaders());
byte[] converted = null;
if (mimeType == null || mimeType.getParameter("Charset") == null) {
converted = ((String) message.getPayload()).getBytes();
}
else {
String encoding = mimeType.getParameter("Charset");
if (encoding != null) {
try {
converted = ((String) message.getPayload()).getBytes(encoding);
}
catch (UnsupportedEncodingException e) {
logger.error(e.getMessage(), e);
}
}
}
return converted;
}
}

View File

@@ -17,23 +17,24 @@
package org.springframework.cloud.stream.converter;
import java.io.IOException;
import java.nio.charset.Charset;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.AbstractMessageConverter;
import org.springframework.tuple.Tuple;
import org.springframework.util.MimeTypeUtils;
import org.springframework.tuple.TupleBuilder;
/**
* A {@link org.springframework.messaging.converter.MessageConverter}
* to convert a {@link Tuple} to a JSON String
*
* @author David Turanski
* @author Ilayaperumal Gopinathan
*/
public class TupleToJsonMessageConverter extends AbstractFromMessageConverter {
public class TupleJsonMessageConverter extends AbstractMessageConverter {
@Value("${typeconversion.json.prettyPrint:false}")
private volatile boolean prettyPrint;
@@ -44,32 +45,27 @@ public class TupleToJsonMessageConverter extends AbstractFromMessageConverter {
this.prettyPrint = prettyPrint;
}
public TupleToJsonMessageConverter(ObjectMapper objectMapper) {
super(MimeTypeUtils.APPLICATION_JSON);
public TupleJsonMessageConverter(ObjectMapper objectMapper) {
super(MessageConverterUtils.X_SPRING_TUPLE);
this.objectMapper = (objectMapper != null) ? objectMapper : new ObjectMapper();
}
@Override
protected Class<?>[] supportedTargetTypes() {
return new Class<?>[] { String.class };
protected boolean supports(Class<?> clazz) {
return Tuple.class.isAssignableFrom(clazz);
}
@Override
protected Class<?>[] supportedPayloadTypes() {
return new Class<?>[] { Tuple.class };
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
Tuple t = (Tuple) message.getPayload();
protected Object convertToInternal(Object payload, MessageHeaders headers, Object conversionHint) {
Tuple t = (Tuple) payload;
String json;
if (prettyPrint) {
if (this.prettyPrint) {
try {
Object tmp = objectMapper.readValue(t.toString(), Object.class);
json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(tmp);
Object tmp = this.objectMapper.readValue(t.toString(), Object.class);
json = this.objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(tmp);
}
catch (IOException e) {
logger.error(e.getMessage(), e);
this.logger.error(e.getMessage(), e);
return null;
}
}
@@ -79,4 +75,16 @@ public class TupleToJsonMessageConverter extends AbstractFromMessageConverter {
return json;
}
@Override
public Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
String source;
if (message.getPayload() instanceof byte[]) {
source = new String((byte[]) message.getPayload(), Charset.forName("UTF-8"));
}
else {
source = message.getPayload().toString();
}
return TupleBuilder.fromString(source);
}
}