| package com.fasterxml.jackson.databind.deser; |
| |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import com.fasterxml.jackson.core.JsonNode; |
| import com.fasterxml.jackson.core.JsonParser; |
| import com.fasterxml.jackson.core.JsonProcessingException; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.annotation.NoClass; |
| import com.fasterxml.jackson.databind.introspect.Annotated; |
| import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; |
| import com.fasterxml.jackson.databind.type.ArrayType; |
| import com.fasterxml.jackson.databind.type.CollectionLikeType; |
| import com.fasterxml.jackson.databind.type.CollectionType; |
| import com.fasterxml.jackson.databind.type.MapLikeType; |
| import com.fasterxml.jackson.databind.type.MapType; |
| import com.fasterxml.jackson.databind.util.ClassUtil; |
| |
| /** |
| * Class that defines caching layer between callers (like |
| * {@link ObjectMapper}, {@link com.fasterxml.jackson.map.deser.DeserializationContext}) |
| * and classes that construct deserializers ({@link com.fasterxml.jackson.map.deser.DeserializerFactory}). |
| */ |
| public final class DeserializerCache |
| { |
| /* |
| /********************************************************** |
| /* Caching |
| /********************************************************** |
| */ |
| |
| /** |
| * We will also cache some dynamically constructed deserializers; |
| * specifically, ones that are expensive to construct. |
| * This currently means bean and Enum deserializers; array, List and Map |
| * deserializers will not be cached. |
| *<p> |
| * Given that we don't expect much concurrency for additions |
| * (should very quickly converge to zero after startup), let's |
| * explicitly define a low concurrency setting. |
| */ |
| final protected ConcurrentHashMap<JavaType, JsonDeserializer<Object>> _cachedDeserializers |
| = new ConcurrentHashMap<JavaType, JsonDeserializer<Object>>(64, 0.75f, 2); |
| |
| /** |
| * During deserializer construction process we may need to keep track of partially |
| * completed deserializers, to resolve cyclic dependencies. This is the |
| * map used for storing deserializers before they are fully complete. |
| */ |
| final protected HashMap<JavaType, JsonDeserializer<Object>> _incompleteDeserializers |
| = new HashMap<JavaType, JsonDeserializer<Object>>(8); |
| |
| /* |
| /********************************************************** |
| /* Configuration |
| /********************************************************** |
| */ |
| |
| /** |
| * Factory responsible for constructing actual deserializers, if not |
| * one of pre-configured types. |
| */ |
| protected final DeserializerFactory _factory; |
| |
| /* |
| /********************************************************** |
| /* Life-cycle |
| /********************************************************** |
| */ |
| |
| public DeserializerCache(DeserializerFactory f) { |
| _factory = f; |
| } |
| |
| /* |
| /********************************************************** |
| /* Fluent factory methods |
| /********************************************************** |
| */ |
| |
| /** |
| * Method that sub-classes need to override, to ensure that fluent-factory |
| * methods will produce proper sub-type. |
| */ |
| public DeserializerCache withFactory(DeserializerFactory factory) { |
| return new DeserializerCache(factory); |
| } |
| |
| /** |
| * Method that is to configure {@link DeserializerFactory} that provider has |
| * to use specified deserializer provider, with highest precedence (that is, |
| * additional providers have higher precedence than default one or previously |
| * added ones) |
| */ |
| public DeserializerCache withAdditionalDeserializers(Deserializers d) { |
| return withFactory(_factory.withAdditionalDeserializers(d)); |
| } |
| |
| public DeserializerCache withAdditionalKeyDeserializers(KeyDeserializers d) { |
| return withFactory(_factory.withAdditionalKeyDeserializers(d)); |
| } |
| |
| public DeserializerCache withDeserializerModifier(BeanDeserializerModifier modifier) { |
| return withFactory(_factory.withDeserializerModifier(modifier)); |
| } |
| |
| public DeserializerCache withAbstractTypeResolver(AbstractTypeResolver resolver) { |
| return withFactory(_factory.withAbstractTypeResolver(resolver)); |
| } |
| |
| /** |
| * Method that will construct a new instance with specified additional value instantiators |
| * (i.e. does NOT replace existing ones) |
| */ |
| public DeserializerCache withValueInstantiators(ValueInstantiators instantiators) { |
| return withFactory(_factory.withValueInstantiators(instantiators)); |
| } |
| |
| /* |
| /********************************************************** |
| /* Access to caching aspects |
| /********************************************************** |
| */ |
| |
| /** |
| * Method that can be used to determine how many deserializers this |
| * provider is caching currently |
| * (if it does caching: default implementation does) |
| * Exact count depends on what kind of deserializers get cached; |
| * default implementation caches only dynamically constructed deserializers, |
| * but not eagerly constructed standard deserializers (which is different |
| * from how serializer provider works). |
| *<p> |
| * The main use case for this method is to allow conditional flushing of |
| * deserializer cache, if certain number of entries is reached. |
| */ |
| public int cachedDeserializersCount() { |
| return _cachedDeserializers.size(); |
| } |
| |
| /** |
| * Method that will drop all dynamically constructed deserializers (ones that |
| * are counted as result value for {@link #cachedDeserializersCount}). |
| * This can be used to remove memory usage (in case some deserializers are |
| * only used once or so), or to force re-construction of deserializers after |
| * configuration changes for mapper than owns the provider. |
| */ |
| public void flushCachedDeserializers() { |
| _cachedDeserializers.clear(); |
| } |
| |
| /* |
| /********************************************************** |
| /* General deserializer locating method |
| /********************************************************** |
| */ |
| |
| /** |
| * Method called to get hold of a deserializer for a value of given type; |
| * or if no such deserializer can be found, a default handler (which |
| * may do a best-effort generic serialization or just simply |
| * throw an exception when invoked). |
| *<p> |
| * Note: this method is only called for value types; not for keys. |
| * Key deserializers can be accessed using {@link #findKeyDeserializer}. |
| * |
| * @param config Deserialization configuration |
| * @param propertyType Declared type of the value to deserializer (obtained using |
| * 'setter' method signature and/or type annotations |
| * @param property Object that represents accessor for property value; field, |
| * setter method or constructor parameter. |
| * |
| * @throws JsonMappingException if there are fatal problems with |
| * accessing suitable deserializer; including that of not |
| * finding any serializer |
| */ |
| @SuppressWarnings("unchecked") |
| public JsonDeserializer<Object> findValueDeserializer(DeserializationContext ctxt, |
| JavaType propertyType, BeanProperty property) |
| throws JsonMappingException |
| { |
| JsonDeserializer<Object> deser = _findCachedDeserializer(propertyType); |
| if (deser != null) { |
| // [JACKSON-385]: need to support contextualization: |
| if (deser instanceof ContextualDeserializer<?>) { |
| JsonDeserializer<?> d = ((ContextualDeserializer<?>) deser).createContextual(ctxt.getConfig(), property); |
| deser = (JsonDeserializer<Object>) d; |
| } |
| return deser; |
| } |
| // If not, need to request factory to construct (or recycle) |
| deser = _createAndCacheValueDeserializer(ctxt, propertyType, property); |
| if (deser == null) { |
| /* Should we let caller handle it? Let's have a helper method |
| * decide it; can throw an exception, or return a valid |
| * deserializer |
| */ |
| deser = _handleUnknownValueDeserializer(propertyType); |
| } |
| // [JACKSON-385]: need to support contextualization: |
| if (deser instanceof ContextualDeserializer<?>) { |
| JsonDeserializer<?> d = ((ContextualDeserializer<?>) deser).createContextual(ctxt.getConfig(), property); |
| deser = (JsonDeserializer<Object>) d; |
| } |
| return deser; |
| } |
| |
| /** |
| * Method called to locate deserializer for given type, as well as matching |
| * type deserializer (if one is needed); and if type deserializer is needed, |
| * construct a "wrapped" deserializer that can extract and use type information |
| * for calling actual deserializer. |
| *<p> |
| * Since this method is only called for root elements, no referral information |
| * is taken. |
| */ |
| public JsonDeserializer<Object> findTypedValueDeserializer(DeserializationContext ctxt, |
| JavaType type, BeanProperty property) |
| throws JsonMappingException |
| { |
| JsonDeserializer<Object> deser = findValueDeserializer(ctxt, type, property); |
| TypeDeserializer typeDeser = _factory.findTypeDeserializer(ctxt.getConfig(), type, property); |
| if (typeDeser != null) { |
| return new WrappedDeserializer(typeDeser, deser); |
| } |
| return deser; |
| } |
| |
| /** |
| * Method called to get hold of a deserializer to use for deserializing |
| * keys for {@link java.util.Map}. |
| * |
| * @throws JsonMappingException if there are fatal problems with |
| * accessing suitable key deserializer; including that of not |
| * finding any serializer |
| */ |
| public KeyDeserializer findKeyDeserializer(DeserializationContext ctxt, |
| JavaType type, BeanProperty property) |
| throws JsonMappingException |
| { |
| KeyDeserializer kd = _factory.createKeyDeserializer(ctxt, type, property); |
| if (kd == null) { // if none found, need to use a placeholder that'll fail |
| return _handleUnknownKeyDeserializer(type); |
| } |
| // First: need to resolve? |
| if (kd instanceof ResolvableDeserializer) { |
| ((ResolvableDeserializer) kd).resolve(ctxt); |
| } |
| // Second: contextualize? |
| if (kd instanceof ContextualKeyDeserializer) { |
| kd = ((ContextualKeyDeserializer) kd).createContextual(ctxt.getConfig(), property); |
| } |
| return kd; |
| } |
| |
| /** |
| * Method called to find out whether provider would be able to find |
| * a deserializer for given type, using a root reference (i.e. not |
| * through fields or membership in an array or collection) |
| */ |
| public boolean hasValueDeserializerFor(DeserializationContext ctxt, JavaType type) |
| { |
| /* Note: mostly copied from findValueDeserializer, except for |
| * handling of unknown types |
| */ |
| JsonDeserializer<Object> deser = _findCachedDeserializer(type); |
| if (deser == null) { |
| try { |
| deser = _createAndCacheValueDeserializer(ctxt, type, null); |
| } catch (Exception e) { |
| return false; |
| } |
| } |
| return (deser != null); |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper methods that handle cache lookups |
| /********************************************************** |
| */ |
| |
| protected JsonDeserializer<Object> _findCachedDeserializer(JavaType type) |
| { |
| if (type == null) { |
| throw new IllegalArgumentException(); |
| } |
| return _cachedDeserializers.get(type); |
| } |
| |
| /** |
| * Method that will try to create a deserializer for given type, |
| * and resolve and cache it if necessary |
| * |
| * @param config Configuration |
| * @param type Type of property to deserializer |
| * @param property Property (field, setter, ctor arg) to use deserializer for |
| */ |
| protected JsonDeserializer<Object>_createAndCacheValueDeserializer(DeserializationContext ctxt, |
| JavaType type, BeanProperty property) |
| throws JsonMappingException |
| { |
| /* Only one thread to construct deserializers at any given point in time; |
| * limitations necessary to ensure that only completely initialized ones |
| * are visible and used. |
| */ |
| synchronized (_incompleteDeserializers) { |
| // Ok, then: could it be that due to a race condition, deserializer can now be found? |
| JsonDeserializer<Object> deser = _findCachedDeserializer(type); |
| if (deser != null) { |
| return deser; |
| } |
| int count = _incompleteDeserializers.size(); |
| // Or perhaps being resolved right now? |
| if (count > 0) { |
| deser = _incompleteDeserializers.get(type); |
| if (deser != null) { |
| return deser; |
| } |
| } |
| // Nope: need to create and possibly cache |
| try { |
| return _createAndCache2(ctxt, type, property); |
| } finally { |
| // also: any deserializers that have been created are complete by now |
| if (count == 0 && _incompleteDeserializers.size() > 0) { |
| _incompleteDeserializers.clear(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Method that handles actual construction (via factory) and caching (both |
| * intermediate and eventual) |
| */ |
| protected JsonDeserializer<Object> _createAndCache2(DeserializationContext ctxt, |
| JavaType type, BeanProperty property) |
| throws JsonMappingException |
| { |
| JsonDeserializer<Object> deser; |
| try { |
| deser = _createDeserializer(ctxt, type, property); |
| } catch (IllegalArgumentException iae) { |
| /* We better only expose checked exceptions, since those |
| * are what caller is expected to handle |
| */ |
| throw new JsonMappingException(iae.getMessage(), null, iae); |
| } |
| if (deser == null) { |
| return null; |
| } |
| /* cache resulting deserializer? always true for "plain" BeanDeserializer |
| * (but can be re-defined for sub-classes by using @JsonCachable!) |
| */ |
| // 08-Jun-2010, tatu: Related to [JACKSON-296], need to avoid caching MapSerializers... so: |
| boolean isResolvable = (deser instanceof ResolvableDeserializer); |
| boolean addToCache = deser.isCachable(); |
| |
| /* we will temporarily hold on to all created deserializers (to |
| * handle cyclic references, and possibly reuse non-cached |
| * deserializers (list, map)) |
| */ |
| /* 07-Jun-2010, tatu: Danger: [JACKSON-296] was caused by accidental |
| * resolution of a reference -- couple of ways to prevent this; |
| * either not add Lists or Maps, or clear references eagerly. |
| * Let's actually do both; since both seem reasonable. |
| */ |
| /* Need to resolve? Mostly done for bean deserializers; required for |
| * resolving cyclic references. |
| */ |
| if (isResolvable) { |
| _incompleteDeserializers.put(type, deser); |
| ((ResolvableDeserializer)deser).resolve(ctxt); |
| _incompleteDeserializers.remove(type); |
| } |
| if (addToCache) { |
| _cachedDeserializers.put(type, deser); |
| } |
| return deser; |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper methods for actual construction of deserializers |
| /********************************************************** |
| */ |
| |
| /** |
| * Method that does the heavy lifting of checking for per-type annotations, |
| * find out full type, and figure out which actual factory method |
| * to call. |
| */ |
| @SuppressWarnings("unchecked") |
| protected JsonDeserializer<Object> _createDeserializer(DeserializationContext ctxt, |
| JavaType type, BeanProperty property) |
| throws JsonMappingException |
| { |
| final DeserializationConfig config = ctxt.getConfig(); |
| |
| // First things first: do we need to use abstract type mapping? |
| if (type.isAbstract() || type.isMapLikeType() || type.isCollectionLikeType()) { |
| type = _factory.mapAbstractType(config, type); |
| } |
| BeanDescription beanDesc = config.introspect(type); |
| // Then: does type define explicit deserializer to use, with annotation(s)? |
| JsonDeserializer<Object> deser = findDeserializerFromAnnotation(ctxt, |
| beanDesc.getClassInfo(), property); |
| if (deser != null) { |
| return deser; |
| } |
| |
| // If not, may have further type-modification annotations to check: |
| JavaType newType = modifyTypeByAnnotation(ctxt, beanDesc.getClassInfo(), type, property); |
| if (newType != type) { |
| type = newType; |
| beanDesc = config.introspect(newType); |
| } |
| |
| // If not, let's see which factory method to use: |
| if (type.isEnumType()) { |
| return (JsonDeserializer<Object>) _factory.createEnumDeserializer(ctxt, type, |
| beanDesc, property); |
| } |
| if (type.isContainerType()) { |
| if (type.isArrayType()) { |
| return (JsonDeserializer<Object>)_factory.createArrayDeserializer(ctxt, |
| (ArrayType) type, beanDesc, property); |
| } |
| if (type.isMapLikeType()) { |
| MapLikeType mlt = (MapLikeType) type; |
| if (mlt.isTrueMapType()) { |
| return (JsonDeserializer<Object>)_factory.createMapDeserializer(ctxt, |
| (MapType) mlt, beanDesc, property); |
| } |
| return (JsonDeserializer<Object>)_factory.createMapLikeDeserializer(ctxt, |
| mlt, beanDesc, property); |
| } |
| if (type.isCollectionLikeType()) { |
| CollectionLikeType clt = (CollectionLikeType) type; |
| if (clt.isTrueCollectionType()) { |
| return (JsonDeserializer<Object>)_factory.createCollectionDeserializer(ctxt, |
| (CollectionType) clt, beanDesc, property); |
| } |
| return (JsonDeserializer<Object>)_factory.createCollectionLikeDeserializer(ctxt, |
| clt, beanDesc, property); |
| } |
| } |
| |
| // 02-Mar-2009, tatu: Let's consider JsonNode to be a type of its own |
| if (JsonNode.class.isAssignableFrom(type.getRawClass())) { |
| return (JsonDeserializer<Object>)_factory.createTreeDeserializer(config, type, beanDesc, property); |
| } |
| return (JsonDeserializer<Object>)_factory.createBeanDeserializer(ctxt, type, beanDesc, property); |
| } |
| |
| /** |
| * Helper method called to check if a class or method |
| * has annotation that tells which class to use for deserialization. |
| * Returns null if no such annotation found. |
| */ |
| protected JsonDeserializer<Object> findDeserializerFromAnnotation(DeserializationContext ctxt, |
| Annotated ann, BeanProperty property) |
| throws JsonMappingException |
| { |
| Object deserDef = ctxt.getAnnotationIntrospector().findDeserializer(ann); |
| if (deserDef == null) { |
| return null; |
| } |
| return ctxt.deserializerInstance(ann, property, deserDef); |
| } |
| |
| /** |
| * Method called to see if given method has annotations that indicate |
| * a more specific type than what the argument specifies. |
| * If annotations are present, they must specify compatible Class; |
| * instance of which can be assigned using the method. This means |
| * that the Class has to be raw class of type, or its sub-class |
| * (or, implementing class if original Class instance is an interface). |
| * |
| * @param a Method or field that the type is associated with |
| * @param type Type derived from the setter argument |
| * @param prop Property that lead to this annotation, if any. |
| * |
| * @return Original type if no annotations are present; or a more |
| * specific type derived from it if type annotation(s) was found |
| * |
| * @throws JsonMappingException if invalid annotation is found |
| */ |
| private JavaType modifyTypeByAnnotation(DeserializationContext ctxt, |
| Annotated a, JavaType type, BeanProperty prop) |
| throws JsonMappingException |
| { |
| // first: let's check class for the instance itself: |
| AnnotationIntrospector intr = ctxt.getAnnotationIntrospector(); |
| Class<?> subclass = intr.findDeserializationType(a, type, |
| (prop == null)? null : prop.getName()); |
| if (subclass != null) { |
| try { |
| type = type.narrowBy(subclass); |
| } catch (IllegalArgumentException iae) { |
| throw new JsonMappingException("Failed to narrow type "+type+" with concrete-type annotation (value "+subclass.getName()+"), method '"+a.getName()+"': "+iae.getMessage(), null, iae); |
| } |
| } |
| |
| // then key class |
| if (type.isContainerType()) { |
| Class<?> keyClass = intr.findDeserializationKeyType(a, type.getKeyType(), |
| (prop == null)? null : prop.getName()); |
| if (keyClass != null) { |
| // illegal to use on non-Maps |
| if (!(type instanceof MapLikeType)) { |
| throw new JsonMappingException("Illegal key-type annotation: type "+type+" is not a Map(-like) type"); |
| } |
| try { |
| type = ((MapLikeType) type).narrowKey(keyClass); |
| } catch (IllegalArgumentException iae) { |
| throw new JsonMappingException("Failed to narrow key type "+type+" with key-type annotation ("+keyClass.getName()+"): "+iae.getMessage(), null, iae); |
| } |
| } |
| JavaType keyType = type.getKeyType(); |
| /* 21-Mar-2011, tatu: ... and associated deserializer too (unless already assigned) |
| * (not 100% why or how, but this does seem to get called more than once, which |
| * is not good: for now, let's just avoid errors) |
| */ |
| if (keyType != null && keyType.getValueHandler() == null) { |
| Object kdDef = intr.findKeyDeserializer(a); |
| if (kdDef != null) { |
| KeyDeserializer kd = ctxt.keyDeserializerInstance(a, prop, kdDef); |
| if (kd != null) { |
| type = ((MapLikeType) type).withKeyValueHandler(kd); |
| keyType = type.getKeyType(); // just in case it's used below |
| } |
| } |
| } |
| |
| // and finally content class; only applicable to structured types |
| Class<?> cc = intr.findDeserializationContentType(a, type.getContentType(), |
| (prop == null) ? null : prop.getName()); |
| if (cc != null) { |
| try { |
| type = type.narrowContentsBy(cc); |
| } catch (IllegalArgumentException iae) { |
| throw new JsonMappingException("Failed to narrow content type "+type+" with content-type annotation ("+cc.getName()+"): "+iae.getMessage(), null, iae); |
| } |
| } |
| // ... as well as deserializer for contents: |
| JavaType contentType = type.getContentType(); |
| if (contentType.getValueHandler() == null) { // as with above, avoid resetting (which would trigger exception) |
| Object cdDef = intr.findContentDeserializer(a); |
| if (cdDef != null) { |
| JsonDeserializer<?> cd = null; |
| if (cdDef instanceof JsonDeserializer<?>) { |
| cdDef = (JsonDeserializer<?>) cdDef; |
| } else { |
| Class<?> cdClass = _verifyAsClass(cdDef, "findContentDeserializer", JsonDeserializer.None.class); |
| if (cdClass != null) { |
| cd = ctxt.deserializerInstance(a, prop, cdClass); |
| } |
| } |
| if (cd != null) { |
| type = type.withContentValueHandler(cd); |
| } |
| } |
| } |
| } |
| return type; |
| } |
| |
| private Class<?> _verifyAsClass(Object src, String methodName, Class<?> noneClass) |
| { |
| if (src == null) { |
| return null; |
| } |
| if (!(src instanceof Class)) { |
| throw new IllegalStateException("AnnotationIntrospector."+methodName+"() returned value of type "+src.getClass().getName()+": expected type JsonSerializer or Class<JsonSerializer> instead"); |
| } |
| Class<?> cls = (Class<?>) src; |
| if (cls == noneClass || cls == NoClass.class) { |
| return null; |
| } |
| return cls; |
| } |
| |
| /* |
| /********************************************************** |
| /* Overridable error reporting methods |
| /********************************************************** |
| */ |
| |
| protected JsonDeserializer<Object> _handleUnknownValueDeserializer(JavaType type) |
| throws JsonMappingException |
| { |
| /* Let's try to figure out the reason, to give better error |
| * messages |
| */ |
| Class<?> rawClass = type.getRawClass(); |
| if (!ClassUtil.isConcrete(rawClass)) { |
| throw new JsonMappingException("Can not find a Value deserializer for abstract type "+type); |
| } |
| throw new JsonMappingException("Can not find a Value deserializer for type "+type); |
| } |
| |
| protected KeyDeserializer _handleUnknownKeyDeserializer(JavaType type) |
| throws JsonMappingException |
| { |
| throw new JsonMappingException("Can not find a (Map) Key deserializer for type "+type); |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper classes |
| /********************************************************** |
| */ |
| |
| /** |
| * Simple deserializer that will call configured type deserializer, passing |
| * in configured data deserializer, and exposing it all as a simple |
| * deserializer. |
| */ |
| protected final static class WrappedDeserializer |
| extends JsonDeserializer<Object> |
| { |
| final TypeDeserializer _typeDeserializer; |
| final JsonDeserializer<Object> _deserializer; |
| |
| public WrappedDeserializer(TypeDeserializer typeDeser, JsonDeserializer<Object> deser) |
| { |
| super(); |
| _typeDeserializer = typeDeser; |
| _deserializer = deser; |
| } |
| |
| @Override |
| public Object deserialize(JsonParser jp, DeserializationContext ctxt) |
| throws IOException, JsonProcessingException |
| { |
| return _deserializer.deserializeWithType(jp, ctxt, _typeDeserializer); |
| } |
| |
| @Override |
| public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, |
| TypeDeserializer typeDeserializer) |
| throws IOException, JsonProcessingException |
| { |
| // should never happen? (if it can, could call on that object) |
| throw new IllegalStateException("Type-wrapped deserializer's deserializeWithType should never get called"); |
| } |
| } |
| } |