• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.ser;
2 
3 import com.fasterxml.jackson.annotation.JsonInclude;
4 import com.fasterxml.jackson.databind.*;
5 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
6 import com.fasterxml.jackson.databind.introspect.*;
7 import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
8 import com.fasterxml.jackson.databind.util.*;
9 
10 /**
11  * Helper class for {@link BeanSerializerFactory} that is used to
12  * construct {@link BeanPropertyWriter} instances. Can be sub-classed
13  * to change behavior.
14  */
15 public class PropertyBuilder
16 {
17     // @since 2.7
18     private final static Object NO_DEFAULT_MARKER = Boolean.FALSE;
19 
20     final protected SerializationConfig _config;
21     final protected BeanDescription _beanDesc;
22 
23     final protected AnnotationIntrospector _annotationIntrospector;
24 
25     /**
26      * If a property has serialization inclusion value of
27      * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT},
28      * we may need to know the default value of the bean, to know if property value
29      * equals default one.
30      *<p>
31      * NOTE: only used if enclosing class defines NON_DEFAULT, but NOT if it is the
32      * global default OR per-property override.
33      */
34     protected Object _defaultBean;
35 
36     /**
37      * Default inclusion mode for properties of the POJO for which
38      * properties are collected; possibly overridden on
39      * per-property basis. Combines global inclusion defaults and
40      * per-type (annotation and type-override) inclusion overrides.
41      */
42     final protected JsonInclude.Value _defaultInclusion;
43 
44     /**
45      * Marker flag used to indicate that "real" default values are to be used
46      * for properties, as per per-type value inclusion of type <code>NON_DEFAULT</code>
47      *
48      * @since 2.8
49      */
50     final protected boolean _useRealPropertyDefaults;
51 
PropertyBuilder(SerializationConfig config, BeanDescription beanDesc)52     public PropertyBuilder(SerializationConfig config, BeanDescription beanDesc)
53     {
54         _config = config;
55         _beanDesc = beanDesc;
56         // 08-Sep-2016, tatu: This gets tricky, with 3 levels of definitions:
57         //  (a) global default inclusion
58         //  (b) per-type default inclusion (from annotation or config overrides;
59         //     config override having precedence)
60         //  (c) per-property override (from annotation on specific property or
61         //     config overrides per type of property;
62         //     annotation having precedence)
63         //
64         //  and not only requiring merging, but also considering special handling
65         //  for NON_DEFAULT in case of (b) (vs (a) or (c))
66         JsonInclude.Value inclPerType = JsonInclude.Value.merge(
67                 beanDesc.findPropertyInclusion(JsonInclude.Value.empty()),
68                 config.getDefaultPropertyInclusion(beanDesc.getBeanClass(),
69                         JsonInclude.Value.empty()));
70         _defaultInclusion = JsonInclude.Value.merge(config.getDefaultPropertyInclusion(),
71                 inclPerType);
72         _useRealPropertyDefaults = inclPerType.getValueInclusion() == JsonInclude.Include.NON_DEFAULT;
73         _annotationIntrospector = _config.getAnnotationIntrospector();
74     }
75 
76     /*
77     /**********************************************************
78     /* Public API
79     /**********************************************************
80      */
81 
getClassAnnotations()82     public Annotations getClassAnnotations() {
83         return _beanDesc.getClassAnnotations();
84     }
85 
86     /**
87      * @param contentTypeSer Optional explicit type information serializer
88      *    to use for contained values (only used for properties that are
89      *    of container type)
90      */
buildWriter(SerializerProvider prov, BeanPropertyDefinition propDef, JavaType declaredType, JsonSerializer<?> ser, TypeSerializer typeSer, TypeSerializer contentTypeSer, AnnotatedMember am, boolean defaultUseStaticTyping)91     protected BeanPropertyWriter buildWriter(SerializerProvider prov,
92             BeanPropertyDefinition propDef, JavaType declaredType, JsonSerializer<?> ser,
93             TypeSerializer typeSer, TypeSerializer contentTypeSer,
94             AnnotatedMember am, boolean defaultUseStaticTyping)
95         throws JsonMappingException
96     {
97         // do we have annotation that forces type to use (to declared type or its super type)?
98         JavaType serializationType;
99         try {
100             serializationType = findSerializationType(am, defaultUseStaticTyping, declaredType);
101         } catch (JsonMappingException e) {
102             if (propDef == null) {
103                 return prov.reportBadDefinition(declaredType, ClassUtil.exceptionMessage(e));
104             }
105             return prov.reportBadPropertyDefinition(_beanDesc, propDef, ClassUtil.exceptionMessage(e));
106         }
107 
108         // Container types can have separate type serializers for content (value / element) type
109         if (contentTypeSer != null) {
110             // 04-Feb-2010, tatu: Let's force static typing for collection, if there is
111             //    type information for contents. Should work well (for JAXB case); can be
112             //    revisited if this causes problems.
113             if (serializationType == null) {
114 //                serializationType = TypeFactory.type(am.getGenericType(), _beanDesc.getType());
115                 serializationType = declaredType;
116             }
117             JavaType ct = serializationType.getContentType();
118             // Not exactly sure why, but this used to occur; better check explicitly:
119             if (ct == null) {
120                 prov.reportBadPropertyDefinition(_beanDesc, propDef,
121                         "serialization type "+serializationType+" has no content");
122             }
123             serializationType = serializationType.withContentTypeHandler(contentTypeSer);
124             ct = serializationType.getContentType();
125         }
126 
127         Object valueToSuppress = null;
128         boolean suppressNulls = false;
129 
130         // 12-Jul-2016, tatu: [databind#1256] Need to make sure we consider type refinement
131         JavaType actualType = (serializationType == null) ? declaredType : serializationType;
132 
133         // 17-Mar-2017: [databind#1522] Allow config override per property type
134         AnnotatedMember accessor = propDef.getAccessor();
135         if (accessor == null) {
136             // neither Setter nor ConstructorParameter are expected here
137             return prov.reportBadPropertyDefinition(_beanDesc, propDef,
138                     "could not determine property type");
139         }
140         Class<?> rawPropertyType = accessor.getRawType();
141 
142         // 17-Aug-2016, tatu: Default inclusion covers global default (for all types), as well
143         //   as type-default for enclosing POJO. What we need, then, is per-type default (if any)
144         //   for declared property type... and finally property annotation overrides
145         JsonInclude.Value inclV = _config.getDefaultInclusion(actualType.getRawClass(),
146                 rawPropertyType, _defaultInclusion);
147 
148         // property annotation override
149 
150         inclV = inclV.withOverrides(propDef.findInclusion());
151 
152         JsonInclude.Include inclusion = inclV.getValueInclusion();
153         if (inclusion == JsonInclude.Include.USE_DEFAULTS) { // should not occur but...
154             inclusion = JsonInclude.Include.ALWAYS;
155         }
156         switch (inclusion) {
157         case NON_DEFAULT:
158             // 11-Nov-2015, tatu: This is tricky because semantics differ between cases,
159             //    so that if enclosing class has this, we may need to access values of property,
160             //    whereas for global defaults OR per-property overrides, we have more
161             //    static definition. Sigh.
162             // First: case of class/type specifying it; try to find POJO property defaults
163             Object defaultBean;
164 
165             // 16-Oct-2016, tatu: Note: if we cannot for some reason create "default instance",
166             //    revert logic to the case of general/per-property handling, so both
167             //    type-default AND null are to be excluded.
168             //    (as per [databind#1417]
169             if (_useRealPropertyDefaults && (defaultBean = getDefaultBean()) != null) {
170                 // 07-Sep-2016, tatu: may also need to front-load access forcing now
171                 if (prov.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
172                     am.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
173                 }
174                 try {
175                     valueToSuppress = am.getValue(defaultBean);
176                 } catch (Exception e) {
177                     _throwWrapped(e, propDef.getName(), defaultBean);
178                 }
179             } else {
180                 valueToSuppress = BeanUtil.getDefaultValue(actualType);
181                 suppressNulls = true;
182             }
183             if (valueToSuppress == null) {
184                 suppressNulls = true;
185             } else {
186                 if (valueToSuppress.getClass().isArray()) {
187                     valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
188                 }
189             }
190             break;
191         case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals
192             // always suppress nulls
193             suppressNulls = true;
194             // and for referential types, also "empty", which in their case means "absent"
195             if (actualType.isReferenceType()) {
196                 valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
197             }
198             break;
199         case NON_EMPTY:
200             // always suppress nulls
201             suppressNulls = true;
202             // but possibly also 'empty' values:
203             valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
204             break;
205         case CUSTOM: // new with 2.9
206             valueToSuppress = prov.includeFilterInstance(propDef, inclV.getValueFilter());
207             if (valueToSuppress == null) { // is this legal?
208                 suppressNulls = true;
209             } else {
210                 suppressNulls = prov.includeFilterSuppressNulls(valueToSuppress);
211             }
212             break;
213         case NON_NULL:
214             suppressNulls = true;
215             // fall through
216         case ALWAYS: // default
217         default:
218             // we may still want to suppress empty collections
219             if (actualType.isContainerType()
220                     && !_config.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS)) {
221                 valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
222             }
223             break;
224         }
225         Class<?>[] views = propDef.findViews();
226         if (views == null) {
227             views = _beanDesc.findDefaultViews();
228         }
229         BeanPropertyWriter bpw = new BeanPropertyWriter(propDef,
230                 am, _beanDesc.getClassAnnotations(), declaredType,
231                 ser, typeSer, serializationType, suppressNulls, valueToSuppress, views);
232 
233         // How about custom null serializer?
234         Object serDef = _annotationIntrospector.findNullSerializer(am);
235         if (serDef != null) {
236             bpw.assignNullSerializer(prov.serializerInstance(am, serDef));
237         }
238         // And then, handling of unwrapping
239         NameTransformer unwrapper = _annotationIntrospector.findUnwrappingNameTransformer(am);
240         if (unwrapper != null) {
241             bpw = bpw.unwrappingWriter(unwrapper);
242         }
243         return bpw;
244     }
245 
246     /*
247     /**********************************************************
248     /* Helper methods; annotation access
249     /**********************************************************
250      */
251 
252     /**
253      * Method that will try to determine statically defined type of property
254      * being serialized, based on annotations (for overrides), and alternatively
255      * declared type (if static typing for serialization is enabled).
256      * If neither can be used (no annotations, dynamic typing), returns null.
257      */
findSerializationType(Annotated a, boolean useStaticTyping, JavaType declaredType)258     protected JavaType findSerializationType(Annotated a, boolean useStaticTyping, JavaType declaredType)
259         throws JsonMappingException
260     {
261         JavaType secondary = _annotationIntrospector.refineSerializationType(_config, a, declaredType);
262 
263         // 11-Oct-2015, tatu: As of 2.7, not 100% sure following checks are needed. But keeping
264         //    for now, just in case
265         if (secondary != declaredType) {
266             Class<?> serClass = secondary.getRawClass();
267             // Must be a super type to be usable
268             Class<?> rawDeclared = declaredType.getRawClass();
269             if (serClass.isAssignableFrom(rawDeclared)) {
270                 ; // fine as is
271             } else {
272                 /* 18-Nov-2010, tatu: Related to fixing [JACKSON-416], an issue with such
273                  *   check is that for deserialization more specific type makes sense;
274                  *   and for serialization more generic. But alas JAXB uses but a single
275                  *   annotation to do both... Hence, we must just discard type, as long as
276                  *   types are related
277                  */
278                 if (!rawDeclared.isAssignableFrom(serClass)) {
279                     throw new IllegalArgumentException("Illegal concrete-type annotation for method '"+a.getName()+"': class "+serClass.getName()+" not a super-type of (declared) class "+rawDeclared.getName());
280                 }
281                 /* 03-Dec-2010, tatu: Actually, ugh, we may need to further relax this
282                  *   and actually accept subtypes too for serialization. Bit dangerous in theory
283                  *   but need to trust user here...
284                  */
285             }
286             useStaticTyping = true;
287             declaredType = secondary;
288         }
289         // If using static typing, declared type is known to be the type...
290         JsonSerialize.Typing typing = _annotationIntrospector.findSerializationTyping(a);
291         if ((typing != null) && (typing != JsonSerialize.Typing.DEFAULT_TYPING)) {
292             useStaticTyping = (typing == JsonSerialize.Typing.STATIC);
293         }
294         if (useStaticTyping) {
295             // 11-Oct-2015, tatu: Make sure JavaType also "knows" static-ness...
296             return declaredType.withStaticTyping();
297 
298         }
299         return null;
300     }
301 
302     /*
303     /**********************************************************
304     /* Helper methods for default value handling
305     /**********************************************************
306      */
307 
getDefaultBean()308     protected Object getDefaultBean()
309     {
310         Object def = _defaultBean;
311         if (def == null) {
312             /* If we can fix access rights, we should; otherwise non-public
313              * classes or default constructor will prevent instantiation
314              */
315             def = _beanDesc.instantiateBean(_config.canOverrideAccessModifiers());
316             if (def == null) {
317                 // 06-Nov-2015, tatu: As per [databind#998], do not fail.
318                 /*
319                 Class<?> cls = _beanDesc.getClassInfo().getAnnotated();
320                 throw new IllegalArgumentException("Class "+cls.getName()+" has no default constructor; cannot instantiate default bean value to support 'properties=JsonSerialize.Inclusion.NON_DEFAULT' annotation");
321                  */
322 
323                 // And use a marker
324                 def = NO_DEFAULT_MARKER;
325             }
326             _defaultBean = def;
327         }
328         return (def == NO_DEFAULT_MARKER) ? null : _defaultBean;
329     }
330 
331     /**
332      * Accessor used to find out "default value" for given property, to use for
333      * comparing values to serialize, to determine whether to exclude value from serialization with
334      * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
335      * This method is called when we specifically want to know default value within context
336      * of a POJO, when annotation is within containing class, and not for property or
337      * defined as global baseline.
338      *<p>
339      * Note that returning of pseudo-type
340      * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_EMPTY} requires special handling.
341      *
342      * @since 2.7
343      * @deprecated Since 2.9 since this will not allow determining difference between "no default instance"
344      *    case and default being `null`.
345      */
346     @Deprecated // since 2.9
getPropertyDefaultValue(String name, AnnotatedMember member, JavaType type)347     protected Object getPropertyDefaultValue(String name, AnnotatedMember member,
348             JavaType type)
349     {
350         Object defaultBean = getDefaultBean();
351         if (defaultBean == null) {
352             return getDefaultValue(type);
353         }
354         try {
355             return member.getValue(defaultBean);
356         } catch (Exception e) {
357             return _throwWrapped(e, name, defaultBean);
358         }
359     }
360 
361     /**
362      * @deprecated Since 2.9
363      */
364     @Deprecated // since 2.9
getDefaultValue(JavaType type)365     protected Object getDefaultValue(JavaType type) {
366         return BeanUtil.getDefaultValue(type);
367     }
368 
369     /*
370     /**********************************************************
371     /* Helper methods for exception handling
372     /**********************************************************
373      */
374 
_throwWrapped(Exception e, String propName, Object defaultBean)375     protected Object _throwWrapped(Exception e, String propName, Object defaultBean)
376     {
377         Throwable t = e;
378         while (t.getCause() != null) {
379             t = t.getCause();
380         }
381         ClassUtil.throwIfError(t);
382         ClassUtil.throwIfRTE(t);
383         throw new IllegalArgumentException("Failed to get property '"+propName+"' of default "+defaultBean.getClass().getName()+" instance");
384     }
385 }
386