MappingJackson2(Http)MessageConverter logs warnings after canRead/canWrite checks

This change involves a general upgrade to Jackson 2.3 in our build.

Issue: SPR-11261
This commit is contained in:
Juergen Hoeller
2013-12-29 21:49:53 +01:00
parent 7f5d6ea3f9
commit 85921808b3
4 changed files with 96 additions and 39 deletions

View File

@@ -93,7 +93,8 @@ configure(allprojects) { project ->
"http://ehcache.org/apidocs/", "http://ehcache.org/apidocs/",
"http://quartz-scheduler.org/api/2.1.7/", "http://quartz-scheduler.org/api/2.1.7/",
"http://jackson.codehaus.org/1.9.4/javadoc/", "http://jackson.codehaus.org/1.9.4/javadoc/",
"http://fasterxml.github.com/jackson-core/javadoc/2.2.0/", "http://fasterxml.github.com/jackson-core/javadoc/2.3.0/",
"http://fasterxml.github.com/jackson-databind/javadoc/2.3.0/",
"http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs" "http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs"
] as String[] ] as String[]
} }
@@ -381,7 +382,7 @@ project("spring-messaging") {
compile(project(":spring-beans")) compile(project(":spring-beans"))
compile(project(":spring-core")) compile(project(":spring-core"))
compile(project(":spring-context")) compile(project(":spring-context"))
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2") optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
optional("org.projectreactor:reactor-core:1.0.0.RELEASE") optional("org.projectreactor:reactor-core:1.0.0.RELEASE")
optional("org.projectreactor:reactor-tcp:1.0.0.RELEASE") optional("org.projectreactor:reactor-tcp:1.0.0.RELEASE")
optional("org.eclipse.jetty.websocket:websocket-server:${jettyVersion}") { optional("org.eclipse.jetty.websocket:websocket-server:${jettyVersion}") {
@@ -474,7 +475,7 @@ project("spring-jms") {
optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1") optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1")
optional("javax.resource:connector-api:1.5") optional("javax.resource:connector-api:1.5")
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12") optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2") optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
} }
} }
@@ -550,7 +551,7 @@ project("spring-web") {
optional("org.apache.httpcomponents:httpclient:4.3.1") optional("org.apache.httpcomponents:httpclient:4.3.1")
optional("org.apache.httpcomponents:httpasyncclient:4.0") optional("org.apache.httpcomponents:httpasyncclient:4.0")
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12") optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2") optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
optional("taglibs:standard:1.1.2") optional("taglibs:standard:1.1.2")
optional("org.eclipse.jetty:jetty-servlet:${jettyVersion}") { optional("org.eclipse.jetty:jetty-servlet:${jettyVersion}") {
exclude group: "javax.servlet", module: "javax.servlet-api" exclude group: "javax.servlet", module: "javax.servlet-api"
@@ -593,8 +594,7 @@ project("spring-websocket") {
exclude group: "javax.servlet", module: "javax.servlet" exclude group: "javax.servlet", module: "javax.servlet"
} }
optional("org.eclipse.jetty.websocket:websocket-client:${jettyVersion}") optional("org.eclipse.jetty.websocket:websocket-client:${jettyVersion}")
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2") optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
testCompile("org.apache.tomcat.embed:tomcat-embed-core:8.0.0-RC5") testCompile("org.apache.tomcat.embed:tomcat-embed-core:8.0.0-RC5")
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
testCompile("log4j:log4j:1.2.17") testCompile("log4j:log4j:1.2.17")
@@ -670,7 +670,7 @@ project("spring-webmvc") {
optional("velocity-tools:velocity-tools-view:1.4") optional("velocity-tools:velocity-tools-view:1.4")
optional("org.freemarker:freemarker:2.3.19") optional("org.freemarker:freemarker:2.3.19")
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12") optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2") optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
provided("javax.servlet:jstl:1.2") provided("javax.servlet:jstl:1.2")
provided("javax.servlet:javax.servlet-api:3.0.1") provided("javax.servlet:javax.servlet-api:3.0.1")
provided("javax.servlet.jsp:jsp-api:2.1") provided("javax.servlet.jsp:jsp-api:2.1")
@@ -789,7 +789,7 @@ project("spring-test") {
testCompile("org.hsqldb:hsqldb:${hsqldbVersion}") testCompile("org.hsqldb:hsqldb:${hsqldbVersion}")
testCompile("org.hibernate:hibernate-validator:4.3.0.Final") testCompile("org.hibernate:hibernate-validator:4.3.0.Final")
testCompile("org.codehaus.jackson:jackson-mapper-asl:1.9.12") testCompile("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
testCompile("com.fasterxml.jackson.core:jackson-databind:2.2.2") testCompile("com.fasterxml.jackson.core:jackson-databind:2.3.0")
testCompile("com.thoughtworks.xstream:xstream:1.4.4") testCompile("com.thoughtworks.xstream:xstream:1.4.4")
testCompile("rome:rome:1.0") testCompile("rome:rome:1.0")
testCompile("javax.activation:activation:1.1") testCompile("javax.activation:activation:1.1")

View File

@@ -21,10 +21,7 @@ import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.MimeType;
import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
@@ -33,14 +30,27 @@ import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
/** /**
* A Jackson 2 based {@link MessageConverter} implementation. * A Jackson 2 based {@link MessageConverter} implementation.
* *
* <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher.
*
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0 * @since 4.0
*/ */
public class MappingJackson2MessageConverter extends AbstractMessageConverter { public class MappingJackson2MessageConverter extends AbstractMessageConverter {
// Check for Jackson 2.3's overloaded canDeserialize/canSerialize variants with cause reference
private static final boolean jackson23Available =
ClassUtils.hasMethod(ObjectMapper.class, "canDeserialize", JavaType.class, AtomicReference.class);
private ObjectMapper objectMapper = new ObjectMapper(); private ObjectMapper objectMapper = new ObjectMapper();
private Boolean prettyPrint; private Boolean prettyPrint;
@@ -76,13 +86,35 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
if (targetClass == null) { if (targetClass == null) {
return false; return false;
} }
JavaType type = this.objectMapper.constructType(targetClass); JavaType javaType = this.objectMapper.constructType(targetClass);
return (this.objectMapper.canDeserialize(type) && supportsMimeType(message.getHeaders())); if (!jackson23Available || !logger.isWarnEnabled()) {
return (this.objectMapper.canDeserialize(javaType) && supportsMimeType(message.getHeaders()));
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canDeserialize(javaType, causeRef) && supportsMimeType(message.getHeaders())) {
return true;
}
Throwable cause = causeRef.get();
if (cause != null) {
logger.warn("Failed to evaluate deserialization for type: " + javaType);
}
return false;
} }
@Override @Override
protected boolean canConvertTo(Object payload, MessageHeaders headers) { protected boolean canConvertTo(Object payload, MessageHeaders headers) {
return (this.objectMapper.canSerialize(payload.getClass()) && supportsMimeType(headers)); if (!jackson23Available || !logger.isWarnEnabled()) {
return (this.objectMapper.canSerialize(payload.getClass()) && supportsMimeType(headers));
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canSerialize(payload.getClass(), causeRef) && supportsMimeType(headers)) {
return true;
}
Throwable cause = causeRef.get();
if (cause != null) {
logger.warn("Failed to evaluate serialization for type: " + payload.getClass());
}
return false;
} }
@Override @Override
@@ -143,7 +175,6 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
/** /**
* Determine the JSON encoding to use for the given content type. * Determine the JSON encoding to use for the given content type.
*
* @param contentType the MIME type from the MessageHeaders, if any * @param contentType the MIME type from the MessageHeaders, if any
* @return the JSON encoding to use (never {@code null}) * @return the JSON encoding to use (never {@code null})
*/ */

View File

@@ -19,6 +19,15 @@ package org.springframework.http.converter.json;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpOutputMessage;
@@ -28,14 +37,7 @@ import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/** /**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that
@@ -46,11 +48,12 @@ import com.fasterxml.jackson.databind.SerializationFeature;
* <p>By default, this converter supports {@code application/json}. This can be overridden by setting the * <p>By default, this converter supports {@code application/json}. This can be overridden by setting the
* {@link #setSupportedMediaTypes supportedMediaTypes} property. * {@link #setSupportedMediaTypes supportedMediaTypes} property.
* *
* <p>Tested against Jackson 2.2; compatible with Jackson 2.0 and higher. * <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Keith Donald * @author Keith Donald
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1.2 * @since 3.1.2
*/ */
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object> public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object>
@@ -58,6 +61,10 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
// Check for Jackson 2.3's overloaded canDeserialize/canSerialize variants with cause reference
private static final boolean jackson23Available =
ClassUtils.hasMethod(ObjectMapper.class, "canDeserialize", JavaType.class, AtomicReference.class);
private ObjectMapper objectMapper = new ObjectMapper(); private ObjectMapper objectMapper = new ObjectMapper();
@@ -147,12 +154,34 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
@Override @Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) { public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
JavaType javaType = getJavaType(type, contextClass); JavaType javaType = getJavaType(type, contextClass);
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType)); if (!jackson23Available || !logger.isWarnEnabled()) {
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canDeserialize(javaType, causeRef) && canRead(mediaType)) {
return true;
}
Throwable cause = causeRef.get();
if (cause != null) {
logger.warn("Failed to evaluate deserialization for type: " + javaType);
}
return false;
} }
@Override @Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) { public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType)); if (!jackson23Available || !logger.isWarnEnabled()) {
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
}
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
if (this.objectMapper.canSerialize(clazz, causeRef) && canWrite(mediaType)) {
return true;
}
Throwable cause = causeRef.get();
if (cause != null) {
logger.warn("Failed to evaluate serialization for type: " + clazz);
}
return false;
} }
@Override @Override

View File

@@ -23,10 +23,6 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
@@ -45,9 +41,13 @@ import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory; import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
import com.fasterxml.jackson.databind.ser.Serializers; import com.fasterxml.jackson.databind.ser.Serializers;
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
import com.fasterxml.jackson.databind.ser.std.NumberSerializers.NumberSerializer; import com.fasterxml.jackson.databind.ser.std.NumberSerializers.NumberSerializer;
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.ClassSerializer;
import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.databind.type.SimpleType;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -80,7 +80,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
@Test(expected = FatalBeanException.class) @Test(expected = FatalBeanException.class)
public void testUnknownFeature() { public void testUnknownFeature() {
this.factory.setFeaturesToEnable(new Object[] { Boolean.TRUE }); this.factory.setFeaturesToEnable(Boolean.TRUE);
this.factory.afterPropertiesSet(); this.factory.afterPropertiesSet();
} }
@@ -178,11 +178,11 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertEquals(ObjectMapper.class, this.factory.getObjectType()); assertEquals(ObjectMapper.class, this.factory.getObjectType());
} }
private static final SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) { private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig(); return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
} }
private static final DeserializerFactoryConfig getDeserializerFactoryConfig(ObjectMapper objectMapper) { private static DeserializerFactoryConfig getDeserializerFactoryConfig(ObjectMapper objectMapper) {
return ((BasicDeserializerFactory) objectMapper.getDeserializationContext().getFactory()).getFactoryConfig(); return ((BasicDeserializerFactory) objectMapper.getDeserializationContext().getFactory()).getFactoryConfig();
} }
@@ -221,16 +221,13 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertFalse(getDeserializerFactoryConfig(objectMapper).hasDeserializers()); assertFalse(getDeserializerFactoryConfig(objectMapper).hasDeserializers());
this.factory.setSerializationInclusion(JsonInclude.Include.NON_NULL); this.factory.setSerializationInclusion(JsonInclude.Include.NON_NULL);
this.factory.afterPropertiesSet(); this.factory.afterPropertiesSet();
assertTrue(objectMapper == this.factory.getObject()); assertTrue(objectMapper == this.factory.getObject());
assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers()); assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers());
assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers()); assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers());
Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next();
assertTrue(serializers.findSerializer(null, SimpleType.construct(Class.class), null) == serializer1); assertTrue(serializers.findSerializer(null, SimpleType.construct(Class.class), null) == serializer1);
assertTrue(serializers.findSerializer(null, SimpleType.construct(Boolean.class), null) == serializer2); assertTrue(serializers.findSerializer(null, SimpleType.construct(Boolean.class), null) == serializer2);
assertNull(serializers.findSerializer(null, SimpleType.construct(Number.class), null)); assertNull(serializers.findSerializer(null, SimpleType.construct(Number.class), null));
@@ -247,7 +244,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_FIELDS)); assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_FIELDS));
assertFalse(objectMapper.getFactory().isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)); assertFalse(objectMapper.getFactory().isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
assertFalse(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES)); assertFalse(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.NON_NULL); assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.NON_NULL);
} }
} }