diff --git a/spring-web/src/main/java/org/springframework/web/context/support/JacksonObjectMapperFactoryBean.java b/spring-web/src/main/java/org/springframework/web/context/support/JacksonObjectMapperFactoryBean.java
new file mode 100644
index 0000000000..dec3e926d6
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/context/support/JacksonObjectMapperFactoryBean.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2002-2012 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.web.context.support;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.map.AnnotationIntrospector;
+import org.codehaus.jackson.map.DeserializationConfig;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.springframework.beans.FatalBeanException;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * A FactoryBean for creating a Jackson {@link ObjectMapper} with setters to
+ * enable or disable Jackson features from within XML configuration.
+ *
+ *
Example usage with MappingJacksonHttpMessageConverter:
+ *
+ * <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
+ * <property name="objectMapper">
+ * <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
+ * p:autoDetectFields="false"
+ * p:autoDetectGettersSetters="false"
+ * p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
+ * </property>
+ * </bean>
+ *
+ *
+ * Example usage with MappingJacksonJsonView:
+ *
+ * <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
+ * <property name="objectMapper">
+ * <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean"
+ * p:autoDetectFields="false"
+ * p:autoDetectGettersSetters="false"
+ * p:annotationIntrospector-ref="jaxbAnnotationIntrospector" />
+ * </property>
+ * </bean>
+ *
+ *
+ * In case there are no specific setters provided (for some rarely used
+ * options), you can still use the more general methods
+ * {@link #setFeaturesToEnable(Object[])} and {@link #setFeaturesToDisable(Object[])}.
+ *
+ *
+ * <bean class="org.springframework.web.context.support.JacksonObjectMapperFactoryBean">
+ * <property name="featuresToEnable">
+ * <array>
+ * <util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.WRAP_ROOT_VALUE"/>
+ * <util:constant static-field="org.codehaus.jackson.map.SerializationConfig$Feature.CLOSE_CLOSEABLE"/>
+ * </array>
+ * </property>
+ * <property name="featuresToDisable">
+ * <array>
+ * <util:constant static-field="org.codehaus.jackson.map.DeserializationConfig$Feature.USE_ANNOTATIONS"/>
+ * </array>
+ * </property>
+ * </bean>
+ *
+ *
+ * Note: This BeanFctory is singleton, so if you need more than one, you'll
+ * need to configure multiple instances.
+ *
+ * @author Dmitry Katsubo
+ * @author Rossen Stoyanchev
+ *
+ * @since 3.2
+ */
+public class JacksonObjectMapperFactoryBean implements FactoryBean, InitializingBean {
+
+ private ObjectMapper objectMapper;
+
+ private Map features = new HashMap();
+
+ private AnnotationIntrospector annotationIntrospector;
+
+ private DateFormat dateFormat;
+
+ /**
+ * Set the ObjectMapper instance to use.
+ * If not set an instance will be created using the default constructor.
+ */
+ public void setObjectMapper(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
+ /**
+ * Define annotationIntrospector for
+ * {@link SerializationConfig#setAnnotationIntrospector(AnnotationIntrospector)}.
+ */
+ public void setAnnotationIntrospector(AnnotationIntrospector annotationIntrospector) {
+ this.annotationIntrospector = annotationIntrospector;
+ }
+
+ /**
+ * Define the date/time format with the given string, which is in turn used
+ * to create a {@link SimpleDateFormat}.
+ * @see #setDateFormat(DateFormat)
+ */
+ public void setSimpleDateFormat(String format) {
+ this.dateFormat = new SimpleDateFormat(format);
+ }
+
+ /**
+ * Define the format for date/time with the given {@link DateFormat} instance.
+ * @see #setSimpleDateFormat(String)
+ */
+ public void setDateFormat(DateFormat dateFormat) {
+ this.dateFormat = dateFormat;
+ }
+
+ /**
+ * Shortcut for {@link SerializationConfig.Feature#AUTO_DETECT_FIELDS} and
+ * {@link DeserializationConfig.Feature#AUTO_DETECT_FIELDS}.
+ */
+ public void setAutoDetectFields(boolean autoDetectFields) {
+ this.features.put(DeserializationConfig.Feature.AUTO_DETECT_FIELDS, Boolean.valueOf(autoDetectFields));
+ this.features.put(SerializationConfig.Feature.AUTO_DETECT_FIELDS, Boolean.valueOf(autoDetectFields));
+ }
+
+ /**
+ * Shortcut for {@link SerializationConfig.Feature#AUTO_DETECT_GETTERS} and
+ * {@link DeserializationConfig.Feature#AUTO_DETECT_SETTERS}.
+ */
+ public void setAutoDetectGettersSetters(boolean autoDetectGettersSetters) {
+ this.features.put(SerializationConfig.Feature.AUTO_DETECT_GETTERS, Boolean.valueOf(autoDetectGettersSetters));
+ this.features.put(DeserializationConfig.Feature.AUTO_DETECT_SETTERS, Boolean.valueOf(autoDetectGettersSetters));
+ }
+
+ /**
+ * Shortcut for {@link SerializationConfig.Feature#FAIL_ON_EMPTY_BEANS}.
+ */
+ public void setFailOnEmptyBeans(boolean failOnEmptyBeans) {
+ this.features.put(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, Boolean.valueOf(failOnEmptyBeans));
+ }
+
+ /**
+ * Shortcut for {@link SerializationConfig.Feature#INDENT_OUTPUT}.
+ */
+ public void setIndentOutput(boolean indentOutput) {
+ this.features.put(SerializationConfig.Feature.INDENT_OUTPUT, Boolean.valueOf(indentOutput));
+ }
+
+ /**
+ * Specify features to enable.
+ * @see SerializationConfig.Feature
+ * @see DeserializationConfig.Feature
+ * @see JsonParser.Feature
+ * @see JsonGenerator.Feature
+ */
+ public void setFeaturesToEnable(Object[] featuresToEnable) {
+ if (featuresToEnable == null) {
+ throw new FatalBeanException("featuresToEnable property should not be null");
+ }
+ for (Object feature : featuresToEnable) {
+ this.features.put(feature, Boolean.TRUE);
+ }
+ }
+
+ /**
+ * Specify features to disable.
+ * @see SerializationConfig.Feature
+ * @see DeserializationConfig.Feature
+ * @see JsonParser.Feature
+ * @see JsonGenerator.Feature
+ */
+ public void setFeaturesToDisable(Object[] featuresToDisable) {
+ if (featuresToDisable == null) {
+ throw new FatalBeanException("featuresToDisable property should not be null");
+ }
+ for (Object feature : featuresToDisable) {
+ this.features.put(feature, Boolean.FALSE);
+ }
+ }
+
+ public ObjectMapper getObject() {
+ return this.objectMapper;
+ }
+
+ public Class> getObjectType() {
+ return ObjectMapper.class;
+ }
+
+ public boolean isSingleton() {
+ return true;
+ }
+
+ /**
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ public void afterPropertiesSet() throws FatalBeanException {
+ if (this.objectMapper == null) {
+ this.objectMapper = new ObjectMapper();
+ }
+
+ if (this.annotationIntrospector != null) {
+ this.objectMapper.getSerializationConfig().setAnnotationIntrospector(annotationIntrospector);
+ this.objectMapper.getDeserializationConfig().setAnnotationIntrospector(annotationIntrospector);
+ }
+
+ if (this.dateFormat != null) {
+ // Deprecated for 1.8+, use
+ // objectMapper.setDateFormat(dateFormat);
+ this.objectMapper.getSerializationConfig().setDateFormat(this.dateFormat);
+ }
+
+ for (Map.Entry entry : features.entrySet()) {
+ setFeatureEnabled(entry.getKey(), entry.getValue().booleanValue());
+ }
+ }
+
+ private void setFeatureEnabled(Object feature, boolean enabled) {
+ if (feature instanceof DeserializationConfig.Feature) {
+ this.objectMapper.configure((DeserializationConfig.Feature) feature, enabled);
+ }
+ else if (feature instanceof SerializationConfig.Feature) {
+ this.objectMapper.configure((SerializationConfig.Feature) feature, enabled);
+ }
+ else 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 {
+ throw new FatalBeanException("Unknown feature class " + feature.getClass().getName());
+ }
+ }
+}
diff --git a/spring-web/src/test/java/org/springframework/web/context/support/JacksonObjectMapperFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/web/context/support/JacksonObjectMapperFactoryBeanTests.java
new file mode 100644
index 0000000000..c086abaf0e
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/context/support/JacksonObjectMapperFactoryBeanTests.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2002-2012 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.web.context.support;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.text.SimpleDateFormat;
+
+import org.codehaus.jackson.JsonFactory;
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.map.DeserializationConfig;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.map.introspect.NopAnnotationIntrospector;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.FatalBeanException;
+
+/**
+ * Test cases for {@link JacksonObjectMapperFactoryBean} class.
+ *
+ * @author Dmitry Katsubo
+ */
+public class JacksonObjectMapperFactoryBeanTests {
+
+ private static final String DATE_FORMAT = "yyyy-MM-dd";
+
+ private JacksonObjectMapperFactoryBean factory;
+
+ @Before
+ public void setUp() {
+ factory = new JacksonObjectMapperFactoryBean();
+ }
+
+ @Test(expected=FatalBeanException.class)
+ public void testSetFeaturesToEnableNull() throws Exception {
+ factory.setFeaturesToEnable(null);
+ factory.setFeaturesToEnable(new Object[0]);
+ }
+
+ @Test(expected=FatalBeanException.class)
+ public void testSetFeaturesToDisableNull() {
+ factory.setFeaturesToDisable(null);
+ }
+
+ @Test
+ public void testSetFeaturesToEnableEmpty() {
+ factory.setFeaturesToEnable(new Object[0]);
+ factory.setFeaturesToDisable(new Object[0]);
+ }
+
+ @Test(expected = FatalBeanException.class)
+ public void testUnknownFeature() {
+ factory.setFeaturesToEnable(new Object[] { Boolean.TRUE });
+ factory.afterPropertiesSet();
+ }
+
+ @Test
+ public void testBooleanSetters() {
+ factory.setAutoDetectFields(false);
+ factory.setAutoDetectGettersSetters(false);
+ factory.setFailOnEmptyBeans(false);
+ factory.setIndentOutput(true);
+
+ factory.afterPropertiesSet();
+
+ ObjectMapper objectMapper = factory.getObject();
+
+ SerializationConfig serializeConfig = objectMapper.getSerializationConfig();
+ DeserializationConfig deserializeConfig = objectMapper.getDeserializationConfig();
+
+ assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.AUTO_DETECT_FIELDS));
+ assertFalse(deserializeConfig.isEnabled(DeserializationConfig.Feature.AUTO_DETECT_FIELDS));
+ assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.AUTO_DETECT_GETTERS));
+ assertFalse(deserializeConfig.isEnabled(DeserializationConfig.Feature.AUTO_DETECT_SETTERS));
+ assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS));
+ assertTrue(serializeConfig.isEnabled(SerializationConfig.Feature.INDENT_OUTPUT));
+ }
+
+ @Test
+ public void testDateTimeFormatSetter() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+
+ factory.setDateFormat(dateFormat);
+ factory.afterPropertiesSet();
+
+ assertEquals(dateFormat, factory.getObject().getSerializationConfig().getDateFormat());
+ }
+
+ @Test
+ public void testSimpleDateFormatStringSetter() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
+
+ factory.setSimpleDateFormat(DATE_FORMAT);
+ factory.afterPropertiesSet();
+
+ assertEquals(dateFormat, factory.getObject().getSerializationConfig().getDateFormat());
+ }
+
+ @Test
+ public void testSimpleSetup() {
+ factory.afterPropertiesSet();
+
+ assertNotNull(factory.getObject());
+ assertTrue(factory.isSingleton());
+ assertEquals(ObjectMapper.class, factory.getObjectType());
+ }
+
+ @Test
+ public void testCompleteSetup() {
+ NopAnnotationIntrospector annotationIntrospector = new NopAnnotationIntrospector();
+ ObjectMapper objectMapper = new ObjectMapper();
+
+ assertTrue(factory.isSingleton());
+ assertEquals(ObjectMapper.class, factory.getObjectType());
+
+ factory.setObjectMapper(objectMapper);
+ factory.setAnnotationIntrospector(annotationIntrospector);
+ factory.setFeaturesToEnable(new Object[] {
+ SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,
+ DeserializationConfig.Feature.USE_ANNOTATIONS,
+ JsonParser.Feature.ALLOW_SINGLE_QUOTES,
+ JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS
+ });
+ factory.setFeaturesToDisable(new Object[] {
+ SerializationConfig.Feature.AUTO_DETECT_GETTERS,
+ DeserializationConfig.Feature.AUTO_DETECT_FIELDS,
+ JsonParser.Feature.AUTO_CLOSE_SOURCE,
+ JsonGenerator.Feature.QUOTE_FIELD_NAMES
+ });
+
+ factory.afterPropertiesSet();
+
+ assertTrue(objectMapper == factory.getObject());
+
+ SerializationConfig serializeConfig = objectMapper.getSerializationConfig();
+ DeserializationConfig deserializeConfig = objectMapper.getDeserializationConfig();
+ JsonFactory jsonFactory = objectMapper.getJsonFactory();
+
+ assertTrue(annotationIntrospector == serializeConfig.getAnnotationIntrospector());
+ assertTrue(annotationIntrospector == deserializeConfig.getAnnotationIntrospector());
+
+ assertTrue(serializeConfig.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS));
+ assertTrue(deserializeConfig.isEnabled(DeserializationConfig.Feature.USE_ANNOTATIONS));
+ assertTrue(jsonFactory.isEnabled(JsonParser.Feature.ALLOW_SINGLE_QUOTES));
+ assertTrue(jsonFactory.isEnabled(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS));
+
+ assertFalse(serializeConfig.isEnabled(SerializationConfig.Feature.AUTO_DETECT_GETTERS));
+ assertFalse(deserializeConfig.isEnabled(DeserializationConfig.Feature.AUTO_DETECT_FIELDS));
+ assertFalse(jsonFactory.isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
+ assertFalse(jsonFactory.isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));
+ }
+}
diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt
index 5b491efc36..c15e1d84d9 100644
--- a/src/dist/changelog.txt
+++ b/src/dist/changelog.txt
@@ -8,6 +8,7 @@ Changes in version 3.2 M2
* spring-test module now depends on junit:junit-dep
* raise RestClientException instead of IllegalArgumentException for unknown status codes
+* add JacksonObjectMapperFactoryBean for configuring a Jackson ObjectMapper in XML
Changes in version 3.2 M1 (2012-05-28)
--------------------------------------