| package com.fasterxml.jackson.databind; |
| |
| import java.lang.reflect.Type; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| |
| import com.fasterxml.jackson.annotation.*; |
| |
| import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; |
| import com.fasterxml.jackson.databind.cfg.MapperConfig; |
| import com.fasterxml.jackson.databind.introspect.Annotated; |
| import com.fasterxml.jackson.databind.introspect.ObjectIdInfo; |
| import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; |
| import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity; |
| import com.fasterxml.jackson.databind.type.TypeFactory; |
| import com.fasterxml.jackson.databind.util.ClassUtil; |
| import com.fasterxml.jackson.databind.util.Converter; |
| |
| /** |
| * Shared base class for {@link DeserializationContext} and |
| * {@link SerializerProvider}, context objects passed through data-binding |
| * process. Designed so that some of implementations can rely on shared |
| * aspects like access to secondary contextual objects like type factories |
| * or handler instantiators. |
| * |
| * @since 2.2 |
| */ |
| public abstract class DatabindContext |
| { |
| /** |
| * Let's limit length of error messages, for cases where underlying data |
| * may be very large -- no point in spamming logs with megabytes of meaningless |
| * data. |
| * |
| * @since 2.9 |
| */ |
| private final static int MAX_ERROR_STR_LEN = 500; |
| |
| /* |
| /********************************************************** |
| /* Generic config access |
| /********************************************************** |
| */ |
| |
| /** |
| * Accessor to currently active configuration (both per-request configs |
| * and per-mapper config). |
| */ |
| public abstract MapperConfig<?> getConfig(); |
| |
| /** |
| * Convenience method for accessing serialization view in use (if any); equivalent to: |
| *<pre> |
| * getConfig().getAnnotationIntrospector(); |
| *</pre> |
| */ |
| public abstract AnnotationIntrospector getAnnotationIntrospector(); |
| |
| /* |
| /********************************************************** |
| /* Access to specific config settings |
| /********************************************************** |
| */ |
| |
| /** |
| * Convenience method for checking whether specified serialization |
| * feature is enabled or not. |
| * Shortcut for: |
| *<pre> |
| * getConfig().isEnabled(feature); |
| *</pre> |
| */ |
| public abstract boolean isEnabled(MapperFeature feature); |
| |
| /** |
| * Convenience method for accessing serialization view in use (if any); equivalent to: |
| *<pre> |
| * getConfig().canOverrideAccessModifiers(); |
| *</pre> |
| */ |
| public abstract boolean canOverrideAccessModifiers(); |
| |
| /** |
| * Accessor for locating currently active view, if any; |
| * returns null if no view has been set. |
| */ |
| public abstract Class<?> getActiveView(); |
| |
| /** |
| * @since 2.6 |
| */ |
| public abstract Locale getLocale(); |
| |
| /** |
| * @since 2.6 |
| */ |
| public abstract TimeZone getTimeZone(); |
| |
| /** |
| * @since 2.7 |
| */ |
| public abstract JsonFormat.Value getDefaultPropertyFormat(Class<?> baseType); |
| |
| /* |
| /********************************************************** |
| /* Generic attributes (2.3+) |
| /********************************************************** |
| */ |
| |
| /** |
| * Method for accessing attributes available in this context. |
| * Per-call attributes have highest precedence; attributes set |
| * via {@link ObjectReader} or {@link ObjectWriter} have lower |
| * precedence. |
| * |
| * @param key Key of the attribute to get |
| * @return Value of the attribute, if any; null otherwise |
| * |
| * @since 2.3 |
| */ |
| public abstract Object getAttribute(Object key); |
| |
| /** |
| * Method for setting per-call value of given attribute. |
| * This will override any previously defined value for the |
| * attribute within this context. |
| * |
| * @param key Key of the attribute to set |
| * @param value Value to set attribute to |
| * |
| * @return This context object, to allow chaining |
| * |
| * @since 2.3 |
| */ |
| public abstract DatabindContext setAttribute(Object key, Object value); |
| |
| /* |
| /********************************************************** |
| /* Type instantiation/resolution |
| /********************************************************** |
| */ |
| |
| /** |
| * Convenience method for constructing {@link JavaType} for given JDK |
| * type (usually {@link java.lang.Class}) |
| */ |
| public JavaType constructType(Type type) { |
| if (type == null) { |
| return null; |
| } |
| return getTypeFactory().constructType(type); |
| } |
| |
| /** |
| * Convenience method for constructing subtypes, retaining generic |
| * type parameter (if any) |
| */ |
| public JavaType constructSpecializedType(JavaType baseType, Class<?> subclass) { |
| // simple optimization to avoid costly introspection if type-erased type does NOT differ |
| if (baseType.getRawClass() == subclass) { |
| return baseType; |
| } |
| return getConfig().constructSpecializedType(baseType, subclass); |
| } |
| |
| /** |
| * Lookup method called when code needs to resolve class name from input; |
| * usually simple lookup. |
| * Note that unlike {@link #resolveAndValidateSubType} this method DOES NOT |
| * validate subtype against configured {@link PolymorphicTypeValidator}: usually |
| * because such check has already been made. |
| * |
| * @since 2.9 |
| */ |
| public JavaType resolveSubType(JavaType baseType, String subClassName) |
| throws JsonMappingException |
| { |
| // 30-Jan-2010, tatu: Most ids are basic class names; so let's first |
| // check if any generics info is added; and only then ask factory |
| // to do translation when necessary |
| if (subClassName.indexOf('<') > 0) { |
| // note: may want to try combining with specialization (esp for EnumMap)? |
| // 17-Aug-2017, tatu: As per [databind#1735] need to ensure assignment |
| // compatibility -- needed later anyway, and not doing so may open |
| // security issues. |
| JavaType t = getTypeFactory().constructFromCanonical(subClassName); |
| if (t.isTypeOrSubTypeOf(baseType.getRawClass())) { |
| return t; |
| } |
| } else { |
| Class<?> cls; |
| try { |
| cls = getTypeFactory().findClass(subClassName); |
| } catch (ClassNotFoundException e) { // let caller handle this problem |
| return null; |
| } catch (Exception e) { |
| throw invalidTypeIdException(baseType, subClassName, String.format( |
| "problem: (%s) %s", |
| e.getClass().getName(), |
| ClassUtil.exceptionMessage(e))); |
| } |
| if (baseType.isTypeOrSuperTypeOf(cls)) { |
| return getTypeFactory().constructSpecializedType(baseType, cls); |
| } |
| } |
| throw invalidTypeIdException(baseType, subClassName, "Not a subtype"); |
| } |
| |
| /** |
| * Lookup method similar to {@link #resolveSubType} but one that also validates |
| * that resulting subtype is valid according to given {@link PolymorphicTypeValidator}. |
| * |
| * @since 2.10 |
| */ |
| public JavaType resolveAndValidateSubType(JavaType baseType, String subClass, |
| PolymorphicTypeValidator ptv) |
| throws JsonMappingException |
| { |
| // Off-line the special case of generic (parameterized) type: |
| final int ltIndex = subClass.indexOf('<'); |
| if (ltIndex > 0) { |
| return _resolveAndValidateGeneric(baseType, subClass, ptv, ltIndex); |
| } |
| final MapperConfig<?> config = getConfig(); |
| PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(config, baseType, subClass); |
| if (vld == Validity.DENIED) { |
| return _throwSubtypeNameNotAllowed(baseType, subClass, ptv); |
| } |
| final Class<?> cls; |
| try { |
| cls = getTypeFactory().findClass(subClass); |
| } catch (ClassNotFoundException e) { // let caller handle this problem |
| return null; |
| } catch (Exception e) { |
| throw invalidTypeIdException(baseType, subClass, String.format( |
| "problem: (%s) %s", |
| e.getClass().getName(), |
| ClassUtil.exceptionMessage(e))); |
| } |
| if (!baseType.isTypeOrSuperTypeOf(cls)) { |
| return _throwNotASubtype(baseType, subClass); |
| } |
| final JavaType subType = config.getTypeFactory().constructSpecializedType(baseType, cls); |
| // May skip check if type was allowed by subclass name already |
| if (vld == Validity.INDETERMINATE) { |
| vld = ptv.validateSubType(config, baseType, subType); |
| if (vld != Validity.ALLOWED) { |
| return _throwSubtypeClassNotAllowed(baseType, subClass, ptv); |
| } |
| } |
| return subType; |
| } |
| |
| private JavaType _resolveAndValidateGeneric(JavaType baseType, String subClass, |
| PolymorphicTypeValidator ptv, int ltIndex) |
| throws JsonMappingException |
| { |
| final MapperConfig<?> config = getConfig(); |
| // 24-Apr-2019, tatu: Not 100% sure if we should pass name with type parameters |
| // or not, but guessing it's more convenient not to have to worry about it so |
| // strip out |
| PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(config, baseType, subClass.substring(0, ltIndex)); |
| if (vld == Validity.DENIED) { |
| return _throwSubtypeNameNotAllowed(baseType, subClass, ptv); |
| } |
| JavaType subType = getTypeFactory().constructFromCanonical(subClass); |
| if (!subType.isTypeOrSubTypeOf(baseType.getRawClass())) { |
| return _throwNotASubtype(baseType, subClass); |
| } |
| // Unless we were approved already by name, check that actual sub-class acceptable: |
| if (vld != Validity.ALLOWED) { |
| if (ptv.validateSubType(config, baseType, subType) != Validity.ALLOWED) { |
| return _throwSubtypeClassNotAllowed(baseType, subClass, ptv); |
| } |
| } |
| return subType; |
| } |
| |
| protected <T> T _throwNotASubtype(JavaType baseType, String subType) throws JsonMappingException { |
| throw invalidTypeIdException(baseType, subType, "Not a subtype"); |
| } |
| |
| protected <T> T _throwSubtypeNameNotAllowed(JavaType baseType, String subType, |
| PolymorphicTypeValidator ptv) throws JsonMappingException { |
| throw invalidTypeIdException(baseType, subType, |
| "Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution"); |
| } |
| |
| protected <T> T _throwSubtypeClassNotAllowed(JavaType baseType, String subType, |
| PolymorphicTypeValidator ptv) throws JsonMappingException { |
| throw invalidTypeIdException(baseType, subType, |
| "Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution"); |
| } |
| |
| /** |
| * Helper method for constructing exception to indicate that given type id |
| * could not be resolved to a valid subtype of specified base type. |
| * Most commonly called during polymorphic deserialization. |
| *<p> |
| * Note that most of the time this method should NOT be called directly: instead, |
| * method <code>handleUnknownTypeId()</code> should be called which will call this method |
| * if necessary. |
| * |
| * @since 2.9 |
| */ |
| protected abstract JsonMappingException invalidTypeIdException(JavaType baseType, String typeId, |
| String extraDesc); |
| |
| public abstract TypeFactory getTypeFactory(); |
| |
| /* |
| /********************************************************** |
| /* Helper object construction |
| /********************************************************** |
| */ |
| |
| public ObjectIdGenerator<?> objectIdGeneratorInstance(Annotated annotated, |
| ObjectIdInfo objectIdInfo) |
| throws JsonMappingException |
| { |
| Class<?> implClass = objectIdInfo.getGeneratorType(); |
| final MapperConfig<?> config = getConfig(); |
| HandlerInstantiator hi = config.getHandlerInstantiator(); |
| ObjectIdGenerator<?> gen = (hi == null) ? null : hi.objectIdGeneratorInstance(config, annotated, implClass); |
| if (gen == null) { |
| gen = (ObjectIdGenerator<?>) ClassUtil.createInstance(implClass, |
| config.canOverrideAccessModifiers()); |
| } |
| return gen.forScope(objectIdInfo.getScope()); |
| } |
| |
| public ObjectIdResolver objectIdResolverInstance(Annotated annotated, ObjectIdInfo objectIdInfo) |
| { |
| Class<? extends ObjectIdResolver> implClass = objectIdInfo.getResolverType(); |
| final MapperConfig<?> config = getConfig(); |
| HandlerInstantiator hi = config.getHandlerInstantiator(); |
| ObjectIdResolver resolver = (hi == null) ? null : hi.resolverIdGeneratorInstance(config, annotated, implClass); |
| if (resolver == null) { |
| resolver = ClassUtil.createInstance(implClass, config.canOverrideAccessModifiers()); |
| } |
| |
| return resolver; |
| } |
| |
| /** |
| * Helper method to use to construct a {@link Converter}, given a definition |
| * that may be either actual converter instance, or Class for instantiating one. |
| * |
| * @since 2.2 |
| */ |
| @SuppressWarnings("unchecked") |
| public Converter<Object,Object> converterInstance(Annotated annotated, |
| Object converterDef) |
| throws JsonMappingException |
| { |
| if (converterDef == null) { |
| return null; |
| } |
| if (converterDef instanceof Converter<?,?>) { |
| return (Converter<Object,Object>) converterDef; |
| } |
| if (!(converterDef instanceof Class)) { |
| throw new IllegalStateException("AnnotationIntrospector returned Converter definition of type " |
| +converterDef.getClass().getName()+"; expected type Converter or Class<Converter> instead"); |
| } |
| Class<?> converterClass = (Class<?>)converterDef; |
| // there are some known "no class" markers to consider too: |
| if (converterClass == Converter.None.class || ClassUtil.isBogusClass(converterClass)) { |
| return null; |
| } |
| if (!Converter.class.isAssignableFrom(converterClass)) { |
| throw new IllegalStateException("AnnotationIntrospector returned Class " |
| +converterClass.getName()+"; expected Class<Converter>"); |
| } |
| final MapperConfig<?> config = getConfig(); |
| HandlerInstantiator hi = config.getHandlerInstantiator(); |
| Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(config, annotated, converterClass); |
| if (conv == null) { |
| conv = (Converter<?,?>) ClassUtil.createInstance(converterClass, |
| config.canOverrideAccessModifiers()); |
| } |
| return (Converter<Object,Object>) conv; |
| } |
| |
| /* |
| /********************************************************** |
| /* Error reporting |
| /********************************************************** |
| */ |
| |
| /** |
| * Helper method called to indicate a generic problem that stems from type |
| * definition(s), not input data, or input/output state; typically this |
| * means throwing a {@link com.fasterxml.jackson.databind.exc.InvalidDefinitionException}. |
| * |
| * @since 2.9 |
| */ |
| public abstract <T> T reportBadDefinition(JavaType type, String msg) throws JsonMappingException; |
| |
| /** |
| * @since 2.9 |
| */ |
| public <T> T reportBadDefinition(Class<?> type, String msg) throws JsonMappingException { |
| return reportBadDefinition(constructType(type), msg); |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper methods |
| /********************************************************** |
| */ |
| |
| /** |
| * @since 2.9 |
| */ |
| protected final String _format(String msg, Object... msgArgs) { |
| if (msgArgs.length > 0) { |
| return String.format(msg, msgArgs); |
| } |
| return msg; |
| } |
| |
| /** |
| * @since 2.9 |
| */ |
| protected final String _truncate(String desc) { |
| if (desc == null) { |
| return ""; |
| } |
| if (desc.length() <= MAX_ERROR_STR_LEN) { |
| return desc; |
| } |
| return desc.substring(0, MAX_ERROR_STR_LEN) + "]...[" + desc.substring(desc.length() - MAX_ERROR_STR_LEN); |
| } |
| |
| /** |
| * @since 2.9 |
| */ |
| protected String _quotedString(String desc) { |
| if (desc == null) { |
| return "[N/A]"; |
| } |
| // !!! should we quote it? (in case there are control chars, linefeeds) |
| return String.format("\"%s\"", _truncate(desc)); |
| } |
| |
| /** |
| * @since 2.9 |
| */ |
| protected String _colonConcat(String msgBase, String extra) { |
| if (extra == null) { |
| return msgBase; |
| } |
| return msgBase + ": " + extra; |
| } |
| |
| /** |
| * @since 2.9 |
| */ |
| protected String _desc(String desc) { |
| if (desc == null) { |
| return "[N/A]"; |
| } |
| // !!! should we quote it? (in case there are control chars, linefeeds) |
| return _truncate(desc); |
| } |
| } |