| package com.fasterxml.jackson.databind.ser; |
| |
| import java.util.*; |
| |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.cfg.MapperConfig; |
| import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig; |
| import com.fasterxml.jackson.databind.introspect.*; |
| import com.fasterxml.jackson.databind.jsontype.NamedType; |
| import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; |
| import com.fasterxml.jackson.databind.jsontype.TypeSerializer; |
| import com.fasterxml.jackson.databind.ser.impl.FilteredBeanPropertyWriter; |
| import com.fasterxml.jackson.databind.ser.std.MapSerializer; |
| import com.fasterxml.jackson.databind.type.*; |
| import com.fasterxml.jackson.databind.util.ArrayBuilders; |
| import com.fasterxml.jackson.databind.util.ClassUtil; |
| |
| /** |
| * Factory class that can provide serializers for any regular Java beans |
| * (as defined by "having at least one get method recognizable as bean |
| * accessor" -- where {@link Object#getClass} does not count); |
| * as well as for "standard" JDK types. Latter is achieved |
| * by delegating calls to {@link BasicSerializerFactory} |
| * to find serializers both for "standard" JDK types (and in some cases, |
| * sub-classes as is the case for collection classes like |
| * {@link java.util.List}s and {@link java.util.Map}s) and bean (value) |
| * classes. |
| *<p> |
| * Note about delegating calls to {@link BasicSerializerFactory}: |
| * although it would be nicer to use linear delegation |
| * for construction (to essentially dispatch all calls first to the |
| * underlying {@link BasicSerializerFactory}; or alternatively after |
| * failing to provide bean-based serializer}, there is a problem: |
| * priority levels for detecting standard types are mixed. That is, |
| * we want to check if a type is a bean after some of "standard" JDK |
| * types, but before the rest. |
| * As a result, "mixed" delegation used, and calls are NOT done using |
| * regular {@link SerializerFactory} interface but rather via |
| * direct calls to {@link BasicSerializerFactory}. |
| *<p> |
| * Finally, since all caching is handled by the serializer provider |
| * (not factory) and there is no configurability, this |
| * factory is stateless. |
| * This means that a global singleton instance can be used. |
| */ |
| public class BeanSerializerFactory |
| extends BasicSerializerFactory |
| { |
| /** |
| * Like {@link BasicSerializerFactory}, this factory is stateless, and |
| * thus a single shared global (== singleton) instance can be used |
| * without thread-safety issues. |
| */ |
| public final static BeanSerializerFactory instance = new BeanSerializerFactory(null); |
| |
| |
| /* |
| /********************************************************** |
| /* Life-cycle: creation, configuration |
| /********************************************************** |
| */ |
| |
| /** |
| * Constructor for creating instances with specified configuration. |
| */ |
| protected BeanSerializerFactory(SerializerFactoryConfig config) |
| { |
| super(config); |
| } |
| |
| /** |
| * Method used by module registration functionality, to attach additional |
| * serializer providers into this serializer factory. This is typically |
| * handled by constructing a new instance with additional serializers, |
| * to ensure thread-safe access. |
| */ |
| @Override |
| public SerializerFactory withConfig(SerializerFactoryConfig config) |
| { |
| if (_factoryConfig == config) { |
| return this; |
| } |
| /* 22-Nov-2010, tatu: Handling of subtypes is tricky if we do immutable-with-copy-ctor; |
| * and we pretty much have to here either choose between losing subtype instance |
| * when registering additional serializers, or losing serializers. |
| * Instead, let's actually just throw an error if this method is called when subtype |
| * has not properly overridden this method; this to indicate problem as soon as possible. |
| */ |
| if (getClass() != BeanSerializerFactory.class) { |
| throw new IllegalStateException("Subtype of BeanSerializerFactory ("+getClass().getName() |
| +") has not properly overridden method 'withAdditionalSerializers': can not instantiate subtype with " |
| +"additional serializer definitions"); |
| } |
| return new BeanSerializerFactory(config); |
| } |
| |
| @Override |
| protected Iterable<Serializers> customSerializers() { |
| return _factoryConfig.serializers(); |
| } |
| |
| /* |
| /********************************************************** |
| /* SerializerFactory impl |
| /********************************************************** |
| */ |
| |
| /** |
| * Main serializer constructor method. We will have to be careful |
| * with respect to ordering of various method calls: essentially |
| * we want to reliably figure out which classes are standard types, |
| * and which are beans. The problem is that some bean Classes may |
| * implement standard interfaces (say, {@link java.lang.Iterable}. |
| *<p> |
| * Note: sub-classes may choose to complete replace implementation, |
| * if they want to alter priority of serializer lookups. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public JsonSerializer<Object> createSerializer(SerializerProvider prov, |
| JavaType origType, |
| BeanProperty property) |
| throws JsonMappingException |
| { |
| // Very first thing, let's check if there is explicit serializer annotation: |
| final SerializationConfig config = prov.getConfig(); |
| BeanDescription beanDesc = config.introspect(origType); |
| JsonSerializer<?> ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo(), property); |
| if (ser != null) { |
| return (JsonSerializer<Object>) ser; |
| } |
| |
| // Next: we may have annotations that further define types to use... |
| JavaType type = modifyTypeByAnnotation(config, beanDesc.getClassInfo(), origType); |
| // and if so, we consider it implicit "force static typing" instruction |
| boolean staticTyping = (type != origType); |
| |
| // Container types differ from non-container types: |
| if (origType.isContainerType()) { |
| return (JsonSerializer<Object>) buildContainerSerializer(prov, type, beanDesc, property, staticTyping); |
| } |
| |
| // Modules may provide serializers of all types: |
| for (Serializers serializers : _factoryConfig.serializers()) { |
| ser = serializers.findSerializer(config, type, beanDesc); |
| if (ser != null) { |
| return (JsonSerializer<Object>) ser; |
| } |
| } |
| |
| /* Otherwise, we will check "primary types"; both marker types that |
| * indicate specific handling (JsonSerializable), or main types that have |
| * precedence over container types |
| */ |
| ser = findSerializerByLookup(type, config, beanDesc, property, staticTyping); |
| if (ser == null) { |
| ser = findSerializerByPrimaryType(prov, type, beanDesc, property, staticTyping); |
| if (ser == null) { |
| /* And this is where this class comes in: if type is not a |
| * known "primary JDK type", perhaps it's a bean? We can still |
| * get a null, if we can't find a single suitable bean property. |
| */ |
| ser = findBeanSerializer(prov, type, beanDesc, property); |
| /* Finally: maybe we can still deal with it as an |
| * implementation of some basic JDK interface? |
| */ |
| if (ser == null) { |
| ser = findSerializerByAddonType(config, type, beanDesc, property, staticTyping); |
| } |
| } |
| } |
| return (JsonSerializer<Object>) ser; |
| } |
| |
| /* |
| /********************************************************** |
| /* Other public methods that are not part of |
| /* JsonSerializerFactory API |
| /********************************************************** |
| */ |
| |
| /** |
| * Method that will try to construct a {@link BeanSerializer} for |
| * given class. Returns null if no properties are found. |
| */ |
| @SuppressWarnings("unchecked") |
| public JsonSerializer<Object> findBeanSerializer(SerializerProvider prov, |
| JavaType type, BeanDescription beanDesc, BeanProperty property) |
| throws JsonMappingException |
| { |
| // First things first: we know some types are not beans... |
| if (!isPotentialBeanType(type.getRawClass())) { |
| return null; |
| } |
| JsonSerializer<Object> serializer = constructBeanSerializer(prov, beanDesc, property); |
| // [JACKSON-440] Need to allow overriding actual serializer, as well... |
| if (_factoryConfig.hasSerializerModifiers()) { |
| for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) { |
| serializer = (JsonSerializer<Object>)mod.modifySerializer(prov.getConfig(), |
| beanDesc, serializer); |
| } |
| } |
| return serializer; |
| } |
| |
| /** |
| * Method called to create a type information serializer for values of given |
| * non-container property |
| * if one is needed. If not needed (no polymorphic handling configured), should |
| * return null. |
| * |
| * @param baseType Declared type to use as the base type for type information serializer |
| * |
| * @return Type serializer to use for property values, if one is needed; null if not. |
| */ |
| public TypeSerializer findPropertyTypeSerializer(JavaType baseType, SerializationConfig config, |
| AnnotatedMember accessor, BeanProperty property) |
| throws JsonMappingException |
| { |
| AnnotationIntrospector ai = config.getAnnotationIntrospector(); |
| TypeResolverBuilder<?> b = ai.findPropertyTypeResolver(config, accessor, baseType); |
| // Defaulting: if no annotations on member, check value class |
| if (b == null) { |
| return createTypeSerializer(config, baseType); |
| } |
| Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypes(accessor, config, ai); |
| return b.buildTypeSerializer(config, baseType, subtypes); |
| } |
| |
| /** |
| * Method called to create a type information serializer for values of given |
| * container property |
| * if one is needed. If not needed (no polymorphic handling configured), should |
| * return null. |
| * |
| * @param containerType Declared type of the container to use as the base type for type information serializer |
| * |
| * @return Type serializer to use for property value contents, if one is needed; null if not. |
| */ |
| public TypeSerializer findPropertyContentTypeSerializer(JavaType containerType, |
| SerializationConfig config, AnnotatedMember accessor) |
| throws JsonMappingException |
| { |
| JavaType contentType = containerType.getContentType(); |
| AnnotationIntrospector ai = config.getAnnotationIntrospector(); |
| TypeResolverBuilder<?> b = ai.findPropertyContentTypeResolver(config, accessor, containerType); |
| // Defaulting: if no annotations on member, check value class |
| if (b == null) { |
| return createTypeSerializer(config, contentType); |
| } |
| Collection<NamedType> subtypes = config.getSubtypeResolver().collectAndResolveSubtypes(accessor, config, ai); |
| return b.buildTypeSerializer(config, contentType, subtypes); |
| } |
| |
| /* |
| /********************************************************** |
| /* Overridable non-public factory methods |
| /********************************************************** |
| */ |
| |
| /** |
| * Method called to construct serializer for serializing specified bean type. |
| */ |
| @SuppressWarnings("unchecked") |
| protected JsonSerializer<Object> constructBeanSerializer(SerializerProvider prov, |
| BeanDescription beanDesc, BeanProperty property) |
| throws JsonMappingException |
| { |
| // 13-Oct-2010, tatu: quick sanity check: never try to create bean serializer for plain Object |
| if (beanDesc.getBeanClass() == Object.class) { |
| throw new IllegalArgumentException("Can not create bean serializer for Object.class"); |
| } |
| |
| final SerializationConfig config = prov.getConfig(); |
| BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc); |
| |
| // First: any detectable (auto-detect, annotations) properties to serialize? |
| List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc); |
| |
| if (props == null) { |
| props = new ArrayList<BeanPropertyWriter>(); |
| } |
| // [JACKSON-440] Need to allow modification bean properties to serialize: |
| if (_factoryConfig.hasSerializerModifiers()) { |
| for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) { |
| props = mod.changeProperties(config, beanDesc, props); |
| } |
| } |
| |
| // Any properties to suppress? |
| props = filterBeanProperties(config, beanDesc, props); |
| |
| // [JACKSON-440] Need to allow reordering of properties to serialize |
| if (_factoryConfig.hasSerializerModifiers()) { |
| for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) { |
| props = mod.orderProperties(config, beanDesc, props); |
| } |
| } |
| |
| builder.setProperties(props); |
| builder.setFilterId(findFilterId(config, beanDesc)); |
| |
| AnnotatedMember anyGetter = beanDesc.findAnyGetter(); |
| if (anyGetter != null) { // since 1.6 |
| if (config.canOverrideAccessModifiers()) { |
| anyGetter.fixAccess(); |
| } |
| JavaType type = anyGetter.getType(beanDesc.bindingsForBeanType()); |
| // copied from BasicSerializerFactory.buildMapSerializer(): |
| boolean staticTyping = config.isEnabled(MapperConfig.Feature.USE_STATIC_TYPING); |
| JavaType valueType = type.getContentType(); |
| TypeSerializer typeSer = createTypeSerializer(config, valueType); |
| // last 2 nulls; don't know key, value serializers (yet) |
| MapSerializer mapSer = MapSerializer.construct(/* ignored props*/ null, type, staticTyping, |
| typeSer, null, null); |
| BeanProperty.Std anyProp = new BeanProperty.Std(anyGetter.getName(), valueType, |
| beanDesc.getClassAnnotations(), anyGetter); |
| builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, mapSer)); |
| } |
| // One more thing: need to gather view information, if any: |
| processViews(config, builder); |
| // And maybe let interested parties mess with the result bit more... |
| if (_factoryConfig.hasSerializerModifiers()) { |
| for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) { |
| builder = mod.updateBuilder(config, beanDesc, builder); |
| } |
| } |
| JsonSerializer<Object> ser = (JsonSerializer<Object>) builder.build(); |
| |
| /* However, after all modifications: no properties, no serializer |
| * (note; as per [JACKSON-670], check was moved later on from an earlier location) |
| */ |
| if (ser == null) { |
| /* 27-Nov-2009, tatu: Except that as per [JACKSON-201], we are |
| * ok with that as long as it has a recognized class annotation |
| * (which may come from a mix-in too) |
| */ |
| if (beanDesc.hasKnownClassAnnotations()) { |
| return builder.createDummy(); |
| } |
| } |
| return ser; |
| } |
| |
| /** |
| * Method called to construct a filtered writer, for given view |
| * definitions. Default implementation constructs filter that checks |
| * active view type to views property is to be included in. |
| */ |
| protected BeanPropertyWriter constructFilteredBeanWriter(BeanPropertyWriter writer, |
| Class<?>[] inViews) |
| { |
| return FilteredBeanPropertyWriter.constructViewBased(writer, inViews); |
| } |
| |
| protected PropertyBuilder constructPropertyBuilder(SerializationConfig config, |
| BeanDescription beanDesc) |
| { |
| return new PropertyBuilder(config, beanDesc); |
| } |
| |
| protected BeanSerializerBuilder constructBeanSerializerBuilder(BeanDescription beanDesc) { |
| return new BeanSerializerBuilder(beanDesc); |
| } |
| |
| /** |
| * Method called to find filter that is configured to be used with bean |
| * serializer being built, if any. |
| */ |
| protected Object findFilterId(SerializationConfig config, BeanDescription beanDesc) |
| { |
| return config.getAnnotationIntrospector().findFilterId(beanDesc.getClassInfo()); |
| } |
| |
| /* |
| /********************************************************** |
| /* Overridable non-public introspection methods |
| /********************************************************** |
| */ |
| |
| /** |
| * Helper method used to skip processing for types that we know |
| * can not be (i.e. are never consider to be) beans: |
| * things like primitives, Arrays, Enums, and proxy types. |
| *<p> |
| * Note that usually we shouldn't really be getting these sort of |
| * types anyway; but better safe than sorry. |
| */ |
| protected boolean isPotentialBeanType(Class<?> type) |
| { |
| return (ClassUtil.canBeABeanType(type) == null) && !ClassUtil.isProxyType(type); |
| } |
| |
| /** |
| * Method used to collect all actual serializable properties. |
| * Can be overridden to implement custom detection schemes. |
| */ |
| protected List<BeanPropertyWriter> findBeanProperties(SerializerProvider prov, |
| BeanDescription beanDesc) |
| throws JsonMappingException |
| { |
| List<BeanPropertyDefinition> properties = beanDesc.findProperties(); |
| final SerializationConfig config = prov.getConfig(); |
| |
| // [JACKSON-429]: ignore specified types |
| removeIgnorableTypes(config, beanDesc, properties); |
| |
| // and possibly remove ones without matching mutator... |
| if (config.isEnabled(MapperConfig.Feature.REQUIRE_SETTERS_FOR_GETTERS)) { |
| removeSetterlessGetters(config, beanDesc, properties); |
| } |
| |
| // nothing? can't proceed (caller may or may not throw an exception) |
| if (properties.isEmpty()) { |
| return null; |
| } |
| // null is for value type serializer, which we don't have access to from here (ditto for bean prop) |
| boolean staticTyping = usesStaticTyping(config, beanDesc, null, null); |
| PropertyBuilder pb = constructPropertyBuilder(config, beanDesc); |
| |
| ArrayList<BeanPropertyWriter> result = new ArrayList<BeanPropertyWriter>(properties.size()); |
| TypeBindings typeBind = beanDesc.bindingsForBeanType(); |
| // [JACKSON-98]: start with field properties, if any |
| for (BeanPropertyDefinition property : properties) { |
| AnnotatedMember accessor = property.getAccessor(); |
| // [JACKSON-235]: suppress writing of back references |
| AnnotationIntrospector.ReferenceProperty refType = property.findReferenceType(); |
| if (refType != null && refType.isBackReference()) { |
| continue; |
| } |
| if (accessor instanceof AnnotatedMethod) { |
| result.add(_constructWriter(property, prov, typeBind, pb, staticTyping, (AnnotatedMethod) accessor)); |
| } else { |
| result.add(_constructWriter(property, prov, typeBind, pb, staticTyping, (AnnotatedField) accessor)); |
| } |
| } |
| return result; |
| } |
| |
| /* |
| /********************************************************** |
| /* Overridable non-public methods for manipulating bean properties |
| /********************************************************** |
| */ |
| |
| /** |
| * Overridable method that can filter out properties. Default implementation |
| * checks annotations class may have. |
| */ |
| protected List<BeanPropertyWriter> filterBeanProperties(SerializationConfig config, |
| BeanDescription beanDesc, List<BeanPropertyWriter> props) |
| { |
| AnnotationIntrospector intr = config.getAnnotationIntrospector(); |
| AnnotatedClass ac = beanDesc.getClassInfo(); |
| String[] ignored = intr.findPropertiesToIgnore(ac); |
| if (ignored != null && ignored.length > 0) { |
| HashSet<String> ignoredSet = ArrayBuilders.arrayToSet(ignored); |
| Iterator<BeanPropertyWriter> it = props.iterator(); |
| while (it.hasNext()) { |
| if (ignoredSet.contains(it.next().getName())) { |
| it.remove(); |
| } |
| } |
| } |
| return props; |
| } |
| |
| /** |
| * Method called to handle view information for constructed serializer, |
| * based on bean property writers. |
| *<p> |
| * Note that this method is designed to be overridden by sub-classes |
| * if they want to provide custom view handling. As such it is not |
| * considered an internal implementation detail, and will be supported |
| * as part of API going forward. |
| */ |
| protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) |
| { |
| // [JACKSON-232]: whether non-annotated fields are included by default or not is configurable |
| List<BeanPropertyWriter> props = builder.getProperties(); |
| boolean includeByDefault = config.isEnabled(MapperConfig.Feature.DEFAULT_VIEW_INCLUSION); |
| final int propCount = props.size(); |
| int viewsFound = 0; |
| BeanPropertyWriter[] filtered = new BeanPropertyWriter[propCount]; |
| // Simple: view information is stored within individual writers, need to combine: |
| for (int i = 0; i < propCount; ++i) { |
| BeanPropertyWriter bpw = props.get(i); |
| Class<?>[] views = bpw.getViews(); |
| if (views == null) { // no view info? include or exclude by default? |
| if (includeByDefault) { |
| filtered[i] = bpw; |
| } |
| } else { |
| ++viewsFound; |
| filtered[i] = constructFilteredBeanWriter(bpw, views); |
| } |
| } |
| // minor optimization: if no view info, include-by-default, can leave out filtering info altogether: |
| if (includeByDefault && viewsFound == 0) { |
| return; |
| } |
| builder.setFilteredProperties(filtered); |
| } |
| |
| /** |
| * Method that will apply by-type limitations (as per [JACKSON-429]); |
| * by default this is based on {@link com.fasterxml.jackson.annotation.JsonIgnoreType} annotation but |
| * can be supplied by module-provided introspectors too. |
| */ |
| protected void removeIgnorableTypes(SerializationConfig config, BeanDescription beanDesc, |
| List<BeanPropertyDefinition> properties) |
| { |
| AnnotationIntrospector intr = config.getAnnotationIntrospector(); |
| HashMap<Class<?>,Boolean> ignores = new HashMap<Class<?>,Boolean>(); |
| Iterator<BeanPropertyDefinition> it = properties.iterator(); |
| while (it.hasNext()) { |
| BeanPropertyDefinition property = it.next(); |
| AnnotatedMember accessor = property.getAccessor(); |
| if (accessor == null) { |
| it.remove(); |
| continue; |
| } |
| Class<?> type = accessor.getRawType(); |
| Boolean result = ignores.get(type); |
| if (result == null) { |
| BeanDescription desc = config.introspectClassAnnotations(type); |
| AnnotatedClass ac = desc.getClassInfo(); |
| result = intr.isIgnorableType(ac); |
| // default to false, non-ignorable |
| if (result == null) { |
| result = Boolean.FALSE; |
| } |
| ignores.put(type, result); |
| } |
| // lotsa work, and yes, it is ignorable type, so: |
| if (result.booleanValue()) { |
| it.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Helper method that will remove all properties that do not have a mutator. |
| */ |
| protected void removeSetterlessGetters(SerializationConfig config, BeanDescription beanDesc, |
| List<BeanPropertyDefinition> properties) |
| { |
| Iterator<BeanPropertyDefinition> it = properties.iterator(); |
| while (it.hasNext()) { |
| BeanPropertyDefinition property = it.next(); |
| if (!property.couldDeserialize()) { |
| it.remove(); |
| } |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Internal helper methods |
| /********************************************************** |
| */ |
| |
| /** |
| * Secondary helper method for constructing {@link BeanPropertyWriter} for |
| * given member (field or method). |
| */ |
| protected BeanPropertyWriter _constructWriter(BeanPropertyDefinition propDef, |
| SerializerProvider prov, TypeBindings typeContext, |
| PropertyBuilder pb, boolean staticTyping, AnnotatedMember accessor) |
| throws JsonMappingException |
| { |
| final String name = propDef.getName(); |
| if (prov.canOverrideAccessModifiers()) { |
| accessor.fixAccess(); |
| } |
| JavaType type = accessor.getType(typeContext); |
| BeanProperty.Std property = new BeanProperty.Std(name, type, pb.getClassAnnotations(), accessor); |
| |
| // Does member specify a serializer? If so, let's use it. |
| JsonSerializer<Object> annotatedSerializer = findSerializerFromAnnotation(prov, accessor, property); |
| // And how about polymorphic typing? First special to cover JAXB per-field settings: |
| TypeSerializer contentTypeSer = null; |
| if (ClassUtil.isCollectionMapOrArray(type.getRawClass())) { |
| contentTypeSer = findPropertyContentTypeSerializer(type, prov.getConfig(), accessor); |
| } |
| |
| // and if not JAXB collection/array with annotations, maybe regular type info? |
| TypeSerializer typeSer = findPropertyTypeSerializer(type, prov.getConfig(), accessor, property); |
| BeanPropertyWriter pbw = pb.buildWriter(propDef, type, annotatedSerializer, |
| typeSer, contentTypeSer, accessor, staticTyping); |
| return pbw; |
| } |
| } |