• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.jsontype;
2 
3 import java.util.*;
4 import java.util.regex.Pattern;
5 
6 import com.fasterxml.jackson.databind.JavaType;
7 import com.fasterxml.jackson.databind.JsonMappingException;
8 import com.fasterxml.jackson.databind.cfg.MapperConfig;
9 
10 /**
11  * Standard {@link BasicPolymorphicTypeValidator} implementation that users may want
12  * to use for constructing validators based on simple class hierarchy and/or name patterns
13  * to allow and/or deny certain subtypes.
14  *<p>
15  * Most commonly this is used to allow known safe subtypes based on common super type
16  * or Java package name.
17  *<br>
18  * For example:
19  *<pre>
20  *</pre>
21  *
22  * @since 2.10
23  */
24 public class BasicPolymorphicTypeValidator
25     extends PolymorphicTypeValidator.Base
26     implements java.io.Serializable
27 {
28     private static final long serialVersionUID = 1L;
29 
30     /*
31     /**********************************************************
32     /* Helper classes: matchers
33     /**********************************************************
34      */
35 
36     /**
37      * General matcher interface (predicate) for validating class values
38      * (base type or resolved subtype)
39      */
40     public abstract static class TypeMatcher { // note: public since 2.11
match(MapperConfig<?> config, Class<?> clazz)41         public abstract boolean match(MapperConfig<?> config, Class<?> clazz);
42     }
43 
44     /**
45      * General matcher interface (predicate) for validating unresolved
46      * subclass class name.
47      */
48     public abstract static class NameMatcher { // note: public since 2.11
match(MapperConfig<?> config, String clazzName)49         public abstract boolean match(MapperConfig<?> config, String clazzName);
50     }
51 
52     /*
53     /**********************************************************
54     /* Builder class for configuring instances
55     /**********************************************************
56      */
57 
58     /**
59      * Builder class for configuring and constructing immutable
60      * {@link BasicPolymorphicTypeValidator} instances. Criteria for allowing
61      * polymorphic subtypes is specified by adding rules in priority order, starting
62      * with the rules to evaluate first: when a matching rule is found, its status
63      * ({@link PolymorphicTypeValidator.Validity#ALLOWED} or {@link PolymorphicTypeValidator.Validity#DENIED}) is used and no further
64      * rules are checked.
65      */
66     public static class Builder {
67         /**
68          * Optional set of base types (exact match) that are NOT accepted
69          * as base types for polymorphic properties. May be used to prevent "unsafe"
70          * base types like {@link java.lang.Object} or {@link java.io.Serializable}.
71          */
72         protected Set<Class<?>> _invalidBaseTypes;
73 
74         /**
75          * Collected matchers for base types to allow.
76          */
77         protected List<TypeMatcher> _baseTypeMatchers;
78 
79         /**
80          * Collected name-based matchers for sub types to allow.
81          */
82         protected List<NameMatcher> _subTypeNameMatchers;
83 
84         /**
85          * Collected Class-based matchers for sub types to allow.
86          */
87         protected List<TypeMatcher> _subTypeClassMatchers;
88 
Builder()89         protected Builder() { }
90 
91         // // Methods for checking solely by base type (before subtype even considered)
92 
93         /**
94          * Method for appending matcher that will allow all subtypes in cases where
95          * nominal base type is specified class, or one of its subtypes.
96          * For example, call to
97          *<pre>
98          *    builder.allowIfBaseType(MyBaseType.class)
99          *</pre>
100          * would indicate that any polymorphic properties where declared base type
101          * is {@code MyBaseType} (or subclass thereof) would allow all legal (assignment-compatible)
102          * subtypes.
103          */
allowIfBaseType(final Class<?> baseOfBase)104         public Builder allowIfBaseType(final Class<?> baseOfBase) {
105             return _appendBaseMatcher(new TypeMatcher() {
106                 @Override
107                 public boolean match(MapperConfig<?> config, Class<?> clazz) {
108                     return baseOfBase.isAssignableFrom(clazz);
109                 }
110             });
111         }
112 
113         /**
114          * Method for appending matcher that will allow all subtypes in cases where
115          * nominal base type's class name matches given {@link Pattern}
116          * For example, call to
117          *<pre>
118          *    builder.allowIfBaseType(Pattern.compile("com\\.mycompany\\..*")
119          *</pre>
120          * would indicate that any polymorphic properties where declared base type
121          * is in package {@code com.mycompany} would allow all legal (assignment-compatible)
122          * subtypes.
123          *<p>
124          * NOTE! {@link Pattern} match is applied using
125          *<code>
126          *   if (patternForBase.matcher(typeId).matches()) { }
127          *</code>
128          * that is, it must match the whole class name, not just part.
129          */
allowIfBaseType(final Pattern patternForBase)130         public Builder allowIfBaseType(final Pattern patternForBase) {
131             return _appendBaseMatcher(new TypeMatcher() {
132                 @Override
133                 public boolean match(MapperConfig<?> config, Class<?> clazz) {
134                     return patternForBase.matcher(clazz.getName()).matches();
135                 }
136             });
137         }
138 
139         /**
140          * Method for appending matcher that will allow all subtypes in cases where
141          * nominal base type's class name starts with specific prefix.
142          * For example, call to
143          *<pre>
144          *    builder.allowIfBaseType("com.mycompany.")
145          *</pre>
146          * would indicate that any polymorphic properties where declared base type
147          * is in package {@code com.mycompany} would allow all legal (assignment-compatible)
148          * subtypes.
149          */
150         public Builder allowIfBaseType(final String prefixForBase) {
151             return _appendBaseMatcher(new TypeMatcher() {
152                 @Override
153                 public boolean match(MapperConfig<?> config, Class<?> clazz) {
154                     return clazz.getName().startsWith(prefixForBase);
155                 }
156             });
157         }
158 
159         /**
160          * Method for appending custom matcher called with base type: if matcher returns
161          * {@code true}, all possible subtypes will be accepted; if {@code false}, other
162          * matchers are applied.
163          *
164          * @param matcher Custom matcher to apply to base type
165          *
166          * @return This Builder to allow call chaining
167          *
168          * @since 2.11
169          */
170         public Builder allowIfBaseType(final TypeMatcher matcher) {
171             return _appendBaseMatcher(matcher);
172         }
173 
174         /**
175          * Method for appending matcher that will mark any polymorphic properties with exact
176          * specific class to be invalid.
177          * For example, call to
178          *<pre>
179          *    builder.denyforExactBaseType(Object.class)
180          *</pre>
181          * would indicate that any polymorphic properties where declared base type
182          * is {@code java.lang.Object}
183          * would be deemed invalid, and attempt to deserialize values of such types
184          * should result in an exception.
185          */
186         public Builder denyForExactBaseType(final Class<?> baseTypeToDeny) {
187             if (_invalidBaseTypes == null) {
188                 _invalidBaseTypes = new HashSet<>();
189             }
190             _invalidBaseTypes.add(baseTypeToDeny);
191             return this;
192         }
193 
194         // // Methods for considering subtype (base type was not enough)
195 
196         /**
197          * Method for appending matcher that will allow specific subtype (regardless
198          * of declared base type) if it is {@code subTypeBase} or its subtype.
199          * For example, call to
200          *<pre>
201          *    builder.allowIfSubType(MyImplType.class)
202          *</pre>
203          * would indicate that any polymorphic values with type of
204          * is {@code MyImplType} (or subclass thereof)
205          * would be allowed.
206          */
207         public Builder allowIfSubType(final Class<?> subTypeBase) {
208             return _appendSubClassMatcher(new TypeMatcher() {
209                 @Override
210                 public boolean match(MapperConfig<?> config, Class<?> clazz) {
211                     return subTypeBase.isAssignableFrom(clazz);
212                 }
213             });
214         }
215 
216         /**
217          * Method for appending matcher that will allow specific subtype (regardless
218          * of declared base type) in cases where subclass name matches given {@link Pattern}.
219          * For example, call to
220          *<pre>
221          *    builder.allowIfSubType(Pattern.compile("com\\.mycompany\\.")
222          *</pre>
223          * would indicate that any polymorphic values in package {@code com.mycompany}
224          * would be allowed.
225          *<p>
226          * NOTE! {@link Pattern} match is applied using
227          *<code>
228          *   if (patternForSubType.matcher(typeId).matches()) { }
229          *</code>
230          * that is, it must match the whole class name, not just part.
231          */
232         public Builder allowIfSubType(final Pattern patternForSubType) {
233             return _appendSubNameMatcher(new NameMatcher() {
234                 @Override
235                 public boolean match(MapperConfig<?> config, String clazzName) {
236                     return patternForSubType.matcher(clazzName).matches();
237                 }
238             });
239         }
240 
241         /**
242          * Method for appending matcher that will allow specific subtype (regardless
243          * of declared base type)
244          * in cases where subclass name starts with specified prefix
245          * For example, call to
246          *<pre>
247          *    builder.allowIfSubType("com.mycompany.")
248          *</pre>
249          * would indicate that any polymorphic values in package {@code com.mycompany}
250          * would be allowed.
251          */
252         public Builder allowIfSubType(final String prefixForSubType) {
253             return _appendSubNameMatcher(new NameMatcher() {
254                 @Override
255                 public boolean match(MapperConfig<?> config, String clazzName) {
256                     return clazzName.startsWith(prefixForSubType);
257                 }
258             });
259         }
260 
261         /**
262          * Method for appending custom matcher called with resolved subtype: if matcher returns
263          * {@code true}, type will be accepted; if {@code false}, other
264          * matchers are applied.
265          *
266          * @param matcher Custom matcher to apply to resolved subtype
267          *
268          * @return This Builder to allow call chaining
269          *
270          * @since 2.11
271          */
272         public Builder allowIfSubType(final TypeMatcher matcher) {
273             return _appendSubClassMatcher(matcher);
274         }
275 
276         /**
277          * Method for appending matcher that will allow all subtypes that are Java arrays
278          * (regardless of element type). Note that this does NOT validate element type
279          * itself as long as Polymorphic Type handling is enabled for element type: this
280          * is the case with all standard "Default Typing" inclusion criteria as well as for
281          * annotation ({@code @JsonTypeInfo}) use case (since annotation only applies to element
282          * types, not container).
283          *<p>
284          * NOTE: not used with other Java collection types ({@link java.util.List}s,
285          *    {@link java.util.Collection}s), mostly since use of generic types as polymorphic
286          *    values is not (well) supported.
287          *
288          * @since 2.11
289          */
290         public Builder allowIfSubTypeIsArray() {
291             return _appendSubClassMatcher(new TypeMatcher() {
292                 @Override
293                 public boolean match(MapperConfig<?> config, Class<?> clazz) {
294                     return clazz.isArray();
295                 }
296             });
297         }
298 
299         // 18-Nov-2019, tatu: alas, [databind#2539] can not be implemented with 2.x due
300         //    to (in hindsight) obvious design flaw: instead `MapperConfig`, `DatabindContext`
301         //    must be available to check what deserializers are registered.
302         /*
303         public Builder allowSubTypesWithExplicitDeserializer() {
304             return _appendSubClassMatcher(new TypeMatcher() {
305                 @Override
306                 public boolean match(MapperConfig<?> config, Class<?> clazz) {
307                     // First things first: "peel off" array type
308                     while (clazz.isArray()) {
309                         clazz = clazz.getComponentType();
310                     }
311                     DeserializerFactory df = ((DeserializationConfig) config).getDes
312                     return clazz.isArray();
313                 }
314             });
315         }
316          */
317 
318         public BasicPolymorphicTypeValidator build() {
319             return new BasicPolymorphicTypeValidator(_invalidBaseTypes,
320                     (_baseTypeMatchers == null) ? null : _baseTypeMatchers.toArray(new TypeMatcher[0]),
321                     (_subTypeNameMatchers == null) ? null : _subTypeNameMatchers.toArray(new NameMatcher[0]),
322                     (_subTypeClassMatchers == null) ? null : _subTypeClassMatchers.toArray(new TypeMatcher[0])
323             );
324         }
325 
326         protected Builder _appendBaseMatcher(TypeMatcher matcher) {
327             if (_baseTypeMatchers == null) {
328                 _baseTypeMatchers = new ArrayList<>();
329             }
330             _baseTypeMatchers.add(matcher);
331             return this;
332         }
333 
334         protected Builder _appendSubNameMatcher(NameMatcher matcher) {
335             if (_subTypeNameMatchers == null) {
336                 _subTypeNameMatchers = new ArrayList<>();
337             }
338             _subTypeNameMatchers.add(matcher);
339             return this;
340         }
341 
342         protected Builder _appendSubClassMatcher(TypeMatcher matcher) {
343             if (_subTypeClassMatchers == null) {
344                 _subTypeClassMatchers = new ArrayList<>();
345             }
346             _subTypeClassMatchers.add(matcher);
347             return this;
348         }
349     }
350 
351     /*
352     /**********************************************************
353     /* Actual implementation
354     /**********************************************************
355      */
356 
357     /**
358      * Set of specifically denied base types to indicate that use of specific
359      * base types is not allowed: most commonly used to fully block use of
360      * {@link java.lang.Object} as the base type.
361      */
362     protected final Set<Class<?>> _invalidBaseTypes;
363 
364     /**
365      * Set of matchers that can validate all values of polymorphic properties
366      * that match specified allowed base types.
367      */
368     protected final TypeMatcher[] _baseTypeMatchers;
369 
370     /**
371      * Set of matchers that can validate specific values of polymorphic properties
372      * that match subtype class name criteria.
373      */
374     protected final NameMatcher[] _subTypeNameMatchers;
375 
376     /**
377      * Set of matchers that can validate specific values of polymorphic properties
378      * that match subtype class criteria.
379      */
380     protected final TypeMatcher[] _subClassMatchers;
381 
382     protected BasicPolymorphicTypeValidator(Set<Class<?>> invalidBaseTypes,
383             TypeMatcher[] baseTypeMatchers,
384             NameMatcher[] subTypeNameMatchers, TypeMatcher[] subClassMatchers) {
385         _invalidBaseTypes = invalidBaseTypes;
386         _baseTypeMatchers = baseTypeMatchers;
387         _subTypeNameMatchers = subTypeNameMatchers;
388         _subClassMatchers = subClassMatchers;
389     }
390 
391     public static Builder builder() {
392         return new Builder();
393     }
394 
395     @Override
396     public Validity validateBaseType(MapperConfig<?> ctxt, JavaType baseType) {
397 //System.err.println("validateBaseType("+baseType+")");
398         final Class<?> rawBase = baseType.getRawClass();
399         if (_invalidBaseTypes != null) {
400             if (_invalidBaseTypes.contains(rawBase)) {
401                 return Validity.DENIED;
402             }
403         }
404         if (_baseTypeMatchers != null) {
405             for (TypeMatcher m : _baseTypeMatchers) {
406                 if (m.match(ctxt, rawBase)) {
407                     return Validity.ALLOWED;
408                 }
409             }
410         }
411         return Validity.INDETERMINATE;
412     }
413 
414     @Override
415     public Validity validateSubClassName(MapperConfig<?> ctxt, JavaType baseType,
416             String subClassName)
417         throws JsonMappingException
418     {
419 //System.err.println("validateSubClassName('"+subClassName+"')");
420         if (_subTypeNameMatchers != null)  {
421             for (NameMatcher m : _subTypeNameMatchers) {
422                 if (m.match(ctxt, subClassName)) {
423                     return Validity.ALLOWED;
424                 }
425             }
426         }
427         // could not yet decide, so:
428         return Validity.INDETERMINATE;
429     }
430 
431     @Override
432     public Validity validateSubType(MapperConfig<?> ctxt, JavaType baseType, JavaType subType)
433             throws JsonMappingException
434     {
435 //System.err.println("validateSubType("+subType+")");
436         if (_subClassMatchers != null)  {
437             final Class<?> subClass = subType.getRawClass();
438             for (TypeMatcher m : _subClassMatchers) {
439                 if (m.match(ctxt, subClass)) {
440                     return Validity.ALLOWED;
441                 }
442             }
443         }
444         // could not decide, callers gets to decide; usually will deny
445         return Validity.INDETERMINATE;
446     }
447 }
448