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 1bebaf72227..5178fe94ed5 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 @@ -65,10 +65,10 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple @Override public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { - JavaType javaType = objectMapper().getTypeFactory().constructType(elementType.getType()); + JavaType javaType = getObjectMapper().getTypeFactory().constructType(elementType.getType()); // Skip String: CharSequenceDecoder + "*/*" comes after return (!CharSequence.class.isAssignableFrom(elementType.resolve(Object.class)) && - objectMapper().canDeserialize(javaType) && supportsMimeType(mimeType)); + getObjectMapper().canDeserialize(javaType) && supportsMimeType(mimeType)); } @Override @@ -89,7 +89,7 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple private Flux tokenize(Publisher input, boolean tokenizeArrayElements) { try { - JsonFactory factory = objectMapper().getFactory(); + JsonFactory factory = getObjectMapper().getFactory(); JsonParser parser = factory.createNonBlockingByteArrayParser(); Jackson2Tokenizer tokenizer = new Jackson2Tokenizer(parser, tokenizeArrayElements); return Flux.from(input).flatMap(tokenizer).doFinally(t -> tokenizer.endOfInput()); @@ -111,8 +111,8 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple Class jsonView = (hints != null ? (Class) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null); ObjectReader reader = (jsonView != null ? - objectMapper().readerWithView(jsonView).forType(javaType) : - objectMapper().readerFor(javaType)); + getObjectMapper().readerWithView(jsonView).forType(javaType) : + getObjectMapper().readerFor(javaType)); return tokens.map(tokenBuffer -> { try { 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 d8dcb5c3973..26a4ec4f935 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 @@ -81,7 +81,7 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple Class clazz = elementType.resolve(Object.class); return (Object.class == clazz) || !String.class.isAssignableFrom(elementType.resolve(clazz)) && - objectMapper().canSerialize(clazz) && supportsMimeType(mimeType); + getObjectMapper().canSerialize(clazz) && supportsMimeType(mimeType); } @Override @@ -116,7 +116,7 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple JavaType javaType = getJavaType(elementType.getType(), null); Class jsonView = (hints != null ? (Class) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null); ObjectWriter writer = (jsonView != null ? - objectMapper().writerWithView(jsonView) : objectMapper().writer()); + getObjectMapper().writerWithView(jsonView) : getObjectMapper().writer()); if (javaType.isContainerType()) { writer = writer.forType(javaType); 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 6096b5abcc3..035b5a9bf49 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 @@ -56,9 +56,10 @@ public abstract class Jackson2CodecSupport { private static final String JSON_VIEW_HINT_ERROR = "@JsonView only supported for write hints with exactly 1 class argument: "; - protected static final List JSON_MIME_TYPES = Arrays.asList( - new MimeType("application", "json", StandardCharsets.UTF_8), - new MimeType("application", "*+json", StandardCharsets.UTF_8)); + private static final List DEFAULT_MIME_TYPES = Collections.unmodifiableList( + Arrays.asList( + new MimeType("application", "json", StandardCharsets.UTF_8), + new MimeType("application", "*+json", StandardCharsets.UTF_8))); private final ObjectMapper objectMapper; @@ -72,14 +73,23 @@ public abstract class Jackson2CodecSupport { protected Jackson2CodecSupport(ObjectMapper objectMapper, MimeType... mimeTypes) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); this.objectMapper = objectMapper; - this.mimeTypes = !ObjectUtils.isEmpty(mimeTypes) ? Arrays.asList(mimeTypes) : JSON_MIME_TYPES; + this.mimeTypes = !ObjectUtils.isEmpty(mimeTypes) ? + Collections.unmodifiableList(Arrays.asList(mimeTypes)) : DEFAULT_MIME_TYPES; } - protected ObjectMapper objectMapper() { + public ObjectMapper getObjectMapper() { return this.objectMapper; } + /** + * Sub-classes should expose this as "decodable" or "encodable" mime types. + */ + protected List getMimeTypes() { + return this.mimeTypes; + } + + protected boolean supportsMimeType(@Nullable MimeType mimeType) { return (mimeType == null || this.mimeTypes.stream().anyMatch(m -> m.isCompatibleWith(mimeType))); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java index b7ea22ba837..e108c134f10 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonDecoder.java @@ -42,6 +42,6 @@ public class Jackson2JsonDecoder extends AbstractJackson2Decoder { @Override public List getDecodableMimeTypes() { - return JSON_MIME_TYPES; + return getMimeTypes(); } } diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java index ddc506f2053..3ceeac10814 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2JsonEncoder.java @@ -73,6 +73,6 @@ public class Jackson2JsonEncoder extends AbstractJackson2Encoder { @Override public List getEncodableMimeTypes() { - return JSON_MIME_TYPES; + return getMimeTypes(); } } 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 e22603797e3..0c43228e95c 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 @@ -16,11 +16,12 @@ package org.springframework.http.codec.json; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.List; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Ignore; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -32,11 +33,15 @@ import org.springframework.core.codec.DecodingException; import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.codec.Pojo; +import org.springframework.util.MimeType; import static java.util.Arrays.asList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.springframework.core.ResolvableType.forClass; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_XML; @@ -63,6 +68,22 @@ public class Jackson2JsonDecoderTests extends AbstractDataBufferAllocatingTestCa assertFalse(decoder.canDecode(forClass(Pojo.class), APPLICATION_XML)); } + @Test // SPR-15866 + public void canDecodeWithProvidedMimeType() { + MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); + Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(new ObjectMapper(), textJavascript); + + assertEquals(Collections.singletonList(textJavascript), decoder.getDecodableMimeTypes()); + } + + @Test(expected = UnsupportedOperationException.class) + public void decodableMimeTypesIsImmutable() { + MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); + Jackson2JsonDecoder decoder = new Jackson2JsonDecoder(new ObjectMapper(), textJavascript); + + decoder.getMimeTypes().add(new MimeType("text", "ecmascript")); + } + @Test public void decodePojo() throws Exception { Flux source = Flux.just(stringBuffer("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}")); 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 c7fe522c9f8..15df132d8fe 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 @@ -16,16 +16,21 @@ package org.springframework.http.codec.json; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.Map; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import static java.util.Collections.*; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.springframework.http.MediaType.*; import static org.springframework.http.codec.json.Jackson2JsonEncoder.*; import static org.springframework.http.codec.json.JacksonViewBean.*; +import org.springframework.util.MimeType; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -58,6 +63,22 @@ public class Jackson2JsonEncoderTests extends AbstractDataBufferAllocatingTestCa assertTrue(this.encoder.canEncode(ResolvableType.NONE, null)); } + @Test // SPR-15866 + public void canEncodeWithCustomMimeType() { + MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); + Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(new ObjectMapper(), textJavascript); + + assertEquals(Collections.singletonList(textJavascript), encoder.getEncodableMimeTypes()); + } + + @Test(expected = UnsupportedOperationException.class) + public void encodableMimeTypesIsImmutable() { + MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); + Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(new ObjectMapper(), textJavascript); + + encoder.getMimeTypes().add(new MimeType("text", "ecmascript")); + } + @Test public void canNotEncode() { assertFalse(this.encoder.canEncode(ResolvableType.forClass(String.class), null));