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