package com.fasterxml.jackson.databind.jsontype.impl;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.annotation.NoClass;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.jsontype.*;
import com.fasterxml.jackson.databind.util.ClassUtil;
Default TypeResolverBuilder
implementation. /**
* Default {@link TypeResolverBuilder} implementation.
*/
public class StdTypeResolverBuilder
implements TypeResolverBuilder<StdTypeResolverBuilder>
{
// Configuration settings:
protected JsonTypeInfo.Id _idType;
protected JsonTypeInfo.As _includeAs;
protected String _typeProperty;
Whether type id should be exposed to deserializers or not
/**
* Whether type id should be exposed to deserializers or not
*/
protected boolean _typeIdVisible = false;
Default class to use in case type information is not available
or is broken.
/**
* Default class to use in case type information is not available
* or is broken.
*/
protected Class<?> _defaultImpl;
// Objects
protected TypeIdResolver _customIdResolver;
/*
/**********************************************************
/* Construction, initialization, actual building
/**********************************************************
*/
public StdTypeResolverBuilder() { }
Since: 2.9
/**
* @since 2.9
*/
protected StdTypeResolverBuilder(JsonTypeInfo.Id idType,
JsonTypeInfo.As idAs, String propName) {
_idType = idType;
_includeAs = idAs;
_typeProperty = propName;
}
Copy-constructor
Since: 2.13
/**
* Copy-constructor
*
* @since 2.13
*/
protected StdTypeResolverBuilder(StdTypeResolverBuilder base,
Class<?> defaultImpl)
{
_idType = base._idType;
_includeAs = base._includeAs;
_typeProperty = base._typeProperty;
_typeIdVisible = base._typeIdVisible;
_customIdResolver = base._customIdResolver;
_defaultImpl = defaultImpl;
}
public static StdTypeResolverBuilder noTypeInfoBuilder() {
return new StdTypeResolverBuilder().init(JsonTypeInfo.Id.NONE, null);
}
@Override
public StdTypeResolverBuilder init(JsonTypeInfo.Id idType, TypeIdResolver idRes)
{
// sanity checks
if (idType == null) {
throw new IllegalArgumentException("idType cannot be null");
}
_idType = idType;
_customIdResolver = idRes;
// Let's also initialize property name as per idType default
_typeProperty = idType.getDefaultPropertyName();
return this;
}
@Override
public TypeSerializer buildTypeSerializer(SerializationConfig config,
JavaType baseType, Collection<NamedType> subtypes)
{
if (_idType == JsonTypeInfo.Id.NONE) { return null; }
// 03-Oct-2016, tatu: As per [databind#1395] better prevent use for primitives,
// regardless of setting
if (baseType.isPrimitive()) {
// 19-Jun-2020, tatu: But for [databind#2753], allow overriding
if (!allowPrimitiveTypes(config, baseType)) {
return null;
}
}
TypeIdResolver idRes = idResolver(config, baseType, subTypeValidator(config),
subtypes, true, false);
if(_idType == JsonTypeInfo.Id.DEDUCTION) {
// Deduction doesn't require a type property. We use EXISTING_PROPERTY with a name of <null> to drive this.
return new AsExistingPropertyTypeSerializer(idRes, null, _typeProperty);
}
switch (_includeAs) {
case WRAPPER_ARRAY:
return new AsArrayTypeSerializer(idRes, null);
case PROPERTY:
return new AsPropertyTypeSerializer(idRes, null, _typeProperty);
case WRAPPER_OBJECT:
return new AsWrapperTypeSerializer(idRes, null);
case EXTERNAL_PROPERTY:
return new AsExternalTypeSerializer(idRes, null, _typeProperty);
case EXISTING_PROPERTY:
// as per [#528]
return new AsExistingPropertyTypeSerializer(idRes, null, _typeProperty);
}
throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
}
// as per [#368]
// removed when fix [#528]
//private IllegalArgumentException _noExisting() {
// return new IllegalArgumentException("Inclusion type "+_includeAs+" not yet supported");
//}
@Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config,
JavaType baseType, Collection<NamedType> subtypes)
{
if (_idType == JsonTypeInfo.Id.NONE) { return null; }
// 03-Oct-2016, tatu: As per [databind#1395] better prevent use for primitives,
// regardless of setting
if (baseType.isPrimitive()) {
// 19-Jun-2020, tatu: But for [databind#2753], allow overriding
if (!allowPrimitiveTypes(config, baseType)) {
return null;
}
}
// 27-Apr-2019, tatu: Part of [databind#2195]; must first check whether any subtypes
// of basetypes might be denied or allowed
final PolymorphicTypeValidator subTypeValidator = verifyBaseTypeValidity(config, baseType);
TypeIdResolver idRes = idResolver(config, baseType, subTypeValidator, subtypes, false, true);
JavaType defaultImpl = defineDefaultImpl(config, baseType);
if(_idType == JsonTypeInfo.Id.DEDUCTION) {
// Deduction doesn't require an includeAs property
return new AsDeductionTypeDeserializer(baseType, idRes, defaultImpl, config, subtypes);
}
// First, method for converting type info to type id:
switch (_includeAs) {
case WRAPPER_ARRAY:
return new AsArrayTypeDeserializer(baseType, idRes,
_typeProperty, _typeIdVisible, defaultImpl);
case PROPERTY:
case EXISTING_PROPERTY: // as per [#528] same class as PROPERTY
return new AsPropertyTypeDeserializer(baseType, idRes,
_typeProperty, _typeIdVisible, defaultImpl, _includeAs);
case WRAPPER_OBJECT:
return new AsWrapperTypeDeserializer(baseType, idRes,
_typeProperty, _typeIdVisible, defaultImpl);
case EXTERNAL_PROPERTY:
return new AsExternalTypeDeserializer(baseType, idRes,
_typeProperty, _typeIdVisible, defaultImpl);
}
throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
}
protected JavaType defineDefaultImpl(DeserializationConfig config, JavaType baseType)
{
if (_defaultImpl != null) {
// 20-Mar-2016, tatu: It is important to do specialization go through
// TypeFactory to ensure proper resolution; with 2.7 and before, direct
// call to JavaType was used, but that cannot work reliably with 2.7
// 20-Mar-2016, tatu: Can finally add a check for type compatibility BUT
// if so, need to add explicit checks for marker types. Not ideal, but
// seems like a reasonable compromise.
if ((_defaultImpl == Void.class) || (_defaultImpl == NoClass.class)) {
// 18-Sep-2021, tatu: This has specific meaning: these two markers will
// be used to conjure `null` value out of invalid type ids
return config.getTypeFactory().constructType(_defaultImpl);
}
if (baseType.hasRawClass(_defaultImpl)) { // tiny optimization
return baseType;
}
if (baseType.isTypeOrSuperTypeOf(_defaultImpl)) {
// most common case with proper base type...
return config.getTypeFactory()
.constructSpecializedType(baseType, _defaultImpl);
}
if (baseType.hasRawClass(_defaultImpl)) {
return baseType;
}
}
// use base type as default should always be used as the last choice.
if (config.isEnabled(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL)
&& !baseType.isAbstract()) {
// still can not resolve by default impl, fall back to use base type as default impl
return baseType;
}
return null;
}
/*
/**********************************************************
/* Construction, configuration
/**********************************************************
*/
@Override
public StdTypeResolverBuilder inclusion(JsonTypeInfo.As includeAs) {
if (includeAs == null) {
throw new IllegalArgumentException("includeAs cannot be null");
}
_includeAs = includeAs;
return this;
}
Method for constructing an instance with specified type property name
(property name to use for type id when using "as-property" inclusion).
/**
* Method for constructing an instance with specified type property name
* (property name to use for type id when using "as-property" inclusion).
*/
@Override
public StdTypeResolverBuilder typeProperty(String typeIdPropName) {
// ok to have null/empty; will restore to use defaults
if (typeIdPropName == null || typeIdPropName.isEmpty()) {
typeIdPropName = _idType.getDefaultPropertyName();
}
_typeProperty = typeIdPropName;
return this;
}
@Override
public StdTypeResolverBuilder defaultImpl(Class<?> defaultImpl) {
_defaultImpl = defaultImpl;
return this;
}
@Override
public StdTypeResolverBuilder typeIdVisibility(boolean isVisible) {
_typeIdVisible = isVisible;
return this;
}
@Override
public StdTypeResolverBuilder withDefaultImpl(Class<?> defaultImpl) {
if (_defaultImpl == defaultImpl) {
return this;
}
ClassUtil.verifyMustOverride(StdTypeResolverBuilder.class, this, "withDefaultImpl");
// NOTE: MUST create new instance, NOT modify this instance
return new StdTypeResolverBuilder(this, defaultImpl);
}
/*
/**********************************************************
/* Accessors
/**********************************************************
*/
@Override public Class<?> getDefaultImpl() { return _defaultImpl; }
public String getTypeProperty() { return _typeProperty; }
public boolean isTypeIdVisible() { return _typeIdVisible; }
/*
/**********************************************************
/* Internal/subtype factory methods
/**********************************************************
*/
Helper method that will either return configured custom
type id resolver, or construct a standard resolver
given configuration.
/**
* Helper method that will either return configured custom
* type id resolver, or construct a standard resolver
* given configuration.
*/
protected TypeIdResolver idResolver(MapperConfig<?> config,
JavaType baseType, PolymorphicTypeValidator subtypeValidator,
Collection<NamedType> subtypes, boolean forSer, boolean forDeser)
{
// Custom id resolver?
if (_customIdResolver != null) { return _customIdResolver; }
if (_idType == null) throw new IllegalStateException("Cannot build, 'init()' not yet called");
switch (_idType) {
case DEDUCTION: // Deduction produces class names to be resolved
case CLASS:
return ClassNameIdResolver.construct(baseType, config, subtypeValidator);
case MINIMAL_CLASS:
return MinimalClassNameIdResolver.construct(baseType, config, subtypeValidator);
case NAME:
return TypeNameIdResolver.construct(config, baseType, subtypes, forSer, forDeser);
case NONE: // hmmh. should never get this far with 'none'
return null;
case CUSTOM: // need custom resolver...
}
throw new IllegalStateException("Do not know how to construct standard type id resolver for idType: "+_idType);
}
/*
/**********************************************************
/* Internal/subtype factory methods
/**********************************************************
*/
Overridable helper method for determining actual validator to use when constructing
type serializers and type deserializers.
Default implementation simply uses one configured and accessible using MapperConfig.getPolymorphicTypeValidator()
.
Since: 2.10
/**
* Overridable helper method for determining actual validator to use when constructing
* type serializers and type deserializers.
*<p>
* Default implementation simply uses one configured and accessible using
* {@link MapperConfig#getPolymorphicTypeValidator()}.
*
* @since 2.10
*/
public PolymorphicTypeValidator subTypeValidator(MapperConfig<?> config) {
return config.getPolymorphicTypeValidator();
}
Helper method called to check that base type is valid regarding possible constraints
on basetype/subtype combinations allowed for polymorphic type handling.
Currently limits are verified for class name - based methods only.
Since: 2.10
/**
* Helper method called to check that base type is valid regarding possible constraints
* on basetype/subtype combinations allowed for polymorphic type handling.
* Currently limits are verified for class name - based methods only.
*
* @since 2.10
*/
protected PolymorphicTypeValidator verifyBaseTypeValidity(MapperConfig<?> config,
JavaType baseType)
{
final PolymorphicTypeValidator ptv = subTypeValidator(config);
if (_idType == JsonTypeInfo.Id.CLASS || _idType == JsonTypeInfo.Id.MINIMAL_CLASS) {
final PolymorphicTypeValidator.Validity validity = ptv.validateBaseType(config, baseType);
// If no subtypes are legal (that is, base type itself is invalid), indicate problem
if (validity == PolymorphicTypeValidator.Validity.DENIED) {
return reportInvalidBaseType(config, baseType, ptv);
}
// If there's indication that any and all subtypes are fine, replace validator itself:
if (validity == PolymorphicTypeValidator.Validity.ALLOWED) {
return LaissezFaireSubTypeValidator.instance;
}
// otherwise just return validator, is to be called for each distinct type
}
return ptv;
}
Since: 2.10
/**
* @since 2.10
*/
protected PolymorphicTypeValidator reportInvalidBaseType(MapperConfig<?> config,
JavaType baseType, PolymorphicTypeValidator ptv)
{
throw new IllegalArgumentException(String.format(
"Configured `PolymorphicTypeValidator` (of type %s) denied resolution of all subtypes of base type %s",
ClassUtil.classNameOf(ptv), ClassUtil.classNameOf(baseType.getRawClass()))
);
}
/*
/**********************************************************
/* Overridable helper methods
/**********************************************************
*/
Overridable helper method that is called to determine whether type serializers and type deserializers may be created even if base type is Java primitive
type. Default implementation simply returns false
(since primitive types can not be sub-classed, are never polymorphic) but custom implementations may change the logic for some special cases. Params: - config – Currently active configuration
- baseType – Primitive base type for property being handled
Returns: True if type (de)serializer may be created even if base type is Java primitive
type; false if not Since: 2.11.1
/**
* Overridable helper method that is called to determine whether type serializers
* and type deserializers may be created even if base type is Java {@code primitive}
* type.
* Default implementation simply returns {@code false} (since primitive types can not
* be sub-classed, are never polymorphic) but custom implementations
* may change the logic for some special cases.
*
* @param config Currently active configuration
* @param baseType Primitive base type for property being handled
*
* @return True if type (de)serializer may be created even if base type is Java
* {@code primitive} type; false if not
*
* @since 2.11.1
*/
protected boolean allowPrimitiveTypes(MapperConfig<?> config,
JavaType baseType) {
return false;
}
}