From 10f2ce36162081f6d9ecc5a1cbc48d86b34240da Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 18 Apr 2018 21:53:35 -0700 Subject: [PATCH] Fix #1998 --- release-notes/VERSION-2.x | 3 + .../jackson/databind/cfg/BaseSettings.java | 20 +++ .../databind/cfg/MapperConfigBase.java | 7 +- .../introspect/BasicClassIntrospector.java | 7 +- .../introspect/ClassIntrospector.java | 7 + .../mixins/MapperMixinsCopy1998Test.java | 128 ++++++++++++++++++ 6 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/mixins/MapperMixinsCopy1998Test.java diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 17d72dc396..6cf05385d6 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,9 @@ Project: jackson-databind #1565: Deserialization failure with Polymorphism using JsonTypeInfo `defaultImpl`, subtype as target +#1998: Removing "type" attribute with Mixin not taken in account if + using ObjectMapper.copy() + (reported by SBKila@github) 2.9.5 (26-Mar-2018) diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java index 8c83734cdd..e289ab6577 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/BaseSettings.java @@ -147,6 +147,26 @@ public BaseSettings(ClassIntrospector ci, AnnotationIntrospector ai, _defaultBase64 = defaultBase64; } + /** + * Turns out we are not necessarily 100% stateless, alas, since {@link ClassIntrospector} + * typically has a cache. So this method is needed for deep copy() of Mapper. + * + * @since 2.9.6 + */ + public BaseSettings copy() { + return new BaseSettings(_classIntrospector.copy(), + _annotationIntrospector, + _propertyNamingStrategy, + _typeFactory, + _typeResolverBuilder, + _dateFormat, + _handlerInstantiator, + _locale, + _timeZone, + _defaultBase64); + + } + /* /********************************************************** /* Factory methods diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java index 2dd89500ff..a7ca2e5adc 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java @@ -134,13 +134,18 @@ protected MapperConfigBase(BaseSettings base, } /** + * Copy constructor usually called to make a copy for use by + * ObjectMapper that is copied. + * * @since 2.8 */ protected MapperConfigBase(MapperConfigBase src, SimpleMixInResolver mixins, RootNameLookup rootNames, ConfigOverrides configOverrides) { - super(src); + // 18-Apr-2018, tatu: [databind#1898] need to force copying of `ClassIntrospector` + // (to clear its cache) to avoid leakage + super(src, src._base.copy()); _mixIns = mixins; _subtypeResolver = src._subtypeResolver; _rootNames = rootNames; diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java index e523adf40b..f511efb90d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java @@ -66,7 +66,12 @@ public BasicClassIntrospector() { // a small cache should go a long way here _cachedFCA = new LRUMap(16, 64); } - + + @Override + public ClassIntrospector copy() { + return new BasicClassIntrospector(); + } + /* /********************************************************** /* Factory method impls diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java index c91a5bf005..6a5dbbf9cd 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/ClassIntrospector.java @@ -47,6 +47,13 @@ public interface MixInResolver protected ClassIntrospector() { } + /** + * Method that may be needed when `copy()`ing `ObjectMapper` instances. + * + * @since 2.9.6 + */ + public abstract ClassIntrospector copy(); + /* /********************************************************** /* Public API: factory methods diff --git a/src/test/java/com/fasterxml/jackson/databind/mixins/MapperMixinsCopy1998Test.java b/src/test/java/com/fasterxml/jackson/databind/mixins/MapperMixinsCopy1998Test.java new file mode 100644 index 0000000000..c6dae916f3 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/mixins/MapperMixinsCopy1998Test.java @@ -0,0 +1,128 @@ +package com.fasterxml.jackson.databind.mixins; + +import java.io.IOException; + +import com.fasterxml.jackson.annotation.*; + +import com.fasterxml.jackson.databind.*; + +public class MapperMixinsCopy1998Test extends BaseMapTest +{ + final static String FULLMODEL="{\"format\":\"1.0\",\"child\":{\"type\":\"CHILD_B\",\"name\":\"testB\"},\"notVisible\":\"should not be present\"}"; + final static String EXPECTED="{\"format\":\"1.0\",\"child\":{\"name\":\"testB\"}}"; + + static class MyModelView { } + + interface MixinConfig { + interface MyModelRoot { + @JsonView(MyModelView.class) + public String getFormat(); + + @JsonView(MyModelView.class) + public MyModelChildBase getChild(); + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.EXISTING_PROPERTY) + interface MyModelChildBase { + @JsonView(MyModelView.class) + public String getName(); + } + + } + + @JsonPropertyOrder({ "format", "child" }) + static class MyModelRoot { + @JsonProperty + private String format = "1.0"; + + public String getFormat() { + return format; + } + @JsonProperty + private MyModelChildBase child; + + public MyModelChildBase getChild() { + return child; + } + + public void setChild(MyModelChildBase child) { + this.child = child; + } + + @JsonProperty + private String notVisible = "should not be present"; + + public String getNotVisible() { + return notVisible; + } + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") + @JsonSubTypes({ + @JsonSubTypes.Type(value = MyChildA.class, name = "CHILD_A"), + @JsonSubTypes.Type(value = MyChildB.class, name = "CHILD_B") + }) + abstract static class MyModelChildBase { + @JsonProperty + private String name; + + public String getName() { + return name; + } + + @JsonIgnore + public void setName(String name) { + this.name = name; + } + } + + static class MyChildA extends MyModelChildBase { + public MyChildA(String name) { + setName(name); + } + } + + static class MyChildB extends MyModelChildBase { + public MyChildB(String name) { + setName(name); + } + } + + public void testB_KO() throws Exception + { + final ObjectMapper DEFAULT = defaultMapper(); + MyModelRoot myModelInstance = new MyModelRoot(); + myModelInstance.setChild(new MyChildB("testB")); + + ObjectMapper myObjectMapper = DEFAULT.copy(); + + String postResult = getString(myModelInstance, myObjectMapper); + assertEquals(FULLMODEL, postResult); +// System.out.println("postResult: "+postResult); + + myObjectMapper = DEFAULT.copy(); +// myObjectMapper = defaultMapper(); + myObjectMapper.addMixIn(MyModelRoot.class, MixinConfig.MyModelRoot.class) + .addMixIn(MyModelChildBase.class, MixinConfig.MyModelChildBase.class) + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .setConfig(myObjectMapper.getSerializationConfig().withView(MyModelView.class)); + + String result = getString(myModelInstance, myObjectMapper); +System.out.println("result: "+result); + assertEquals(EXPECTED, result); + + } + + private String getString(MyModelRoot myModelInstance, ObjectMapper myObjectMapper) throws IOException { + return myObjectMapper.writerFor(MyModelRoot.class).writeValueAsString(myModelInstance); + } + + private ObjectMapper defaultMapper() + { + return new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) + ; + } +}