diff --git a/build.gradle b/build.gradle index 9a6e589527..aca6a4c5cb 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/JsonToObjectTransformerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/JsonToObjectTransformerParser.java index c0501b9ef2..588432554d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/JsonToObjectTransformerParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/JsonToObjectTransformerParser.java @@ -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 diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToJsonTransformerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToJsonTransformerParser.java index 31f8be64f6..714845e051 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToJsonTransformerParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/ObjectToJsonTransformerParser.java @@ -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")); } } -} \ No newline at end of file + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/AbstractJacksonJsonMessageParser.java b/spring-integration-core/src/main/java/org/springframework/integration/json/AbstractJacksonJsonMessageParser.java new file mode 100644 index 0000000000..4ab1297313 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/AbstractJacksonJsonMessageParser.java @@ -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

implements JsonInboundMessageMapper.JsonMessageParser

{ + + protected final JsonObjectMapper

objectMapper; + + protected volatile JsonInboundMessageMapper messageMapper; + + protected AbstractJacksonJsonMessageParser(JsonObjectMapper

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; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/AbstractJsonInboundMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/json/AbstractJsonInboundMessageMapper.java new file mode 100644 index 0000000000..c5d2102135 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/AbstractJsonInboundMessageMapper.java @@ -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

implements InboundMessageMapper { + + 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> DEFAULT_HEADER_TYPES = new HashMap>(); + + 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> 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> 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 readHeaders(P parser, String jsonMessage) throws Exception; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/Jackson2JsonMessageParser.java b/spring-integration-core/src/main/java/org/springframework/integration/json/Jackson2JsonMessageParser.java new file mode 100644 index 0000000000..90b1748bda --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/Jackson2JsonMessageParser.java @@ -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 { + + 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 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 readHeaders(JsonParser parser, String jsonMessage) throws Exception { + Map headers = new LinkedHashMap(); + 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; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/Jackson2JsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/json/Jackson2JsonObjectMapper.java new file mode 100644 index 0000000000..a543f7feeb --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/Jackson2JsonObjectMapper.java @@ -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 toJson and fromJson + * to the {@linkplain com.fasterxml.jackson.databind.ObjectMapper} + * + * @author Artem Bilan + * @since 3.0 + */ +public class Jackson2JsonObjectMapper implements JsonObjectMapper { + + 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 fromJson(String json, Class valueType) throws Exception { + return this.objectMapper.readValue(json, valueType); + } + + @Override + public T fromJson(Reader json, Class valueType) throws Exception { + return this.objectMapper.readValue(json, valueType); + } + + @Override + public T fromJson(JsonParser parser, Type valueType) throws Exception { + return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType)); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonMessageParser.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonMessageParser.java new file mode 100644 index 0000000000..b081cb48d3 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonMessageParser.java @@ -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 { + + 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 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 readHeaders(JsonParser parser, String jsonMessage) throws Exception { + Map headers = new LinkedHashMap(); + 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; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonObjectMapper.java new file mode 100644 index 0000000000..f769e66052 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonObjectMapper.java @@ -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 toJson and fromJson + * to the {@linkplain org.codehaus.jackson.map.ObjectMapper} + * + * @author Artem Bilan + * @since 3.0 + */ +public class JacksonJsonObjectMapper implements JsonObjectMapper { + + 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 fromJson(String json, Class valueType) throws Exception { + return this.objectMapper.readValue(json, valueType); + } + + @Override + public T fromJson(Reader json, Class valueType) throws Exception { + return this.objectMapper.readValue(json, valueType); + } + + @Override + public T fromJson(JsonParser parser, Type valueType) throws Exception { + return this.objectMapper.readValue(parser, this.objectMapper.constructType(valueType)); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonObjectMapperProvider.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonObjectMapperProvider.java new file mode 100644 index 0000000000..9a8818d0e8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JacksonJsonObjectMapperProvider.java @@ -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; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonInboundMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonInboundMessageMapper.java index 582ca852a7..5126e629d5 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonInboundMessageMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonInboundMessageMapper.java @@ -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 { - - 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> DEFAULT_HEADER_TYPES = new HashMap>(); - - 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> headerTypes = DEFAULT_HEADER_TYPES; - - private volatile ObjectMapper objectMapper = new ObjectMapper(); - - private volatile boolean mapToPayload = false; +public class JsonInboundMessageMapper extends AbstractJsonInboundMessageMapper> { + 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> 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> 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 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 readHeaders(JsonParser parser, String jsonMessage) throws Exception{ - Map headers = new LinkedHashMap(); - 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 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

{ + + Message doInParser(JsonInboundMessageMapper messageMapper, String jsonMessage) throws Exception; + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonObjectMapper.java new file mode 100644 index 0000000000..63df16a0d1 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonObjectMapper.java @@ -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

{ + + String toJson(Object value) throws Exception; + + void toJson(Object value, Writer writer) throws Exception; + + T fromJson(String json, Class valueType) throws Exception; + + T fromJson(Reader json, Class valueType) throws Exception; + + T fromJson(P parser, Type valueType) throws Exception; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonObjectMapperAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonObjectMapperAdapter.java new file mode 100644 index 0000000000..5d4728cd39 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonObjectMapperAdapter.java @@ -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

implements JsonObjectMapper

{ + + @Override + public String toJson(Object value) throws Exception { + return null; + } + + @Override + public void toJson(Object value, Writer writer) throws Exception { + } + + @Override + public T fromJson(String json, Class valueType) throws Exception { + return null; + } + + @Override + public T fromJson(Reader json, Class valueType) throws Exception { + return null; + } + + @Override + public T fromJson(P parser, Type valueType) throws Exception { + return null; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonOutboundMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonOutboundMessageMapper.java index 89e729048b..ab2f233145 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonOutboundMessageMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonOutboundMessageMapper.java @@ -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 { 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 } 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); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java index 641146c2af..30d8c5d828 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java @@ -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 extends AbstractPayloadTransformer { private final Class targetClass; - private final ObjectMapper objectMapper; - + private final JsonObjectMapper jsonObjectMapper; public JsonToObjectTransformer(Class targetClass) { this(targetClass, null); } - public JsonToObjectTransformer(Class 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 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 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); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/ObjectToJsonTransformer.java b/spring-integration-core/src/main/java/org/springframework/integration/json/ObjectToJsonTransformer.java index c0d5dc9c92..8a212a7b03 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/ObjectToJsonTransformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/ObjectToJsonTransformer.java @@ -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 messageBuilder = MessageBuilder.withPayload(payload); LinkedCaseInsensitiveMap headers = new LinkedCaseInsensitiveMap(); 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(); } -} \ No newline at end of file + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ObjectToMapTransformer.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ObjectToMapTransformer.java index 593919b723..ce7b65e059 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ObjectToMapTransformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ObjectToMapTransformer.java @@ -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.
- * + * * * public class Person { * public String name = "John"; @@ -41,17 +42,20 @@ import org.springframework.util.StringUtils; * private String street = "123 Main Street"; * } * - * + * * The resulting Map would look like this: * * {name=John, address.street=123 Main Street} - * - * + * + * * @author Oleg Zhurakousky + * @author Artem Bilan * @since 2.0 */ public class ObjectToMapTransformer extends AbstractPayloadTransformer> { + 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 transformPayload(Object payload) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - Map result = new ObjectMapper().readValue(mapper.writeValueAsString(payload), Map.class); + Map 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) 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 { diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd index 29a21cecb9..d41221a903 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd @@ -2124,15 +2124,19 @@ - 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. + @@ -2166,15 +2170,19 @@ - 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. + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonInboundMessageMapperTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonInboundMessageMapperTests.java index 7141b1a116..81b587bc57 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonInboundMessageMapperTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonInboundMessageMapperTests.java @@ -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> 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 expectedList = Arrays.asList(new String[]{"myPayloadStuff1", "myPayloadStuff2", "myPayloadStuff3"}); + List expectedList = Arrays.asList("myPayloadStuff1", "myPayloadStuff2", "myPayloadStuff3"); Message> expected = MessageBuilder.withPayload(expectedList).setHeader("foo", 123).setHeader("bar", "abc").build(); - JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new TypeReference>(){}); + JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new ParameterizedTypeReference>(){}.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 expectedList = Arrays.asList(new TestBean[]{bean1, bean2}); + List expectedList = Arrays.asList(bean1, bean2); Message> expected = MessageBuilder.withPayload(expectedList).setHeader("foo", 123).setHeader("bar", "abc").build(); - JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new TypeReference>(){}); + JsonInboundMessageMapper mapper = new JsonInboundMessageMapper(new ParameterizedTypeReference>(){}.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); + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests-context.xml index 736862809d..7034732677 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests-context.xml @@ -2,18 +2,24 @@ - - - \ No newline at end of file + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java index 1efa1d091b..e282c0d3e9 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java @@ -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 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 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 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; + } + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java index 26c48708d6..c7c865b388 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java @@ -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 transformer = new JsonToObjectTransformer(TestPerson.class, customMapper); + JsonToObjectTransformer transformer = + new JsonToObjectTransformer(TestPerson.class, new JacksonJsonObjectMapper(customMapper)); String jsonString = "{firstName:'John', lastName:'Doe', age:42, address:{number:123, street:'Main Street'}}"; TestPerson person = transformer.transformPayload(jsonString); 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.class, new Object()); + } + @SuppressWarnings("unused") private static class TestPerson { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml index 36e0fed0af..896373ea54 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests-context.xml @@ -2,7 +2,7 @@ @@ -17,4 +17,9 @@ - \ No newline at end of file + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java index 8388a87d33..8752f4e1c1 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java @@ -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 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 { + + @Override + public String toJson(Object value) throws Exception { + return "{" + value.toString() + "}"; + } + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java index 40ed23380a..6592c95a80 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java @@ -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(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) { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ParameterizedTypeReference.java b/spring-integration-core/src/test/java/org/springframework/integration/json/ParameterizedTypeReference.java new file mode 100644 index 0000000000..b11c22b3b9 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ParameterizedTypeReference.java @@ -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 { + + 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 + ">"; + } +} diff --git a/src/reference/docbook/transformer.xml b/src/reference/docbook/transformer.xml index 82543517ee..8bd2394f4b 100644 --- a/src/reference/docbook/transformer.xml +++ b/src/reference/docbook/transformer.xml @@ -225,17 +225,31 @@ public class Kid { ]]> + + + Beginning with version 3.0, the object-mapper attribute references an instance of a new + strategy interface JsonObjectMapper. 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 + JacksonJsonObjectMapper and Jackson2JsonObjectMapper. + + + For backward compatibility, a simple Jackson 1.x ObjectMapper can be provided + instead of a JsonObjectMapper. This will be removed in a future release. + + - You may wish to consider using a FactoryBean or simple factory method to create the ObjectMapper with + You may wish to consider using a FactoryBean or simple factory + method to create the JsonObjectMapper with the required characteristics. diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 8dffebfff2..cbef2d2e10 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -127,5 +127,14 @@ please see . +
+ Jackson Support (JSON) + + 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 . + +