Skip to content

Commit 8592180

Browse files
committed
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
1 parent 7f5d6ea commit 8592180

File tree

4 files changed

+96
-39
lines changed

4 files changed

+96
-39
lines changed

build.gradle

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ configure(allprojects) { project ->
9393
"http://ehcache.org/apidocs/",
9494
"http://quartz-scheduler.org/api/2.1.7/",
9595
"http://jackson.codehaus.org/1.9.4/javadoc/",
96-
"http://fasterxml.github.com/jackson-core/javadoc/2.2.0/",
96+
"http://fasterxml.github.com/jackson-core/javadoc/2.3.0/",
97+
"http://fasterxml.github.com/jackson-databind/javadoc/2.3.0/",
9798
"http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs"
9899
] as String[]
99100
}
@@ -381,7 +382,7 @@ project("spring-messaging") {
381382
compile(project(":spring-beans"))
382383
compile(project(":spring-core"))
383384
compile(project(":spring-context"))
384-
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2")
385+
optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
385386
optional("org.projectreactor:reactor-core:1.0.0.RELEASE")
386387
optional("org.projectreactor:reactor-tcp:1.0.0.RELEASE")
387388
optional("org.eclipse.jetty.websocket:websocket-server:${jettyVersion}") {
@@ -474,7 +475,7 @@ project("spring-jms") {
474475
optional("org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1")
475476
optional("javax.resource:connector-api:1.5")
476477
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
477-
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2")
478+
optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
478479
}
479480
}
480481

@@ -550,7 +551,7 @@ project("spring-web") {
550551
optional("org.apache.httpcomponents:httpclient:4.3.1")
551552
optional("org.apache.httpcomponents:httpasyncclient:4.0")
552553
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
553-
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2")
554+
optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
554555
optional("taglibs:standard:1.1.2")
555556
optional("org.eclipse.jetty:jetty-servlet:${jettyVersion}") {
556557
exclude group: "javax.servlet", module: "javax.servlet-api"
@@ -593,8 +594,7 @@ project("spring-websocket") {
593594
exclude group: "javax.servlet", module: "javax.servlet"
594595
}
595596
optional("org.eclipse.jetty.websocket:websocket-client:${jettyVersion}")
596-
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2")
597-
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
597+
optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
598598
testCompile("org.apache.tomcat.embed:tomcat-embed-core:8.0.0-RC5")
599599
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
600600
testCompile("log4j:log4j:1.2.17")
@@ -670,7 +670,7 @@ project("spring-webmvc") {
670670
optional("velocity-tools:velocity-tools-view:1.4")
671671
optional("org.freemarker:freemarker:2.3.19")
672672
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
673-
optional("com.fasterxml.jackson.core:jackson-databind:2.2.2")
673+
optional("com.fasterxml.jackson.core:jackson-databind:2.3.0")
674674
provided("javax.servlet:jstl:1.2")
675675
provided("javax.servlet:javax.servlet-api:3.0.1")
676676
provided("javax.servlet.jsp:jsp-api:2.1")
@@ -789,7 +789,7 @@ project("spring-test") {
789789
testCompile("org.hsqldb:hsqldb:${hsqldbVersion}")
790790
testCompile("org.hibernate:hibernate-validator:4.3.0.Final")
791791
testCompile("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
792-
testCompile("com.fasterxml.jackson.core:jackson-databind:2.2.2")
792+
testCompile("com.fasterxml.jackson.core:jackson-databind:2.3.0")
793793
testCompile("com.thoughtworks.xstream:xstream:1.4.4")
794794
testCompile("rome:rome:1.0")
795795
testCompile("javax.activation:activation:1.1")

spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@
2121
import java.io.StringWriter;
2222
import java.io.Writer;
2323
import java.nio.charset.Charset;
24-
25-
import org.springframework.messaging.Message;
26-
import org.springframework.messaging.MessageHeaders;
27-
import org.springframework.util.MimeType;
24+
import java.util.concurrent.atomic.AtomicReference;
2825

2926
import com.fasterxml.jackson.core.JsonEncoding;
3027
import com.fasterxml.jackson.core.JsonGenerator;
@@ -33,14 +30,27 @@
3330
import com.fasterxml.jackson.databind.ObjectMapper;
3431
import com.fasterxml.jackson.databind.SerializationFeature;
3532

33+
import org.springframework.messaging.Message;
34+
import org.springframework.messaging.MessageHeaders;
35+
import org.springframework.util.ClassUtils;
36+
import org.springframework.util.MimeType;
37+
3638
/**
3739
* A Jackson 2 based {@link MessageConverter} implementation.
3840
*
41+
* <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher.
42+
*
3943
* @author Rossen Stoyanchev
44+
* @author Juergen Hoeller
4045
* @since 4.0
4146
*/
4247
public class MappingJackson2MessageConverter extends AbstractMessageConverter {
4348

49+
// Check for Jackson 2.3's overloaded canDeserialize/canSerialize variants with cause reference
50+
private static final boolean jackson23Available =
51+
ClassUtils.hasMethod(ObjectMapper.class, "canDeserialize", JavaType.class, AtomicReference.class);
52+
53+
4454
private ObjectMapper objectMapper = new ObjectMapper();
4555

4656
private Boolean prettyPrint;
@@ -76,13 +86,35 @@ protected boolean canConvertFrom(Message<?> message, Class<?> targetClass) {
7686
if (targetClass == null) {
7787
return false;
7888
}
79-
JavaType type = this.objectMapper.constructType(targetClass);
80-
return (this.objectMapper.canDeserialize(type) && supportsMimeType(message.getHeaders()));
89+
JavaType javaType = this.objectMapper.constructType(targetClass);
90+
if (!jackson23Available || !logger.isWarnEnabled()) {
91+
return (this.objectMapper.canDeserialize(javaType) && supportsMimeType(message.getHeaders()));
92+
}
93+
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
94+
if (this.objectMapper.canDeserialize(javaType, causeRef) && supportsMimeType(message.getHeaders())) {
95+
return true;
96+
}
97+
Throwable cause = causeRef.get();
98+
if (cause != null) {
99+
logger.warn("Failed to evaluate deserialization for type: " + javaType);
100+
}
101+
return false;
81102
}
82103

83104
@Override
84105
protected boolean canConvertTo(Object payload, MessageHeaders headers) {
85-
return (this.objectMapper.canSerialize(payload.getClass()) && supportsMimeType(headers));
106+
if (!jackson23Available || !logger.isWarnEnabled()) {
107+
return (this.objectMapper.canSerialize(payload.getClass()) && supportsMimeType(headers));
108+
}
109+
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
110+
if (this.objectMapper.canSerialize(payload.getClass(), causeRef) && supportsMimeType(headers)) {
111+
return true;
112+
}
113+
Throwable cause = causeRef.get();
114+
if (cause != null) {
115+
logger.warn("Failed to evaluate serialization for type: " + payload.getClass());
116+
}
117+
return false;
86118
}
87119

88120
@Override
@@ -143,7 +175,6 @@ public Object convertToInternal(Object payload, MessageHeaders headers) {
143175

144176
/**
145177
* Determine the JSON encoding to use for the given content type.
146-
*
147178
* @param contentType the MIME type from the MessageHeaders, if any
148179
* @return the JSON encoding to use (never {@code null})
149180
*/

spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
import java.io.IOException;
2020
import java.lang.reflect.Type;
2121
import java.nio.charset.Charset;
22+
import java.util.concurrent.atomic.AtomicReference;
23+
24+
import com.fasterxml.jackson.core.JsonEncoding;
25+
import com.fasterxml.jackson.core.JsonGenerator;
26+
import com.fasterxml.jackson.core.JsonProcessingException;
27+
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
28+
import com.fasterxml.jackson.databind.JavaType;
29+
import com.fasterxml.jackson.databind.ObjectMapper;
30+
import com.fasterxml.jackson.databind.SerializationFeature;
2231

2332
import org.springframework.http.HttpInputMessage;
2433
import org.springframework.http.HttpOutputMessage;
@@ -28,14 +37,7 @@
2837
import org.springframework.http.converter.HttpMessageNotReadableException;
2938
import org.springframework.http.converter.HttpMessageNotWritableException;
3039
import org.springframework.util.Assert;
31-
32-
import com.fasterxml.jackson.core.JsonEncoding;
33-
import com.fasterxml.jackson.core.JsonGenerator;
34-
import com.fasterxml.jackson.core.JsonProcessingException;
35-
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
36-
import com.fasterxml.jackson.databind.JavaType;
37-
import com.fasterxml.jackson.databind.ObjectMapper;
38-
import com.fasterxml.jackson.databind.SerializationFeature;
40+
import org.springframework.util.ClassUtils;
3941

4042
/**
4143
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that
@@ -46,18 +48,23 @@
4648
* <p>By default, this converter supports {@code application/json}. This can be overridden by setting the
4749
* {@link #setSupportedMediaTypes supportedMediaTypes} property.
4850
*
49-
* <p>Tested against Jackson 2.2; compatible with Jackson 2.0 and higher.
51+
* <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher.
5052
*
5153
* @author Arjen Poutsma
5254
* @author Keith Donald
5355
* @author Rossen Stoyanchev
56+
* @author Juergen Hoeller
5457
* @since 3.1.2
5558
*/
5659
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object>
5760
implements GenericHttpMessageConverter<Object> {
5861

5962
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
6063

64+
// Check for Jackson 2.3's overloaded canDeserialize/canSerialize variants with cause reference
65+
private static final boolean jackson23Available =
66+
ClassUtils.hasMethod(ObjectMapper.class, "canDeserialize", JavaType.class, AtomicReference.class);
67+
6168

6269
private ObjectMapper objectMapper = new ObjectMapper();
6370

@@ -147,12 +154,34 @@ public boolean canRead(Class<?> clazz, MediaType mediaType) {
147154
@Override
148155
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
149156
JavaType javaType = getJavaType(type, contextClass);
150-
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
157+
if (!jackson23Available || !logger.isWarnEnabled()) {
158+
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
159+
}
160+
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
161+
if (this.objectMapper.canDeserialize(javaType, causeRef) && canRead(mediaType)) {
162+
return true;
163+
}
164+
Throwable cause = causeRef.get();
165+
if (cause != null) {
166+
logger.warn("Failed to evaluate deserialization for type: " + javaType);
167+
}
168+
return false;
151169
}
152170

153171
@Override
154172
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
155-
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
173+
if (!jackson23Available || !logger.isWarnEnabled()) {
174+
return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
175+
}
176+
AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
177+
if (this.objectMapper.canSerialize(clazz, causeRef) && canWrite(mediaType)) {
178+
return true;
179+
}
180+
Throwable cause = causeRef.get();
181+
if (cause != null) {
182+
logger.warn("Failed to evaluate serialization for type: " + clazz);
183+
}
184+
return false;
156185
}
157186

158187
@Override

spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@
2323
import java.util.HashMap;
2424
import java.util.Map;
2525

26-
import org.junit.Before;
27-
import org.junit.Test;
28-
import org.springframework.beans.FatalBeanException;
29-
3026
import com.fasterxml.jackson.annotation.JsonInclude;
3127
import com.fasterxml.jackson.core.JsonGenerator;
3228
import com.fasterxml.jackson.core.JsonParser;
@@ -45,9 +41,13 @@
4541
import com.fasterxml.jackson.databind.module.SimpleModule;
4642
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
4743
import com.fasterxml.jackson.databind.ser.Serializers;
44+
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
4845
import com.fasterxml.jackson.databind.ser.std.NumberSerializers.NumberSerializer;
49-
import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.ClassSerializer;
5046
import com.fasterxml.jackson.databind.type.SimpleType;
47+
import org.junit.Before;
48+
import org.junit.Test;
49+
50+
import org.springframework.beans.FatalBeanException;
5151

5252
import static org.junit.Assert.*;
5353

@@ -80,7 +80,7 @@ public void testSettersWithNullValues() {
8080

8181
@Test(expected = FatalBeanException.class)
8282
public void testUnknownFeature() {
83-
this.factory.setFeaturesToEnable(new Object[] { Boolean.TRUE });
83+
this.factory.setFeaturesToEnable(Boolean.TRUE);
8484
this.factory.afterPropertiesSet();
8585
}
8686

@@ -178,11 +178,11 @@ public void testSimpleSetup() {
178178
assertEquals(ObjectMapper.class, this.factory.getObjectType());
179179
}
180180

181-
private static final SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
181+
private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
182182
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
183183
}
184184

185-
private static final DeserializerFactoryConfig getDeserializerFactoryConfig(ObjectMapper objectMapper) {
185+
private static DeserializerFactoryConfig getDeserializerFactoryConfig(ObjectMapper objectMapper) {
186186
return ((BasicDeserializerFactory) objectMapper.getDeserializationContext().getFactory()).getFactoryConfig();
187187
}
188188

@@ -221,16 +221,13 @@ public void testCompleteSetup() {
221221
assertFalse(getDeserializerFactoryConfig(objectMapper).hasDeserializers());
222222

223223
this.factory.setSerializationInclusion(JsonInclude.Include.NON_NULL);
224-
225224
this.factory.afterPropertiesSet();
226225

227226
assertTrue(objectMapper == this.factory.getObject());
228-
229227
assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers());
230228
assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers());
231229

232230
Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next();
233-
234231
assertTrue(serializers.findSerializer(null, SimpleType.construct(Class.class), null) == serializer1);
235232
assertTrue(serializers.findSerializer(null, SimpleType.construct(Boolean.class), null) == serializer2);
236233
assertNull(serializers.findSerializer(null, SimpleType.construct(Number.class), null));
@@ -247,7 +244,7 @@ public void testCompleteSetup() {
247244
assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_FIELDS));
248245
assertFalse(objectMapper.getFactory().isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
249246
assertFalse(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
250-
251247
assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.NON_NULL);
252248
}
249+
253250
}

0 commit comments

Comments
 (0)