INT-3370: Add BoonJsonObjectMapper

JIRA: https://jira.spring.io/browse/INT-3370

* Add `BoonJsonObjectMapper`
* Provide some tests
* Remove deprecations
* Polishing test according removed deprecations
* Change `JsonObjectMapper#populateJavaTypes` to get deal with `payload`, not its `class`,
since we can't (and Jackson's `TypeFactory`, too) determine generic type from `Collection`.
Use `iterators` instead to retrieve the type from the first item.

Conflicts:
	build.gradle
	spring-integration-amqp/src/test/java/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapperTests.java

INT-3370: Polishing according PR comments

* Apply Gary's polishing to the Docs
* Fix `RecipientListRouter` JavaDoc warn
* Fix typo in test name for `UdpUnicastEndToEndTests`
* Fix `RedisQueueOutboundChannelAdapterTests` do not use Jackson 1.x

Final Polish
This commit is contained in:
Artem Bilan
2014-07-15 10:56:07 +03:00
committed by Gary Russell
parent cda4a99023
commit f84e798272
32 changed files with 451 additions and 429 deletions

View File

@@ -70,6 +70,7 @@ subprojects { subproject ->
activeMqVersion = '5.9.0'
aspectjVersion = '1.8.0'
apacheSshdVersion = '0.10.1'
boonVersion = '0.22'
commonsDbcpVersion = '1.4'
commonsIoVersion = '2.4'
commonsNetVersion = '3.3'
@@ -83,7 +84,6 @@ subprojects { subproject ->
hibernateVersion = '4.2.11.Final'
hsqldbVersion = '2.3.2'
h2Version = '1.3.175'
jacksonVersion = '1.9.13'
jackson2Version = '2.3.2'
javaxActivationVersion = '1.1.1'
javaxMailVersion = '1.4.7'
@@ -246,9 +246,9 @@ project('spring-integration-core') {
compile "org.springframework.retry:spring-retry:$springRetryVersion"
compile "org.projectreactor:reactor-core:$reactorVersion"
compile("org.projectreactor.spring:reactor-spring-context:$reactorSpringVersion", optional)
compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional)
compile("com.fasterxml.jackson.core:jackson-databind:$jackson2Version", optional)
compile("com.jayway.jsonpath:json-path:$jsonpathVersion", optional)
compile("io.fastjson:boon:$boonVersion", optional)
testCompile ("org.aspectj:aspectjweaver:$aspectjVersion")
}

View File

@@ -28,6 +28,7 @@ import static org.mockito.Mockito.when;
import java.util.Map;
import com.rabbitmq.client.Channel;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
@@ -41,7 +42,7 @@ import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.support.converter.JsonMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
@@ -60,8 +61,6 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import com.rabbitmq.client.Channel;
/**
* @author Artem Bilan
* @author Gary Russell
@@ -85,7 +84,7 @@ public class InboundEndpointTests {
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(container);
adapter.setMessageConverter(new JsonMessageConverter());
adapter.setMessageConverter(new Jackson2JsonMessageConverter());
PollableChannel channel = new QueueChannel();
@@ -140,7 +139,8 @@ public class InboundEndpointTests {
Object payload = new Foo("bar1");
MessageProperties amqpMessageProperties = new MessageProperties();
org.springframework.amqp.core.Message amqpMessage = new JsonMessageConverter().toMessage(payload, amqpMessageProperties);
org.springframework.amqp.core.Message amqpMessage =
new Jackson2JsonMessageConverter().toMessage(payload, amqpMessageProperties);
ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
listener.onMessage(amqpMessage, null);
@@ -186,20 +186,22 @@ public class InboundEndpointTests {
}));
AmqpInboundGateway gateway = new AmqpInboundGateway(container);
gateway.setMessageConverter(new JsonMessageConverter());
gateway.setMessageConverter(new Jackson2JsonMessageConverter());
gateway.setRequestChannel(channel);
gateway.setBeanFactory(mock(BeanFactory.class));
gateway.afterPropertiesSet();
RabbitTemplate rabbitTemplate = Mockito.spy(TestUtils.getPropertyValue(gateway, "amqpTemplate", RabbitTemplate.class));
RabbitTemplate rabbitTemplate = Mockito.spy(TestUtils.getPropertyValue(gateway, "amqpTemplate",
RabbitTemplate.class));
Mockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
org.springframework.amqp.core.Message message = (org.springframework.amqp.core.Message) invocation.getArguments()[2];
Map<String,Object> headers = message.getMessageProperties().getHeaders();
org.springframework.amqp.core.Message message =
(org.springframework.amqp.core.Message) invocation.getArguments()[2];
Map<String, Object> headers = message.getMessageProperties().getHeaders();
assertTrue(headers.containsKey(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, "")));
assertNotEquals("foo", headers.get(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, "")));
assertFalse(headers.containsKey(JsonHeaders.CONTENT_TYPE_ID.replaceFirst(JsonHeaders.PREFIX, "")));
@@ -209,9 +211,7 @@ public class InboundEndpointTests {
assertFalse(headers.containsKey(JsonHeaders.CONTENT_TYPE_ID));
return null;
}
}
).when(rabbitTemplate).send(Mockito.anyString(), Mockito.anyString(),
}).when(rabbitTemplate).send(Mockito.anyString(), Mockito.anyString(),
Mockito.any(org.springframework.amqp.core.Message.class), Mockito.any(CorrelationData.class));
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(gateway);
@@ -222,7 +222,8 @@ public class InboundEndpointTests {
MessageProperties amqpMessageProperties = new MessageProperties();
amqpMessageProperties.setReplyTo("test");
amqpMessageProperties.setDeliveryTag(123L);
org.springframework.amqp.core.Message amqpMessage = new JsonMessageConverter().toMessage(payload, amqpMessageProperties);
org.springframework.amqp.core.Message amqpMessage =
new Jackson2JsonMessageConverter().toMessage(payload, amqpMessageProperties);
ChannelAwareMessageListener listener = (ChannelAwareMessageListener) container.getMessageListener();
listener.onMessage(amqpMessage, rabbitChannel);
@@ -230,7 +231,6 @@ public class InboundEndpointTests {
}
public static class Foo {
private String bar;
@@ -257,11 +257,8 @@ public class InboundEndpointTests {
Foo foo = (Foo) o;
if (bar != null ? !bar.equals(foo.bar) : foo.bar != null) {
return false;
}
return !(bar != null ? !bar.equals(foo.bar) : foo.bar != null);
return true;
}
@Override

View File

@@ -27,14 +27,16 @@ import java.util.Map;
import java.util.Set;
import org.junit.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.JsonMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.http.MediaType;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.integration.amqp.AmqpHeaders;
import org.springframework.messaging.MessageHeaders;
/**
* @author Mark Fisher
@@ -198,7 +200,7 @@ public class DefaultAmqpHeaderMapperTests {
@Test // INT-2090
public void jsonTypeIdNotOverwritten() {
DefaultAmqpHeaderMapper headerMapper = new DefaultAmqpHeaderMapper();
JsonMessageConverter converter = new JsonMessageConverter();
MessageConverter converter = new Jackson2JsonMessageConverter();
MessageProperties amqpProperties = new MessageProperties();
converter.toMessage("123", amqpProperties);
Map<String, Object> headerMap = new HashMap<String, Object>();

View File

@@ -19,14 +19,14 @@ package org.springframework.integration.json;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.integration.mapping.support.JsonHeaders;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.integration.transformer.AbstractTransformer;
import org.springframework.messaging.Message;
/**
* Transformer implementation that converts a JSON string payload into an instance of the provided target Class.
* By default this transformer uses {@linkplain JacksonJsonObjectMapperProvider} factory
* By default this transformer uses {@linkplain org.springframework.integration.support.json.JsonObjectMapperProvider} factory
* to get an instance of Jackson 1 or Jackson 2 JSON-processor {@linkplain JsonObjectMapper} implementation
* depending on the jackson-databind or jackson-mapper-asl libs on the classpath.
* Any other {@linkplain JsonObjectMapper} implementation can be provided.
@@ -34,7 +34,7 @@ import org.springframework.messaging.Message;
* @author Mark Fisher
* @author Artem Bilan
* @see JsonObjectMapper
* @see JacksonJsonObjectMapperProvider
* @see org.springframework.integration.support.json.JsonObjectMapperProvider
* @since 2.0
*/
public class JsonToObjectTransformer extends AbstractTransformer implements BeanClassLoaderAware {
@@ -57,7 +57,7 @@ public class JsonToObjectTransformer extends AbstractTransformer implements Bean
public JsonToObjectTransformer(Class<?> targetClass, JsonObjectMapper<?, ?> jsonObjectMapper) {
this.targetClass = targetClass;
this.jsonObjectMapper = (jsonObjectMapper != null) ? jsonObjectMapper : JacksonJsonObjectMapperProvider.newInstance();
this.jsonObjectMapper = (jsonObjectMapper != null) ? jsonObjectMapper : JsonObjectMapperProvider.newInstance();
}
@Override

View File

@@ -16,7 +16,7 @@
package org.springframework.integration.json;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.integration.transformer.AbstractTransformer;
import org.springframework.messaging.Message;
@@ -27,7 +27,7 @@ import org.springframework.util.StringUtils;
/**
* Transformer implementation that converts a payload instance into a JSON string representation.
* By default this transformer uses {@linkplain JacksonJsonObjectMapperProvider} factory
* By default this transformer uses {@linkplain org.springframework.integration.support.json.JsonObjectMapperProvider} factory
* to get an instance of a Jackson or Jackson 2 JSON-processor {@linkplain JsonObjectMapper} implementation
* depending on the jackson-databind or jackson-mapper-asl libs on the classpath.
* Any other {@linkplain JsonObjectMapper} implementation can be provided.
@@ -56,7 +56,7 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
private volatile boolean contentTypeExplicitlySet = false;
public ObjectToJsonTransformer() {
this(JacksonJsonObjectMapperProvider.newInstance());
this(JsonObjectMapperProvider.newInstance());
}
public ObjectToJsonTransformer(JsonObjectMapper<?, ?> jsonObjectMapper) {
@@ -64,7 +64,7 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
}
public ObjectToJsonTransformer(ResultType resultType) {
this(JacksonJsonObjectMapperProvider.newInstance(), resultType);
this(JsonObjectMapperProvider.newInstance(), resultType);
}
public ObjectToJsonTransformer(JsonObjectMapper<?, ?> jsonObjectMapper, ResultType resultType) {
@@ -113,7 +113,7 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
}
this.jsonObjectMapper.populateJavaTypes(headers, message.getPayload().getClass());
this.jsonObjectMapper.populateJavaTypes(headers, message.getPayload());
messageBuilder.copyHeaders(headers);
return messageBuilder.build();

View File

@@ -104,7 +104,7 @@ public class RecipientListRouter extends AbstractMessageRouter
/**
* Set the recipients for this router.
* @param recipientMappings, map contains channelName and expression
* @param recipientMappings map contains channelName and expression
*/
@Override
@ManagedAttribute

View File

@@ -38,7 +38,8 @@ import org.springframework.util.ClassUtils;
* @author Artem Bilan
* @since 3.0
*/
public abstract class AbstractJacksonJsonObjectMapper<N, P, J> implements JsonObjectMapper<N, P>, BeanClassLoaderAware {
public abstract class AbstractJacksonJsonObjectMapper<N, P, J> extends JsonObjectMapperAdapter<N, P>
implements BeanClassLoaderAware {
protected static final Collection<Class<?>> supportedJsonTypes =
Arrays.<Class<?>> asList(String.class, byte[].class, File.class, URL.class, InputStream.class, Reader.class);

View File

@@ -0,0 +1,198 @@
package org.springframework.integration.support.json;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.boon.json.JsonFactory;
import org.boon.json.JsonParserAndMapper;
import org.boon.json.JsonParserFactory;
import org.boon.json.JsonSerializerFactory;
import org.boon.json.JsonSlurper;
import org.boon.json.ObjectMapper;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.integration.mapping.support.JsonHeaders;
import org.springframework.util.ClassUtils;
/**
* The Boon (@link https://github.com/RichardHightower/boon) {@link JsonObjectMapper} implementation.
*
* @author Artem Bilan
* @since 4.1
*/
public class BoonJsonObjectMapper extends JsonObjectMapperAdapter<Map<String, Object>, Object>
implements BeanClassLoaderAware {
private static final Log logger = LogFactory.getLog(BoonJsonObjectMapper.class);
private static final Collection<Class<?>> supportedJsonTypes =
Arrays.<Class<?>>asList(String.class, byte[].class, byte[].class, File.class, InputStream.class, Reader.class);
private final ObjectMapper objectMapper;
private final JsonSlurper slurper = new JsonSlurper();
private volatile ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
public BoonJsonObjectMapper() {
this.objectMapper = JsonFactory.create();
}
public BoonJsonObjectMapper(JsonParserFactory parserFactory, JsonSerializerFactory serializerFactory) {
this.objectMapper = JsonFactory.create(parserFactory, serializerFactory);
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public String toJson(Object value) throws Exception {
return this.objectMapper.writeValueAsString(value);
}
@Override
public void toJson(Object value, Writer writer) {
this.objectMapper.toJson(value, writer);
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> toJsonNode(final Object value) throws Exception {
PipedReader in = new PipedReader();
final PipedWriter out = new PipedWriter(in);
Executors.newSingleThreadExecutor()
.execute(new Runnable() {
@Override
public void run() {
toJson(value, out);
}
});
return (Map<String, Object>) this.slurper.parse(in);
}
@Override
public <T> T fromJson(Object json, Class<T> 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 char[]) {
return this.objectMapper.readValue((char[]) json, type);
}
else if (json instanceof File) {
return this.objectMapper.readValue((File) 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
+ " , but gotten: " + json.getClass());
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws Exception {
JsonParserAndMapper parser = this.objectMapper.parser();
Class<?> classType = this.createJavaType(javaTypes, JsonHeaders.TYPE_ID);
Class<?> contentClassType = this.createJavaType(javaTypes, JsonHeaders.CONTENT_TYPE_ID);
Class<?> keyClassType = this.createJavaType(javaTypes, JsonHeaders.KEY_TYPE_ID);
if (keyClassType != null) {
logger.warn("Boon doesn't support the Map 'key' conversion. Will be returned raw Map<String, Object>");
if (json instanceof String) {
return (T) parser.parseMap((String) json);
}
else if (json instanceof byte[]) {
return (T) parser.parseMap((byte[]) json);
}
else if (json instanceof char[]) {
return (T) parser.parseMap((char[]) json);
}
else if (json instanceof File) {
return (T) parser.parseMap(new FileReader((File) json));
}
else if (json instanceof InputStream) {
return (T) parser.parseMap((InputStream) json);
}
else if (json instanceof Reader) {
return (T) parser.parseMap((Reader) json);
}
else {
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes
+ " , but gotten: " + json.getClass());
}
}
if (contentClassType != null) {
if (json instanceof String) {
return (T) this.objectMapper.readValue((String) json, (Class<Collection>) classType, contentClassType);
}
else if (json instanceof byte[]) {
return (T) this.objectMapper.readValue((byte[]) json, (Class<Collection>) classType, contentClassType);
}
else if (json instanceof char[]) {
return (T) this.objectMapper.readValue((char[]) json, (Class<Collection>) classType, contentClassType);
}
else if (json instanceof File) {
return (T) this.objectMapper.readValue((File) json, (Class<Collection>) classType, contentClassType);
}
else if (json instanceof InputStream) {
return (T) this.objectMapper.readValue((InputStream) json, (Class<Collection>) classType,
contentClassType);
}
else if (json instanceof Reader) {
return (T) this.objectMapper.readValue((Reader) json, (Class<Collection>) classType, contentClassType);
}
else {
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes
+ " , but gotten: " + json.getClass());
}
}
return (T) fromJson(json, classType);
}
protected Class<?> createJavaType(Map<String, Object> javaTypes, String javaTypeKey) throws Exception {
Object classValue = javaTypes.get(javaTypeKey);
if (classValue instanceof Class<?>) {
return (Class<?>) classValue;
}
else if (classValue != null) {
return ClassUtils.forName(classValue.toString(), this.classLoader);
}
else {
return null;
}
}
@Override
public <T> T fromJson(Object parser, Type valueType) throws Exception {
throw new UnsupportedOperationException("Boon doesn't support JSON reader parser abstraction");
}
}

View File

@@ -25,14 +25,14 @@ import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.springframework.integration.mapping.support.JsonHeaders;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.integration.mapping.support.JsonHeaders;
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>
@@ -90,7 +90,8 @@ public class Jackson2JsonObjectMapper extends AbstractJacksonJsonObjectMapper<Js
return this.objectMapper.readValue((Reader) json, type);
}
else {
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes);
throw new IllegalArgumentException("'json' argument must be an instance of: " + supportedJsonTypes
+ " , but gotten: " + json.getClass());
}
}
@@ -99,20 +100,6 @@ public class Jackson2JsonObjectMapper extends AbstractJacksonJsonObjectMapper<Js
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 {

View File

@@ -1,86 +0,0 @@
/*
* Copyright 2002-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.util.LinkedHashMap;
import java.util.Map;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* {@link JsonInboundMessageMapper.JsonMessageParser} implementation that parses JSON messages
* and builds a {@link Message} with the specified payload type from provided {@link JsonInboundMessageMapper}.
* Uses Jackson JSON-processor (@link http://jackson.codehaus.org).
*
* @deprecated Please migrate to {@link Jackson2JsonMessageParser} for Jackson 2.x.
*
* @author Artem Bilan
* @since 3.0
*/
@Deprecated
public class JacksonJsonMessageParser extends AbstractJacksonJsonMessageParser<JsonParser> {
public JacksonJsonMessageParser() {
super(new JacksonJsonObjectMapper());
}
@Override
protected JsonParser createJsonParser(String jsonMessage) throws Exception {
return new JsonFactory().createJsonParser(jsonMessage);
}
@Override
protected Message<?> parseWithHeaders(JsonParser parser, String jsonMessage) throws Exception {
String error = AbstractJsonInboundMessageMapper.MESSAGE_FORMAT_ERROR + jsonMessage;
Assert.isTrue(parser.nextToken() == JsonToken.START_OBJECT, error);
Map<String, Object> headers = null;
Object payload = null;
while (parser.nextToken() != JsonToken.END_OBJECT) {
Assert.isTrue(parser.getCurrentToken() == JsonToken.FIELD_NAME, error);
boolean isHeadersToken = "headers".equals(parser.getCurrentName());
boolean isPayloadToken = "payload".equals(parser.getCurrentName());
Assert.isTrue(isHeadersToken || isPayloadToken, error);
if (isHeadersToken) {
Assert.isTrue(parser.nextToken() == JsonToken.START_OBJECT, error);
headers = readHeaders(parser, jsonMessage);
}
else if (isPayloadToken) {
parser.nextToken();
payload = this.readPayload(parser, jsonMessage);
}
}
Assert.notNull(headers, error);
return this.getMessageBuilderFactory().withPayload(payload).copyHeaders(headers).build();
}
private Map<String, Object> readHeaders(JsonParser parser, String jsonMessage) throws Exception {
Map<String, Object> headers = new LinkedHashMap<String, Object>();
while (parser.nextToken() != JsonToken.END_OBJECT) {
String headerName = parser.getCurrentName();
parser.nextToken();
Object headerValue = this.readHeader(parser, headerName, jsonMessage);
headers.put(headerName, headerValue);
}
return headers;
}
}

View File

@@ -1,143 +0,0 @@
/*
* Copyright 2002-2014 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.io.Writer;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.JavaType;
import org.springframework.integration.mapping.support.JsonHeaders;
import org.springframework.util.Assert;
/**
* Jackson JSON-processor (@link http://jackson.codehaus.org) {@linkplain JsonObjectMapper} implementation.
* Delegates <code>toJson</code> and <code>fromJson</code>
* to the {@linkplain org.codehaus.jackson.map.ObjectMapper}
*
* @deprecated Please migrate to {@link Jackson2JsonObjectMapper} for Jackson 2.x.
*
* @author Artem Bilan
* @since 3.0
*/
@Deprecated
public class JacksonJsonObjectMapper extends AbstractJacksonJsonObjectMapper<JsonNode, JsonParser, JavaType> {
private final ObjectMapper objectMapper;
public JacksonJsonObjectMapper() {
this.objectMapper = new ObjectMapper();
}
public JacksonJsonObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper must not be null");
this.objectMapper = objectMapper;
}
@Override
public String toJson(Object value) throws Exception {
return this.objectMapper.writeValueAsString(value);
}
@Override
public void toJson(Object value, Writer writer) throws Exception {
this.objectMapper.writeValue(writer, value);
}
@Override
public JsonNode toJsonNode(Object value) throws Exception {
return this.objectMapper.valueToTree(value);
}
@Override
public <T> T fromJson(JsonParser parser, Type valueType) throws Exception {
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

@@ -38,9 +38,6 @@ public final class JacksonJsonUtils {
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", classLoader) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", classLoader);
private static final IllegalStateException NO_JACKSON_LIB_EXCEPTION =
new IllegalStateException("Neither jackson-databind.jar, nor jackson-mapper-asl.jar aren't presented in the classpath.");
public static boolean isJackson2Present() {
return jackson2Present;
}
@@ -49,7 +46,4 @@ public final class JacksonJsonUtils {
return jacksonPresent;
}
public static IllegalStateException getNoJacksonLibException() {
return NO_JACKSON_LIB_EXCEPTION;
}
}

View File

@@ -44,5 +44,5 @@ public interface JsonObjectMapper<N, P> {
<T> T fromJson(P parser, Type valueType) throws Exception;
void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass);
void populateJavaTypes(Map<String, Object> map, Object object);
}

View File

@@ -18,8 +18,11 @@ package org.springframework.integration.support.json;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import org.springframework.integration.mapping.support.JsonHeaders;
/**
* Simple {@linkplain JsonObjectMapper} adapter implementation, if there is no need
* to provide entire operations implementation.
@@ -59,7 +62,15 @@ public abstract class JsonObjectMapperAdapter<N, P> implements JsonObjectMapper<
}
@Override
public void populateJavaTypes(Map<String, Object> map, Class<?> sourceClass) {
public void populateJavaTypes(Map<String, Object> map, Object object) {
map.put(JsonHeaders.TYPE_ID, object.getClass());
if (object instanceof Collection && !((Collection) object).isEmpty()) {
map.put(JsonHeaders.CONTENT_TYPE_ID, ((Collection) object).iterator().next().getClass());
}
if (object instanceof Map && !((Map) object).isEmpty()) {
map.put(JsonHeaders.CONTENT_TYPE_ID, ((Map) object).values().iterator().next().getClass());
map.put(JsonHeaders.KEY_TYPE_ID, ((Map) object).keySet().iterator().next().getClass());
}
}
}

View File

@@ -17,9 +17,11 @@
package org.springframework.integration.support.json;
import org.springframework.util.ClassUtils;
/**
* Simple factory to provide {@linkplain Jackson2JsonObjectMapper} or {@linkplain JacksonJsonObjectMapper}
* instances dependently of jackson-databind or jackson-mapper-asl libs in the classpath.
* Simple factory to provide {@linkplain JsonObjectMapper}
* instances dependently of jackson-databind or boon libs in the classpath.
* If there are both libs in the classpath, it prefers Jackson 2 JSON-processor implementation.
* If there is not any of them, {@linkplain IllegalStateException} will be thrown.
*
@@ -28,18 +30,25 @@ package org.springframework.integration.support.json;
* @since 3.0
*
* @see Jackson2JsonObjectMapper
* @see org.springframework.integration.support.json.BoonJsonObjectMapper
*/
public final class JacksonJsonObjectMapperProvider {
public final class JsonObjectMapperProvider {
private static final ClassLoader classLoader = JsonObjectMapperProvider.class.getClassLoader();
private static final boolean boonPresent =
ClassUtils.isPresent("org.boon.json.ObjectMapper", classLoader);
@SuppressWarnings("deprecation")
public static JsonObjectMapper<?, ?> newInstance() {
if (JacksonJsonUtils.isJackson2Present()) {
return new Jackson2JsonObjectMapper();
}
if(JacksonJsonUtils.isJacksonPresent()) {
return new JacksonJsonObjectMapper();
else if (boonPresent) {
return new BoonJsonObjectMapper();
}
else {
throw new IllegalStateException("Neither jackson-databind.jar, nor boon.jar is present in the classpath.");
}
throw JacksonJsonUtils.getNoJacksonLibException();
}
}

View File

@@ -35,7 +35,7 @@ public class JsonOutboundMessageMapper implements OutboundMessageMapper<String>
private volatile JsonObjectMapper<?, ?> jsonObjectMapper;
public JsonOutboundMessageMapper() {
this(JacksonJsonObjectMapperProvider.newInstance());
this(JsonObjectMapperProvider.newInstance());
}
public JsonOutboundMessageMapper(JsonObjectMapper<?, ?> jsonObjectMapper) {

View File

@@ -20,7 +20,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -54,7 +54,7 @@ import org.springframework.util.StringUtils;
*/
public class ObjectToMapTransformer extends AbstractPayloadTransformer<Object, Map<?,?>> {
private final JsonObjectMapper<?, ?> jsonObjectMapper = JacksonJsonObjectMapperProvider.newInstance();
private final JsonObjectMapper<?, ?> jsonObjectMapper = JsonObjectMapperProvider.newInstance();
private volatile boolean shouldFlattenKeys = true;

View File

@@ -33,10 +33,10 @@ import org.junit.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.integration.message.MessageMatcher;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonInboundMessageMapper;
import org.springframework.integration.support.json.JsonInboundMessageMapper.JsonMessageParser;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.integration.support.json.JsonObjectMapperProvider;
import org.springframework.messaging.Message;
/**
@@ -48,7 +48,7 @@ import org.springframework.messaging.Message;
*/
public abstract class AbstractJsonInboundMessageMapperTests {
private final JsonObjectMapper<?, ?> mapper = JacksonJsonObjectMapperProvider.newInstance();
private final JsonObjectMapper<?, ?> mapper = JsonObjectMapperProvider.newInstance();
@Factory
public static Matcher<Message<?>> sameExceptImmutableHeaders(Message<?> operand) {

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2002-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 org.springframework.integration.support.json.JsonInboundMessageMapper.JsonMessageParser;
/**
* @author Gary Russell
* @since 3.0
*
*/
@Deprecated
public class JacksonJsonInboundMessageMapperTests extends AbstractJsonInboundMessageMapperTests {
@Override
protected JsonMessageParser<?> getParser() {
return new org.springframework.integration.support.json.JacksonJsonMessageParser();
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2002-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 org.springframework.integration.support.json.JsonInboundMessageMapper.JsonMessageParser;
/**
* @author Gary Russell
* @since 3.0
*
*/
@Deprecated
public class JacksonJsonSymmetricalMessageMappingTests extends AbstractJsonSymmetricalMessageMappingTests {
@Override
protected JsonMessageParser<?> getParser() {
return new org.springframework.integration.support.json.JacksonJsonMessageParser();
}
}

View File

@@ -16,16 +16,14 @@
package org.springframework.integration.json;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.io.IOException;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.ObjectMapper;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.springframework.integration.history.MessageHistory;
@@ -112,8 +110,8 @@ public class JsonOutboundMessageMapperTests {
assertEquals(payload, parsedPayload);
}
private TestBean extractJsonPayloadToTestBean(String json) throws JsonParseException, IOException {
JsonParser parser = jsonFactory.createJsonParser(json);
private TestBean extractJsonPayloadToTestBean(String json) throws IOException {
JsonParser parser = jsonFactory.createParser(json);
do {
parser.nextToken();
} while(parser.getCurrentToken() != JsonToken.FIELD_NAME || !parser.getCurrentName().equals("payload"));

View File

@@ -16,16 +16,16 @@
package org.springframework.integration.json;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import static org.junit.Assert.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.springframework.integration.support.json.BoonJsonObjectMapper;
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
/**
* @author Mark Fisher
@@ -62,4 +62,17 @@ public class JsonToObjectTransformerTests {
assertEquals("123 Main Street", person.getAddress().toString());
}
@Test
public void testBoonJsonObjectMapper() throws Exception {
JsonToObjectTransformer transformer = new JsonToObjectTransformer(TestPerson.class, new BoonJsonObjectMapper());
String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42,\"address\":{\"number\":123,\"street\":\"Main Street\"}}";
Message<?> message = transformer.transform(new GenericMessage<String>(jsonString));
TestPerson person = (TestPerson) message.getPayload();
assertEquals("John", person.getFirstName());
assertEquals("Doe", person.getLastName());
assertEquals(42, person.getAge());
assertEquals("123 Main Street", person.getAddress().toString());
}
}

View File

@@ -16,10 +16,15 @@
package org.springframework.integration.json;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.integration.support.json.BoonJsonObjectMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
@@ -35,13 +40,35 @@ public class JsonTransformersSymmetricalTests {
TestPerson person = new TestPerson("John", "Doe", 42);
person.setAddress(new TestAddress(123, "Main Street"));
List<TestPerson> payload = new ArrayList<TestPerson>();
payload.add(person);
ObjectToJsonTransformer objectToJsonTransformer = new ObjectToJsonTransformer();
Message<?> jsonMessage = objectToJsonTransformer.transform(new GenericMessage<Object>(person));
Message<?> jsonMessage = objectToJsonTransformer.transform(new GenericMessage<Object>(payload));
JsonToObjectTransformer jsonToObjectTransformer = new JsonToObjectTransformer();
Message<?> result = jsonToObjectTransformer.transform(jsonMessage);
assertEquals(person, result.getPayload());
Object result = jsonToObjectTransformer.transform(jsonMessage).getPayload();
assertThat(result, Matchers.instanceOf(List.class));
assertEquals(person, ((List) result).get(0));
}
@Test
public void testBoonObjectToJson_JsonToObject() {
TestPerson person = new TestPerson("John", "Doe", 42);
person.setAddress(new TestAddress(123, "Main Street"));
List<TestPerson> payload = new ArrayList<TestPerson>();
payload.add(person);
ObjectToJsonTransformer objectToJsonTransformer = new ObjectToJsonTransformer(new BoonJsonObjectMapper());
Message<?> jsonMessage = objectToJsonTransformer.transform(new GenericMessage<Object>(payload));
JsonToObjectTransformer jsonToObjectTransformer = new JsonToObjectTransformer(new BoonJsonObjectMapper());
Object result = jsonToObjectTransformer.transform(jsonMessage).getPayload();
assertThat(result, Matchers.instanceOf(List.class));
assertEquals(person, ((List) result).get(0));
}
}

View File

@@ -18,6 +18,11 @@
<object-to-json-transformer id="jsonNodeTransformer" input-channel="jsonNodeInput" result-type="NODE"/>
<object-to-json-transformer input-channel="boonJsonNodeInput" result-type="NODE"
object-mapper="boonJsonObjectMapper"/>
<beans:bean id="boonJsonObjectMapper" class="org.springframework.integration.support.json.BoonJsonObjectMapper"/>
<beans:bean id="customJsonObjectMapper" class="org.springframework.integration.json.ObjectToJsonTransformerParserTests$CustomJsonObjectMapper"/>
</beans:beans>

View File

@@ -16,15 +16,13 @@
package org.springframework.integration.json;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.databind.JsonNode;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,6 +33,7 @@ import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.mapping.support.JsonHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
import org.springframework.integration.support.json.JsonObjectMapperAdapter;
@@ -45,8 +44,6 @@ import org.springframework.messaging.MessageHeaders;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.databind.JsonNode;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
@@ -70,6 +67,9 @@ public class ObjectToJsonTransformerParserTests {
@Autowired
private volatile MessageChannel jsonNodeInput;
@Autowired
private volatile MessageChannel boonJsonNodeInput;
@Test
public void testContentType(){
ObjectToJsonTransformer transformer =
@@ -165,6 +165,25 @@ public class ObjectToJsonTransformerParserTests {
assertTrue(expression.getValue(evaluationContext, payload, Boolean.class));
}
@Test
public void testBoonNodeResultType() {
TestPerson person = new TestPerson();
person.setFirstName("John");
person.setLastName("Doe");
person.setAge(42);
QueueChannel replyChannel = new QueueChannel();
Message<TestPerson> message = MessageBuilder.withPayload(person).setReplyChannel(replyChannel).build();
this.boonJsonNodeInput.send(message);
Message<?> reply = replyChannel.receive(0);
assertNotNull(reply);
Object payload = reply.getPayload();
assertThat(payload, Matchers.instanceOf(Map.class));
assertEquals(TestPerson.class, reply.getHeaders().get(JsonHeaders.TYPE_ID));
Expression expression = new SpelExpressionParser().parseExpression("[firstName] == 'John' and [age] == 42");
assertTrue(expression.getValue(new StandardEvaluationContext(), payload, Boolean.class));
}
static class CustomJsonObjectMapper extends JsonObjectMapperAdapter<Object, Object> {
@Override

View File

@@ -16,16 +16,24 @@
package org.springframework.integration.json;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.json.BoonJsonObjectMapper;
import org.springframework.integration.support.json.Jackson2JsonObjectMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
@@ -142,4 +150,39 @@ public class ObjectToJsonTransformerTests {
assertTrue(addressResult.contains("street:\"Main Street\""));
}
@Test
public void testBoonJsonObjectMapper() throws Exception {
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer(new BoonJsonObjectMapper());
TestPerson person = new TestPerson("John", "Doe", 42);
person.setAddress(new TestAddress(123, "Main Street"));
String result = (String) transformer.transform(new GenericMessage<TestPerson>(person)).getPayload();
assertTrue(result.contains("\"firstName\":\"John\""));
assertTrue(result.contains("\"lastName\":\"Doe\""));
assertTrue(result.contains("\"age\":42"));
Pattern addressPattern = Pattern.compile("(\"address\":\\{.*?\\})");
Matcher matcher = addressPattern.matcher(result);
assertTrue(matcher.find());
String addressResult = matcher.group(1);
assertTrue(addressResult.contains("\"number\":123"));
assertTrue(addressResult.contains("\"street\":\"Main Street\""));
}
@Test
public void testBoonJsonObjectMapper_toNode() throws Exception {
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer(new BoonJsonObjectMapper(),
ObjectToJsonTransformer.ResultType.NODE);
TestPerson person = new TestPerson("John", "Doe", 42);
person.setAddress(new TestAddress(123, "Main Street"));
Object payload = transformer.transform(new GenericMessage<TestPerson>(person)).getPayload();
assertThat(payload, instanceOf(Map.class));
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("firstName + ': ' + address.street");
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.addPropertyAccessor(new MapAccessor());
String value = expression.getValue(evaluationContext, payload, String.class);
assertEquals("John: Main Street", value);
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.integration.transformer;
import static org.junit.Assert.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -23,22 +25,15 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.junit.Test;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.messaging.Message;
import org.springframework.integration.support.MessageBuilder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import org.springframework.messaging.Message;
/**
*
@@ -47,9 +42,10 @@ import static org.junit.Assert.assertNull;
* @since 2.0
*/
public class ObjectToMapTransformerTests {
@SuppressWarnings("unchecked")
@Test
public void testObjectToSpelMapTransformer() throws JsonParseException, JsonMappingException, JsonGenerationException, IOException{
public void testObjectToSpelMapTransformer() throws IOException {
Employee employee = this.buildEmployee();
StandardEvaluationContext context = new StandardEvaluationContext();
context.addPropertyAccessor(new MapAccessor());

View File

@@ -26,13 +26,13 @@ import java.util.Map;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
import org.springframework.integration.support.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapperProvider;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.util.Assert;
/**
* Serializes a {@link Map} as JSON. Deserializes JSON to
* a {@link Map}. The default {@link JacksonJsonObjectMapperProvider#newInstance()} can be
* a {@link Map}. The default {@link org.springframework.integration.support.json.JsonObjectMapperProvider#newInstance()} can be
* overridden using {@link #setJsonObjectMapper(JsonObjectMapper)}.
* <p>
* The JSON deserializer can't delimit multiple JSON
@@ -48,7 +48,7 @@ import org.springframework.util.Assert;
*/
public class MapJsonSerializer implements Serializer<Map<?, ?>>, Deserializer<Map<?, ?>> {
private volatile JsonObjectMapper<?, ?> jsonObjectMapper = JacksonJsonObjectMapperProvider.newInstance();
private volatile JsonObjectMapper<?, ?> jsonObjectMapper = JsonObjectMapperProvider.newInstance();
private volatile Deserializer<byte[]> packetDeserializer = new ByteArrayLfSerializer();

View File

@@ -96,7 +96,7 @@ public class UdpUnicastEndToEndTests implements Runnable {
}
@Test
public void tesUudpOutboundChannelAdapterWithinChain() throws Exception {
public void tesUdpOutboundChannelAdapterWithinChain() throws Exception {
UdpUnicastEndToEndTests launcher = new UdpUnicastEndToEndTests();
Thread t = new Thread(launcher);
t.start(); // launch the receiver
@@ -189,7 +189,7 @@ public class UdpUnicastEndToEndTests implements Runnable {
public static void main(String[] args) throws Exception {
hangAroundFor = 120000;
new UdpUnicastEndToEndTests().runIt();
new UdpUnicastEndToEndTests().tesUudpOutboundChannelAdapterWithinChain();
new UdpUnicastEndToEndTests().tesUdpOutboundChannelAdapterWithinChain();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors
* Copyright 2013-2014 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.
@@ -31,7 +31,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.integration.mapping.InboundMessageMapper;
@@ -69,7 +69,8 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
final String queueName = "si.test.testRedisQueueOutboundChannelAdapter";
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, this.connectionFactory);
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName,
this.connectionFactory);
String payload = "testing";
handler.handleMessage(MessageBuilder.withPayload(payload).build());
@@ -105,7 +106,8 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2";
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, this.connectionFactory);
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName,
this.connectionFactory);
handler.setExtractPayload(false);
Message<String> message = MessageBuilder.withPayload("testing").build();
@@ -131,8 +133,9 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
final String queueName = "si.test.testRedisQueueOutboundChannelAdapter2";
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName, this.connectionFactory);
handler.setSerializer(new JacksonJsonRedisSerializer<Object>(Object.class));
final RedisQueueOutboundChannelAdapter handler = new RedisQueueOutboundChannelAdapter(queueName,
this.connectionFactory);
handler.setSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
RedisTemplate<String, ?> redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(this.connectionFactory);
@@ -168,7 +171,8 @@ public class RedisQueueOutboundChannelAdapterTests extends RedisAvailableTests {
String result = redisTemplate.boundListOps(queueName).rightPop(5000, TimeUnit.MILLISECONDS);
assertNotNull(result);
InboundMessageMapper<String> mapper = new JsonInboundMessageMapper(String.class, new Jackson2JsonMessageParser());
InboundMessageMapper<String> mapper = new JsonInboundMessageMapper(String.class,
new Jackson2JsonMessageParser());
Message<?> resultMessage = mapper.toMessage(result);
assertEquals(message.getPayload(), resultMessage.getPayload());
}

View File

@@ -240,9 +240,9 @@ public class Child {
type="foo.MyDomainObject"/>]]></programlisting>
</para>
<para>
These use a vanilla Jackson ObjectMapper by default. If you wish to customize the ObjectMapper (for example,
to configure the 'ALLOW_COMMENTS' feature when parsing JSON), you can supply a reference to your custom ObjectMapper bean using
the object-mapper attribute.
These use a vanilla <interfacename>JsonObjectMapper</interfacename> by default based on implementation from
classpath. You can provide your own custom <interfacename>JsonObjectMapper</interfacename> implementation
with appropriate options or based on required library (e.g. GSON).
</para>
<para>
<programlisting language="xml"><![CDATA[<int:json-to-object-transformer input-channel="objectMapperInput"
@@ -253,29 +253,33 @@ public class Child {
Beginning with version 3.0, the <code>object-mapper</code> attribute references an instance of a new
strategy interface <interfacename>JsonObjectMapper</interfacename>. This abstraction allows multiple
implementations of json mappers to be used. Implementations that wrap
<ulink url="http://jackson.codehaus.org">Jackson 1.x</ulink> and
<ulink url="https://github.com/RichardHightower/boon">Boon</ulink> and
<ulink url="https://github.com/FasterXML">Jackson 2</ulink> are
provided, with the version being detected on the classpath. These classes are
<classname>JacksonJsonObjectMapper</classname> and <classname>Jackson2JsonObjectMapper</classname>.
<classname>BoonJsonObjectMapper</classname> and <classname>Jackson2JsonObjectMapper</classname>.
</para>
<para>
For backward compatibility, a simple Jackson 1.x <classname>ObjectMapper</classname> can be provided
instead of a <interfacename>JsonObjectMapper</interfacename>. This will be removed in a future release.
Note, <classname>BoonJsonObjectMapper</classname> is provided since <emphasis>version 4.1</emphasis>.
</para>
</note>
<para>
<important>
If there are requirements to use both Jackson libraries in the same application, keep in mind that
before version 3.0, the JSON transformers used only Jackson 1.x and, from 3.0 on, the framework will
select Jackson 2 by default, if both are on the classpath.
So, to avoid unexpected issues with Jackson's mapping features, when using annotations, there may be
a need to apply annotations from both Jacksons on domain classes:
If there are requirements to use both Jackson libraries and/or Boon in the same application,
keep in mind that before version 3.0, the JSON transformers used only Jackson 1.x.
From <emphasis>4.1</emphasis> on, the framework will select Jackson 2 by default ahead of the
Boon implementation if both are on the classpath.
Jackson 1.x is no longer supported by the framework internally but, of course, you can still use it
within your code.
To avoid unexpected issues with JSON mapping features, when using annotations, there may be
a need to apply annotations from both Jacksons and/or Boon on domain classes:
<programlisting language="java"><![CDATA[@org.codehaus.jackson.annotate.JsonIgnoreProperties(ignoreUnknown=true)
@com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown=true)
@org.boon.json.annotations.JsonIgnoreProperties("foo")
public class Foo {
@org.codehaus.jackson.annotate.JsonProperty("fooBar")
@com.fasterxml.jackson.annotation.JsonProperty("fooBar")
@org.boon.json.annotations.JsonProperty("fooBar")
public Object bar;
}]]></programlisting>
@@ -377,6 +381,7 @@ public class Foo {
The node JSON representation provides efficiency for using the <classname>JsonPropertyAccessor</classname>, when the
downstream message flow uses SpEL expressions with access to the properties of the JSON data. See
<xref linkend="spel-property-accessors"/>.
When using Boon, the <code>NODE</code> representation is a <interfacename>Map&lt;String, Object&gt;</interfacename>
</para>
</section>

View File

@@ -162,5 +162,13 @@
See <xref linkend="amqp-channels"/> for more information.
</para>
</section>
<section id="4.1-json-transformers">
<title>BoonJsonObjectMapper</title>
<para>
The <emphasis>Boon</emphasis> <interfacename>JsonObjectMapper</interfacename> is now provided
for the JSON transformers. See <xref linkend="transformer"/> for more information.
</para>
</section>
</section>
</chapter>