blob: de766fafa75e2e607a2b3782ed06f8e9ee32ed7e [file] [log] [blame]
package com.fasterxml.jackson.databind.ser;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.databind.jsonschema.SchemaAware;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
import com.fasterxml.jackson.databind.util.Annotations;
import com.fasterxml.jackson.databind.util.NameTransformer;
/**
* Base bean property handler class, which implements common parts of
* reflection-based functionality for accessing a property value
* and serializing it.
*<p>
* Note that current design tries to keep instances immutable (semi-functional
* style); mostly because these instances are exposed to application
* code and this is to reduce likelihood of data corruption and
* synchronization issues.
*/
public class BeanPropertyWriter extends PropertyWriter
implements BeanProperty
{
/**
* Marker object used to indicate "do not serialize if empty"
*/
public final static Object MARKER_FOR_EMPTY = new Object();
/*
/**********************************************************
/* Settings for accessing property value to serialize
/**********************************************************
*/
/**
* Member (field, method) that represents property and allows access
* to associated annotations.
*/
protected final AnnotatedMember _member;
/**
* Annotations from context (most often, class that declares property,
* or in case of sub-class serializer, from that sub-class)
*/
protected final Annotations _contextAnnotations;
/**
* Type property is declared to have, either in class definition
* or associated annotations.
*/
protected final JavaType _declaredType;
/**
* Accessor method used to get property value, for
* method-accessible properties.
* Null if and only if {@link #_field} is null.
*/
protected final Method _accessorMethod;
/**
* Field that contains the property value for field-accessible
* properties.
* Null if and only if {@link #_accessorMethod} is null.
*/
protected final Field _field;
/*
/**********************************************************
/* Opaque internal data that bean serializer factory and
/* bean serializers can add.
/**********************************************************
*/
protected HashMap<Object,Object> _internalSettings;
/*
/**********************************************************
/* Serialization settings
/**********************************************************
*/
/**
* Logical name of the property; will be used as the field name
* under which value for the property is written.
*/
protected final SerializableString _name;
/**
* Wrapper name to use for this element, if any
*
* @since 2.2
*/
protected final PropertyName _wrapperName;
/**
* Type to use for locating serializer; normally same as return
* type of the accessor method, but may be overridden by annotations.
*/
protected final JavaType _cfgSerializationType;
/**
* Serializer to use for writing out the value: null if it can not
* be known statically; non-null if it can.
*/
protected JsonSerializer<Object> _serializer;
/**
* Serializer used for writing out null values, if any: if null,
* null values are to be suppressed.
*/
protected JsonSerializer<Object> _nullSerializer;
/**
* In case serializer is not known statically (i.e. <code>_serializer</code>
* is null), we will use a lookup structure for storing dynamically
* resolved mapping from type(s) to serializer(s).
*/
protected transient PropertySerializerMap _dynamicSerializers;
/**
* Whether null values are to be suppressed (nothing written out if
* value is null) or not.
*/
protected final boolean _suppressNulls;
/**
* Value that is considered default value of the property; used for
* default-value-suppression if enabled.
*/
protected final Object _suppressableValue;
/**
* Alternate set of property writers used when view-based filtering
* is available for the Bean.
*/
protected final Class<?>[] _includeInViews;
/**
* If property being serialized needs type information to be
* included this is the type serializer to use.
* Declared type (possibly augmented with annotations) of property
* is used for determining exact mechanism to use (compared to
* actual runtime type used for serializing actual state).
*/
protected TypeSerializer _typeSerializer;
/**
* Base type of the property, if the declared type is "non-trivial";
* meaning it is either a structured type (collection, map, array),
* or parameterized. Used to retain type information about contained
* type, which is mostly necessary if type meta-data is to be
* included.
*/
protected JavaType _nonTrivialBaseType;
/**
* Additional information about property
*
* @since 2.3
*/
protected final PropertyMetadata _metadata;
/*
/**********************************************************
/* Construction, configuration
/**********************************************************
*/
@SuppressWarnings("unchecked")
public BeanPropertyWriter(BeanPropertyDefinition propDef,
AnnotatedMember member, Annotations contextAnnotations,
JavaType declaredType,
JsonSerializer<?> ser, TypeSerializer typeSer, JavaType serType,
boolean suppressNulls, Object suppressableValue)
{
_member = member;
_contextAnnotations = contextAnnotations;
_name = new SerializedString(propDef.getName());
_wrapperName = propDef.getWrapperName();
_declaredType = declaredType;
_serializer = (JsonSerializer<Object>) ser;
_dynamicSerializers = (ser == null) ? PropertySerializerMap.emptyMap() : null;
_typeSerializer = typeSer;
_cfgSerializationType = serType;
_metadata = propDef.getMetadata();
if (member instanceof AnnotatedField) {
_accessorMethod = null;
_field = (Field) member.getMember();
} else if (member instanceof AnnotatedMethod) {
_accessorMethod = (Method) member.getMember();
_field = null;
} else {
throw new IllegalArgumentException("Can not pass member of type "+member.getClass().getName());
}
_suppressNulls = suppressNulls;
_suppressableValue = suppressableValue;
_includeInViews = propDef.findViews();
// this will be resolved later on, unless nulls are to be suppressed
_nullSerializer = null;
}
/**
* "Copy constructor" to be used by filtering sub-classes
*/
protected BeanPropertyWriter(BeanPropertyWriter base) {
this(base, base._name);
}
protected BeanPropertyWriter(BeanPropertyWriter base, SerializableString name) {
_name = name;
_wrapperName = base._wrapperName;
_member = base._member;
_contextAnnotations = base._contextAnnotations;
_declaredType = base._declaredType;
_accessorMethod = base._accessorMethod;
_field = base._field;
_serializer = base._serializer;
_nullSerializer = base._nullSerializer;
// one more thing: copy internal settings, if any (since 1.7)
if (base._internalSettings != null) {
_internalSettings = new HashMap<Object,Object>(base._internalSettings);
}
_cfgSerializationType = base._cfgSerializationType;
_dynamicSerializers = base._dynamicSerializers;
_suppressNulls = base._suppressNulls;
_suppressableValue = base._suppressableValue;
_includeInViews = base._includeInViews;
_typeSerializer = base._typeSerializer;
_nonTrivialBaseType = base._nonTrivialBaseType;
_metadata = base._metadata;
}
public BeanPropertyWriter rename(NameTransformer transformer) {
String newName = transformer.transform(_name.getValue());
if (newName.equals(_name.toString())) {
return this;
}
return new BeanPropertyWriter(this, new SerializedString(newName));
}
/**
* Method called to assign value serializer for property
*
* @since 2.0
*/
public void assignSerializer(JsonSerializer<Object> ser) {
// may need to disable check in future?
if (_serializer != null && _serializer != ser) {
throw new IllegalStateException("Can not override serializer");
}
_serializer = ser;
}
/**
* Method called to assign null value serializer for property
*
* @since 2.0
*/
public void assignNullSerializer(JsonSerializer<Object> nullSer) {
// may need to disable check in future?
if (_nullSerializer != null && _nullSerializer != nullSer) {
throw new IllegalStateException("Can not override null serializer");
}
_nullSerializer = nullSer;
}
/**
* Method called create an instance that handles details of unwrapping
* contained value.
*/
public BeanPropertyWriter unwrappingWriter(NameTransformer unwrapper) {
return new UnwrappingBeanPropertyWriter(this, unwrapper);
}
/**
* Method called to define type to consider as "non-trivial" basetype,
* needed for dynamic serialization resolution for complex (usually container)
* types
*/
public void setNonTrivialBaseType(JavaType t) {
_nonTrivialBaseType = t;
}
/*
/**********************************************************
/* BeanProperty impl
/**********************************************************
*/
// Note: also part of 'PropertyWriter'
@Override public String getName() { return _name.getValue(); }
// Note: also part of 'PropertyWriter'
@Override public PropertyName getFullName() { // !!! TODO: impl properly
return new PropertyName(_name.getValue());
}
@Override public JavaType getType() { return _declaredType; }
@Override public PropertyName getWrapperName() { return _wrapperName; }
@Override public boolean isRequired() { return _metadata.isRequired(); }
@Override public PropertyMetadata getMetadata() { return _metadata; }
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
return _member.getAnnotation(acls);
}
@Override
public <A extends Annotation> A getContextAnnotation(Class<A> acls) {
return _contextAnnotations.get(acls);
}
@Override public AnnotatedMember getMember() { return _member; }
// @since 2.3 -- needed so it can be overridden by unwrapping writer
protected void _depositSchemaProperty(ObjectNode propertiesNode, JsonNode schemaNode) {
propertiesNode.set(getName(), schemaNode);
}
/*
/**********************************************************
/* Managing and accessing of opaque internal settings
/* (used by extensions)
/**********************************************************
*/
/**
* Method for accessing value of specified internal setting.
*
* @return Value of the setting, if any; null if none.
*/
public Object getInternalSetting(Object key) {
return (_internalSettings == null) ? null : _internalSettings.get(key);
}
/**
* Method for setting specific internal setting to given value
*
* @return Old value of the setting, if any (null if none)
*/
public Object setInternalSetting(Object key, Object value) {
if (_internalSettings == null) {
_internalSettings = new HashMap<Object,Object>();
}
return _internalSettings.put(key, value);
}
/**
* Method for removing entry for specified internal setting.
*
* @return Existing value of the setting, if any (null if none)
*/
public Object removeInternalSetting(Object key) {
Object removed = null;
if (_internalSettings != null) {
removed = _internalSettings.remove(key);
// to reduce memory usage, let's also drop the Map itself, if empty
if (_internalSettings.size() == 0) {
_internalSettings = null;
}
}
return removed;
}
/*
/**********************************************************
/* Accessors
/**********************************************************
*/
public SerializableString getSerializedName() { return _name; }
public boolean hasSerializer() { return _serializer != null; }
public boolean hasNullSerializer() { return _nullSerializer != null; }
/**
* Accessor that will return true if this bean property has to support
* "unwrapping"; ability to replace POJO structural wrapping with optional
* name prefix and/or suffix (or in some cases, just removal of wrapper name).
*<p>
* Default implementation simply returns false.
*
* @since 2.3
*/
public boolean isUnwrapping() { return false; }
public boolean willSuppressNulls() { return _suppressNulls; }
// Needed by BeanSerializer#getSchema
public JsonSerializer<Object> getSerializer() { return _serializer; }
public JavaType getSerializationType() { return _cfgSerializationType; }
public Class<?> getRawSerializationType() {
return (_cfgSerializationType == null) ? null : _cfgSerializationType.getRawClass();
}
public Class<?> getPropertyType() {
return (_accessorMethod != null) ? _accessorMethod.getReturnType() : _field.getType();
}
/**
* Get the generic property type of this property writer.
*
* @return The property type, or null if not found.
*/
public Type getGenericPropertyType() {
if (_accessorMethod != null) {
return _accessorMethod.getGenericReturnType();
}
return _field.getGenericType();
}
public Class<?>[] getViews() { return _includeInViews; }
/**
*<p>
* NOTE: due to introspection, this is a <b>slow</b> method to call
* and should never be called during actual serialization or filtering
* of the property. Rather it is needed for traversal needed for things
* like constructing JSON Schema instances.
*
* @since 2.1
*
* @deprecated since 2.2, use {@link #isRequired()} instead.
*/
@Deprecated
protected boolean isRequired(AnnotationIntrospector intr) { return _metadata.isRequired(); }
/*
/**********************************************************
/* PropertyWriter methods (serialization)
/**********************************************************
*/
/**
* Method called to access property that this bean stands for, from
* within given bean, and to serialize it as a JSON Object field
* using appropriate serializer.
*/
@Override
public void serializeAsField(Object bean, JsonGenerator jgen, SerializerProvider prov) throws Exception
{
Object value = get(bean);
// Null handling is bit different, check that first
if (value == null) {
if (_nullSerializer != null) {
jgen.writeFieldName(_name);
_nullSerializer.serialize(null, jgen, prov);
}
return;
}
// then find serializer to use
JsonSerializer<Object> ser = _serializer;
if (ser == null) {
Class<?> cls = value.getClass();
PropertySerializerMap m = _dynamicSerializers;
ser = m.serializerFor(cls);
if (ser == null) {
ser = _findAndAddDynamic(m, cls, prov);
}
}
// and then see if we must suppress certain values (default, empty)
if (_suppressableValue != null) {
if (MARKER_FOR_EMPTY == _suppressableValue) {
if (ser.isEmpty(value)) {
return;
}
} else if (_suppressableValue.equals(value)) {
return;
}
}
// For non-nulls: simple check for direct cycles
if (value == bean) {
// three choices: exception; handled by call; or pass-through
if (_handleSelfReference(bean, jgen, prov, ser)) {
return;
}
}
jgen.writeFieldName(_name);
if (_typeSerializer == null) {
ser.serialize(value, jgen, prov);
} else {
ser.serializeWithType(value, jgen, prov, _typeSerializer);
}
}
/**
* Method called to indicate that serialization of a field was omitted
* due to filtering, in cases where backend data format does not allow
* basic omission.
*
* @since 2.3
*/
@Override
public void serializeAsOmittedField(Object bean, JsonGenerator jgen, SerializerProvider prov) throws Exception
{
if (!jgen.canOmitFields()) {
jgen.writeOmittedField(_name.getValue());
}
}
/**
* Alternative to {@link #serializeAsField} that is used when a POJO
* is serialized as JSON Array; the difference is that no field names
* are written.
*
* @since 2.3
*/
@Override
public void serializeAsElement(Object bean, JsonGenerator jgen, SerializerProvider prov)
throws Exception
{
Object value = get(bean);
if (value == null) { // nulls need specialized handling
if (_nullSerializer != null) {
_nullSerializer.serialize(null, jgen, prov);
} else { // can NOT suppress entries in tabular output
jgen.writeNull();
}
return;
}
// otherwise find serializer to use
JsonSerializer<Object> ser = _serializer;
if (ser == null) {
Class<?> cls = value.getClass();
PropertySerializerMap map = _dynamicSerializers;
ser = map.serializerFor(cls);
if (ser == null) {
ser = _findAndAddDynamic(map, cls, prov);
}
}
// and then see if we must suppress certain values (default, empty)
if (_suppressableValue != null) {
if (MARKER_FOR_EMPTY == _suppressableValue) {
if (ser.isEmpty(value)) { // can NOT suppress entries in tabular output
serializeAsPlaceholder(bean, jgen, prov);
return;
}
} else if (_suppressableValue.equals(value)) { // can NOT suppress entries in tabular output
serializeAsPlaceholder(bean, jgen, prov);
return;
}
}
// For non-nulls: simple check for direct cycles
if (value == bean) {
if (_handleSelfReference(bean, jgen, prov, ser)) {
return;
}
}
if (_typeSerializer == null) {
ser.serialize(value, jgen, prov);
} else {
ser.serializeWithType(value, jgen, prov, _typeSerializer);
}
}
/**
* Method called to serialize a placeholder used in tabular output when
* real value is not to be included (is filtered out), but when we need
* an entry so that field indexes will not be off. Typically this should
* output null or empty String, depending on datatype.
*
* @since 2.1
*/
@Override
public void serializeAsPlaceholder(Object bean, JsonGenerator jgen, SerializerProvider prov)
throws Exception
{
if (_nullSerializer != null) {
_nullSerializer.serialize(null, jgen, prov);
} else {
jgen.writeNull();
}
}
/*
/**********************************************************
/* PropertyWriter methods (schema generation)
/**********************************************************
*/
// Also part of BeanProperty implementation
@Override
public void depositSchemaProperty(JsonObjectFormatVisitor objectVisitor)
throws JsonMappingException
{
if (objectVisitor != null) {
if (isRequired()) {
objectVisitor.property(this);
} else {
objectVisitor.optionalProperty(this);
}
}
}
// // // Legacy support for JsonFormatVisitable
/**
* Attempt to add the output of the given {@link BeanPropertyWriter} in the given {@link ObjectNode}.
* Otherwise, add the default schema {@link JsonNode} in place of the writer's output
*
* @param propertiesNode Node which the given property would exist within
* @param provider Provider that can be used for accessing dynamic aspects of serialization
* processing
*/
@Override
@Deprecated
public void depositSchemaProperty(ObjectNode propertiesNode, SerializerProvider provider)
throws JsonMappingException
{
JavaType propType = getSerializationType();
// 03-Dec-2010, tatu: SchemaAware REALLY should use JavaType, but alas it doesn't...
Type hint = (propType == null) ? getGenericPropertyType() : propType.getRawClass();
JsonNode schemaNode;
// Maybe it already has annotated/statically configured serializer?
JsonSerializer<Object> ser = getSerializer();
if (ser == null) { // nope
Class<?> serType = getRawSerializationType();
if (serType == null) {
serType = getPropertyType();
}
ser = provider.findValueSerializer(serType, this);
}
boolean isOptional = !isRequired();
if (ser instanceof SchemaAware) {
schemaNode = ((SchemaAware) ser).getSchema(provider, hint, isOptional) ;
} else {
schemaNode = com.fasterxml.jackson.databind.jsonschema.JsonSchema.getDefaultSchemaNode();
}
_depositSchemaProperty(propertiesNode, schemaNode);
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
protected JsonSerializer<Object> _findAndAddDynamic(PropertySerializerMap map,
Class<?> type, SerializerProvider provider) throws JsonMappingException
{
PropertySerializerMap.SerializerAndMapResult result;
if (_nonTrivialBaseType != null) {
JavaType t = provider.constructSpecializedType(_nonTrivialBaseType, type);
result = map.findAndAddPrimarySerializer(t, provider, this);
} else {
result = map.findAndAddPrimarySerializer(type, provider, this);
}
// did we get a new map of serializers? If so, start using it
if (map != result.map) {
_dynamicSerializers = result.map;
}
return result.serializer;
}
/**
* Method that can be used to access value of the property this
* Object describes, from given bean instance.
*<p>
* Note: method is final as it should not need to be overridden -- rather,
* calling method(s) ({@link #serializeAsField}) should be overridden
* to change the behavior
*/
public final Object get(Object bean) throws Exception
{
if (_accessorMethod != null) {
return _accessorMethod.invoke(bean);
}
return _field.get(bean);
}
/**
* Method called to handle a direct self-reference through this property.
* Method can choose to indicate an error by throwing {@link JsonMappingException};
* fully handle serialization (and return true); or indicate that it should be
* serialized normally (return false).
*<p>
* Default implementation will throw {@link JsonMappingException} if
* {@link SerializationFeature#FAIL_ON_SELF_REFERENCES} is enabled;
* or return <code>false</code> if it is disabled.
*
* @return True if method fully handled self-referential value; false if not (caller
* is to handle it) or {@link JsonMappingException} if there is no way handle it
*/
protected boolean _handleSelfReference(Object bean, JsonGenerator jgen, SerializerProvider prov, JsonSerializer<?> ser)
throws JsonMappingException {
if (prov.isEnabled(SerializationFeature.FAIL_ON_SELF_REFERENCES)
&& !ser.usesObjectId()) {
// 05-Feb-2013, tatu: Usually a problem, but NOT if we are handling
// object id; this may be the case for BeanSerializers at least.
// 13-Feb-2014, tatu: another possible ok case: custom serializer (something
// OTHER than {@link BeanSerializerBase}
if (ser instanceof BeanSerializerBase) {
throw new JsonMappingException("Direct self-reference leading to cycle");
}
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(40);
sb.append("property '").append(getName()).append("' (");
if (_accessorMethod != null) {
sb.append("via method ").append(_accessorMethod.getDeclaringClass().getName()).append("#").append(_accessorMethod.getName());
} else {
sb.append("field \"").append(_field.getDeclaringClass().getName()).append("#").append(_field.getName());
}
if (_serializer == null) {
sb.append(", no static serializer");
} else {
sb.append(", static serializer of type "+_serializer.getClass().getName());
}
sb.append(')');
return sb.toString();
}
}