From 01ce468ff263f9c8ec232a7748715cb34b948d7f Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 27 Nov 2009 14:14:13 +0000 Subject: [PATCH] SPR-6386 - MappingJacksonHttpMessageConverter ignores supported media types property --- .../MappingJacksonHttpMessageConverter.java | 63 ++++++++++++------- ...ppingJacksonHttpMessageConverterTests.java | 36 ++++++++--- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java index dfb2681b403..b076cc390d8 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java @@ -20,9 +20,14 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.List; +import javax.xml.bind.Marshaller; +import javax.xml.bind.PropertyException; + import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.type.JavaType; import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.type.TypeFactory; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; @@ -39,24 +44,24 @@ import org.springframework.util.Assert; *

This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances. * *

By default, this converter supports {@code application/json}. This can be overridden by setting the {@link - * #setSupportedMediaTypes(List) supportedMediaTypes} property, and overriding the {@link #getContentType(Object)} + * #setSupportedMediaTypes(List) supportedMediaTypes} property. * method. * * @author Arjen Poutsma * @see org.springframework.web.servlet.view.json.BindingJacksonJsonView * @since 3.0 */ -public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter { +public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter { + + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private ObjectMapper objectMapper = new ObjectMapper(); - private JsonEncoding encoding = JsonEncoding.UTF8; - private boolean prefixJson = false; /** Construct a new {@code BindingJacksonHttpMessageConverter}, */ public MappingJacksonHttpMessageConverter() { - super(new MediaType("application", "json")); + super(new MediaType("application", "json", DEFAULT_CHARSET)); } /** @@ -73,12 +78,6 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageCo this.objectMapper = objectMapper; } - /** Sets the {@code JsonEncoding} for this converter. By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. */ - public void setEncoding(JsonEncoding encoding) { - Assert.notNull(encoding, "'encoding' must not be null"); - this.encoding = encoding; - } - /** * Indicates whether the JSON output by this view should be prefixed with "{} &&". Default is false. * @@ -91,30 +90,50 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageCo } @Override - public boolean supports(Class clazz) { - return objectMapper.canSerialize(clazz); + public boolean canRead(Class clazz, MediaType mediaType) { + JavaType javaType = TypeFactory.fromClass(clazz); + return objectMapper.canDeserialize(javaType) && isSupported(mediaType); } @Override - protected T readInternal(Class clazz, HttpInputMessage inputMessage) + public boolean canWrite(Class clazz, MediaType mediaType) { + return objectMapper.canSerialize(clazz) && isSupported(mediaType); + } + + @Override + protected boolean supports(Class clazz) { + // should not be called, since we override canRead/Write + throw new UnsupportedOperationException(); + } + + @Override + protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return objectMapper.readValue(inputMessage.getBody(), clazz); } @Override - protected MediaType getDefaultContentType(T t) { - Charset charset = Charset.forName(encoding.getJavaName()); - return new MediaType("application", "json", charset); - } - - @Override - protected void writeInternal(T t, HttpOutputMessage outputMessage) + protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + JsonEncoding encoding = getEncoding(outputMessage.getHeaders().getContentType()); JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding); if (prefixJson) { jsonGenerator.writeRaw("{} && "); } - objectMapper.writeValue(jsonGenerator, t); + objectMapper.writeValue(jsonGenerator, o); } + + private JsonEncoding getEncoding(MediaType contentType) { + if (contentType != null && contentType.getCharSet() != null) { + Charset charset = contentType.getCharSet(); + for (JsonEncoding encoding : JsonEncoding.values()) { + if (charset.name().equals(encoding.getJavaName())) { + return encoding; + } + } + } + return JsonEncoding.UTF8; + } + } diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java index 501d90ad981..1fd36ee12ec 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java @@ -21,6 +21,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.junit.Assert.*; import org.junit.Before; @@ -33,25 +34,33 @@ import org.springframework.http.MockHttpOutputMessage; /** @author Arjen Poutsma */ public class MappingJacksonHttpMessageConverterTests { - private MappingJacksonHttpMessageConverter converter; + private MappingJacksonHttpMessageConverter converter; @Before public void setUp() { - converter = new MappingJacksonHttpMessageConverter(); + converter = new MappingJacksonHttpMessageConverter(); } @Test - public void supports() { - assertTrue(converter.supports(MyBean.class)); + public void canRead() { + assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json"))); + assertTrue(converter.canRead(Map.class, new MediaType("application", "json"))); } @Test + public void canWrite() { + assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json"))); + assertTrue(converter.canWrite(Map.class, new MediaType("application", "json"))); + } + + @Test + @SuppressWarnings("unchecked") public void readTyped() throws IOException { String body = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); - MyBean result = converter.read(MyBean.class, inputMessage); + MyBean result = (MyBean) converter.read((Class) MyBean.class, inputMessage); assertEquals("Foo", result.getString()); assertEquals(42, result.getNumber()); assertEquals(42F, result.getFraction(), 0F); @@ -61,14 +70,13 @@ public class MappingJacksonHttpMessageConverterTests { } @Test - @SuppressWarnings({"unchecked"}) + @SuppressWarnings("unchecked") public void readUntyped() throws IOException { - MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter(); String body = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8")); inputMessage.getHeaders().setContentType(new MediaType("application", "json")); - HashMap result = converter.read(HashMap.class, inputMessage); + HashMap result = (HashMap) converter.read((Class)HashMap.class, inputMessage); assertEquals("Foo", result.get("string")); assertEquals(42, result.get("number")); assertEquals(42D, (Double) result.get("fraction"), 0D); @@ -103,6 +111,18 @@ public class MappingJacksonHttpMessageConverterTests { outputMessage.getHeaders().getContentType()); } + @Test + public void writeUTF16() throws IOException { + Charset utf16 = Charset.forName("UTF-16BE"); + MediaType contentType = new MediaType("application", "json", utf16); + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + String body = "H\u00e9llo W\u00f6rld"; + converter.write(body, contentType, outputMessage); + assertEquals("Invalid result", "\"" + body + "\"", outputMessage.getBodyAsString(utf16)); + assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType()); + } + + public static class MyBean { private String string;