From fab8dd3d0bb726cee22c042775baf575a3e2a7c2 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 11 Feb 2015 19:03:54 +0100 Subject: [PATCH] Combined backport of Jackson configuration enhancements: Locale/TimeZone settings, refined module configuration Issue: SPR-12594 Issue: SPR-12634 --- build.gradle | 1 + .../json/Jackson2ObjectMapperBuilder.java | 154 ++++++++++++++---- .../json/Jackson2ObjectMapperFactoryBean.java | 27 ++- .../Jackson2ObjectMapperBuilderTests.java | 133 ++++++++++++++- .../Jackson2ObjectMapperFactoryBeanTests.java | 129 ++++++++++++++- 5 files changed, 404 insertions(+), 40 deletions(-) diff --git a/build.gradle b/build.gradle index 5d65ea38757..bed67bf8daf 100644 --- a/build.gradle +++ b/build.gradle @@ -697,6 +697,7 @@ project("spring-web") { testCompile("org.apache.taglibs:taglibs-standard-jstlel:1.2.1") { exclude group: "org.apache.taglibs", module: "taglibs-standard-spec" } + testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:${jackson2Version}") testRuntime("com.sun.mail:javax.mail:1.5.2") } 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 index 25100fe890a..d0871c86286 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -18,11 +18,14 @@ package org.springframework.http.converter.json; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; @@ -46,6 +49,7 @@ import org.springframework.beans.FatalBeanException; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * A builder used to create {@link ObjectMapper} instances with a fluent API. @@ -75,6 +79,10 @@ public class Jackson2ObjectMapperBuilder { private DateFormat dateFormat; + private Locale locale; + + private TimeZone timeZone; + private AnnotationIntrospector annotationIntrospector; private PropertyNamingStrategy propertyNamingStrategy; @@ -91,9 +99,11 @@ public class Jackson2ObjectMapperBuilder { private List modules; - private Class[] modulesToInstall; + private Class[] moduleClasses; + + private boolean findModulesViaServiceLoader = false; - private boolean findModulesViaServiceLoader; + private boolean findWellKnownModules = true; private ClassLoader moduleClassLoader = getClass().getClassLoader(); @@ -134,6 +144,48 @@ public class Jackson2ObjectMapperBuilder { return this; } + /** + * Override the default {@link Locale} to use for formatting. + * Default value used is {@link Locale#getDefault()}. + * @since 4.1.5 + */ + public Jackson2ObjectMapperBuilder locale(Locale locale) { + this.locale = locale; + return this; + } + + /** + * Override the default {@link Locale} to use for formatting. + * Default value used is {@link Locale#getDefault()}. + * @param localeString the locale ID as a String representation + * @since 4.1.5 + */ + public Jackson2ObjectMapperBuilder locale(String localeString) { + this.locale = StringUtils.parseLocaleString(localeString); + return this; + } + + /** + * Override the default {@link TimeZone} to use for formatting. + * Default value used is UTC (NOT local timezone). + * @since 4.1.5 + */ + public Jackson2ObjectMapperBuilder timeZone(TimeZone timeZone) { + this.timeZone = timeZone; + return this; + } + + /** + * Override the default {@link TimeZone} to use for formatting. + * Default value used is UTC (NOT local timezone). + * @param timeZoneString the zone ID as a String representation + * @since 4.1.5 + */ + public Jackson2ObjectMapperBuilder timeZone(String timeZoneString) { + this.timeZone = StringUtils.parseTimeZoneString(timeZoneString); + return this; + } + /** * Set an {@link AnnotationIntrospector} for both serialization and deserialization. */ @@ -337,6 +389,21 @@ public class Jackson2ObjectMapperBuilder { return this; } + /** + * Specify one or more 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. + * @since 4.1.5 + * @see #modules(List) + * @see com.fasterxml.jackson.databind.Module + */ + public Jackson2ObjectMapperBuilder modules(Module... modules) { + return modules(Arrays.asList(modules)); + } + /** * 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 @@ -344,24 +411,46 @@ public class Jackson2ObjectMapperBuilder { * As a consequence, specifying an empty list here will suppress any kind of * module detection. *

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

Modules specified here will be registered after + * Spring's autodetection of JSR-310 and Joda-Time, or Jackson's + * finding of modules (see {@link #findModulesViaServiceLoader}), + * allowing to eventually override their configuration. + *

Specify either this or {@link #modules}, not both. + * @since 4.1.5 + * @see com.fasterxml.jackson.databind.Module + */ + public Jackson2ObjectMapperBuilder modulesToInstall(Module... modules) { + this.modules = Arrays.asList(modules); + this.findWellKnownModules = true; 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 + * Specify one or more modules by class to be registered with + * the {@link ObjectMapper}. + *

Modules specified here will be registered after * Spring's autodetection of JSR-310 and Joda-Time, or Jackson's - * finding of modules (see {@link #findModulesViaServiceLoader}). + * finding of modules (see {@link #findModulesViaServiceLoader}), + * allowing to eventually override their configuration. *

Specify either this or {@link #modules}, not both. + * @see #modulesToInstall(Module...) * @see com.fasterxml.jackson.databind.Module */ public Jackson2ObjectMapperBuilder modulesToInstall(Class... modules) { - this.modulesToInstall = modules; + this.moduleClasses = modules; + this.findWellKnownModules = true; return this; } @@ -444,9 +533,35 @@ public class Jackson2ObjectMapperBuilder { public void configure(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); + if (this.findModulesViaServiceLoader) { + // Jackson 2.2+ + objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader)); + } + else if (this.findWellKnownModules) { + registerWellKnownModulesIfAvailable(objectMapper); + } + + if (this.modules != null) { + for (Module module : this.modules) { + // Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules + objectMapper.registerModule(module); + } + } + if (this.moduleClasses != null) { + for (Class module : this.moduleClasses) { + objectMapper.registerModule(BeanUtils.instantiate(module)); + } + } + if (this.dateFormat != null) { objectMapper.setDateFormat(this.dateFormat); } + if (this.locale != null) { + objectMapper.setLocale(this.locale); + } + if (this.timeZone != null) { + objectMapper.setTimeZone(this.timeZone); + } if (this.annotationIntrospector != null) { objectMapper.setAnnotationIntrospector(this.annotationIntrospector); @@ -468,29 +583,6 @@ public class Jackson2ObjectMapperBuilder { configureFeature(objectMapper, 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 - 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) { - objectMapper.registerModule(BeanUtils.instantiate(module)); - } - } - if (this.findModulesViaServiceLoader) { - // Jackson 2.2+ - objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader)); - } - else { - registerWellKnownModulesIfAvailable(objectMapper); - } - } - if (this.propertyNamingStrategy != null) { objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy); } 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 bdca6e41bbf..0058b0f74ce 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 @@ -19,7 +19,9 @@ package org.springframework.http.converter.json; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.AnnotationIntrospector; @@ -171,6 +173,24 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBeanModules specified here will be registered in combination with + *

Modules specified here will be registered after * Spring's autodetection of JSR-310 and Joda-Time, or Jackson's - * finding of modules (see {@link #setFindModulesViaServiceLoader}). + * finding of modules (see {@link #setFindModulesViaServiceLoader}), + * allowing to eventually override their configuration. *

Specify either this or {@link #setModules}, not both. * @since 4.0.1 * @see com.fasterxml.jackson.databind.Module 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 index 4fcc86dfbab..c083a0595e3 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -16,16 +16,22 @@ package org.springframework.http.converter.json; +import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; @@ -35,6 +41,7 @@ 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.SerializerProvider; import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig; import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig; import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory; @@ -42,16 +49,20 @@ import com.fasterxml.jackson.databind.deser.Deserializers; 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.module.SimpleSerializers; 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.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.junit.Test; import org.springframework.beans.FatalBeanException; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** @@ -167,15 +178,94 @@ public class Jackson2ObjectMapperBuilderTests { } @Test - public void setModules() { + public void localeSetter() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().locale(Locale.FRENCH).build(); + assertEquals(Locale.FRENCH, objectMapper.getSerializationConfig().getLocale()); + assertEquals(Locale.FRENCH, objectMapper.getDeserializationConfig().getLocale()); + } + + @Test + public void timeZoneSetter() { + TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris"); + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().timeZone(timeZone).build(); + assertEquals(timeZone, objectMapper.getSerializationConfig().getTimeZone()); + assertEquals(timeZone, objectMapper.getDeserializationConfig().getTimeZone()); + } + + @Test + public void timeZoneStringSetter() { + String zoneId = "Europe/Paris"; + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().timeZone(zoneId).build(); + TimeZone timeZone = TimeZone.getTimeZone(zoneId); + assertEquals(timeZone, objectMapper.getSerializationConfig().getTimeZone()); + assertEquals(timeZone, objectMapper.getDeserializationConfig().getTimeZone()); + } + + @Test(expected = IllegalArgumentException.class) + public void wrongTimeZoneStringSetter() { + String zoneId = "foo"; + Jackson2ObjectMapperBuilder.json().timeZone(zoneId).build(); + } + + @Test + public void modules() { NumberSerializer serializer1 = new NumberSerializer(); SimpleModule module = new SimpleModule(); module.addSerializer(Integer.class, serializer1); - ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(Arrays.asList(new Module[]{module})).build(); + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(module).build(); Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1); } + @Test + public void modulesToInstallByClass() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(CustomIntegerModule.class).build(); + Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); + assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null).getClass() == CustomIntegerSerializer.class); + } + + @Test + public void modulesToInstallByInstance() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(new CustomIntegerModule()).build(); + Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); + assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null).getClass() == CustomIntegerSerializer.class); + } + + @Test + public void defaultModules() throws JsonProcessingException, UnsupportedEncodingException { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); + Long timestamp = 1322903730000L; + DateTime dateTime = new DateTime(timestamp, DateTimeZone.UTC); + assertEquals(timestamp.toString(), new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + } + + @Test // SPR-12634 + public void customizeDefaultModulesWithModule() throws JsonProcessingException, UnsupportedEncodingException { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() + .modulesToInstall(new CustomIntegerModule()).build(); + DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); + } + + @Test // SPR-12634 + public void customizeDefaultModulesWithModuleClass() throws JsonProcessingException, UnsupportedEncodingException { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(CustomIntegerModule.class).build(); + DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); + } + + @Test // SPR-12634 + public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() + .serializerByType(Integer.class, new CustomIntegerSerializer()).build(); + DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); + } + + private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) { return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig(); } @@ -196,6 +286,7 @@ public class Jackson2ObjectMapperBuilderTests { public void serializerByType() { JsonSerializer serializer = new NumberSerializer(); ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() + .modules(new ArrayList<>()) // Disable well-known modules detection .serializerByType(Boolean.class, serializer).build(); assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers()); Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next(); @@ -206,6 +297,7 @@ public class Jackson2ObjectMapperBuilderTests { public void deserializerByType() throws JsonMappingException { JsonDeserializer deserializer = new DateDeserializers.DateDeserializer(); ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() + .modules(new ArrayList<>()) // Disable well-known modules detection .deserializerByType(Date.class, deserializer).build(); assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers()); Deserializers deserializers = getDeserializerFactoryConfig(objectMapper).deserializers().iterator().next(); @@ -249,6 +341,7 @@ public class Jackson2ObjectMapperBuilderTests { JsonSerializer serializer2 = new NumberSerializer(); Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json() + .modules(new ArrayList<>()) // Disable well-known modules detection .serializers(serializer1) .serializersByType(Collections., JsonSerializer>singletonMap(Boolean.class, serializer2)) .deserializersByType(deserializerMap) @@ -311,4 +404,36 @@ public class Jackson2ObjectMapperBuilderTests { assertTrue(xmlObjectMapper.getClass().isAssignableFrom(XmlMapper.class)); } + + public static class CustomIntegerModule extends Module { + + @Override + public String getModuleName() { + return this.getClass().getSimpleName(); + } + + @Override + public Version version() { + return Version.unknownVersion(); + } + + @Override + public void setupModule(SetupContext context) { + SimpleSerializers serializers = new SimpleSerializers(); + serializers.addSerializer(Integer.class, new CustomIntegerSerializer()); + context.addSerializers(serializers); + } + } + + + public static class CustomIntegerSerializer extends JsonSerializer { + + @Override + public void serialize(Integer value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("customid", value); + gen.writeEndObject(); + } + } + } 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 011384f4c2b..c4ccaea1c1f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -16,16 +16,23 @@ package org.springframework.http.converter.json; +import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; @@ -34,23 +41,28 @@ 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.SerializerProvider; 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.DateDeserializer; import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.module.SimpleSerializers; 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.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.junit.Before; import org.junit.Test; import org.springframework.beans.FatalBeanException; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** @@ -58,6 +70,7 @@ import static org.junit.Assert.*; * * @author Dmitry Katsubo * @author Brian Clozel + * @author Sebastien Deleuze */ public class Jackson2ObjectMapperFactoryBeanTests { @@ -161,13 +174,57 @@ public class Jackson2ObjectMapperFactoryBeanTests { assertEquals(dateFormat, this.factory.getObject().getDeserializationConfig().getDateFormat()); } + @Test + public void localeSetter() { + this.factory.setLocale(Locale.FRENCH); + this.factory.afterPropertiesSet(); + + assertEquals(Locale.FRENCH, this.factory.getObject().getSerializationConfig().getLocale()); + assertEquals(Locale.FRENCH, this.factory.getObject().getDeserializationConfig().getLocale()); + } + + @Test + public void timeZoneSetter() { + TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris"); + + this.factory.setTimeZone(timeZone); + this.factory.afterPropertiesSet(); + + assertEquals(timeZone, this.factory.getObject().getSerializationConfig().getTimeZone()); + assertEquals(timeZone, this.factory.getObject().getDeserializationConfig().getTimeZone()); + } + + @Test + public void timeZoneStringSetter() { + String zoneId = "Europe/Paris"; + + this.factory.setTimeZone(TimeZone.getTimeZone(zoneId)); + this.factory.afterPropertiesSet(); + + TimeZone timeZone = TimeZone.getTimeZone(zoneId); + assertEquals(timeZone, this.factory.getObject().getSerializationConfig().getTimeZone()); + assertEquals(timeZone, this.factory.getObject().getDeserializationConfig().getTimeZone()); + } + + @Test + public void wrongTimeZoneStringSetter() { + String zoneId = "foo"; + + this.factory.setTimeZone(TimeZone.getTimeZone(zoneId)); + this.factory.afterPropertiesSet(); + + TimeZone timeZone = TimeZone.getTimeZone("GMT"); + assertEquals(timeZone, this.factory.getObject().getSerializationConfig().getTimeZone()); + assertEquals(timeZone, this.factory.getObject().getDeserializationConfig().getTimeZone()); + } + @Test public void setModules() { NumberSerializer serializer1 = new NumberSerializer(); SimpleModule module = new SimpleModule(); module.addSerializer(Integer.class, serializer1); - this.factory.setModules(Arrays.asList(new Module[] {module})); + this.factory.setModules(Arrays.asList(new Module[]{module})); this.factory.afterPropertiesSet(); ObjectMapper objectMapper = this.factory.getObject(); @@ -175,6 +232,41 @@ public class Jackson2ObjectMapperFactoryBeanTests { assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1); } + @Test + public void defaultModules() throws JsonProcessingException, UnsupportedEncodingException { + this.factory.afterPropertiesSet(); + ObjectMapper objectMapper = this.factory.getObject(); + + Long timestamp = 1322903730000L; + DateTime dateTime = new DateTime(timestamp, DateTimeZone.UTC); + assertEquals(timestamp.toString(), new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + } + + @Test // SPR-12634 + public void customizeDefaultModulesWithModuleClass() throws JsonProcessingException, UnsupportedEncodingException { + this.factory.setModulesToInstall(CustomIntegerModule.class); + this.factory.afterPropertiesSet(); + ObjectMapper objectMapper = this.factory.getObject(); + + DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); + } + + @Test // SPR-12634 + public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException { + Map, JsonSerializer> serializers = new HashMap<>(); + serializers.put(Integer.class, new CustomIntegerSerializer()); + + this.factory.setSerializersByType(serializers); + this.factory.afterPropertiesSet(); + ObjectMapper objectMapper = this.factory.getObject(); + + DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC); + assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8")); + assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid")); + } + @Test public void simpleSetup() { this.factory.afterPropertiesSet(); @@ -237,6 +329,7 @@ public class Jackson2ObjectMapperFactoryBeanTests { JsonSerializer> serializer1 = new ClassSerializer(); JsonSerializer serializer2 = new NumberSerializer(); + factory.setModules(new ArrayList<>()); // Disable well-known modules detection factory.setSerializers(serializer1); factory.setSerializersByType(Collections., JsonSerializer> singletonMap(Boolean.class, serializer2)); factory.setDeserializersByType(deserializers); @@ -304,4 +397,36 @@ public class Jackson2ObjectMapperFactoryBeanTests { assertEquals(XmlMapper.class, this.factory.getObjectType()); } + + public static class CustomIntegerModule extends Module { + + @Override + public String getModuleName() { + return this.getClass().getSimpleName(); + } + + @Override + public Version version() { + return Version.unknownVersion(); + } + + @Override + public void setupModule(SetupContext context) { + SimpleSerializers serializers = new SimpleSerializers(); + serializers.addSerializer(Integer.class, new CustomIntegerSerializer()); + context.addSerializers(serializers); + } + } + + + public static class CustomIntegerSerializer extends JsonSerializer { + + @Override + public void serialize(Integer value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartObject(); + gen.writeNumberField("customid", value); + gen.writeEndObject(); + } + } + }