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