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

@@ -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());