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:
committed by
Gary Russell
parent
c389604c2e
commit
4dd95c41ea
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 '" +
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"/>.
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user