1 package com.fasterxml.jackson.databind.jsontype.impl;
2 
3 import java.util.Collection;
4 
5 import com.fasterxml.jackson.annotation.JsonTypeInfo;
6 
7 import com.fasterxml.jackson.databind.*;
8 import com.fasterxml.jackson.databind.annotation.NoClass;
9 import com.fasterxml.jackson.databind.cfg.MapperConfig;
10 import com.fasterxml.jackson.databind.jsontype.*;
11 import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity;
12 import com.fasterxml.jackson.databind.util.ClassUtil;
13 
14 /**
15  * Default {@link TypeResolverBuilder} implementation.
16  */
17 public class StdTypeResolverBuilder
18     implements TypeResolverBuilder<StdTypeResolverBuilder>
19 {
20     // Configuration settings:
21 
22     protected JsonTypeInfo.Id _idType;
23 
24     protected JsonTypeInfo.As _includeAs;
25 
26     protected String _typeProperty;
27 
28     /**
29      * Whether type id should be exposed to deserializers or not
30      */
31     protected boolean _typeIdVisible = false;
32 
33     /**
34      * Default class to use in case type information is not available
35      * or is broken.
36      */
37     protected Class<?> _defaultImpl;
38 
39     // Objects
40 
41     protected TypeIdResolver _customIdResolver;
42 
43     /*
44     /**********************************************************
45     /* Construction, initialization, actual building
46     /**********************************************************
47      */
48 
StdTypeResolverBuilder()49     public StdTypeResolverBuilder() { }
50 
51     /**
52      * @since 2.9
53      */
StdTypeResolverBuilder(JsonTypeInfo.Id idType, JsonTypeInfo.As idAs, String propName)54     protected StdTypeResolverBuilder(JsonTypeInfo.Id idType,
55             JsonTypeInfo.As idAs, String propName) {
56         _idType = idType;
57         _includeAs = idAs;
58         _typeProperty = propName;
59     }
60 
noTypeInfoBuilder()61     public static StdTypeResolverBuilder noTypeInfoBuilder() {
62         return new StdTypeResolverBuilder().init(JsonTypeInfo.Id.NONE, null);
63     }
64 
65     @Override
init(JsonTypeInfo.Id idType, TypeIdResolver idRes)66     public StdTypeResolverBuilder init(JsonTypeInfo.Id idType, TypeIdResolver idRes)
67     {
68         // sanity checks
69         if (idType == null) {
70             throw new IllegalArgumentException("idType cannot be null");
71         }
72         _idType = idType;
73         _customIdResolver = idRes;
74         // Let's also initialize property name as per idType default
75         _typeProperty = idType.getDefaultPropertyName();
76         return this;
77     }
78 
79     @Override
buildTypeSerializer(SerializationConfig config, JavaType baseType, Collection<NamedType> subtypes)80     public TypeSerializer buildTypeSerializer(SerializationConfig config,
81             JavaType baseType, Collection<NamedType> subtypes)
82     {
83         if (_idType == JsonTypeInfo.Id.NONE) { return null; }
84         // 03-Oct-2016, tatu: As per [databind#1395] better prevent use for primitives,
85         //    regardless of setting
86         if (baseType.isPrimitive()) {
87             // 19-Jun-2020, tatu: But for [databind#2753], allow overriding
88             if (!allowPrimitiveTypes(config, baseType)) {
89                 return null;
90             }
91         }
92         TypeIdResolver idRes = idResolver(config, baseType, subTypeValidator(config),
93                 subtypes, true, false);
94         switch (_includeAs) {
95         case WRAPPER_ARRAY:
96             return new AsArrayTypeSerializer(idRes, null);
97         case PROPERTY:
98             return new AsPropertyTypeSerializer(idRes, null, _typeProperty);
99         case WRAPPER_OBJECT:
100             return new AsWrapperTypeSerializer(idRes, null);
101         case EXTERNAL_PROPERTY:
102             return new AsExternalTypeSerializer(idRes, null, _typeProperty);
103         case EXISTING_PROPERTY:
104         	// as per [#528]
105         	return new AsExistingPropertyTypeSerializer(idRes, null, _typeProperty);
106         }
107         throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
108     }
109 
110     // as per [#368]
111     // removed when fix [#528]
112     //private IllegalArgumentException _noExisting() {
113     //    return new IllegalArgumentException("Inclusion type "+_includeAs+" not yet supported");
114     //}
115 
116     @Override
buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes)117     public TypeDeserializer buildTypeDeserializer(DeserializationConfig config,
118             JavaType baseType, Collection<NamedType> subtypes)
119     {
120         if (_idType == JsonTypeInfo.Id.NONE) { return null; }
121         // 03-Oct-2016, tatu: As per [databind#1395] better prevent use for primitives,
122         //    regardless of setting
123         if (baseType.isPrimitive()) {
124             // 19-Jun-2020, tatu: But for [databind#2753], allow overriding
125             if (!allowPrimitiveTypes(config, baseType)) {
126                 return null;
127             }
128         }
129 
130         // 27-Apr-2019, tatu: Part of [databind#2195]; must first check whether any subtypes
131         //    of basetypes might be denied or allowed
132         final PolymorphicTypeValidator subTypeValidator = verifyBaseTypeValidity(config, baseType);
133 
134         TypeIdResolver idRes = idResolver(config, baseType, subTypeValidator, subtypes, false, true);
135 
136         JavaType defaultImpl = defineDefaultImpl(config, baseType);
137 
138         // First, method for converting type info to type id:
139         switch (_includeAs) {
140         case WRAPPER_ARRAY:
141             return new AsArrayTypeDeserializer(baseType, idRes,
142                     _typeProperty, _typeIdVisible, defaultImpl);
143         case PROPERTY:
144         case EXISTING_PROPERTY: // as per [#528] same class as PROPERTY
145             return new AsPropertyTypeDeserializer(baseType, idRes,
146                     _typeProperty, _typeIdVisible, defaultImpl, _includeAs);
147         case WRAPPER_OBJECT:
148             return new AsWrapperTypeDeserializer(baseType, idRes,
149                     _typeProperty, _typeIdVisible, defaultImpl);
150         case EXTERNAL_PROPERTY:
151             return new AsExternalTypeDeserializer(baseType, idRes,
152                     _typeProperty, _typeIdVisible, defaultImpl);
153         }
154         throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
155     }
156 
defineDefaultImpl(DeserializationConfig config, JavaType baseType)157     protected JavaType defineDefaultImpl(DeserializationConfig config, JavaType baseType) {
158         JavaType defaultImpl;
159         if (_defaultImpl == null) {
160             if (config.isEnabled(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL) && !baseType.isAbstract()) {
161                 defaultImpl = baseType;
162             } else {
163                 defaultImpl = null;
164             }
165         } else {
166             // 20-Mar-2016, tatu: It is important to do specialization go through
167             //   TypeFactory to ensure proper resolution; with 2.7 and before, direct
168             //   call to JavaType was used, but that cannot work reliably with 2.7
169             // 20-Mar-2016, tatu: Can finally add a check for type compatibility BUT
170             //   if so, need to add explicit checks for marker types. Not ideal, but
171             //   seems like a reasonable compromise.
172             if ((_defaultImpl == Void.class)
173                     || (_defaultImpl == NoClass.class)) {
174                 defaultImpl = config.getTypeFactory().constructType(_defaultImpl);
175             } else {
176                 if (baseType.hasRawClass(_defaultImpl)) { // common enough to check
177                     defaultImpl = baseType;
178                 } else if (baseType.isTypeOrSuperTypeOf(_defaultImpl)) {
179                     // most common case with proper base type...
180                     defaultImpl = config.getTypeFactory()
181                             .constructSpecializedType(baseType, _defaultImpl);
182                 } else {
183                     // 05-Apr-2018, tatu: As [databind#1565] and [databind#1861] need to allow
184                     //    some cases of seemingly incompatible `defaultImpl`. Easiest to just clear
185                     //    the setting.
186 
187                     /*
188                     throw new IllegalArgumentException(
189                             String.format("Invalid \"defaultImpl\" (%s): not a subtype of basetype (%s)",
190                                     ClassUtil.nameOf(_defaultImpl), ClassUtil.nameOf(baseType.getRawClass()))
191                             );
192                             */
193                     defaultImpl = null;
194                 }
195             }
196         }
197         return defaultImpl;
198     }
199 
200     /*
201     /**********************************************************
202     /* Construction, configuration
203     /**********************************************************
204      */
205 
206     @Override
inclusion(JsonTypeInfo.As includeAs)207     public StdTypeResolverBuilder inclusion(JsonTypeInfo.As includeAs) {
208         if (includeAs == null) {
209             throw new IllegalArgumentException("includeAs cannot be null");
210         }
211         _includeAs = includeAs;
212         return this;
213     }
214 
215     /**
216      * Method for constructing an instance with specified type property name
217      * (property name to use for type id when using "as-property" inclusion).
218      */
219     @Override
typeProperty(String typeIdPropName)220     public StdTypeResolverBuilder typeProperty(String typeIdPropName) {
221         // ok to have null/empty; will restore to use defaults
222         if (typeIdPropName == null || typeIdPropName.length() == 0) {
223             typeIdPropName = _idType.getDefaultPropertyName();
224         }
225         _typeProperty = typeIdPropName;
226         return this;
227     }
228 
229     @Override
defaultImpl(Class<?> defaultImpl)230     public StdTypeResolverBuilder defaultImpl(Class<?> defaultImpl) {
231         _defaultImpl = defaultImpl;
232         return this;
233     }
234 
235     @Override
typeIdVisibility(boolean isVisible)236     public StdTypeResolverBuilder typeIdVisibility(boolean isVisible) {
237         _typeIdVisible = isVisible;
238         return this;
239     }
240 
241     /*
242     /**********************************************************
243     /* Accessors
244     /**********************************************************
245      */
246 
getDefaultImpl()247     @Override public Class<?> getDefaultImpl() { return _defaultImpl; }
248 
getTypeProperty()249     public String getTypeProperty() { return _typeProperty; }
isTypeIdVisible()250     public boolean isTypeIdVisible() { return _typeIdVisible; }
251 
252     /*
253     /**********************************************************
254     /* Internal/subtype factory methods
255     /**********************************************************
256      */
257 
258     /**
259      * Helper method that will either return configured custom
260      * type id resolver, or construct a standard resolver
261      * given configuration.
262      */
idResolver(MapperConfig<?> config, JavaType baseType, PolymorphicTypeValidator subtypeValidator, Collection<NamedType> subtypes, boolean forSer, boolean forDeser)263     protected TypeIdResolver idResolver(MapperConfig<?> config,
264             JavaType baseType, PolymorphicTypeValidator subtypeValidator,
265             Collection<NamedType> subtypes, boolean forSer, boolean forDeser)
266     {
267         // Custom id resolver?
268         if (_customIdResolver != null) { return _customIdResolver; }
269         if (_idType == null) throw new IllegalStateException("Cannot build, 'init()' not yet called");
270         switch (_idType) {
271         case CLASS:
272             return ClassNameIdResolver.construct(baseType, config, subtypeValidator);
273         case MINIMAL_CLASS:
274             return MinimalClassNameIdResolver.construct(baseType, config, subtypeValidator);
275         case NAME:
276             return TypeNameIdResolver.construct(config, baseType, subtypes, forSer, forDeser);
277         case NONE: // hmmh. should never get this far with 'none'
278             return null;
279         case CUSTOM: // need custom resolver...
280         }
281         throw new IllegalStateException("Do not know how to construct standard type id resolver for idType: "+_idType);
282     }
283 
284     /*
285     /**********************************************************
286     /* Internal/subtype factory methods
287     /**********************************************************
288      */
289 
290     /**
291      * Overridable helper method for determining actual validator to use when constructing
292      * type serializers and type deserializers.
293      *<p>
294      * Default implementation simply uses one configured and accessible using
295      * {@link MapperConfig#getPolymorphicTypeValidator()}.
296      *
297      * @since 2.10
298      */
subTypeValidator(MapperConfig<?> config)299     public PolymorphicTypeValidator subTypeValidator(MapperConfig<?> config) {
300         return config.getPolymorphicTypeValidator();
301     }
302 
303     /**
304      * Helper method called to check that base type is valid regarding possible constraints
305      * on basetype/subtype combinations allowed for polymorphic type handling.
306      * Currently limits are verified for class name - based methods only.
307      *
308      * @since 2.10
309      */
verifyBaseTypeValidity(MapperConfig<?> config, JavaType baseType)310     protected PolymorphicTypeValidator verifyBaseTypeValidity(MapperConfig<?> config,
311             JavaType baseType)
312     {
313         final PolymorphicTypeValidator ptv = subTypeValidator(config);
314         if (_idType == JsonTypeInfo.Id.CLASS || _idType == JsonTypeInfo.Id.MINIMAL_CLASS) {
315             final Validity validity = ptv.validateBaseType(config, baseType);
316             // If no subtypes are legal (that is, base type itself is invalid), indicate problem
317             if (validity == Validity.DENIED) {
318                 return reportInvalidBaseType(config, baseType, ptv);
319             }
320             // If there's indication that any and all subtypes are fine, replace validator itself:
321             if (validity == Validity.ALLOWED) {
322                 return LaissezFaireSubTypeValidator.instance;
323             }
324             // otherwise just return validator, is to be called for each distinct type
325         }
326         return ptv;
327     }
328 
329     /**
330      * @since 2.10
331      */
reportInvalidBaseType(MapperConfig<?> config, JavaType baseType, PolymorphicTypeValidator ptv)332     protected PolymorphicTypeValidator reportInvalidBaseType(MapperConfig<?> config,
333             JavaType baseType, PolymorphicTypeValidator ptv)
334     {
335         throw new IllegalArgumentException(String.format(
336 "Configured `PolymorphicTypeValidator` (of type %s) denied resolution of all subtypes of base type %s",
337                         ClassUtil.classNameOf(ptv), ClassUtil.classNameOf(baseType.getRawClass()))
338         );
339     }
340 
341     /*
342     /**********************************************************
343     /* Overridable helper methods
344     /**********************************************************
345      */
346 
347     /**
348      * Overridable helper method that is called to determine whether type serializers
349      * and type deserializers may be created even if base type is Java {@code primitive}
350      * type.
351      * Default implementation simply returns {@code false} (since primitive types can not
352      * be sub-classed, are never polymorphic) but custom implementations
353      * may change the logic for some special cases.
354      *
355      * @param config Currently active configuration
356      * @param baseType Primitive base type for property being handled
357      *
358      * @return True if type (de)serializer may be created even if base type is Java
359      *    {@code primitive} type; false if not
360      *
361      * @since 2.11.1
362      */
allowPrimitiveTypes(MapperConfig<?> config, JavaType baseType)363     protected boolean allowPrimitiveTypes(MapperConfig<?> config,
364             JavaType baseType) {
365         return false;
366     }
367 }
368