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