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 extends Module>[] 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 extends Module>... 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 extends ObjectMapper> xmlMapper = (Class extends ObjectMapper>)
+ 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 extends Module> 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 extends T>) type, (JsonSerializer) this.serializers.get(type));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addDeserializers(SimpleModule module) {
+ for (Class> type : this.deserializers.keySet()) {
+ module.addDeserializer((Class) type, (JsonDeserializer extends T>) 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 extends Module> jsr310Module = (Class extends Module>)
+ 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 extends Module> jodaModule = (Class extends Module>)
+ 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 extends ObjectMapper>)
+ 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);