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 6162be280c91..63eabcd75012 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 @@ -18,13 +18,18 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; @@ -75,6 +80,9 @@ public abstract class Jackson2CodecSupport { new MimeType("application", "json"), new MimeType("application", "*+json"))); + private static final Map ENCODINGS = jsonEncodings(); + + protected final Log logger = HttpLogging.forLogName(getClass()); @@ -107,7 +115,17 @@ protected List getMimeTypes() { protected boolean supportsMimeType(@Nullable MimeType mimeType) { - return (mimeType == null || this.mimeTypes.stream().anyMatch(m -> m.isCompatibleWith(mimeType))); + if (mimeType == null) { + return true; + } + else if (this.mimeTypes.stream().noneMatch(m -> m.isCompatibleWith(mimeType))) { + return false; + } + else if (mimeType.getCharset() != null) { + Charset charset = mimeType.getCharset(); + return ENCODINGS.containsKey(charset.name()); + } + return true; } protected JavaType getJavaType(Type type, @Nullable Class contextClass) { @@ -145,4 +163,10 @@ protected MethodParameter getParameter(ResolvableType type) { @Nullable protected abstract A getAnnotation(MethodParameter parameter, Class annotType); + private static Map jsonEncodings() { + return EnumSet.allOf(JsonEncoding.class).stream() + .collect(Collectors.toMap(JsonEncoding::getJavaName, Function.identity())); + } + + } diff --git a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java index 63eaf2488cfd..041ad618b5d4 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java @@ -16,6 +16,7 @@ package org.springframework.http.codec.cbor; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -27,13 +28,13 @@ import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.testfixture.codec.AbstractDecoderTests; +import org.springframework.http.MediaType; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; import org.springframework.web.testfixture.xml.Pojo; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.core.ResolvableType.forClass; import static org.springframework.http.MediaType.APPLICATION_JSON; /** @@ -58,11 +59,16 @@ public Jackson2CborDecoderTests() { @Override @Test public void canDecode() { - assertThat(decoder.canDecode(forClass(Pojo.class), CBOR_MIME_TYPE)).isTrue(); - assertThat(decoder.canDecode(forClass(Pojo.class), null)).isTrue(); + assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), CBOR_MIME_TYPE)).isTrue(); + assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), null)).isTrue(); - assertThat(decoder.canDecode(forClass(String.class), null)).isFalse(); - assertThat(decoder.canDecode(forClass(Pojo.class), APPLICATION_JSON)).isFalse(); + assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse(); + assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse(); + + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.ISO_8859_1))).isFalse(); } @Override diff --git a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java index a2ce8c51a3d1..b9b984581d6e 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.function.Consumer; import com.fasterxml.jackson.databind.ObjectMapper; @@ -28,6 +29,7 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests; import org.springframework.core.testfixture.io.buffer.DataBufferTestUtils; +import org.springframework.http.MediaType; import org.springframework.http.codec.ServerSentEvent; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; @@ -73,6 +75,12 @@ public void canEncode() { // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue(); + + + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.ISO_8859_1))).isFalse(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java index e3305e5bce55..12f999a27256 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java @@ -85,6 +85,10 @@ public void canDecode() { assertThat(decoder.canDecode(forClass(String.class), null)).isFalse(); assertThat(decoder.canDecode(forClass(Pojo.class), APPLICATION_XML)).isFalse(); + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isFalse(); } @Test // SPR-15866 diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java index 718c928a7b11..256d95220e1d 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java @@ -69,11 +69,18 @@ public void canEncode() { assertThat(this.encoder.canEncode(pojoType, APPLICATION_STREAM_JSON)).isTrue(); assertThat(this.encoder.canEncode(pojoType, null)).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isFalse(); + // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue(); // SPR-15910 assertThat(this.encoder.canEncode(ResolvableType.forClass(Object.class), APPLICATION_OCTET_STREAM)).isFalse(); + + } @Override diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java index f02b19b72866..f67772cdae25 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java @@ -16,6 +16,7 @@ package org.springframework.http.codec.json; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -27,12 +28,12 @@ import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.testfixture.codec.AbstractDecoderTests; +import org.springframework.http.MediaType; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; import org.springframework.web.testfixture.xml.Pojo; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.core.ResolvableType.forClass; import static org.springframework.http.MediaType.APPLICATION_JSON; /** @@ -58,12 +59,18 @@ public Jackson2SmileDecoderTests() { @Override @Test public void canDecode() { - assertThat(decoder.canDecode(forClass(Pojo.class), SMILE_MIME_TYPE)).isTrue(); - assertThat(decoder.canDecode(forClass(Pojo.class), STREAM_SMILE_MIME_TYPE)).isTrue(); - assertThat(decoder.canDecode(forClass(Pojo.class), null)).isTrue(); + assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), SMILE_MIME_TYPE)).isTrue(); + assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), STREAM_SMILE_MIME_TYPE)).isTrue(); + assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), null)).isTrue(); + + assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse(); + assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse(); + + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))).isFalse(); - assertThat(decoder.canDecode(forClass(String.class), null)).isFalse(); - assertThat(decoder.canDecode(forClass(Pojo.class), APPLICATION_JSON)).isFalse(); } @Override diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java index 32b15ba0b52f..f12171a052e4 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -32,6 +33,7 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.testfixture.codec.AbstractEncoderTests; +import org.springframework.http.MediaType; import org.springframework.http.codec.ServerSentEvent; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; @@ -68,6 +70,11 @@ public void canEncode() { assertThat(this.encoder.canEncode(pojoType, STREAM_SMILE_MIME_TYPE)).isTrue(); assertThat(this.encoder.canEncode(pojoType, null)).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))).isFalse(); + // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue(); }