INT-2809: Add/Extract JSON JavaType Headers

* Add `AmqpHeaders` headers
* Add `JavaType` headers mapping to `DefaultAmqpHeaderMapper`
* Add ability to Json Transformers to add/extract `JavaType` headers
* Make `JsonToObjectTransformer#targetClass` as non-required and do fallback
to extract type from `JavaType` headers
* Add `JavaType` extraction in the `JsonObjectMapper` implementors

JIRA: https://jira.springsource.org/browse/INT-2809

INT-2809: Polishing and refactoring

* Introduce `JsonHeaders`, `AbstractJacksonJsonObjectMapper`
* Move `TestPerson` and `TestAddress` to package level
* Now `JsonToObjectTransformer` supports not only `String` payload
* Remove json headers after transformation in the `JsonToObjectTransformer`

INT-2809 Minor Polishing

INT-2809 Docs and What's New
This commit is contained in:
Artem Bilan
2013-10-11 17:18:01 +03:00
committed by Gary Russell
parent c389604c2e
commit 4dd95c41ea
23 changed files with 845 additions and 413 deletions

View File

@@ -26,6 +26,7 @@ import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.amqp.AmqpHeaders;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.integration.mapping.AbstractHeaderMapper;
import org.springframework.util.StringUtils;
@@ -45,6 +46,8 @@ import org.springframework.util.StringUtils;
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.1
*/
public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessageProperties> implements AmqpHeaderMapper {
@@ -70,6 +73,9 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
STANDARD_HEADER_NAMES.add(AmqpHeaders.TIMESTAMP);
STANDARD_HEADER_NAMES.add(AmqpHeaders.TYPE);
STANDARD_HEADER_NAMES.add(AmqpHeaders.USER_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.TYPE_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.CONTENT_TYPE_ID);
STANDARD_HEADER_NAMES.add(JsonHeaders.KEY_TYPE_ID);
STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_CORRELATION);
STANDARD_HEADER_NAMES.add(AmqpHeaders.SPRING_REPLY_TO_STACK);
}
@@ -157,6 +163,14 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
if (StringUtils.hasText(userId)) {
headers.put(AmqpHeaders.USER_ID, userId);
}
for (String jsonHeader : JsonHeaders.HEADERS) {
Object value = amqpMessageProperties.getHeaders().get(jsonHeader.replaceFirst(JsonHeaders.PREFIX, ""));
if (value instanceof String && StringUtils.hasText((String) value)) {
headers.put(jsonHeader, value);
}
}
Object replyCorrelation = amqpMessageProperties.getHeaders().get(AmqpHeaders.STACKED_CORRELATION_HEADER);
if (replyCorrelation instanceof String) {
if (StringUtils.hasText((String) replyCorrelation)) {
@@ -186,6 +200,7 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
Map<String, Object> headers = amqpMessageProperties.getHeaders();
headers.remove(AmqpHeaders.STACKED_CORRELATION_HEADER);
headers.remove(AmqpHeaders.STACKED_REPLY_TO_HEADER);
return headers;
}
@@ -272,6 +287,18 @@ public class DefaultAmqpHeaderMapper extends AbstractHeaderMapper<MessagePropert
if (StringUtils.hasText(userId)) {
amqpMessageProperties.setUserId(userId);
}
for (String jsonHeader : JsonHeaders.HEADERS) {
Object value = getHeaderIfAvailable(headers, jsonHeader, Object.class);
if (value != null) {
headers.remove(jsonHeader);
if (value instanceof Class<?>) {
value = ((Class<?>) value).getName();
}
amqpMessageProperties.setHeader(jsonHeader.replaceFirst(JsonHeaders.PREFIX, ""), value.toString());
}
}
String replyCorrelation = getHeaderIfAvailable(headers, AmqpHeaders.SPRING_REPLY_CORRELATION, String.class);
if (StringUtils.hasLength(replyCorrelation)) {
amqpMessageProperties.setHeader("spring_reply_correlation", replyCorrelation);

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2013 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.integration.amqp.inbound;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.JsonMessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.integration.Message;
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.integration.json.ObjectToJsonTransformer;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.transformer.Transformer;
import com.rabbitmq.client.Channel;
/**
* @author Artem Bilan
* @since 3.0
*/
public class InboundEndpointTests {
@Test
public void testInt2809JavaTypePropertiesToAmqp() {
Connection connection = mock(Connection.class);
doAnswer(new Answer<Channel>() {
public Channel answer(InvocationOnMock invocation) throws Throwable {
return mock(Channel.class);
}
}).when(connection).createChannel(anyBoolean());
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
when(connectionFactory.createConnection()).thenReturn(connection);
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container);
adapter.setMessageConverter(new JsonMessageConverter());
PollableChannel channel = new QueueChannel();
adapter.setOutputChannel(channel);
adapter.afterPropertiesSet();
Object payload = new Foo("bar1");
Transformer objectToJsonTransformer = new ObjectToJsonTransformer();
Message<?> jsonMessage = objectToJsonTransformer.transform(new GenericMessage<Object>(payload));
MessageProperties amqpMessageProperties = new MessageProperties();
org.springframework.amqp.core.Message amqpMessage =
new SimpleMessageConverter().toMessage(jsonMessage.getPayload(), amqpMessageProperties);
new DefaultAmqpHeaderMapper().fromHeadersToRequest(jsonMessage.getHeaders(), amqpMessageProperties);
MessageListener listener = (MessageListener) container.getMessageListener();
listener.onMessage(amqpMessage);
Message<?> result = channel.receive(1000);
assertEquals(payload, result.getPayload());
}
@Test
public void testInt2809JavaTypePropertiesFromAmqp() {
Connection connection = mock(Connection.class);
doAnswer(new Answer<Channel>() {
public Channel answer(InvocationOnMock invocation) throws Throwable {
return mock(Channel.class);
}
}).when(connection).createChannel(anyBoolean());
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
when(connectionFactory.createConnection()).thenReturn(connection);
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container);
PollableChannel channel = new QueueChannel();
adapter.setOutputChannel(channel);
adapter.afterPropertiesSet();
Object payload = new Foo("bar1");
MessageProperties amqpMessageProperties = new MessageProperties();
org.springframework.amqp.core.Message amqpMessage = new JsonMessageConverter().toMessage(payload, amqpMessageProperties);
MessageListener listener = (MessageListener) container.getMessageListener();
listener.onMessage(amqpMessage);
Message<?> receive = channel.receive(1000);
Message<?> result = new JsonToObjectTransformer().transform(receive);
assertEquals(payload, result.getPayload());
}
public static class Foo {
private String bar;
public Foo() {
}
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Foo foo = (Foo) o;
if (bar != null ? !bar.equals(foo.bar) : foo.bar != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return bar != null ? bar.hashCode() : 0;
}
}
}

View File

@@ -16,12 +16,12 @@
package org.springframework.integration.config.xml;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.json.JsonToObjectTransformer;
import org.springframework.util.StringUtils;
/**
* @author Mark Fisher
@@ -39,7 +39,9 @@ public class JsonToObjectTransformerParser extends AbstractTransformerParser {
protected void parseTransformer(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String type = element.getAttribute("type");
String objectMapper = element.getAttribute("object-mapper");
builder.addConstructorArgValue(type);
if (StringUtils.hasText(type)) {
builder.addConstructorArgValue(type);
}
if (StringUtils.hasText(objectMapper)) {
builder.addConstructorArgReference(objectMapper);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2013 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.integration.json;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* Pre-defined names and prefixes to be used for setting and/or retrieving JSON
* entries from/to Message Headers and other adapter, e.g. AMQP.
*
* @author Artem Bilan
* @since 3.0
*/
public class JsonHeaders {
public static final String PREFIX = "json";
public static final String TYPE_ID = PREFIX + "__TypeId__";
public static final String CONTENT_TYPE_ID = PREFIX + "__ContentTypeId__";
public static final String KEY_TYPE_ID = PREFIX + "__KeyTypeId__";
public static final Collection<String> HEADERS =
Collections.unmodifiableList(Arrays.asList(TYPE_ID, CONTENT_TYPE_ID, KEY_TYPE_ID));
}

View File

@@ -16,10 +16,13 @@
package org.springframework.integration.json;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.json.JacksonJsonObjectMapper;
import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.integration.transformer.AbstractPayloadTransformer;
import org.springframework.integration.transformer.AbstractTransformer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -36,13 +39,17 @@ import org.springframework.util.ClassUtils;
* @see JacksonJsonObjectMapperProvider
* @since 2.0
*/
public class JsonToObjectTransformer<T> extends AbstractPayloadTransformer<String, T> {
public class JsonToObjectTransformer extends AbstractTransformer implements BeanClassLoaderAware {
private final Class<T> targetClass;
private final Class<?> targetClass;
private final JsonObjectMapper<?> jsonObjectMapper;
public JsonToObjectTransformer(Class<T> targetClass) {
public JsonToObjectTransformer() {
this((Class<?>) null);
}
public JsonToObjectTransformer(Class<?> targetClass) {
this(targetClass, null);
}
@@ -52,8 +59,7 @@ public class JsonToObjectTransformer<T> extends AbstractPayloadTransformer<Strin
* @deprecated in favor of {@link #JsonToObjectTransformer(Class, JsonObjectMapper)}
*/
@Deprecated
public JsonToObjectTransformer(Class<T> targetClass, Object objectMapper) throws ClassNotFoundException {
Assert.notNull(targetClass, "targetClass must not be null");
public JsonToObjectTransformer(Class<?> targetClass, Object objectMapper) throws ClassNotFoundException {
this.targetClass = targetClass;
if (objectMapper != null) {
try {
@@ -70,15 +76,34 @@ public class JsonToObjectTransformer<T> extends AbstractPayloadTransformer<Strin
}
}
public JsonToObjectTransformer(Class<T> targetClass, JsonObjectMapper<?> jsonObjectMapper) {
Assert.notNull(targetClass, "targetClass must not be null");
public JsonToObjectTransformer(JsonObjectMapper<?> jsonObjectMapper) {
this(null, jsonObjectMapper);
}
public JsonToObjectTransformer(Class<?> targetClass, JsonObjectMapper<?> jsonObjectMapper) {
this.targetClass = targetClass;
this.jsonObjectMapper = (jsonObjectMapper != null) ? jsonObjectMapper : JacksonJsonObjectMapperProvider.newInstance();
}
@Override
protected T transformPayload(String payload) throws Exception {
return this.jsonObjectMapper.fromJson(payload, this.targetClass);
public void setBeanClassLoader(ClassLoader classLoader) {
if (this.jsonObjectMapper instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) this.jsonObjectMapper).setBeanClassLoader(classLoader);
}
}
@Override
protected Object doTransform(Message<?> message) throws Exception {
if (this.targetClass != null) {
return this.jsonObjectMapper.fromJson(message.getPayload(), this.targetClass);
}
else {
Object result = this.jsonObjectMapper.fromJson(message.getPayload(), message.getHeaders());
MessageBuilder<Object> messageBuilder = MessageBuilder.withPayload(result)
.copyHeaders(message.getHeaders())
.removeHeaders(JsonHeaders.HEADERS.toArray(new String[3]));
return messageBuilder.build();
}
}
}

View File

@@ -109,6 +109,9 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
else if (StringUtils.hasLength(this.contentType)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
}
this.jsonObjectMapper.populateJavaTypes(headers, message.getPayload().getClass());
messageBuilder.copyHeaders(headers);
return messageBuilder.build();
}

View File

@@ -26,7 +26,9 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -248,7 +250,7 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
if (!type.isAssignableFrom(value.getClass())) {
if (logger.isWarnEnabled()) {
logger.warn("skipping header '" + name + "' since it is not of expected type [" + type + "], it is [" +
value.getClass() + "]");
value.getClass() + "]");
}
return null;
}
@@ -271,9 +273,14 @@ public abstract class AbstractHeaderMapper<T> implements RequestReplyHeaderMappe
*/
private String addPrefixIfNecessary(String prefix, String propertyName) {
String headerName = propertyName;
if (StringUtils.hasText(prefix) && !headerName.startsWith(prefix) && !headerName.equals(MessageHeaders.CONTENT_TYPE)) {
if (StringUtils.hasText(prefix) && !headerName.startsWith(prefix) &&
!headerName.equals(MessageHeaders.CONTENT_TYPE) &&
(!JsonHeaders.HEADERS.contains(headerName) || !JsonHeaders.HEADERS.contains(JsonHeaders.PREFIX + headerName))) {
headerName = prefix + propertyName;
}
if (JsonHeaders.HEADERS.contains(JsonHeaders.PREFIX + headerName)) {
headerName = JsonHeaders.PREFIX + headerName;
}
return headerName;
}

View File

@@ -16,6 +16,8 @@
package org.springframework.integration.support.json;
import java.lang.reflect.Type;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
@@ -68,7 +70,7 @@ abstract class AbstractJacksonJsonMessageParser<P> implements JsonInboundMessage
Class<?> headerType = this.messageMapper.getHeaderTypes().containsKey(headerName) ?
this.messageMapper.getHeaderTypes().get(headerName) : Object.class;
try {
return this.objectMapper.fromJson(parser, headerType);
return this.objectMapper.fromJson(parser, (Type) headerType);
}
catch (Exception e) {
throw new IllegalArgumentException("Mapping header '" + headerName + "' of JSON message '" +

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2013 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.integration.support.json;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.util.ClassUtils;
/**
* Base class for Jackson {@link JsonObjectMapper} implementations.
*
* @author Artem Bilan
* @since 3.0
*/
public abstract class AbstractJacksonJsonObjectMapper<P, J> implements JsonObjectMapper<P>, BeanClassLoaderAware {
protected static final Collection<Class<?>> supportedJsonTypes =
Arrays.<Class<?>> asList(String.class, byte[].class, File.class, URL.class, InputStream.class, Reader.class);
private volatile ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public <T> T fromJson(Object json, Class<T> valueType) throws Exception {
return this.fromJson(json, this.constructType(valueType));
}
@Override
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws Exception {
J javaType = this.extractJavaType(javaTypes);
return this.fromJson(json, javaType);
}
protected J createJavaType(Map<String, Object> javaTypes, String javaTypeKey) throws Exception {
Object classValue = javaTypes.get(javaTypeKey);
if (classValue == null) {
throw new IllegalArgumentException("Could not resolve '" + javaTypeKey + "' in 'javaTypes'.");
}
else {
Class<?> aClass = null;
if (classValue instanceof Class<?>) {
aClass = (Class<?>) classValue;
}
else {
aClass = ClassUtils.forName(classValue.toString(), this.classLoader);
}
return this.constructType(aClass);
}
}
protected abstract <T> T fromJson(Object json, J type) throws Exception;
protected abstract J extractJavaType(Map<String, Object> javaTypes) throws Exception;
protected abstract J constructType(Type type);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2013 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,15 +16,22 @@
package org.springframework.integration.support.json;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.Assert;
/**
* Jackson 2 JSON-processor (@link https://github.com/FasterXML) {@linkplain JsonObjectMapper} implementation.
* Delegates <code>toJson</code> and <code>fromJson</code>
@@ -33,7 +40,7 @@ import org.springframework.util.Assert;
* @author Artem Bilan
* @since 3.0
*/
public class Jackson2JsonObjectMapper implements JsonObjectMapper<JsonParser> {
public class Jackson2JsonObjectMapper extends AbstractJacksonJsonObjectMapper<JsonParser, JavaType> {
private final ObjectMapper objectMapper;
@@ -46,6 +53,7 @@ public class Jackson2JsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper = objectMapper;
}
@Override
public String toJson(Object value) throws Exception {
return this.objectMapper.writeValueAsString(value);
}
@@ -55,18 +63,72 @@ public class Jackson2JsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper.writeValue(writer, value);
}
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
protected <T> T fromJson(Object json, JavaType type) throws Exception {
if (json instanceof String) {
return this.objectMapper.readValue((String) json, type);
}
else if(json instanceof byte[]) {
return this.objectMapper.readValue((byte[]) json, type);
}
else if (json instanceof File) {
return this.objectMapper.readValue((File) json, type);
}
else if (json instanceof URL) {
return this.objectMapper.readValue((URL) json, type);
}
else if (json instanceof InputStream) {
return this.objectMapper.readValue((InputStream) json, type);
}
else if (json instanceof Reader) {
return this.objectMapper.readValue((Reader) json, type);
}
else {
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes);
}
}
@Override
public <T> T fromJson(JsonParser parser, Type valueType) throws Exception {
return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType));
return this.objectMapper.readValue(parser, this.constructType(valueType));
}
@Override
public void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass) {
JavaType javaType = this.objectMapper.constructType(sourceClass);
map.put(JsonHeaders.TYPE_ID, javaType.getRawClass());
if (javaType.isContainerType() && !javaType.isArrayType()) {
map.put(JsonHeaders.CONTENT_TYPE_ID, javaType.getContentType().getRawClass());
}
if (javaType.getKeyType() != null) {
map.put(JsonHeaders.KEY_TYPE_ID, javaType.getKeyType().getRawClass());
}
}
@Override
@SuppressWarnings({ "unchecked" })
protected JavaType extractJavaType(Map<String, Object> javaTypes) throws Exception {
JavaType classType = this.createJavaType(javaTypes, JsonHeaders.TYPE_ID);
if (!classType.isContainerType() || classType.isArrayType()) {
return classType;
}
JavaType contentClassType = this.createJavaType(javaTypes, JsonHeaders.CONTENT_TYPE_ID);
if (classType.getKeyType() == null) {
return this.objectMapper.getTypeFactory()
.constructCollectionType((Class<? extends Collection<?>>) classType.getRawClass(), contentClassType);
}
JavaType keyClassType = this.createJavaType(javaTypes, JsonHeaders.KEY_TYPE_ID);
return this.objectMapper.getTypeFactory()
.constructMapType((Class<? extends Map<?, ?>>) classType.getRawClass(), keyClassType, contentClassType);
}
@Override
protected JavaType constructType(Type type) {
return this.objectMapper.constructType(type);
}
}

View File

@@ -16,13 +16,20 @@
package org.springframework.integration.support.json;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.JavaType;
import org.springframework.integration.json.JsonHeaders;
import org.springframework.util.Assert;
/**
@@ -33,7 +40,7 @@ import org.springframework.util.Assert;
* @author Artem Bilan
* @since 3.0
*/
public class JacksonJsonObjectMapper implements JsonObjectMapper<JsonParser> {
public class JacksonJsonObjectMapper extends AbstractJacksonJsonObjectMapper<JsonParser, JavaType> {
private final ObjectMapper objectMapper;
@@ -46,6 +53,7 @@ public class JacksonJsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper = objectMapper;
}
@Override
public String toJson(Object value) throws Exception {
return this.objectMapper.writeValueAsString(value);
}
@@ -55,18 +63,72 @@ public class JacksonJsonObjectMapper implements JsonObjectMapper<JsonParser> {
this.objectMapper.writeValue(writer, value);
}
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(JsonParser parser, Type valueType) throws Exception {
return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType));
return this.objectMapper.readValue(parser, this.constructType(valueType));
}
@Override
protected <T> T fromJson(Object json, JavaType type) throws Exception {
if (json instanceof String) {
return this.objectMapper.readValue((String) json, type);
}
else if(json instanceof byte[]) {
return this.objectMapper.readValue((byte[]) json, type);
}
else if (json instanceof File) {
return this.objectMapper.readValue((File) json, type);
}
else if (json instanceof URL) {
return this.objectMapper.readValue((URL) json, type);
}
else if (json instanceof InputStream) {
return this.objectMapper.readValue((InputStream) json, type);
}
else if (json instanceof Reader) {
return this.objectMapper.readValue((Reader) json, type);
}
else {
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes);
}
}
@Override
public void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass) {
JavaType javaType = this.constructType(sourceClass);
map.put(JsonHeaders.TYPE_ID, javaType.getRawClass());
if (javaType.isContainerType() && !javaType.isArrayType()) {
map.put(JsonHeaders.CONTENT_TYPE_ID, javaType.getContentType().getRawClass());
}
if (javaType.getKeyType() != null) {
map.put(JsonHeaders.KEY_TYPE_ID, javaType.getKeyType().getRawClass());
}
}
@Override
protected JavaType constructType(Type type) {
return this.objectMapper.constructType(type);
}
@Override
@SuppressWarnings({ "unchecked" })
protected JavaType extractJavaType(Map<String, Object> javaTypes) throws Exception {
JavaType classType = this.createJavaType(javaTypes, JsonHeaders.TYPE_ID);
if (!classType.isContainerType() || classType.isArrayType()) {
return classType;
}
JavaType contentClassType = this.createJavaType(javaTypes, JsonHeaders.CONTENT_TYPE_ID);
if (classType.getKeyType() == null) {
return this.objectMapper.getTypeFactory()
.constructCollectionType((Class<? extends Collection<?>>) classType.getRawClass(), contentClassType);
}
JavaType keyClassType = this.createJavaType(javaTypes, JsonHeaders.KEY_TYPE_ID);
return this.objectMapper.getTypeFactory()
.constructMapType((Class<? extends Map<?, ?>>) classType.getRawClass(), keyClassType, contentClassType);
}
}

View File

@@ -16,9 +16,9 @@
package org.springframework.integration.support.json;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.Map;
/**
* Strategy interface to convert an Object to/from the JSON representation.
@@ -33,10 +33,11 @@ public interface JsonObjectMapper<P> {
void toJson(Object value, Writer writer) throws Exception;
<T> T fromJson(String json, Class<T> valueType) throws Exception;
<T> T fromJson(Object json, Class<T> valueType) throws Exception;
<T> T fromJson(Reader json, Class<T> valueType) throws Exception;
<T> T fromJson(Object json, Map<String, Object> javaTypes) throws Exception;
<T> T fromJson(P parser, Type valueType) throws Exception;
void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass);
}

View File

@@ -16,9 +16,9 @@
package org.springframework.integration.support.json;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.Map;
/**
* Simple {@linkplain JsonObjectMapper} adapter implementation, if there is no need
@@ -39,12 +39,7 @@ public abstract class JsonObjectMapperAdapter<P> implements JsonObjectMapper<P>
}
@Override
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return null;
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
public <T> T fromJson(Object json, Class<T> valueType) throws Exception {
return null;
}
@@ -53,4 +48,13 @@ public abstract class JsonObjectMapperAdapter<P> implements JsonObjectMapper<P>
return null;
}
@Override
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws Exception {
return null;
}
@Override
public void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass) {
}
}

View File

@@ -8,16 +8,16 @@
http://www.springframework.org/schema/integration/spring-integration.xsd">
<json-to-object-transformer id="defaultJacksonMapperTransformer" input-channel="defaultObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"/>
type="org.springframework.integration.json.TestPerson"/>
<json-to-object-transformer id="customJacksonMapperTransformer" input-channel="customObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"
type="org.springframework.integration.json.TestPerson"
object-mapper="customObjectMapper"/>
<beans:bean id="customObjectMapper" class="org.springframework.integration.json.JsonToObjectTransformerParserTests$CustomObjectMapper"/>
<json-to-object-transformer id="customJsonMapperTransformer" input-channel="customJsonObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"
type="org.springframework.integration.json.TestPerson"
object-mapper="customJsonObjectMapper"/>
<beans:bean id="customJsonObjectMapper" class="org.springframework.integration.json.JsonToObjectTransformerParserTests$CustomJsonObjectMapper"/>

View File

@@ -134,79 +134,6 @@ public class JsonToObjectTransformerParserTests {
}
static class TestPerson {
private String firstName;
private String lastName;
private int age;
private TestAddress address;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public String toString() {
return "name=" + this.firstName + " " + this.lastName
+ ", age=" + this.age + ", address=" + this.address;
}
}
static class TestAddress {
private int number;
private String street;
public void setNumber(int number) {
this.number = number;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}
static class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
@@ -219,8 +146,8 @@ public class JsonToObjectTransformerParserTests {
static class CustomJsonObjectMapper extends JsonObjectMapperAdapter {
@Override
public Object fromJson(String json, Class valueType) throws Exception {
return new TestJsonContainer(json);
public Object fromJson(Object json, Class valueType) throws Exception {
return new TestJsonContainer((String) json);
}
}

View File

@@ -22,6 +22,8 @@ import org.codehaus.jackson.JsonParser.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Test;
import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.json.JacksonJsonObjectMapper;
/**
@@ -33,9 +35,11 @@ public class JsonToObjectTransformerTests {
@Test
public void objectPayload() throws Exception {
JsonToObjectTransformer<TestPerson> transformer = new JsonToObjectTransformer<TestPerson>(TestPerson.class);
JsonToObjectTransformer transformer = new JsonToObjectTransformer(TestPerson.class);
String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42,\"address\":{\"number\":123,\"street\":\"Main Street\"}}";
TestPerson person = transformer.transformPayload(jsonString);
Message<?> message = transformer.transform(new GenericMessage<String>(jsonString));
@SuppressWarnings("unchecked")
TestPerson person = (TestPerson) message.getPayload();
assertEquals("John", person.getFirstName());
assertEquals("Doe", person.getLastName());
assertEquals(42, person.getAge());
@@ -47,10 +51,12 @@ public class JsonToObjectTransformerTests {
ObjectMapper customMapper = new ObjectMapper();
customMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, Boolean.TRUE);
customMapper.configure(Feature.ALLOW_SINGLE_QUOTES, Boolean.TRUE);
JsonToObjectTransformer<TestPerson> transformer =
new JsonToObjectTransformer<TestPerson>(TestPerson.class, new JacksonJsonObjectMapper(customMapper));
JsonToObjectTransformer transformer =
new JsonToObjectTransformer(TestPerson.class, new JacksonJsonObjectMapper(customMapper));
String jsonString = "{firstName:'John', lastName:'Doe', age:42, address:{number:123, street:'Main Street'}}";
TestPerson person = transformer.transformPayload(jsonString);
Message<?> message = transformer.transform(new GenericMessage<String>(jsonString));
@SuppressWarnings("unchecked")
TestPerson person = (TestPerson) message.getPayload();
assertEquals("John", person.getFirstName());
assertEquals("Doe", person.getLastName());
assertEquals(42, person.getAge());
@@ -60,82 +66,8 @@ public class JsonToObjectTransformerTests {
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testInt2831IllegalArgument() throws Exception {
new JsonToObjectTransformer<String>(String.class, new Object());
new JsonToObjectTransformer(String.class, new Object());
}
@SuppressWarnings("unused")
private static class TestPerson {
private String firstName;
private String lastName;
private int age;
private TestAddress address;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public String toString() {
return "name=" + this.firstName + " " + this.lastName
+ ", age=" + this.age + ", address=" + this.address;
}
}
@SuppressWarnings("unused")
private static class TestAddress {
private int number;
private String street;
public void setNumber(int number) {
this.number = number;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2013 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.integration.json;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.integration.Message;
import org.springframework.integration.message.GenericMessage;
/**
* @author Artem Bilan
* @since 3.0
*/
public class JsonTransformersSymmetricalTests {
@Test
public void testInt2809ObjectToJson_JsonToObject() {
TestPerson person = new TestPerson("John", "Doe", 42);
person.setAddress(new TestAddress(123, "Main Street"));
ObjectToJsonTransformer objectToJsonTransformer = new ObjectToJsonTransformer();
Message<?> jsonMessage = objectToJsonTransformer.transform(new GenericMessage<Object>(person));
JsonToObjectTransformer jsonToObjectTransformer = new JsonToObjectTransformer();
Message<?> result = jsonToObjectTransformer.transform(jsonMessage);
assertEquals(person, result.getPayload());
}
}

View File

@@ -174,87 +174,6 @@ public class ObjectToJsonTransformerParserTests {
}
static class TestPerson {
private String firstName;
private String lastName;
private int age;
private TestAddress address;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public String toString() {
return "\"name\":\"" + this.firstName + " " + this.lastName
+ "\", \"age\":" + this.age + ", \"address\":\"" + this.address + "\"";
}
}
static class TestAddress {
private int number;
private String street;
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
public String getStreet() {
return this.street;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}
static class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {

View File

@@ -147,66 +147,4 @@ public class ObjectToJsonTransformerTests {
new ObjectToJsonTransformer(new Object());
}
@SuppressWarnings("unused")
private static class TestPerson {
private final String firstName;
private final String lastName;
private final int age;
private TestAddress address;
public TestPerson(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public TestAddress getAddress() {
return address;
}
public void setAddress(TestAddress address) {
this.address = address;
}
}
@SuppressWarnings("unused")
private static class TestAddress {
private final int number;
private final String street;
public TestAddress(int number, String street) {
this.number = number;
this.street = street;
}
public int getNumber() {
return number;
}
public String getStreet() {
return street;
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2013 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.integration.json;
/**
* @author Mark Fisher
* @since 2.0
*/
@SuppressWarnings("unused")
class TestAddress {
private volatile int number;
private volatile String street;
TestAddress() {
}
public TestAddress(int number, String street) {
this.number = number;
this.street = street;
}
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
public String getStreet() {
return this.street;
}
public void setStreet(String street) {
this.street = street;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestAddress that = (TestAddress) o;
if (number != that.number) return false;
if (street != null ? !street.equals(that.street) : that.street != null) return false;
return true;
}
@Override
public int hashCode() {
int result = number;
result = 31 * result + (street != null ? street.hashCode() : 0);
return result;
}
@Override
public String toString() {
return this.number + " " + this.street;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2013 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.integration.json;
/**
* @author Mark Fisher
* @since 2.0
*/
@SuppressWarnings("unused")
class TestPerson {
private volatile String firstName;
private volatile String lastName;
private volatile int age;
private volatile TestAddress address;
TestPerson() {
}
public TestPerson(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return this.lastName;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAddress(TestAddress address) {
this.address = address;
}
public TestAddress getAddress() {
return this.address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestPerson that = (TestPerson) o;
if (age != that.age) return false;
if (address != null ? !address.equals(that.address) : that.address != null) return false;
if (firstName != null ? !firstName.equals(that.firstName) : that.firstName != null) return false;
if (lastName != null ? !lastName.equals(that.lastName) : that.lastName != null) return false;
return true;
}
@Override
public int hashCode() {
int result = firstName != null ? firstName.hashCode() : 0;
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
result = 31 * result + age;
result = 31 * result + (address != null ? address.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "name=" + this.firstName + " " + this.lastName
+ ", age=" + this.age + ", address=" + this.address;
}
}

View File

@@ -206,7 +206,7 @@ public class Kid {
a BeanCreationException will be thrown. 
</note>
<para>
<emphasis>JSON Transformers</emphasis>
<emphasis role="bold">JSON Transformers</emphasis>
</para>
<para>
<emphasis>Object to JSON</emphasis> and <emphasis>JSON to Object</emphasis> transformers are provided.
@@ -279,7 +279,7 @@ public class Foo {
</para>
<important>
<para>
Beginning with version 2.2, the <code>object-to-json-transformer</code> sets the <emphasis>content-type</emphasis>
Beginning with <emphasis>version 2.2</emphasis>, the <code>object-to-json-transformer</code> sets the <emphasis>content-type</emphasis>
header to <code>application/json</code>, by default, if the input message does not already have that header
present.
</para>
@@ -290,66 +290,50 @@ public class Foo {
attribute to an empty string (<code>""</code>). This will result in a message with no <code>content-type</code>
header, unless such a header was present on the input message.
</para>
<para>
The behavior of adding the default header has a side affect - causing applications with the following
sequence to fail:
</para>
<para>
<code>->object-to-json-transformer->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->json-to-object-transformer-></code>
</para>
<para>
This is because the default <classname>SimpleMessageConverter</classname> used by the inbound adapter doesn't
recognize this content type and the adapter emits a message with a <code>byte[]</code> payload instead of
<code>String</code>, which was the case with earlier versions.
</para>
<para>
If you are using this pattern, there are a number of ways to configure the environment so that JSON
conversion will be performed correctly.
</para>
<para>
One solution is to set the content type to a text type, so the inbound converter will convert the JSON to
String. This solution requires a change to just the outbound application.
</para>
<para>
<programlisting language="xml"><![CDATA[<object-to-json-transformer ... content-type="text/x-json"/>]]></programlisting>
</para>
<para>
The second solution is to eliminate the json transformers altogether and use an
<classname>org.springframework.amqp.support.converter.JsonMessageConverter</classname> on both the
outbound and inbound adapters. This configures the adapters to perform the JSON conversion and
the transformers are not necessary. The converter on the outbound adapter adds
type information to the message properties; the inbound converter uses this type information for the conversion.
The converter is provided to the adapters using the <emphasis>message-converter</emphasis> attribute.
This solution requires a change to both the inbound and outbound applications.
</para>
<para>
The third solution is to eliminate the <emphasis>json-to-object-transformer</emphasis> in just
the inbound application and use an
<classname>org.springframework.amqp.support.converter.JsonMessageConverter</classname> on the
inbound adapter. The converter
is provided to the adapter using the <emphasis>message-converter</emphasis> attribute.
However, because there will be no type information in the message properties,
this also requires adding the <emphasis>defaultType</emphasis> to the converter, using the
same type as currently configured on the <emphasis>json-to-object-transformer</emphasis>.
This solution requires a change to just the inbound application. The configuration below shows
how to configure the message converter; it requires <code>spring-amqp</code> 1.1.3 or
above.
</para>
<programlisting language="xml"><![CDATA[<bean id="jsonConverterWithPOType"
class="org.springframework.amqp.support.converter.JsonMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType"
value="foo.PurchaseOrder" />
</bean>
</property>
</bean>
<int-amqp:inbound-channel-adapter ... message-converter="jsonConverterWithPOType" ... />]]></programlisting>
</important>
<para>
Beginning with <emphasis>version 3.0</emphasis>, the <classname>ObjectToJsonTransformer</classname> adds headers,
reflecting the source type, to the message. Similarly, the <classname>JsonToObjectTransformer</classname> can
use those type headers when converting the JSON to an object. These headers are mapped in the AMQP adapters so that
they are entirely compatible with the Spring-AMQP
<ulink url="http://docs.spring.io/spring-amqp/api/">JsonMessageConverter</ulink>.
</para>
<para>
This enables the following flows to work without any special configuration...
</para>
<para>
<code>...->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->json-to-object-transformer->...</code>
</para>
<para>
Where the outbound adapter is configured with a <classname>JsonMessageConverter</classname> and the
inbound adapter uses the default <classname>SimpleMessageConverter</classname>.
</para>
<para>
<code>...->object-to-json-transformer->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->...</code>
</para>
<para>
Where the outbound adapter is configured with a <classname>SimpleMessageConverter</classname> and the
inbound adapter uses the default <classname>JsonMessageConverter</classname>.
</para>
<para>
<code>...->object-to-json-transformer->amqp-outbound-adapter----></code>
</para>
<para>
<code>---->amqp-inbound-adapter->json-to-object-transformer-></code>
</para>
<para>
Where both adapters are configured with a <classname>SimpleMessageConverter</classname>.
</para>
<note>
When using the headers to determine the type, you should <emphasis role="bold">not</emphasis> provide
a <code>class</code> attribute, because it takes precedence over the headers.
</note>
<para>
In addition to JSON Transformers, Spring Integration provides a built-in <emphasis>#jsonPath</emphasis>
SpEL function for use in expressions. For more information see <xref linkend="spel"/>.

View File

@@ -325,10 +325,20 @@
<section id="3.0-json-transformers">
<title>Jackson Support (JSON)</title>
<para>
A new abstraction for JSON conversion has been introduced. Implementations for Jackson 1.x
and Jackson 2 are currently provided, with the version being determined by presence on
the classpath. Previously, only Jackson 1.x was supported. For more information,
see 'JSON Transformers' in <xref linkend="transformer"/>.
<itemizedlist>
<listitem>
A new abstraction for JSON conversion has been introduced. Implementations for Jackson 1.x
and Jackson 2 are currently provided, with the version being determined by presence on
the classpath. Previously, only Jackson 1.x was supported.
</listitem>
<listitem>
The <classname>ObjectToJsonTransformer</classname> and <classname>JsonToObjectTransformer</classname>
now emit/consume headers containing type information.
</listitem>
</itemizedlist>
</para>
<para>
For more information, see 'JSON Transformers' in <xref linkend="transformer"/>.
</para>
</section>
<section id="3.0-http-endpointss">