diff --git a/build.gradle b/build.gradle index f58d6040ca1..d4f6432434f 100644 --- a/build.gradle +++ b/build.gradle @@ -182,6 +182,7 @@ configure(allprojects) { project -> "http://quartz-scheduler.org/api/2.2.0/", "http://fasterxml.github.com/jackson-core/javadoc/2.3.0/", "http://fasterxml.github.com/jackson-databind/javadoc/2.3.0/", + "http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.3.0/", "http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/" ] as String[] } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java new file mode 100644 index 00000000000..205ed17f09b --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java @@ -0,0 +1,483 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter.json; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.AnnotationIntrospector; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.FatalBeanException; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * A builder used to create {@link ObjectMapper} instances with a fluent API. + * + *

Note that Jackson's JSR-310 and Joda-Time support modules will be registered automatically + * when available (and when Java 8 and Joda-Time themselves are available, respectively). + * + *

Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher. + * + * @author Sebastien Deleuze + * @since 4.1.1 + */ +public class Jackson2ObjectMapperBuilder { + + private static final boolean jackson2XmlPresent = + ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", getClassLoader()); + + private ObjectMapper objectMapper; + + private boolean createXmlMapper = false; + + private DateFormat dateFormat; + + private JsonInclude.Include serializationInclusion; + + private AnnotationIntrospector annotationIntrospector; + + private final Map, JsonSerializer> + serializers = new LinkedHashMap, JsonSerializer>(); + + private final Map, JsonDeserializer> + deserializers = new LinkedHashMap, JsonDeserializer>(); + + private final Map features = new HashMap(); + + private List modules; + + private Class[] modulesToInstall; + + private boolean findModulesViaServiceLoader; + + private PropertyNamingStrategy propertyNamingStrategy; + + + private Jackson2ObjectMapperBuilder() { + } + + private Jackson2ObjectMapperBuilder(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + /** + * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build an {@link ObjectMapper} instance. + */ + public static Jackson2ObjectMapperBuilder json() { + return new Jackson2ObjectMapperBuilder(); + } + + /** + * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build a {@link XmlMapper} instance. + */ + @SuppressWarnings("unchecked") + public static Jackson2ObjectMapperBuilder xml() { + return new Jackson2ObjectMapperBuilder().createXmlMapper(true); + } + + /** + * Obtain a {@link Jackson2ObjectMapperBuilder} in order to customize the {@link ObjectMapper} parameter. + */ + public static Jackson2ObjectMapperBuilder instance(ObjectMapper objectMapper) { + return new Jackson2ObjectMapperBuilder(objectMapper); + } + + private static ClassLoader getClassLoader() { + ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); + Assert.state(classLoader != null, "No classloader available"); + return classLoader; + } + + /** + * If set to true and no custom {@link ObjectMapper} has been set, a {@link XmlMapper} + * will be created using its default constructor. + */ + public Jackson2ObjectMapperBuilder createXmlMapper(boolean createXmlMapper) { + this.createXmlMapper = createXmlMapper; + return this; + } + + /** + * Define the format for date/time with the given {@link DateFormat}. + *

Note: Setting this property makes the exposed {@link ObjectMapper} + * non-thread-safe, according to Jackson's thread safety rules. + * @see #simpleDateFormat(String) + */ + public Jackson2ObjectMapperBuilder dateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + return this; + } + + /** + * Define the date/time format with a {@link SimpleDateFormat}. + *

Note: Setting this property makes the exposed {@link ObjectMapper} + * non-thread-safe, according to Jackson's thread safety rules. + * @see #dateFormat(DateFormat) + */ + public Jackson2ObjectMapperBuilder simpleDateFormat(String format) { + this.dateFormat = new SimpleDateFormat(format); + return this; + } + + /** + * Set an {@link AnnotationIntrospector} for both serialization and deserialization. + */ + public Jackson2ObjectMapperBuilder annotationIntrospector(AnnotationIntrospector annotationIntrospector) { + this.annotationIntrospector = annotationIntrospector; + return this; + } + + /** + * Set a custom inclusion strategy for serialization. + * @see com.fasterxml.jackson.annotation.JsonInclude.Include + */ + public Jackson2ObjectMapperBuilder serializationInclusion(JsonInclude.Include serializationInclusion) { + this.serializationInclusion = serializationInclusion; + return this; + } + + /** + * Configure custom serializers. Each serializer is registered for the type + * returned by {@link JsonSerializer#handledType()}, which must not be + * {@code null}. + * @see #serializersByType(Map) + */ + public Jackson2ObjectMapperBuilder serializers(JsonSerializer... serializers) { + if (serializers != null) { + for (JsonSerializer serializer : serializers) { + Class handledType = serializer.handledType(); + Assert.isTrue(handledType != null && handledType != Object.class, + "Unknown handled type in " + serializer.getClass().getName()); + this.serializers.put(serializer.handledType(), serializer); + } + } + return this; + } + + /** + * Configure custom serializers for the given types. + * @see #serializers(JsonSerializer...) + */ + public Jackson2ObjectMapperBuilder serializersByType(Map, JsonSerializer> serializers) { + if (serializers != null) { + this.serializers.putAll(serializers); + } + return this; + } + + /** + * Configure custom deserializers for the given types. + */ + public Jackson2ObjectMapperBuilder deserializersByType(Map, JsonDeserializer> deserializers) { + if (deserializers != null) { + this.deserializers.putAll(deserializers); + } + return this; + } + + /** + * Shortcut for {@link MapperFeature#AUTO_DETECT_FIELDS} option. + */ + public Jackson2ObjectMapperBuilder autoDetectFields(boolean autoDetectFields) { + this.features.put(MapperFeature.AUTO_DETECT_FIELDS, autoDetectFields); + return this; + } + + /** + * Shortcut for {@link MapperFeature#AUTO_DETECT_SETTERS}/ + * {@link MapperFeature#AUTO_DETECT_GETTERS} option. + */ + public Jackson2ObjectMapperBuilder autoDetectGettersSetters(boolean autoDetectGettersSetters) { + this.features.put(MapperFeature.AUTO_DETECT_GETTERS, autoDetectGettersSetters); + this.features.put(MapperFeature.AUTO_DETECT_SETTERS, autoDetectGettersSetters); + return this; + } + + /** + * Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option. + */ + public Jackson2ObjectMapperBuilder failOnUnknownProperties(boolean failOnUnknownProperties) { + this.features.put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties); + return this; + } + + /** + * Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option. + */ + public Jackson2ObjectMapperBuilder defaultViewInclusion(boolean defaultViewInclusion) { + this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion); + return this; + } + + /** + * Shortcut for {@link SerializationFeature#FAIL_ON_EMPTY_BEANS} option. + */ + public Jackson2ObjectMapperBuilder failOnEmptyBeans(boolean failOnEmptyBeans) { + this.features.put(SerializationFeature.FAIL_ON_EMPTY_BEANS, failOnEmptyBeans); + return this; + } + + /** + * Shortcut for {@link SerializationFeature#INDENT_OUTPUT} option. + */ + public Jackson2ObjectMapperBuilder indentOutput(boolean indentOutput) { + this.features.put(SerializationFeature.INDENT_OUTPUT, indentOutput); + return this; + } + + /** + * Specify features to enable. + * @see com.fasterxml.jackson.core.JsonParser.Feature + * @see com.fasterxml.jackson.core.JsonGenerator.Feature + * @see com.fasterxml.jackson.databind.SerializationFeature + * @see com.fasterxml.jackson.databind.DeserializationFeature + * @see com.fasterxml.jackson.databind.MapperFeature + */ + public Jackson2ObjectMapperBuilder featuresToEnable(Object... featuresToEnable) { + if (featuresToEnable != null) { + for (Object feature : featuresToEnable) { + this.features.put(feature, Boolean.TRUE); + } + } + return this; + } + + /** + * Specify features to disable. + * @see com.fasterxml.jackson.core.JsonParser.Feature + * @see com.fasterxml.jackson.core.JsonGenerator.Feature + * @see com.fasterxml.jackson.databind.SerializationFeature + * @see com.fasterxml.jackson.databind.DeserializationFeature + * @see com.fasterxml.jackson.databind.MapperFeature + */ + public Jackson2ObjectMapperBuilder featuresToDisable(Object... featuresToDisable) { + if (featuresToDisable != null) { + for (Object feature : featuresToDisable) { + this.features.put(feature, Boolean.FALSE); + } + } + return this; + } + + /** + * Set a complete list of modules to be registered with the {@link ObjectMapper}. + *

Note: If this is set, no finding of modules is going to happen - not by + * Jackson, and not by Spring either (see {@link #findModulesViaServiceLoader}). + * As a consequence, specifying an empty list here will suppress any kind of + * module detection. + *

Specify either this or {@link #modulesToInstall}, not both. + * @see com.fasterxml.jackson.databind.Module + */ + public Jackson2ObjectMapperBuilder modules(List modules) { + this.modules = new LinkedList(modules); + return this; + } + + /** + * Specify one or more modules by class (or class name in XML), + * to be registered with the {@link ObjectMapper}. + *

Modules specified here will be registered in combination with + * Spring's autodetection of JSR-310 and Joda-Time, or Jackson's + * finding of modules (see {@link #findModulesViaServiceLoader}). + *

Specify either this or {@link #modules}, not both. + * @see com.fasterxml.jackson.databind.Module + */ + public Jackson2ObjectMapperBuilder modulesToInstall(Class... modules) { + this.modulesToInstall = modules; + return this; + } + + /** + * Set whether to let Jackson find available modules via the JDK ServiceLoader, + * based on META-INF metadata in the classpath. Requires Jackson 2.2 or higher. + *

If this mode is not set, Spring's Jackson2ObjectMapperBuilder itself + * will try to find the JSR-310 and Joda-Time support modules on the classpath - + * provided that Java 8 and Joda-Time themselves are available, respectively. + * @see com.fasterxml.jackson.databind.ObjectMapper#findModules() + */ + public Jackson2ObjectMapperBuilder findModulesViaServiceLoader(boolean findModules) { + this.findModulesViaServiceLoader = findModules; + return this; + } + + /** + * Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to + * configure the {@link ObjectMapper} with. + */ + public Jackson2ObjectMapperBuilder propertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) { + this.propertyNamingStrategy = propertyNamingStrategy; + return this; + } + + /** + * Build a new {@link T} instance. + */ + @SuppressWarnings("unchecked") + public T build() { + if (this.objectMapper == null) { + if(this.createXmlMapper) { + ClassLoader cl = getClassLoader(); + try { + Class xmlMapper = (Class) + cl.loadClass("com.fasterxml.jackson.dataformat.xml.XmlMapper"); + this.objectMapper = BeanUtils.instantiate(xmlMapper); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Could not instantiate XmlMapper, it has not been found on the classpath"); + } + } + else { + this.objectMapper = new ObjectMapper(); + } + } + + if (this.dateFormat != null) { + this.objectMapper.setDateFormat(this.dateFormat); + } + + if (this.annotationIntrospector != null) { + this.objectMapper.setAnnotationIntrospector(this.annotationIntrospector); + } + + if (this.serializationInclusion != null) { + this.objectMapper.setSerializationInclusion(this.serializationInclusion); + } + + if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) { + SimpleModule module = new SimpleModule(); + addSerializers(module); + addDeserializers(module); + this.objectMapper.registerModule(module); + } + + for (Object feature : this.features.keySet()) { + configureFeature(feature, this.features.get(feature)); + } + + if (this.modules != null) { + // Complete list of modules given + for (Module module : this.modules) { + // Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules + this.objectMapper.registerModule(module); + } + } + else { + // Combination of modules by class names specified and class presence in the classpath + if (this.modulesToInstall != null) { + for (Class module : this.modulesToInstall) { + this.objectMapper.registerModule(BeanUtils.instantiate(module)); + } + } + if (this.findModulesViaServiceLoader) { + // Jackson 2.2+ + this.objectMapper.registerModules(ObjectMapper.findModules(getClassLoader())); + } + else { + registerWellKnownModulesIfAvailable(); + } + } + + if (this.propertyNamingStrategy != null) { + this.objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy); + } + + return (T)this.objectMapper; + } + + @SuppressWarnings("unchecked") + private void addSerializers(SimpleModule module) { + for (Class type : this.serializers.keySet()) { + module.addSerializer((Class) type, (JsonSerializer) this.serializers.get(type)); + } + } + + @SuppressWarnings("unchecked") + private void addDeserializers(SimpleModule module) { + for (Class type : this.deserializers.keySet()) { + module.addDeserializer((Class) type, (JsonDeserializer) this.deserializers.get(type)); + } + } + + private void configureFeature(Object feature, boolean enabled) { + if (feature instanceof JsonParser.Feature) { + this.objectMapper.configure((JsonParser.Feature) feature, enabled); + } + else if (feature instanceof JsonGenerator.Feature) { + this.objectMapper.configure((JsonGenerator.Feature) feature, enabled); + } + else if (feature instanceof SerializationFeature) { + this.objectMapper.configure((SerializationFeature) feature, enabled); + } + else if (feature instanceof DeserializationFeature) { + this.objectMapper.configure((DeserializationFeature) feature, enabled); + } + else if (feature instanceof MapperFeature) { + this.objectMapper.configure((MapperFeature) feature, enabled); + } + else { + throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName()); + } + } + + @SuppressWarnings("unchecked") + private void registerWellKnownModulesIfAvailable() { + ClassLoader cl = getClassLoader(); + // Java 8 java.time package present? + if (ClassUtils.isPresent("java.time.LocalDate", cl)) { + try { + Class jsr310Module = (Class) + cl.loadClass("com.fasterxml.jackson.datatype.jsr310.JSR310Module"); + this.objectMapper.registerModule(BeanUtils.instantiate(jsr310Module)); + } + catch (ClassNotFoundException ex) { + // jackson-datatype-jsr310 not available + } + } + // Joda-Time present? + if (ClassUtils.isPresent("org.joda.time.LocalDate", cl)) { + try { + Class jodaModule = (Class) + cl.loadClass("com.fasterxml.jackson.datatype.joda.JodaModule"); + this.objectMapper.registerModule(BeanUtils.instantiate(jodaModule)); + } + catch (ClassNotFoundException ex) { + // jackson-datatype-joda not available + } + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java index 1dbc8e810c2..b5e4e267cd8 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java @@ -37,6 +37,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.springframework.beans.BeanUtils; import org.springframework.beans.FatalBeanException; @@ -47,7 +48,8 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** - * A {@link FactoryBean} for creating a Jackson 2.x {@link ObjectMapper} with setters + * A {@link FactoryBean} for creating a Jackson 2.x {@link ObjectMapper} (default) or + * {@link XmlMapper} ({@code createXmlMapper} property set to true) with setters * to enable or disable Jackson features from within XML configuration. * *

Example usage with @@ -126,6 +128,8 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBeanNote: Setting this property makes the exposed {@link ObjectMapper} @@ -244,6 +256,13 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean xmlMapper = (Class) + cl.loadClass("com.fasterxml.jackson.dataformat.xml.XmlMapper"); + this.objectMapper = BeanUtils.instantiate(xmlMapper); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Could not instantiate XmlMapper", ex); + } + } + else { + this.objectMapper = new ObjectMapper(); + } } if (this.dateFormat != null) { diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java index 1cc57706fbe..ba0c879319a 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverter.java @@ -32,6 +32,8 @@ import org.springframework.http.MediaType; *

By default, this converter supports {@code application/json}. This can be overridden by setting the * {@link #setSupportedMediaTypes supportedMediaTypes} property. * + *

The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. + * *

Compatible with Jackson 2.1 and higher. * * @author Arjen Poutsma @@ -47,13 +49,22 @@ public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMes /** - * Construct a new {@code MappingJackson2HttpMessageConverter}. + * Construct a new {@link MappingJackson2HttpMessageConverter} using default configuration + * provided by {@link Jackson2ObjectMapperBuilder}. */ public MappingJackson2HttpMessageConverter() { - super(new ObjectMapper(), new MediaType("application", "json", DEFAULT_CHARSET), - new MediaType("application", "*+json", DEFAULT_CHARSET)); + this(Jackson2ObjectMapperBuilder.json().build()); } + /** + * Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}. + * You can use {@link Jackson2ObjectMapperBuilder} to build it easily. + * @see Jackson2ObjectMapperBuilder#json() + */ + public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, new MediaType("application", "json", DEFAULT_CHARSET), + new MediaType("application", "*+json", DEFAULT_CHARSET)); + } /** * Specify a custom prefix to use for this view's JSON output. diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java index 1dc6a5a7e05..379db90ab41 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverter.java @@ -16,23 +16,52 @@ package org.springframework.http.converter.xml; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.springframework.http.MediaType; import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.util.Assert; /** * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} * that can read and write XML using * Jackson 2.x extension component for reading and writing XML encoded data. * + *

The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. + * * @author Sebastien Deleuze * @since 4.1 */ public class MappingJackson2XmlHttpMessageConverter extends AbstractJackson2HttpMessageConverter { + /** + * Construct a new {@code MappingJackson2XmlHttpMessageConverter} using default configuration + * provided by {@code Jackson2ObjectMapperBuilder}. + */ public MappingJackson2XmlHttpMessageConverter() { - super(new XmlMapper(), new MediaType("application", "xml", DEFAULT_CHARSET)); + this(Jackson2ObjectMapperBuilder.xml().build()); } + /** + * Construct a new {@code MappingJackson2XmlHttpMessageConverter} with a custom {@link ObjectMapper} + * (must be a {@link XmlMapper} instance). + * You can use {@link Jackson2ObjectMapperBuilder} to build it easily. + * @see Jackson2ObjectMapperBuilder#xml() + */ + public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, new MediaType("application", "xml", DEFAULT_CHARSET)); + Assert.isAssignable(XmlMapper.class, objectMapper.getClass()); + } + + /** + * {@inheritDoc} + * The {@code objectMapper} parameter must be a {@link XmlMapper} instance. + */ + @Override + public void setObjectMapper(ObjectMapper objectMapper) { + Assert.isAssignable(XmlMapper.class, objectMapper.getClass()); + super.setObjectMapper(objectMapper); + } } diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java new file mode 100644 index 00000000000..ff29cebbfae --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java @@ -0,0 +1,268 @@ +/* + * Copyright 2002-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter.json; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig; +import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig; +import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory; +import com.fasterxml.jackson.databind.deser.std.DateDeserializers; +import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.BasicSerializerFactory; +import com.fasterxml.jackson.databind.ser.Serializers; +import com.fasterxml.jackson.databind.ser.std.ClassSerializer; +import com.fasterxml.jackson.databind.ser.std.NumberSerializer; +import com.fasterxml.jackson.databind.type.SimpleType; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.junit.Test; + +import org.springframework.beans.FatalBeanException; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +/** + * Test class for {@link Jackson2ObjectMapperBuilder}. + * + * @author Sebastien Deleuze + */ +public class Jackson2ObjectMapperBuilderTests { + + private static final String DATE_FORMAT = "yyyy-MM-dd"; + + + @Test + public void settersWithNullValues() { + // Should not crash: + Jackson2ObjectMapperBuilder.json().serializers((JsonSerializer[]) null) + .serializersByType(null).deserializersByType(null) + .featuresToEnable((Object[]) null).featuresToDisable((Object[]) null); + } + + @Test(expected = FatalBeanException.class) + public void unknownFeature() { + Jackson2ObjectMapperBuilder.json().featuresToEnable(Boolean.TRUE).build(); + } + + @Test + public void defaultProperties() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); + assertNotNull(objectMapper); + assertTrue(objectMapper.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + assertTrue(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_FIELDS)); + assertTrue(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_GETTERS)); + assertTrue(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_SETTERS)); + assertFalse(objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)); + assertTrue(objectMapper.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS)); + } + + @Test + public void propertiesShortcut() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().autoDetectFields(false) + .defaultViewInclusion(true).failOnUnknownProperties(true).failOnEmptyBeans(false) + .autoDetectGettersSetters(false).indentOutput(true).build(); + assertNotNull(objectMapper); + assertTrue(objectMapper.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + assertFalse(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_FIELDS)); + assertFalse(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_GETTERS)); + assertFalse(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_SETTERS)); + assertTrue(objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)); + assertFalse(objectMapper.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS)); + } + + @Test + public void booleanSetters() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() + .featuresToEnable(MapperFeature.DEFAULT_VIEW_INCLUSION, + DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + SerializationFeature.INDENT_OUTPUT) + .featuresToDisable(MapperFeature.AUTO_DETECT_FIELDS, + MapperFeature.AUTO_DETECT_GETTERS, + MapperFeature.AUTO_DETECT_SETTERS, + SerializationFeature.FAIL_ON_EMPTY_BEANS).build(); + assertNotNull(objectMapper); + assertTrue(objectMapper.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + assertFalse(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_FIELDS)); + assertFalse(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_GETTERS)); + assertFalse(objectMapper.isEnabled(MapperFeature.AUTO_DETECT_SETTERS)); + assertTrue(objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)); + assertFalse(objectMapper.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS)); + } + + @Test + public void setNotNullSerializationInclusion() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); + assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == + JsonInclude.Include.ALWAYS); + objectMapper = Jackson2ObjectMapperBuilder.json().serializationInclusion(JsonInclude.Include.NON_NULL).build(); + assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.NON_NULL); + } + + @Test + public void setNotDefaultSerializationInclusion() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); + assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.ALWAYS); + objectMapper = Jackson2ObjectMapperBuilder.json().serializationInclusion(JsonInclude.Include.NON_DEFAULT).build(); + assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.NON_DEFAULT); + } + + @Test + public void setNotEmptySerializationInclusion() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); + assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.ALWAYS); + objectMapper = Jackson2ObjectMapperBuilder.json().serializationInclusion(JsonInclude.Include.NON_EMPTY).build(); + assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.NON_EMPTY); + } + + @Test + public void dateTimeFormatSetter() { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().dateFormat(dateFormat).build(); + assertEquals(dateFormat, objectMapper.getSerializationConfig().getDateFormat()); + assertEquals(dateFormat, objectMapper.getDeserializationConfig().getDateFormat()); + } + + @Test + public void simpleDateFormatStringSetter() { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().simpleDateFormat(DATE_FORMAT).build(); + assertEquals(dateFormat, objectMapper.getSerializationConfig().getDateFormat()); + assertEquals(dateFormat, objectMapper.getDeserializationConfig().getDateFormat()); + } + + @Test + public void setModules() { + NumberSerializer serializer1 = new NumberSerializer(); + SimpleModule module = new SimpleModule(); + module.addSerializer(Integer.class, serializer1); + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(Arrays.asList(new Module[]{module})).build(); + Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); + assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1); + } + + private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) { + return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig(); + } + + private static DeserializerFactoryConfig getDeserializerFactoryConfig(ObjectMapper objectMapper) { + return ((BasicDeserializerFactory) objectMapper.getDeserializationContext().getFactory()).getFactoryConfig(); + } + + @Test + public void propertyNamingStrategy() { + PropertyNamingStrategy strategy = new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy(); + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().propertyNamingStrategy(strategy).build(); + assertSame(strategy, objectMapper.getSerializationConfig().getPropertyNamingStrategy()); + assertSame(strategy, objectMapper.getDeserializationConfig().getPropertyNamingStrategy()); + } + + @Test + public void completeSetup() { + NopAnnotationIntrospector annotationIntrospector = NopAnnotationIntrospector.instance; + + Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.instance(new ObjectMapper()); + + Map, JsonDeserializer> + deserializers = new HashMap, JsonDeserializer>(); + deserializers.put(Date.class, new DateDeserializers.DateDeserializer()); + + JsonSerializer> serializer1 = new ClassSerializer(); + JsonSerializer serializer2 = new NumberSerializer(); + + builder.serializers(serializer1); + builder.serializersByType(Collections + ., JsonSerializer>singletonMap(Boolean.class, serializer2)); + builder.deserializersByType(deserializers); + builder.annotationIntrospector(annotationIntrospector); + + builder.featuresToEnable(SerializationFeature.FAIL_ON_EMPTY_BEANS, + DeserializationFeature.UNWRAP_ROOT_VALUE, + JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, + JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS); + + builder.featuresToDisable(MapperFeature.AUTO_DETECT_GETTERS, + MapperFeature.AUTO_DETECT_FIELDS, JsonParser.Feature.AUTO_CLOSE_SOURCE, + JsonGenerator.Feature.QUOTE_FIELD_NAMES); + + builder.serializationInclusion(JsonInclude.Include.NON_NULL); + + ObjectMapper objectMapper = builder.build(); + + assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers()); + assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers()); + + Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); + assertTrue(serializers.findSerializer(null, SimpleType.construct(Class.class), null) == serializer1); + assertTrue(serializers.findSerializer(null, SimpleType.construct(Boolean.class), null) == serializer2); + assertNull(serializers.findSerializer(null, SimpleType.construct(Number.class), null)); + + assertTrue(annotationIntrospector == objectMapper.getSerializationConfig().getAnnotationIntrospector()); + assertTrue(annotationIntrospector == objectMapper.getDeserializationConfig().getAnnotationIntrospector()); + + assertTrue(objectMapper.getSerializationConfig().isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS)); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.UNWRAP_ROOT_VALUE)); + assertTrue(objectMapper.getFactory().isEnabled(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)); + assertTrue(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS)); + + assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.AUTO_DETECT_GETTERS)); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_FIELDS)); + assertFalse(objectMapper.getFactory().isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)); + assertFalse(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES)); + assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.NON_NULL); + } + + @Test + public void xmlMapper() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().build(); + assertNotNull(objectMapper); + assertEquals(XmlMapper.class, objectMapper.getClass()); + } + + @Test + public void createXmlMapper() { + Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json().indentOutput(true); + ObjectMapper jsonObjectMapper = builder.build(); + ObjectMapper xmlObjectMapper = builder.createXmlMapper(true).build(); + assertTrue(jsonObjectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)); + assertTrue(xmlObjectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)); + assertTrue(xmlObjectMapper.getClass().isAssignableFrom(XmlMapper.class)); + } + +} diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java index 0fc48238b3f..82e009ff1d8 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java @@ -260,6 +260,7 @@ public class Jackson2ObjectMapperFactoryBeanTests { assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.AUTO_DETECT_GETTERS)); assertTrue(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_FIELDS)); assertFalse(objectMapper.getFactory().isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)); assertFalse(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES)); @@ -276,4 +277,14 @@ public class Jackson2ObjectMapperFactoryBeanTests { assertEquals(XmlMapper.class, this.factory.getObjectType()); } + @Test + public void createXmlMapper() { + this.factory.setCreateXmlMapper(true); + this.factory.afterPropertiesSet(); + + assertNotNull(this.factory.getObject()); + assertTrue(this.factory.isSingleton()); + assertEquals(XmlMapper.class, this.factory.getObjectType()); + } + } diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index 3d5dad3a6c5..31f3f7e2815 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -253,8 +253,8 @@ public class MappingJackson2HttpMessageConverterTests { String result = outputMessage.getBodyAsString(Charset.forName("UTF-8")); assertThat(result, containsString("\"withView1\":\"with\"")); - assertThat(result, containsString("\"withoutView\":\"without\"")); assertThat(result, not(containsString("\"withView2\":\"with\""))); + assertThat(result, containsString("\"withoutView\":\"without\"")); } @Test @@ -286,8 +286,8 @@ public class MappingJackson2HttpMessageConverterTests { assertThat(result, startsWith("callback(")); assertThat(result, endsWith(");")); assertThat(result, containsString("\"withView1\":\"with\"")); - assertThat(result, containsString("\"withoutView\":\"without\"")); assertThat(result, not(containsString("\"withView2\":\"with\""))); + assertThat(result, containsString("\"withoutView\":\"without\"")); } diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java index a289f31363b..fbfd1dc9544 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java @@ -22,6 +22,7 @@ import java.lang.reflect.Method; import java.nio.charset.Charset; import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; @@ -123,8 +124,14 @@ public class MappingJackson2XmlHttpMessageConverterTests { String result = outputMessage.getBodyAsString(Charset.forName("UTF-8")); assertThat(result, containsString("with")); - assertThat(result, containsString("without")); assertThat(result, not(containsString("with"))); + assertThat(result, containsString("without")); + } + + @Test + public void customXmlMapper() { + new MappingJackson2XmlHttpMessageConverter(new MyXmlMapper()); + // Assert no exception is thrown } private void writeInternal(Object object, HttpOutputMessage outputMessage) @@ -238,4 +245,8 @@ public class MappingJackson2XmlHttpMessageConverterTests { } } + private static class MyXmlMapper extends XmlMapper { + + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java index 9adf4106438..7542601ce28 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java @@ -103,7 +103,7 @@ public abstract class AbstractJackson2View extends AbstractView { } /** - * Whether to use the default pretty printer when writing JSON. + * Whether to use the default pretty printer when writing the output. * This is a shortcut for setting up an {@code ObjectMapper} as follows: *

 	 * ObjectMapper mapper = new ObjectMapper();
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
index 4145c259836..b410a9b8690 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java
@@ -30,6 +30,7 @@ import com.fasterxml.jackson.annotation.JsonView;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 import org.springframework.http.converter.json.MappingJacksonValue;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
@@ -44,6 +45,8 @@ import org.springframework.web.servlet.View;
  * will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON
  * alone via  {@link #setExtractValueFromSingleKeyModel}.
  *
+ * 

The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. + * *

Compatible with Jackson 2.1 and higher. * * @author Jeremy Grelle @@ -76,10 +79,12 @@ public class MappingJackson2JsonView extends AbstractJackson2View { /** - * Construct a new {@code MappingJackson2JsonView}, setting the content type to {@code application/json}. + * Construct a new {@code MappingJackson2JsonView} using default configuration + * provided by {@link Jackson2ObjectMapperBuilder} and setting the content type + * to {@code application/json}. */ public MappingJackson2JsonView() { - super(new ObjectMapper(), DEFAULT_CONTENT_TYPE); + super(Jackson2ObjectMapperBuilder.json().build(), DEFAULT_CONTENT_TYPE); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java index b375c2f39b9..66b9d4c4eb7 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/xml/MappingJackson2XmlView.java @@ -21,6 +21,7 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.validation.BindingResult; import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.json.AbstractJackson2View; @@ -33,6 +34,8 @@ import org.springframework.web.servlet.view.json.AbstractJackson2View; * entry is used. Users can either specify a specific entry in the model via the * {@link #setModelKey(String) sourceKey} property. * + *

The default constructor uses the default configuration provided by {@link Jackson2ObjectMapperBuilder}. + * *

Compatible with Jackson 2.1 and higher. * * @author Sebastien Deleuze @@ -46,8 +49,13 @@ public class MappingJackson2XmlView extends AbstractJackson2View { private String modelKey; + /** + * Construct a new {@code MappingJackson2XmlView} using default configuration + * provided by {@link Jackson2ObjectMapperBuilder} and setting the content type + * to {@code application/xml}. + */ public MappingJackson2XmlView() { - super(new XmlMapper(), DEFAULT_CONTENT_TYPE); + super(Jackson2ObjectMapperBuilder.xml().build(), DEFAULT_CONTENT_TYPE); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java index b0bd56aa604..85f5cbf426e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java @@ -78,13 +78,13 @@ public class AnnotationDrivenBeanDefinitionParserTests { @Test public void testPathMatchingConfiguration() { - loadBeanDefinitions("mvc-config-path-matching.xml"); - RequestMappingHandlerMapping hm = appContext.getBean(RequestMappingHandlerMapping.class); - assertNotNull(hm); + loadBeanDefinitions("mvc-config-path-matching.xml"); + RequestMappingHandlerMapping hm = appContext.getBean(RequestMappingHandlerMapping.class); + assertNotNull(hm); assertTrue(hm.useSuffixPatternMatch()); assertFalse(hm.useTrailingSlashMatch()); assertTrue(hm.useRegisteredSuffixPatternMatch()); - assertThat(hm.getUrlPathHelper(), Matchers.instanceOf(TestPathHelper.class)); + assertThat(hm.getUrlPathHelper(), Matchers.instanceOf(TestPathHelper.class)); assertThat(hm.getPathMatcher(), Matchers.instanceOf(TestPathMatcher.class)); List fileExtensions = hm.getContentNegotiationManager().getAllFileExtensions(); assertThat(fileExtensions, Matchers.contains("xml")); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 92523958b10..8852628a4d0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -16,6 +16,10 @@ package org.springframework.web.servlet.config; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import static org.junit.Assert.*; import java.lang.annotation.Retention; @@ -44,6 +48,8 @@ import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockRequestDispatcher; @@ -162,8 +168,19 @@ public class MvcNamespaceTests { assertNotNull(adapter); assertEquals(false, new DirectFieldAccessor(adapter).getPropertyValue("ignoreDefaultModelOnRedirect")); - List> messageConverters = adapter.getMessageConverters(); - assertTrue(messageConverters.size() > 0); + List> converters = adapter.getMessageConverters(); + assertTrue(converters.size() > 0); + for(HttpMessageConverter converter : converters) { + if(converter instanceof AbstractJackson2HttpMessageConverter) { + ObjectMapper objectMapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper(); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + if(converter instanceof MappingJackson2XmlHttpMessageConverter) { + assertEquals(XmlMapper.class, objectMapper.getClass()); + } + } + } assertNotNull(appContext.getBean(FormattingConversionServiceFactoryBean.class)); assertNotNull(appContext.getBean(ConversionService.class)); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java index 7eca7a92814..1555d560ec7 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java @@ -16,14 +16,15 @@ package org.springframework.web.servlet.config.annotation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; +import com.fasterxml.jackson.databind.DeserializationFeature; import java.util.Arrays; import java.util.List; + import org.junit.Before; import org.junit.Test; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.DirectFieldAccessor; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; @@ -72,6 +73,8 @@ import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; +import static org.junit.Assert.*; + /** * A test fixture with a sub-class of {@link WebMvcConfigurationSupport} that also * implements the various {@link WebMvcConfigurer} extension points. @@ -157,6 +160,14 @@ public class WebMvcConfigurationSupportExtensionTests { // Message converters assertEquals(1, adapter.getMessageConverters().size()); + assertEquals(MappingJackson2HttpMessageConverter.class, adapter.getMessageConverters().get(0).getClass()); + ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter)adapter.getMessageConverters().get(0)).getObjectMapper(); + assertTrue(objectMapper.getDeserializationConfig() + .isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getSerializationConfig() + .isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getDeserializationConfig() + .isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index b7d049b6c73..2168cffb146 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -21,6 +21,10 @@ import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.joda.time.DateTime; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; @@ -36,6 +40,9 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockServletContext; import org.springframework.stereotype.Controller; @@ -150,7 +157,19 @@ public class WebMvcConfigurationSupportTests { public void requestMappingHandlerAdapter() throws Exception { ApplicationContext context = initContext(WebConfig.class); RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); - assertEquals(9, adapter.getMessageConverters().size()); + List> converters = adapter.getMessageConverters(); + assertEquals(9, converters.size()); + for(HttpMessageConverter converter : converters) { + if(converter instanceof AbstractJackson2HttpMessageConverter) { + ObjectMapper objectMapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper(); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(objectMapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + if(converter instanceof MappingJackson2XmlHttpMessageConverter) { + assertEquals(XmlMapper.class, objectMapper.getClass()); + } + } + } ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer(); assertNotNull(initializer);