From 0f6038af70434a20fc4112a79c00c411ab88e519 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 2 Nov 2020 17:25:39 +0000 Subject: [PATCH] Log can[Se]Deserialize error in Jackson codecs Closes gh-25892 --- .../codec/json/AbstractJackson2Decoder.java | 17 +++++++++-- .../codec/json/AbstractJackson2Encoder.java | 20 +++++++++++-- .../http/codec/json/Jackson2CodecSupport.java | 30 ++++++++++++++++++- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java index a0b400ef7d0..c6f75a8ffda 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Decoder.java @@ -21,6 +21,7 @@ import java.lang.annotation.Annotation; import java.math.BigDecimal; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -99,8 +100,20 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { JavaType javaType = getObjectMapper().constructType(elementType.getType()); // Skip String: CharSequenceDecoder + "*/*" comes after - return (!CharSequence.class.isAssignableFrom(elementType.toClass()) && - getObjectMapper().canDeserialize(javaType) && supportsMimeType(mimeType)); + if (CharSequence.class.isAssignableFrom(elementType.toClass()) || !supportsMimeType(mimeType)) { + return false; + } + if (!logger.isDebugEnabled()) { + return getObjectMapper().canDeserialize(javaType); + } + else { + AtomicReference causeRef = new AtomicReference<>(); + if (getObjectMapper().canDeserialize(javaType, causeRef)) { + return true; + } + logWarningIfNecessary(javaType, causeRef.get()); + return false; + } } @Override diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java index 70d9df4c7ed..ae90df4115f 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; @@ -111,8 +112,23 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple return false; } } - return (Object.class == clazz || - (!String.class.isAssignableFrom(elementType.resolve(clazz)) && getObjectMapper().canSerialize(clazz))); + if (String.class.isAssignableFrom(elementType.resolve(clazz))) { + return false; + } + if (Object.class == clazz) { + return true; + } + if (!logger.isDebugEnabled()) { + return getObjectMapper().canSerialize(clazz); + } + else { + AtomicReference causeRef = new AtomicReference<>(); + if (getObjectMapper().canSerialize(clazz, causeRef)) { + return true; + } + logWarningIfNecessary(clazz, causeRef.get()); + return false; + } } @Override diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java index 07390ba362e..5d68e63265f 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java @@ -26,6 +26,7 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; @@ -108,7 +109,34 @@ public abstract class Jackson2CodecSupport { protected boolean supportsMimeType(@Nullable MimeType mimeType) { - return (mimeType == null || this.mimeTypes.stream().anyMatch(m -> m.isCompatibleWith(mimeType))); + if (mimeType == null) { + return true; + } + for (MimeType supportedMimeType : this.mimeTypes) { + if (supportedMimeType.isCompatibleWith(mimeType)) { + return true; + } + } + return false; + } + + /** + * Determine whether to log the given exception coming from a + * {@link ObjectMapper#canDeserialize} / {@link ObjectMapper#canSerialize} check. + * @param type the class that Jackson tested for (de-)serializability + * @param cause the Jackson-thrown exception to evaluate + * (typically a {@link JsonMappingException}) + * @since 5.3.1 + */ + protected void logWarningIfNecessary(Type type, @Nullable Throwable cause) { + if (cause == null) { + return; + } + if (logger.isDebugEnabled()) { + String msg = "Failed to evaluate Jackson " + (type instanceof JavaType ? "de" : "") + + "serialization for type [" + type + "]"; + logger.debug(msg, cause); + } } protected JavaType getJavaType(Type type, @Nullable Class contextClass) {