blob: 8af9d58a34c9ea77cf4108cb0e664ec589168ab7 [file] [log] [blame]
package org.codehaus.jackson.map.ser.std;
import java.io.IOException;
import java.lang.reflect.Type;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.ser.*;
import org.codehaus.jackson.node.ObjectNode;
import org.codehaus.jackson.schema.JsonSchema;
import org.codehaus.jackson.schema.SchemaAware;
import org.codehaus.jackson.type.JavaType;
/**
* Base class both for the standard bean serializer, and couple
* of variants that only differ in small details.
* Can be used for custom bean serializers as well, although that
* is not the primary design goal.
*
* @since 1.9
*/
public abstract class BeanSerializerBase
extends SerializerBase<Object>
implements ResolvableSerializer, SchemaAware
{
final protected static BeanPropertyWriter[] NO_PROPS = new BeanPropertyWriter[0];
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
/**
* Writers used for outputting actual property values
*/
final protected BeanPropertyWriter[] _props;
/**
* Optional filters used to suppress output of properties that
* are only to be included in certain views
*/
final protected BeanPropertyWriter[] _filteredProps;
/**
* Handler for {@link org.codehaus.jackson.annotate.JsonAnyGetter}
* annotated properties
*
* @since 1.6
*/
final protected AnyGetterWriter _anyGetterWriter;
/**
* Id of the bean property filter to use, if any; null if none.
*/
final protected Object _propertyFilterId;
/*
/**********************************************************
/* Life-cycle: constructors
/**********************************************************
*/
/**
* @param type Nominal type of values handled by this serializer
* @param properties Property writers used for actual serialization
*/
protected BeanSerializerBase(JavaType type,
BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties,
AnyGetterWriter anyGetterWriter,
Object filterId)
{
super(type);
_props = properties;
_filteredProps = filteredProperties;
_anyGetterWriter = anyGetterWriter;
_propertyFilterId = filterId;
}
@SuppressWarnings("unchecked")
public BeanSerializerBase(Class<?> rawType,
BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties,
AnyGetterWriter anyGetterWriter,
Object filterId)
{
super((Class<Object>) rawType);
_props = properties;
_filteredProps = filteredProperties;
_anyGetterWriter = anyGetterWriter;
_propertyFilterId = filterId;
}
/**
* Copy-constructor that is useful for sub-classes that just want to
* copy all super-class properties without modifications.
*/
protected BeanSerializerBase(BeanSerializerBase src) {
this(src._handledType,
src._props, src._filteredProps, src._anyGetterWriter, src._propertyFilterId);
}
/*
/**********************************************************
/* Partial JsonSerializer implementation
/**********************************************************
*/
// Main serialization method left unimplemented
@Override
public abstract void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException;
// Type-info-augmented case implemented as it does not usually differ between impls
@Override
public void serializeWithType(Object bean, JsonGenerator jgen, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException, JsonGenerationException
{
typeSer.writeTypePrefixForObject(bean, jgen);
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, jgen, provider);
} else {
serializeFields(bean, jgen, provider);
}
typeSer.writeTypeSuffixForObject(bean, jgen);
}
/*
/**********************************************************
/* Field serialization methods
/**********************************************************
*/
protected void serializeFields(Object bean, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
final BeanPropertyWriter[] props;
if (_filteredProps != null && provider.getSerializationView() != null) {
props = _filteredProps;
} else {
props = _props;
}
int i = 0;
try {
for (final int len = props.length; i < len; ++i) {
BeanPropertyWriter prop = props[i];
if (prop != null) { // can have nulls in filtered list
prop.serializeAsField(bean, jgen, provider);
}
}
if (_anyGetterWriter != null) {
_anyGetterWriter.getAndSerialize(bean, jgen, provider);
}
} catch (Exception e) {
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
wrapAndThrow(provider, e, bean, name);
} catch (StackOverflowError e) {
/* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
* have many stack frames to spare... just one or two; can't
* make many calls.
*/
JsonMappingException mapE = new JsonMappingException("Infinite recursion (StackOverflowError)", e);
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
mapE.prependPath(new JsonMappingException.Reference(bean, name));
throw mapE;
}
}
/**
* Alternative serialization method that gets called when there is a
* {@link BeanPropertyFilter} that needs to be called to determine
* which properties are to be serialized (and possibly how)
*
* @since 1.7
*/
protected void serializeFieldsFiltered(Object bean, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
/* note: almost verbatim copy of "serializeFields"; copied (instead of merged)
* so that old method need not add check for existence of filter.
*/
final BeanPropertyWriter[] props;
if (_filteredProps != null && provider.getSerializationView() != null) {
props = _filteredProps;
} else {
props = _props;
}
final BeanPropertyFilter filter = findFilter(provider);
// better also allow missing filter actually..
if (filter == null) {
serializeFields(bean, jgen, provider);
return;
}
int i = 0;
try {
for (final int len = props.length; i < len; ++i) {
BeanPropertyWriter prop = props[i];
if (prop != null) { // can have nulls in filtered list
filter.serializeAsField(bean, jgen, provider, prop);
}
}
if (_anyGetterWriter != null) {
_anyGetterWriter.getAndSerialize(bean, jgen, provider);
}
} catch (Exception e) {
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
wrapAndThrow(provider, e, bean, name);
} catch (StackOverflowError e) {
JsonMappingException mapE = new JsonMappingException("Infinite recursion (StackOverflowError)", e);
String name = (i == props.length) ? "[anySetter]" : props[i].getName();
mapE.prependPath(new JsonMappingException.Reference(bean, name));
throw mapE;
}
}
/**
* Helper method used to locate filter that is needed, based on filter id
* this serializer was constructed with.
*
* @since 1.7
*/
protected BeanPropertyFilter findFilter(SerializerProvider provider)
throws JsonMappingException
{
final Object filterId = _propertyFilterId;
FilterProvider filters = provider.getFilterProvider();
// Not ok to miss the provider, if a filter is declared to be needed.
if (filters == null) {
throw new JsonMappingException("Can not resolve BeanPropertyFilter with id '"+filterId+"'; no FilterProvider configured");
}
BeanPropertyFilter filter = filters.findFilter(filterId);
// But whether unknown ids are ok just depends on filter provider; if we get null that's fine
return filter;
}
@Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
throws JsonMappingException
{
ObjectNode o = createSchemaNode("object", true);
//todo: should the classname go in the title?
//o.put("title", _className);
ObjectNode propertiesNode = o.objectNode();
for (int i = 0; i < _props.length; i++) {
BeanPropertyWriter prop = _props[i];
JavaType propType = prop.getSerializationType();
// 03-Dec-2010, tatu: SchemaAware REALLY should use JavaType, but alas it doesn't...
Type hint = (propType == null) ? prop.getGenericPropertyType() : propType.getRawClass();
// Maybe it already has annotated/statically configured serializer?
JsonSerializer<Object> ser = prop.getSerializer();
if (ser == null) { // nope
Class<?> serType = prop.getRawSerializationType();
if (serType == null) {
serType = prop.getPropertyType();
}
ser = provider.findValueSerializer(serType, prop);
}
JsonNode schemaNode = (ser instanceof SchemaAware) ?
((SchemaAware) ser).getSchema(provider, hint) :
JsonSchema.getDefaultSchemaNode();
propertiesNode.put(prop.getName(), schemaNode);
}
o.put("properties", propertiesNode);
return o;
}
/*
/**********************************************************
/* ResolvableSerializer impl
/**********************************************************
*/
@Override
public void resolve(SerializerProvider provider)
throws JsonMappingException
{
int filteredCount = (_filteredProps == null) ? 0 : _filteredProps.length;
for (int i = 0, len = _props.length; i < len; ++i) {
BeanPropertyWriter prop = _props[i];
if (prop.hasSerializer()) {
continue;
}
// Was the serialization type hard-coded? If so, use it
JavaType type = prop.getSerializationType();
/* It not, we can use declared return type if and only if
* declared type is final -- if not, we don't really know
* the actual type until we get the instance.
*/
if (type == null) {
type = provider.constructType(prop.getGenericPropertyType());
if (!type.isFinal()) {
/* 18-Feb-2010, tatus: But even if it is non-final, we may
* need to retain some of type information so that we can
* accurately handle contained types
*/
if (type.isContainerType() || type.containedTypeCount() > 0) {
prop.setNonTrivialBaseType(type);
}
continue;
}
}
JsonSerializer<Object> ser = provider.findValueSerializer(type, prop);
/* 04-Feb-2010, tatu: We may have stashed type serializer for content types
* too, earlier; if so, it's time to connect the dots here:
*/
if (type.isContainerType()) {
TypeSerializer typeSer = type.getContentType().getTypeHandler();
if (typeSer != null) {
// for now, can do this only for standard containers...
if (ser instanceof ContainerSerializerBase<?>) {
// ugly casts... but necessary
@SuppressWarnings("unchecked")
JsonSerializer<Object> ser2 = (JsonSerializer<Object>)((ContainerSerializerBase<?>) ser).withValueTypeSerializer(typeSer);
ser = ser2;
}
}
}
prop = prop.withSerializer(ser);
_props[i] = prop;
// and maybe replace filtered property too? (see [JACKSON-364])
if (i < filteredCount) {
BeanPropertyWriter w2 = _filteredProps[i];
if (w2 != null) {
_filteredProps[i] = w2.withSerializer(ser);
}
}
}
// also, any-getter may need to be resolved
if (_anyGetterWriter != null) {
_anyGetterWriter.resolve(provider);
}
}
}