Merge pull request #774 from artembilan/INT-2831

* INT-2831:
  INT-2831: Add Jackson 2 support
This commit is contained in:
Gary Russell
2013-04-30 09:38:15 -04:00
28 changed files with 1105 additions and 234 deletions

View File

@@ -40,6 +40,7 @@ subprojects { subproject ->
groovyVersion = '2.1.0'
hamcrestVersion = '1.3'
jacksonVersion = '1.9.2'
jackson2Version = '2.1.2'
javaxActivationVersion = '1.1.1'
junitVersion = '4.11'
log4jVersion = '1.2.12'
@@ -145,7 +146,6 @@ project('spring-integration-amqp') {
dependencies {
compile project(":spring-integration-core")
compile "org.springframework:spring-tx:$springVersion"
compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional)
compile("org.springframework.amqp:spring-rabbit:$springAmqpVersion") {
exclude group: 'org.springframework', module: 'spring-aop'
exclude group: 'org.springframework', module: 'spring-beans'
@@ -171,6 +171,7 @@ project('spring-integration-core') {
compile "org.springframework:spring-tx:$springVersion"
compile "org.springframework.retry:spring-retry:$springRetryVersion"
compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional)
compile("com.fasterxml.jackson.core:jackson-databind:$jackson2Version", optional)
testCompile "org.aspectj:aspectjweaver:$aspectjVersion"
}
}
@@ -407,8 +408,6 @@ project('spring-integration-redis') {
dependencies {
compile project(":spring-integration-core")
compile "org.springframework:spring-tx:$springVersion"
compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion")
compile("org.codehaus.jackson:jackson-core-asl:$jacksonVersion")
compile ("org.springframework.data:spring-data-redis:$springDataRedisVersion") {
exclude group: 'org.springframework', module: 'spring-core'
exclude group: 'org.springframework', module: 'spring-context-support'

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,20 +16,23 @@
package org.springframework.integration.config.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.json.JsonToObjectTransformer;
/**
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
public class JsonToObjectTransformerParser extends AbstractTransformerParser {
@Override
protected String getTransformerClassName() {
return "org.springframework.integration.json.JsonToObjectTransformer";
return JsonToObjectTransformer.class.getName();
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* 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.
@@ -16,22 +16,24 @@
package org.springframework.integration.config.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.MessageHeaders;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.json.ObjectToJsonTransformer;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
public class ObjectToJsonTransformerParser extends AbstractTransformerParser {
@Override
protected String getTransformerClassName() {
return "org.springframework.integration.json.ObjectToJsonTransformer";
return ObjectToJsonTransformer.class.getName();
}
@Override
@@ -40,8 +42,9 @@ public class ObjectToJsonTransformerParser extends AbstractTransformerParser {
if (StringUtils.hasText(objectMapper)) {
builder.addConstructorArgReference(objectMapper);
}
if (element.hasAttribute(MessageHeaders.CONTENT_TYPE)){
if (element.hasAttribute("content-type")){
builder.addPropertyValue("contentType", element.getAttribute("content-type"));
}
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.Message;
import org.springframework.integration.support.MessageBuilder;
/**
* Base {@link JsonInboundMessageMapper.JsonMessageParser} implementation for Jackson processors.
*
* @author Artem Bilan
* @since 3.0
*
* @see Jackson2JsonMessageParser
* @see JacksonJsonMessageParser
*/
abstract class AbstractJacksonJsonMessageParser<P> implements JsonInboundMessageMapper.JsonMessageParser<P> {
protected final JsonObjectMapper<P> objectMapper;
protected volatile JsonInboundMessageMapper messageMapper;
protected AbstractJacksonJsonMessageParser(JsonObjectMapper<P> objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public Message<?> doInParser(JsonInboundMessageMapper messageMapper, String jsonMessage) throws Exception {
if (this.messageMapper == null) {
this.messageMapper = messageMapper;
}
P parser = this.createJsonParser(jsonMessage);
if (messageMapper.isMapToPayload()) {
try {
return MessageBuilder.withPayload(this.readPayload(parser)).build();
}
catch (Exception ex) {
throw new IllegalArgumentException("Mapping of JSON message " + jsonMessage +
" directly to payload of type " + messageMapper.getPayloadType() + " failed.", ex);
}
}
else {
return this.parseWithHeaders(parser, jsonMessage);
}
}
protected Object readPayload(P parser) throws Exception {
return objectMapper.fromJson(parser, this.messageMapper.getPayloadType());
}
protected abstract Message<?> parseWithHeaders(P parser, String jsonMessage) throws Exception;
protected abstract P createJsonParser(String jsonMessage) throws Exception;
}

View File

@@ -0,0 +1,73 @@
/*
* 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 java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.mapping.InboundMessageMapper;
import org.springframework.util.Assert;
/**
* Abstract {@link InboundMessageMapper} implementation that maps incoming JSON messages
* to a {@link org.springframework.integration.Message} with the specified payload type.
*
* @author Artem Bilan
* @since 3.0
*
* @see JsonInboundMessageMapper
*/
public abstract class AbstractJsonInboundMessageMapper<P> implements InboundMessageMapper<String> {
protected static final String MESSAGE_FORMAT_ERROR = "JSON message is invalid. Expected a message in the format of either " +
"{\"headers\":{...},\"payload\":{...}} or {\"payload\":{...}.\"headers\":{...}} but was ";
protected static final Map<String, Class<?>> DEFAULT_HEADER_TYPES = new HashMap<String, Class<?>>();
static {
DEFAULT_HEADER_TYPES.put(MessageHeaders.PRIORITY, Integer.class);
DEFAULT_HEADER_TYPES.put(MessageHeaders.EXPIRATION_DATE, Long.class);
DEFAULT_HEADER_TYPES.put(MessageHeaders.SEQUENCE_SIZE, Integer.class);
DEFAULT_HEADER_TYPES.put(MessageHeaders.SEQUENCE_NUMBER, Integer.class);
}
protected final Type payloadType;
protected final Map<String, Class<?>> headerTypes = DEFAULT_HEADER_TYPES;
protected volatile boolean mapToPayload = false;
public AbstractJsonInboundMessageMapper(Type payloadType) {
Assert.notNull(payloadType, "payloadType must not be null");
this.payloadType = payloadType;
}
public void setHeaderTypes(Map<String, Class<?>> headerTypes) {
this.headerTypes.putAll(headerTypes);
}
public void setMapToPayload(boolean mapToPayload) {
this.mapToPayload = mapToPayload;
}
protected abstract Object readPayload(P parser, String jsonMessage) throws Exception;
protected abstract Map<String, Object> readHeaders(P parser, String jsonMessage) throws Exception;
}

View File

@@ -0,0 +1,99 @@
/*
* 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 java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
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 2 JSON-processor (@link https://github.com/FasterXML).
*
* @author Artem Bilan
* @since 3.0
*/
class Jackson2JsonMessageParser extends AbstractJacksonJsonMessageParser<JsonParser> {
public Jackson2JsonMessageParser() {
super(new Jackson2JsonObjectMapper());
}
@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();
try {
payload = this.readPayload(parser);
}
catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping payload of JSON message " + jsonMessage +
" to payload type " + messageMapper.getPayloadType() + " failed.", ex);
}
}
}
Assert.notNull(headers, error);
return MessageBuilder.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();
Class<?> headerType = this.messageMapper.getHeaderTypes().containsKey(headerName) ?
this.messageMapper.getHeaderTypes().get(headerName) : Object.class;
try {
headers.put(headerName, this.objectMapper.fromJson(parser, headerType));
}
catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping header \"" + headerName + "\" of JSON message " +
jsonMessage + " to header type " + messageMapper.getPayloadType() + " failed.", ex);
}
}
return headers;
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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 java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.Assert;
/**
* Jackson 2 JSON-processor (@link https://github.com/FasterXML) {@linkplain JsonObjectMapper} implementation.
* Delegates <code>toJson</code> and <code>fromJson</code>
* to the {@linkplain com.fasterxml.jackson.databind.ObjectMapper}
*
* @author Artem Bilan
* @since 3.0
*/
public class Jackson2JsonObjectMapper implements JsonObjectMapper<JsonParser> {
private final ObjectMapper objectMapper;
public Jackson2JsonObjectMapper() {
this.objectMapper = new ObjectMapper();
}
public Jackson2JsonObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper must not be null");
this.objectMapper = objectMapper;
}
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);
}
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(JsonParser parser, Type valueType) throws Exception {
return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType));
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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 java.util.LinkedHashMap;
import java.util.Map;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.JsonMappingException;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
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).
*
* @author Artem Bilan
* @since 3.0
*/
class JacksonJsonMessageParser extends AbstractJacksonJsonMessageParser<JsonParser> {
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();
try {
payload = this.readPayload(parser);
}
catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping payload of JSON message " + jsonMessage +
" to payload type " + messageMapper.getPayloadType() + " failed.", ex);
}
}
}
Assert.notNull(headers, error);
return MessageBuilder.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();
Class<?> headerType = this.messageMapper.getHeaderTypes().containsKey(headerName) ?
this.messageMapper.getHeaderTypes().get(headerName) : Object.class;
try {
headers.put(headerName, this.objectMapper.fromJson(parser, headerType));
}
catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping header \"" + headerName + "\" of JSON message " +
jsonMessage + " to header type " + messageMapper.getPayloadType() + " failed.", ex);
}
}
return headers;
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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 java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
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}
*
* @author Artem Bilan
* @since 3.0
*/
public class JacksonJsonObjectMapper implements JsonObjectMapper<JsonParser> {
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;
}
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);
}
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
return this.objectMapper.readValue(json, valueType);
}
@Override
public <T> T fromJson(JsonParser parser, Type valueType) throws Exception {
return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType));
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.util.ClassUtils;
/**
* Simple factory to provide {@linkplain Jackson2JsonObjectMapper} or {@linkplain JacksonJsonObjectMapper}
* instances dependently of jackson-databind or jackson-mapper-asl libs in the classpath.
* If there are both libs in the classpath, it prefers Jackson 2 JSON-processor implementation.
* If there is no any of them, {@linkplain IllegalStateException} will be thrown.
*
* @author Artem Bilan
* @since 3.0
*
* @see Jackson2JsonObjectMapper
* @see JacksonJsonObjectMapper
* @see JsonToObjectTransformer
* @see ObjectToJsonTransformer
*/
public final class JacksonJsonObjectMapperProvider {
private static final ClassLoader classLoader = JacksonJsonObjectMapperProvider.class.getClassLoader();
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
private static final boolean jacksonPresent =
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 JsonObjectMapper<?> newInstance() {
if (jackson2Present) {
return new Jackson2JsonObjectMapper();
}
if(jacksonPresent) {
return new JacksonJsonObjectMapper();
}
throw NO_JACKSON_LIB_EXCEPTION;
}
public static JsonInboundMessageMapper.JsonMessageParser<?> newJsonMessageParser() {
if (jackson2Present) {
return new Jackson2JsonMessageParser();
}
if(jacksonPresent) {
return new Jackson2JsonMessageParser();
}
throw NO_JACKSON_LIB_EXCEPTION;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* 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.
@@ -16,142 +16,78 @@
package org.springframework.integration.json;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.lang.reflect.Type;
import java.util.Map;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.mapping.InboundMessageMapper;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
/**
* {@link InboundMessageMapper} implementation that maps incoming JSON messages to a {@link Message} with the specified payload type.
*
* {@link org.springframework.integration.mapping.InboundMessageMapper} implementation that maps incoming JSON messages
* to a {@link Message} with the specified payload type.
* By default it uses {@link JacksonJsonObjectMapperProvider} to get Jackson or Jackson 2 {@link JsonMessageParser}
* implementation dependently from classpath.
* Any other {@link JsonMessageParser} implementation may be provided through the appropriate constructor.
*
* @author Jeremy Grelle
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
public class JsonInboundMessageMapper implements InboundMessageMapper<String> {
private static final String MESSAGE_FORMAT_ERROR = "JSON message is invalid. Expected a message in the format of either " +
"{\"headers\":{...},\"payload\":{...}} or {\"payload\":{...}.\"headers\":{...}} but was ";
private static final Map<String, Class<?>> DEFAULT_HEADER_TYPES = new HashMap<String, Class<?>>();
static {
DEFAULT_HEADER_TYPES.put(MessageHeaders.PRIORITY, Integer.class);
DEFAULT_HEADER_TYPES.put(MessageHeaders.EXPIRATION_DATE, Long.class);
DEFAULT_HEADER_TYPES.put(MessageHeaders.SEQUENCE_SIZE, Integer.class);
DEFAULT_HEADER_TYPES.put(MessageHeaders.SEQUENCE_NUMBER, Integer.class);
}
private final JavaType payloadType;
private final Map<String, Class<?>> headerTypes = DEFAULT_HEADER_TYPES;
private volatile ObjectMapper objectMapper = new ObjectMapper();
private volatile boolean mapToPayload = false;
public class JsonInboundMessageMapper extends AbstractJsonInboundMessageMapper<JsonInboundMessageMapper.JsonMessageParser<?>> {
private volatile JsonMessageParser<?> messageParser;
public JsonInboundMessageMapper(Class<?> payloadType) {
Assert.notNull(payloadType, "payloadType must not be null");
this.payloadType = TypeFactory.defaultInstance().constructType(payloadType);
this((Type) payloadType);
}
public JsonInboundMessageMapper(TypeReference<?> typeReference) {
Assert.notNull(typeReference, "typeReference must not be null");
this.payloadType = TypeFactory.defaultInstance().constructType(typeReference);
public JsonInboundMessageMapper(Type payloadType) {
this(payloadType, null);
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper must not be null");
this.objectMapper = objectMapper;
public JsonInboundMessageMapper(Class<?> payloadType, JsonMessageParser<?> messageParser) {
this((Type) payloadType, messageParser);
}
public void setHeaderTypes(Map<String, Class<?>> headerTypes) {
this.headerTypes.putAll(headerTypes);
public JsonInboundMessageMapper(Type payloadType, JsonMessageParser<?> messageParser) {
super(payloadType);
this.messageParser = messageParser != null ? messageParser : JacksonJsonObjectMapperProvider.newJsonMessageParser();
}
public void setMapToPayload(boolean mapToPayload) {
this.mapToPayload = mapToPayload;
public boolean isMapToPayload() {
return mapToPayload;
}
public Type getPayloadType() {
return payloadType;
}
public Map<String, Class<?>> getHeaderTypes() {
return headerTypes;
}
public Message<?> toMessage(String jsonMessage) throws Exception {
JsonParser parser = new JsonFactory().createJsonParser(jsonMessage);
if (this.mapToPayload) {
try {
return MessageBuilder.withPayload(readPayload(parser, jsonMessage)).build();
}
catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping of JSON message " + jsonMessage +
" directly to payload of type " + this.payloadType.getRawClass().getName() + " failed.", ex);
}
}
else {
String error = 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();
try {
payload = readPayload(parser, jsonMessage);
}
catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping payload of JSON message " + jsonMessage +
" to payload type " + this.payloadType.getRawClass().getName() + " failed.", ex);
}
}
}
Assert.notNull(headers, error);
return MessageBuilder.withPayload(payload).copyHeaders(headers).build();
}
return this.messageParser.doInParser(this, jsonMessage);
}
protected 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();
Class<?> headerType = this.headerTypes.containsKey(headerName) ?
this.headerTypes.get(headerName) : Object.class;
try {
headers.put(headerName, this.objectMapper.readValue(parser, headerType));
}
catch (JsonMappingException ex) {
throw new IllegalArgumentException("Mapping header \"" + headerName + "\" of JSON message " +
jsonMessage + " to header type " + this.payloadType.getRawClass().getName() + " failed.", ex);
}
}
return headers;
@Override
protected Map<String, Object> readHeaders(JsonMessageParser<?> parser, String jsonMessage) throws Exception {
//No-op
return null;
}
protected Object readPayload(JsonParser parser, String jsonMessage) throws Exception {
return this.objectMapper.readValue(parser, this.payloadType);
@Override
protected Object readPayload(JsonMessageParser<?> parser, String jsonMessage) throws Exception {
//No-op
return null;
}
public static interface JsonMessageParser<P> {
Message<?> doInParser(JsonInboundMessageMapper messageMapper, String jsonMessage) throws Exception;
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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 java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
/**
* Strategy interface to convert an Object to/from the JSON representation.
*
* @author Artem Bilan
* @since 3.0
*
* @see JsonToObjectTransformer
* @see ObjectToJsonTransformer
* @see JacksonJsonObjectMapper
* @see Jackson2JsonObjectMapper
*/
public interface JsonObjectMapper<P> {
String toJson(Object value) throws Exception;
void toJson(Object value, Writer writer) throws Exception;
<T> T fromJson(String json, Class<T> valueType) throws Exception;
<T> T fromJson(Reader json, Class<T> valueType) throws Exception;
<T> T fromJson(P parser, Type valueType) throws Exception;
}

View File

@@ -0,0 +1,56 @@
/*
* 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 java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
/**
* Simple {@linkplain JsonObjectMapper} adapter implementation, if there is no need
* to provide entire operations implementation.
*
* @author Artem Bilan
* @since 3.0
*/
public abstract class JsonObjectMapperAdapter<P> implements JsonObjectMapper<P> {
@Override
public String toJson(Object value) throws Exception {
return null;
}
@Override
public void toJson(Object value, Writer writer) throws Exception {
}
@Override
public <T> T fromJson(String json, Class<T> valueType) throws Exception {
return null;
}
@Override
public <T> T fromJson(Reader json, Class<T> valueType) throws Exception {
return null;
}
@Override
public <T> T fromJson(P parser, Type valueType) throws Exception {
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -15,32 +15,31 @@
*/
package org.springframework.integration.json;
import java.io.StringWriter;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.integration.Message;
import org.springframework.integration.mapping.OutboundMessageMapper;
import org.springframework.util.Assert;
/**
* {@link OutboundMessageMapper} implementation the converts a {@link Message} to a JSON string representation.
*
*
* @author Jeremy Grelle
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
public class JsonOutboundMessageMapper implements OutboundMessageMapper<String> {
private volatile boolean shouldExtractPayload = false;
private volatile ObjectMapper objectMapper = new ObjectMapper();
private volatile JsonObjectMapper<?> jsonObjectMapper;
public JsonOutboundMessageMapper() {
this(JacksonJsonObjectMapperProvider.newInstance());
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper must not be null");
this.objectMapper = objectMapper;
public JsonOutboundMessageMapper(JsonObjectMapper<?> jsonObjectMapper) {
Assert.notNull(jsonObjectMapper, "jsonObjectMapper must not be null");
this.jsonObjectMapper = jsonObjectMapper;
}
public void setShouldExtractPayload(boolean shouldExtractPayload) {
@@ -48,14 +47,7 @@ public class JsonOutboundMessageMapper implements OutboundMessageMapper<String>
}
public String fromMessage(Message<?> message) throws Exception {
StringWriter writer = new StringWriter();
if (this.shouldExtractPayload) {
this.objectMapper.writeValue(writer, message.getPayload());
}
else {
this.objectMapper.writeValue(writer, message);
}
return writer.toString();
return this.jsonObjectMapper.toJson(this.shouldExtractPayload ? message.getPayload() : message);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,37 +16,66 @@
package org.springframework.integration.json;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.integration.transformer.AbstractPayloadTransformer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Transformer implementation that converts a JSON string payload into an instance of the provided target Class.
* By default this transformer uses {@linkplain JacksonJsonObjectMapperProvider} 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.
*
* @author Mark Fisher
* @author Artem Bilan
* @see JsonObjectMapper
* @see JacksonJsonObjectMapperProvider
* @since 2.0
*/
public class JsonToObjectTransformer<T> extends AbstractPayloadTransformer<String, T> {
private final Class<T> targetClass;
private final ObjectMapper objectMapper;
private final JsonObjectMapper<?> jsonObjectMapper;
public JsonToObjectTransformer(Class<T> targetClass) {
this(targetClass, null);
}
public JsonToObjectTransformer(Class<T> targetClass, ObjectMapper objectMapper) {
/**
* Backward compatibility - allows existing configurations using Jackson 1.x to inject
* an ObjectMapper directly.
* @deprecated in favor of {@link #JsonToObjectTransformer(Class, JsonObjectMapper)}
*/
@Deprecated
public JsonToObjectTransformer(Class<T> targetClass, Object objectMapper) throws ClassNotFoundException {
Assert.notNull(targetClass, "targetClass must not be null");
this.targetClass = targetClass;
this.objectMapper = (objectMapper != null) ? objectMapper : new ObjectMapper();
if (objectMapper != null) {
try {
Class<?> objectMapperClass = ClassUtils.forName("org.codehaus.jackson.map.ObjectMapper", ClassUtils.getDefaultClassLoader());
Assert.isTrue(objectMapperClass.isAssignableFrom(objectMapper.getClass()));
this.jsonObjectMapper = new JacksonJsonObjectMapper((org.codehaus.jackson.map.ObjectMapper) objectMapper);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
else {
this.jsonObjectMapper = JacksonJsonObjectMapperProvider.newInstance();
}
}
public JsonToObjectTransformer(Class<T> targetClass, JsonObjectMapper<?> jsonObjectMapper) {
Assert.notNull(targetClass, "targetClass must not be null");
this.targetClass = targetClass;
this.jsonObjectMapper = (jsonObjectMapper != null) ? jsonObjectMapper : JacksonJsonObjectMapperProvider.newInstance();
}
@Override
protected T transformPayload(String payload) throws Exception {
return this.objectMapper.readValue(payload, this.targetClass);
return this.jsonObjectMapper.fromJson(payload, this.targetClass);
}
}

View File

@@ -15,42 +15,64 @@
*/
package org.springframework.integration.json;
import java.io.StringWriter;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.transformer.AbstractTransformer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
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
* 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.
*
* @author Mark Fisher
* @author James Carr
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class ObjectToJsonTransformer extends AbstractTransformer {
public static final String JSON_CONTENT_TYPE = "application/json";
private final ObjectMapper objectMapper;
private final JsonObjectMapper<?> jsonObjectMapper;
private volatile String contentType = JSON_CONTENT_TYPE;
private volatile boolean contentTypeExplicitlySet = false;
public ObjectToJsonTransformer(ObjectMapper objectMapper) {
/**
* Backward compatibility - allows existing configurations using Jackson 1.x to inject
* an ObjectMapper directly.
* @deprecated in favor of {@link #ObjectToJsonTransformer(JsonObjectMapper)}
*/
@Deprecated
public ObjectToJsonTransformer(Object objectMapper) {
Assert.notNull(objectMapper, "objectMapper must not be null");
this.objectMapper = objectMapper;
try {
Class<?> objectMapperClass = ClassUtils.forName("org.codehaus.jackson.map.ObjectMapper", ClassUtils.getDefaultClassLoader());
Assert.isTrue(objectMapperClass.isAssignableFrom(objectMapper.getClass()));
this.jsonObjectMapper = new JacksonJsonObjectMapper((org.codehaus.jackson.map.ObjectMapper) objectMapper);
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
public ObjectToJsonTransformer(JsonObjectMapper<?> jsonObjectMapper) {
Assert.notNull(jsonObjectMapper, "jsonObjectMapper must not be null");
this.jsonObjectMapper = jsonObjectMapper;
}
public ObjectToJsonTransformer() {
this.objectMapper = new ObjectMapper();
this.jsonObjectMapper = JacksonJsonObjectMapperProvider.newInstance();
}
/**
@@ -58,29 +80,23 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
*
* @param contentType
*/
public void setContentType(String contentType){
public void setContentType(String contentType) {
// only null assertion is needed since "" is a valid value
Assert.notNull(contentType, "'contentType' must not be null");
this.contentTypeExplicitlySet = true;
this.contentType = contentType.trim();
}
private String transformPayload(Object payload) throws Exception {
StringWriter writer = new StringWriter();
this.objectMapper.writeValue(writer, payload);
return writer.toString();
}
@Override
protected Object doTransform(Message<?> message) throws Exception {
String payload = this.transformPayload(message.getPayload());
String payload = this.jsonObjectMapper.toJson(message.getPayload());
MessageBuilder<String> messageBuilder = MessageBuilder.withPayload(payload);
LinkedCaseInsensitiveMap<Object> headers = new LinkedCaseInsensitiveMap<Object>();
headers.putAll(message.getHeaders());
if (headers.containsKey(MessageHeaders.CONTENT_TYPE)) {
if (this.contentTypeExplicitlySet){
if (this.contentTypeExplicitlySet) {
// override, unless empty
if (StringUtils.hasLength(this.contentType)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
@@ -93,4 +109,5 @@ public class ObjectToJsonTransformer extends AbstractTransformer {
messageBuilder.copyHeaders(headers);
return messageBuilder.build();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* 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.
@@ -20,7 +20,8 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.integration.json.JacksonJsonObjectMapperProvider;
import org.springframework.integration.json.JsonObjectMapper;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -31,7 +32,7 @@ import org.springframework.util.StringUtils;
* via the {@link ObjectToMapTransformer#setShouldFlattenKeys(boolean)} method. It supports Collections, Maps and Arrays
* which means that for flat maps it will flatten an Object's properties. Below is an example showing how a flattened
* Object hierarchy is represented when 'shouldFlattenKeys' is TRUE.<br>
*
*
* <code>
* public class Person {
* public String name = "John";
@@ -41,17 +42,20 @@ import org.springframework.util.StringUtils;
* private String street = "123 Main Street";
* }
* </code>
*
*
* The resulting Map would look like this:
* <code>
* {name=John, address.street=123 Main Street}
* </code>
*
* </code>
*
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
public class ObjectToMapTransformer extends AbstractPayloadTransformer<Object, Map<?,?>> {
private final JsonObjectMapper<?> jsonObjectMapper = JacksonJsonObjectMapperProvider.newInstance();
private volatile boolean shouldFlattenKeys = true;
public void setShouldFlattenKeys(boolean shouldFlattenKeys) {
@@ -60,8 +64,7 @@ public class ObjectToMapTransformer extends AbstractPayloadTransformer<Object, M
@SuppressWarnings("unchecked")
protected Map<String, Object> transformPayload(Object payload) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> result = new ObjectMapper().readValue(mapper.writeValueAsString(payload), Map.class);
Map<String,Object> result = this.jsonObjectMapper.fromJson(this.jsonObjectMapper.toJson(payload), Map.class);
if (this.shouldFlattenKeys) {
result = this.flattenMap(result);
}
@@ -99,9 +102,9 @@ public class ObjectToMapTransformer extends AbstractPayloadTransformer<Object, M
}
else if (element instanceof Collection) {
this.doProcessCollection(propertyPrefix, (Collection<?>) element, resultMap);
}
}
else if (element != null && element.getClass().isArray()) {
Collection<?> collection = CollectionUtils.arrayToList(element);
Collection<?> collection = CollectionUtils.arrayToList(element);
this.doProcessCollection(propertyPrefix, collection, resultMap);
}
else {

View File

@@ -2124,15 +2124,19 @@
<xsd:attribute name="object-mapper" use="optional">
<xsd:annotation>
<xsd:documentation>
Reference to a Jackson ObjectMapper instance to be provided optionally
if the default ObjectMapper
configuration is not desirable.
Optional reference to a JsonObjectMapper instance.
By default, a JsonObjectMapper that uses a Jackson 2 ObjectMapper, or Jackson ObjectMapper
implementation is used, depending on the jars on the classpath.
Note: for backward compatibility, this attribute can take a reference to the Jackson 1 ObjectMapper bean.
This Jackson 1 ObjectMapper backward compatibility is deprecated
and will be removed in the Spring Integration 3.1 or above.
</xsd:documentation>
<!-- TODO: Revert in the Spring Integration 3.1 or above after removing Jackson 1 backward compatibility
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.codehaus.jackson.map.ObjectMapper" />
<tool:expected-type type="org.springframework.integration.json.JsonObjectMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:appinfo>-->
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
@@ -2166,15 +2170,19 @@
<xsd:attribute name="object-mapper" use="optional">
<xsd:annotation>
<xsd:documentation>
Reference to a Jackson ObjectMapper instance to be provided optionally
if the default ObjectMapper
configuration is not desirable.
Optional reference to a JsonObjectMapper instance.
By default, a JsonObjectMapper that uses a Jackson 2 ObjectMapper, or Jackson ObjectMapper
implementation is used, depending on the jars on the classpath.
Note: for backward compatibility this attribute can take a reference to the Jackson 1 ObjectMapper bean.
This Jackson 1 ObjectMapper backward compatibility is deprecated
and will be removed in the Spring Integration 3.1 or above.
</xsd:documentation>
<!-- TODO: Revert in the Spring Integration 3.1 or above after removing Jackson 1 backward compatibility
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.codehaus.jackson.map.ObjectMapper" />
<tool:expected-type type="org.springframework.integration.json.JsonObjectMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:appinfo>-->
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -20,18 +20,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.junit.Test;
@@ -43,11 +37,11 @@ import org.springframework.integration.support.MessageBuilder;
* @author Jeremy Grelle
* @author Mark Fisher
* @author Dave Syer
* @author Artem Bilan
*/
public class JsonInboundMessageMapperTests {
private ObjectMapper mapper = new ObjectMapper();
private final JsonObjectMapper<?> mapper = JacksonJsonObjectMapperProvider.newInstance();
@Factory
public static Matcher<Message<?>> sameExceptImmutableHeaders(Message<?> operand) {
@@ -64,7 +58,7 @@ public class JsonInboundMessageMapperTests {
Message<?> result = mapper.toMessage(jsonMessage);
assertThat(result, sameExceptImmutableHeaders(expected));
}
@Test
public void testToMessageWithStringPayload() throws Exception {
String jsonMessage = "\"myPayloadStuff\"";
@@ -74,7 +68,7 @@ public class JsonInboundMessageMapperTests {
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result.getPayload());
}
@Test
public void testToMessageWithHeadersAndBeanPayload() throws Exception {
TestBean bean = new TestBean();
@@ -85,7 +79,7 @@ public class JsonInboundMessageMapperTests {
Message<?> result = mapper.toMessage(jsonMessage);
assertThat(result, sameExceptImmutableHeaders(expected));
}
@Test
public void testToMessageWithBeanPayload() throws Exception {
TestBean expected = new TestBean();
@@ -95,7 +89,7 @@ public class JsonInboundMessageMapperTests {
Message<?> result = mapper.toMessage(jsonMessage);
assertEquals(expected, result.getPayload());
}
@Test
public void testToMessageWithBeanHeaderAndStringPayload() throws Exception {
TestBean bean = new TestBean();
@@ -109,27 +103,27 @@ public class JsonInboundMessageMapperTests {
Message<?> result = mapper.toMessage(jsonMessage);
assertThat(result, sameExceptImmutableHeaders(expected));
}
@Test
@Test
public void testToMessageWithHeadersAndListOfStringsPayload() throws Exception {
UUID id = UUID.randomUUID();
String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":[\"myPayloadStuff1\",\"myPayloadStuff2\",\"myPayloadStuff3\"]}";
List<String> expectedList = Arrays.asList(new String[]{"myPayloadStuff1", "myPayloadStuff2", "myPayloadStuff3"});
List<String> expectedList = Arrays.asList("myPayloadStuff1", "myPayloadStuff2", "myPayloadStuff3");
Message<List<String>> expected = MessageBuilder.withPayload(expectedList).setHeader("foo", 123).setHeader("bar", "abc").build();
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new TypeReference<List<String>>(){});
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new ParameterizedTypeReference<List<String>>(){}.getType());
Message<?> result = mapper.toMessage(jsonMessage);
assertThat(result, sameExceptImmutableHeaders(expected));
}
@Test
@Test
public void testToMessageWithHeadersAndListOfBeansPayload() throws Exception {
TestBean bean1 = new TestBean();
TestBean bean2 = new TestBean();
UUID id = UUID.randomUUID();
String jsonMessage = "{\"headers\":{\"timestamp\":1,\"id\":\"" + id + "\",\"foo\":123,\"bar\":\"abc\"},\"payload\":[" + getBeanAsJson(bean1) + "," + getBeanAsJson(bean2) + "]}";
List<TestBean> expectedList = Arrays.asList(new TestBean[]{bean1, bean2});
List<TestBean> expectedList = Arrays.asList(bean1, bean2);
Message<List<TestBean>> expected = MessageBuilder.withPayload(expectedList).setHeader("foo", 123).setHeader("bar", "abc").build();
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new TypeReference<List<TestBean>>(){});
JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new ParameterizedTypeReference<List<TestBean>>(){}.getType());
Message<?> result = mapper.toMessage(jsonMessage);
assertThat(result, sameExceptImmutableHeaders(expected));
}
@@ -236,10 +230,8 @@ public class JsonInboundMessageMapperTests {
}
private String getBeanAsJson(TestBean bean) throws JsonGenerationException, JsonMappingException, IOException {
StringWriter writer = new StringWriter();
mapper.writeValue(writer, bean);
return writer.toString();
};
private String getBeanAsJson(TestBean bean) throws Exception {
return mapper.toJson(bean);
}
}

View File

@@ -2,18 +2,24 @@
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<json-to-object-transformer input-channel="defaultObjectMapperInput"
<json-to-object-transformer id="defaultJacksonMapperTransformer" input-channel="defaultObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"/>
<json-to-object-transformer input-channel="customObjectMapperInput"
<json-to-object-transformer id="customJacksonMapperTransformer" input-channel="customObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"
object-mapper="customObjectMapper"/>
<beans:bean id="customObjectMapper" class="org.springframework.integration.json.JsonToObjectTransformerParserTests$CustomObjectMapper"/>
</beans:beans>
<json-to-object-transformer id="customJsonMapperTransformer" input-channel="customJsonObjectMapperInput"
type="org.springframework.integration.json.JsonToObjectTransformerParserTests$TestPerson"
object-mapper="customJsonObjectMapper"/>
<beans:bean id="customJsonObjectMapper" class="org.springframework.integration.json.JsonToObjectTransformerParserTests$CustomJsonObjectMapper"/>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -18,22 +18,26 @@ package org.springframework.integration.json;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import org.codehaus.jackson.JsonParser.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
@ContextConfiguration
@@ -46,9 +50,32 @@ public class JsonToObjectTransformerParserTests {
@Autowired
private volatile MessageChannel customObjectMapperInput;
@Autowired
private volatile MessageChannel customJsonObjectMapperInput;
@Autowired
@Qualifier("defaultJacksonMapperTransformer.handler")
private MessageHandler defaultJacksonMapperTransformer;
@Autowired
@Qualifier("customJacksonMapperTransformer.handler")
private MessageHandler customJacksonMapperTransformer;
@Autowired
@Qualifier("customJsonMapperTransformer.handler")
private MessageHandler customJsonMapperTransformer;
@Autowired
private ObjectMapper customObjectMapper;
@Autowired
private JsonObjectMapper<?> jsonObjectMapper;
@Test
public void defaultObjectMapper() {
Object jsonToObjectTransformer = TestUtils.getPropertyValue(this.defaultJacksonMapperTransformer, "transformer");
assertEquals(Jackson2JsonObjectMapper.class, TestUtils.getPropertyValue(jsonToObjectTransformer, "jsonObjectMapper").getClass());
String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42,\"address\":{\"number\":123,\"street\":\"Main Street\"}}";
QueueChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload(jsonString).setReplyChannel(replyChannel).build();
@@ -66,6 +93,10 @@ public class JsonToObjectTransformerParserTests {
@Test
public void customObjectMapper() {
Object jsonToObjectTransformer = TestUtils.getPropertyValue(this.customJacksonMapperTransformer, "transformer");
JsonObjectMapper<?> jsonObjectMapper = TestUtils.getPropertyValue(jsonToObjectTransformer, "jsonObjectMapper", JsonObjectMapper.class);
assertSame(this.customObjectMapper, TestUtils.getPropertyValue(jsonObjectMapper, "objectMapper"));
String jsonString = "{firstName:'John', lastName:'Doe', age:42, address:{number:123, street:'Main Street'}}";
QueueChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload(jsonString).setReplyChannel(replyChannel).build();
@@ -81,6 +112,23 @@ public class JsonToObjectTransformerParserTests {
assertEquals("123 Main Street", person.getAddress().toString());
}
@Test
public void testInt2831CustomJsonObjectMapper() {
Object jsonToObjectTransformer = TestUtils.getPropertyValue(this.customJsonMapperTransformer, "transformer");
assertSame(this.jsonObjectMapper, TestUtils.getPropertyValue(jsonToObjectTransformer, "jsonObjectMapper", JsonObjectMapper.class));
String jsonString = "{firstName:'John', lastName:'Doe', age:42, address:{number:123, street:'Main Street'}}";
QueueChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload(jsonString).setReplyChannel(replyChannel).build();
this.customJsonObjectMapperInput.send(message);
Message<?> reply = replyChannel.receive(0);
assertNotNull(reply);
assertNotNull(reply.getPayload());
assertEquals(TestJsonContainer.class, reply.getPayload().getClass());
TestJsonContainer result = (TestJsonContainer) reply.getPayload();
assertEquals(jsonString, result.getJson());
}
static class TestPerson {
@@ -163,4 +211,26 @@ public class JsonToObjectTransformerParserTests {
}
}
@SuppressWarnings("rawtypes")
static class CustomJsonObjectMapper extends JsonObjectMapperAdapter {
@Override
public Object fromJson(String json, Class valueType) throws Exception {
return new TestJsonContainer(json);
}
}
static class TestJsonContainer {
private final String json;
TestJsonContainer(String json) {
this.json = json;
}
public String getJson() {
return json;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -24,6 +24,7 @@ import org.junit.Test;
/**
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
public class JsonToObjectTransformerTests {
@@ -44,7 +45,8 @@ public class JsonToObjectTransformerTests {
ObjectMapper customMapper = new ObjectMapper();
customMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, Boolean.TRUE);
customMapper.configure(Feature.ALLOW_SINGLE_QUOTES, Boolean.TRUE);
JsonToObjectTransformer<TestPerson> transformer = new JsonToObjectTransformer<TestPerson>(TestPerson.class, customMapper);
JsonToObjectTransformer<TestPerson> transformer =
new JsonToObjectTransformer<TestPerson>(TestPerson.class, new JacksonJsonObjectMapper(customMapper));
String jsonString = "{firstName:'John', lastName:'Doe', age:42, address:{number:123, street:'Main Street'}}";
TestPerson person = transformer.transformPayload(jsonString);
assertEquals("John", person.getFirstName());
@@ -53,6 +55,12 @@ public class JsonToObjectTransformerTests {
assertEquals("123 Main Street", person.getAddress().toString());
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testInt2831IllegalArgument() throws Exception {
new JsonToObjectTransformer<String>(String.class, new Object());
}
@SuppressWarnings("unused")
private static class TestPerson {

View File

@@ -2,7 +2,7 @@
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
@@ -17,4 +17,9 @@
<beans:bean id="customObjectMapper" class="org.springframework.integration.json.ObjectToJsonTransformerParserTests$CustomObjectMapper"/>
</beans:beans>
<object-to-json-transformer id="customJsonObjectMapperTransformer" input-channel="customJsonObjectMapperInput"
object-mapper="customJsonObjectMapper"/>
<beans:bean id="customJsonObjectMapper" class="org.springframework.integration.json.ObjectToJsonTransformerParserTests$CustomJsonObjectMapper"/>
</beans:beans>

View File

@@ -43,6 +43,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
@ContextConfiguration
@@ -58,12 +59,17 @@ public class ObjectToJsonTransformerParserTests {
@Autowired
private volatile MessageChannel customObjectMapperInput;
@Autowired
private volatile MessageChannel customJsonObjectMapperInput;
@Test
public void testContentType(){
ObjectToJsonTransformer transformer =
TestUtils.getPropertyValue(context.getBean("defaultTransformer"), "handler.transformer", ObjectToJsonTransformer.class);
assertEquals("application/json", TestUtils.getPropertyValue(transformer, "contentType"));
assertEquals(Jackson2JsonObjectMapper.class, TestUtils.getPropertyValue(transformer, "jsonObjectMapper").getClass());
Message<?> transformed = transformer.transform(MessageBuilder.withPayload("foo").build());
assertTrue(transformed.getHeaders().containsKey(MessageHeaders.CONTENT_TYPE));
assertEquals("application/json", transformed.getHeaders().get(MessageHeaders.CONTENT_TYPE));
@@ -147,6 +153,23 @@ public class ObjectToJsonTransformerParserTests {
assertTrue(addressResult.contains("street:\"Main Street\""));
}
@Test
public void testInt2831CustomJsonObjectMapper() {
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.customJsonObjectMapperInput.send(message);
Message<?> reply = replyChannel.receive(0);
assertNotNull(reply);
assertNotNull(reply.getPayload());
assertEquals(String.class, reply.getPayload().getClass());
String resultString = (String) reply.getPayload();
assertEquals("{" + person.toString() + "}", resultString);
}
static class TestPerson {
@@ -193,8 +216,8 @@ public class ObjectToJsonTransformerParserTests {
@Override
public String toString() {
return "name=" + this.firstName + " " + this.lastName
+ ", age=" + this.age + ", address=" + this.address;
return "\"name\":\"" + this.firstName + " " + this.lastName
+ "\", \"age\":" + this.age + ", \"address\":\"" + this.address + "\"";
}
}
@@ -236,4 +259,12 @@ public class ObjectToJsonTransformerParserTests {
}
}
static class CustomJsonObjectMapper extends JsonObjectMapperAdapter<Object> {
@Override
public String toJson(Object value) throws Exception {
return "{" + value.toString() + "}";
}
}
}

View File

@@ -35,6 +35,7 @@ import org.springframework.integration.support.MessageBuilder;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class ObjectToJsonTransformerTests {
@@ -123,7 +124,7 @@ public class ObjectToJsonTransformerTests {
public void objectPayloadWithCustomObjectMapper() throws Exception {
ObjectMapper customMapper = new ObjectMapper();
customMapper.configure(Feature.QUOTE_FIELD_NAMES, Boolean.FALSE);
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer(customMapper);
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer(new JacksonJsonObjectMapper(customMapper));
TestPerson person = new TestPerson("John", "Doe", 42);
person.setAddress(new TestAddress(123, "Main Street"));
String result = (String) transformer.transform(new GenericMessage<TestPerson>(person)).getPayload();
@@ -138,15 +139,20 @@ public class ObjectToJsonTransformerTests {
assertTrue(addressResult.contains("street:\"Main Street\""));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testInt2831IllegalArgument() throws Exception {
new ObjectToJsonTransformer(new Object());
}
@SuppressWarnings("unused")
private static class TestPerson {
private String firstName;
private final String firstName;
private String lastName;
private final String lastName;
private int age;
private final int age;
private TestAddress address;
@@ -182,9 +188,9 @@ public class ObjectToJsonTransformerTests {
@SuppressWarnings("unused")
private static class TestAddress {
private int number;
private final int number;
private String street;
private final String street;
public TestAddress(int number, String street) {

View File

@@ -0,0 +1,86 @@
/*
* 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 java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.springframework.util.Assert;
/**
* Copy of {@link org.springframework.core.ParameterizedTypeReference} from Spring Framework 3.2
* @author Artem Bilan
* @since 3.0
*/
//TODO Remove it after upgrade to Spring Framework 3.2 in favor to use org.springframework.core.ParameterizedTypeReference
public abstract class ParameterizedTypeReference<T> {
private final Type type;
protected ParameterizedTypeReference() {
Class<?> parameterizedTypeReferenceSubClass = findParameterizedTypeReferenceSubClass(getClass());
Type type = parameterizedTypeReferenceSubClass.getGenericSuperclass();
Assert.isInstanceOf(ParameterizedType.class, type);
ParameterizedType parameterizedType = (ParameterizedType) type;
Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1);
this.type = parameterizedType.getActualTypeArguments()[0];
}
private static Class<?> findParameterizedTypeReferenceSubClass(Class<?> child) {
Class<?> parent = child.getSuperclass();
if (Object.class.equals(parent)) {
throw new IllegalStateException("Expected ParameterizedTypeReference superclass");
}
else if (ParameterizedTypeReference.class.equals(parent)) {
return child;
}
else {
return findParameterizedTypeReferenceSubClass(parent);
}
}
public Type getType() {
return this.type;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof ParameterizedTypeReference) {
ParameterizedTypeReference<?> other = (ParameterizedTypeReference<?>) o;
return this.type.equals(other.type);
}
return false;
}
@Override
public int hashCode() {
return this.type.hashCode();
}
@Override
public String toString() {
return "ParameterizedTypeReference<" + this.type + ">";
}
}

View File

@@ -225,17 +225,31 @@ public class Kid {
<programlisting language="xml"><![CDATA[<int:json-to-object-transformer input-channel="objectMapperInput"
type="foo.MyDomainObject" object-mapper="customObjectMapper"/>]]></programlisting>
</para>
<note>
<para>
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 Jackson 1.x and Jackson 2 are
provided, with the version being detected on the classpath. These classes are
<classname>JacksonJsonObjectMapper</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.
</para>
</note>
<para>
You may wish to consider using a FactoryBean or simple factory method to create the ObjectMapper with
You may wish to consider using a <interfacename>FactoryBean</interfacename> or simple factory
method to create the <classname>JsonObjectMapper</classname> with
the required characteristics.
</para>
<para>
<programlisting language="java"><![CDATA[public class ObjectMapperFactory {
public static ObjectMapper getMapper() {
public static Jackson2JsonObjectMapper getMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
return mapper;
return new Jackson2JsonObjectMapper(mapper);
}
}]]></programlisting>
</para>

View File

@@ -127,5 +127,14 @@
please see <xref linkend="jdbc-message-store-generic"/>.
</para>
</section>
<section>
<title>Jackson Support (JSON)</title>
<para>
A new abstraction for JSON conversion has been introduced. Implementations for Jackson 1.x
and Jackson 2 are currently provided, with the version being determined by presence on
the classpath. Previously, only Jackson 1.x was supported. For more information,
see 'JSON Transformers' in <xref linkend="transformer"/>.
</para>
</section>
</section>
</chapter>