package com.fasterxml.jackson.databind.ser.std; import java.io.IOException; import java.lang.reflect.Type; import java.util.*; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.ObjectIdGenerator; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.io.SerializedString; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.ObjectIdInfo; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; import com.fasterxml.jackson.databind.jsonschema.JsonSerializableSchema; 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.*; import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter; import com.fasterxml.jackson.databind.ser.impl.PropertyBasedObjectIdGenerator; import com.fasterxml.jackson.databind.ser.impl.WritableObjectId; import com.fasterxml.jackson.databind.util.ArrayBuilders; import com.fasterxml.jackson.databind.util.NameTransformer; /** * 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. */ public abstract class BeanSerializerBase extends StdSerializer<Object> implements ContextualSerializer, ResolvableSerializer, JsonFormatVisitable, 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 com.fasterxml.jackson.annotation.JsonAnyGetter} * annotated properties */ final protected AnyGetterWriter _anyGetterWriter; /** * Id of the bean property filter to use, if any; null if none. */ final protected Object _propertyFilterId; /** * If using custom type ids (usually via getter, or field), this is the * reference to that member. */ final protected AnnotatedMember _typeId; /** * If this POJO can be alternatively serialized using just an object id * to denote a reference to previously serialized object, * this Object will handle details. */ final protected ObjectIdWriter _objectIdWriter; /** * Requested shape from bean class annotations. */ final protected JsonFormat.Shape _serializationShape; /* /********************************************************** /* Life-cycle: constructors /********************************************************** */ /** * Constructor used by {@link BeanSerializerBuilder} to create an * instance * * @param type Nominal type of values handled by this serializer * @param builder Builder for accessing other collected information */ protected BeanSerializerBase(JavaType type, BeanSerializerBuilder builder, BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) { super(type); _props = properties; _filteredProps = filteredProperties; if (builder == null) { // mostly for testing _typeId = null; _anyGetterWriter = null; _propertyFilterId = null; _objectIdWriter = null; _serializationShape = null; } else { _typeId = builder.getTypeId(); _anyGetterWriter = builder.getAnyGetter(); _propertyFilterId = builder.getFilterId(); _objectIdWriter = builder.getObjectIdWriter(); JsonFormat.Value format = builder.getBeanDescription().findExpectedFormat(null); _serializationShape = (format == null) ? null : format.getShape(); } } public BeanSerializerBase(BeanSerializerBase src, BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) { super(src._handledType); _props = properties; _filteredProps = filteredProperties; _typeId = src._typeId; _anyGetterWriter = src._anyGetterWriter; _objectIdWriter = src._objectIdWriter; _propertyFilterId = src._propertyFilterId; _serializationShape = src._serializationShape; } protected BeanSerializerBase(BeanSerializerBase src, ObjectIdWriter objectIdWriter) { super(src._handledType); _props = src._props; _filteredProps = src._filteredProps; _typeId = src._typeId; _anyGetterWriter = src._anyGetterWriter; _objectIdWriter = objectIdWriter; _propertyFilterId = src._propertyFilterId; _serializationShape = src._serializationShape; } protected BeanSerializerBase(BeanSerializerBase src, String[] toIgnore) { super(src._handledType); // Bit clumsy, but has to do: HashSet<String> ignoredSet = ArrayBuilders.arrayToSet(toIgnore); final BeanPropertyWriter[] propsIn = src._props; final BeanPropertyWriter[] fpropsIn = src._filteredProps; final int len = propsIn.length; ArrayList<BeanPropertyWriter> propsOut = new ArrayList<BeanPropertyWriter>(len); ArrayList<BeanPropertyWriter> fpropsOut = (fpropsIn == null) ? null : new ArrayList<BeanPropertyWriter>(len); for (int i = 0; i < len; ++i) { BeanPropertyWriter bpw = propsIn[i]; // should be ignored? if (ignoredSet.contains(bpw.getName())) { continue; } propsOut.add(bpw); if (fpropsIn != null) { fpropsOut.add(fpropsIn[i]); } } _props = propsOut.toArray(new BeanPropertyWriter[propsOut.size()]); _filteredProps = (fpropsOut == null) ? null : fpropsOut.toArray(new BeanPropertyWriter[fpropsOut.size()]); _typeId = src._typeId; _anyGetterWriter = src._anyGetterWriter; _objectIdWriter = src._objectIdWriter; _propertyFilterId = src._propertyFilterId; _serializationShape = src._serializationShape; } /** * Fluent factory used for creating a new instance with different * {@link ObjectIdWriter}. * * @since 2.0 */ public abstract BeanSerializerBase withObjectIdWriter(ObjectIdWriter objectIdWriter); /** * Fluent factory used for creating a new instance with additional * set of properties to ignore (from properties this instance otherwise has) * * @since 2.0 */ protected abstract BeanSerializerBase withIgnorals(String[] toIgnore); /** * Fluent factory for creating a variant that output POJO as a * JSON Array. Implementations may ignore this request if output * as array is not possible (either at all, or reliably). * * @since 2.1 */ protected abstract BeanSerializerBase asArraySerializer(); /** * 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, src._props, src._filteredProps); } /** * Copy-constructor that will also rename properties with given prefix * (if it's non-empty) */ protected BeanSerializerBase(BeanSerializerBase src, NameTransformer unwrapper) { this(src, rename(src._props, unwrapper), rename(src._filteredProps, unwrapper)); } private final static BeanPropertyWriter[] rename(BeanPropertyWriter[] props, NameTransformer transformer) { if (props == null || props.length == 0 || transformer == null || transformer == NameTransformer.NOP) { return props; } final int len = props.length; BeanPropertyWriter[] result = new BeanPropertyWriter[len]; for (int i = 0; i < len; ++i) { BeanPropertyWriter bpw = props[i]; if (bpw != null) { result[i] = bpw.rename(transformer); } } return result; } /* /********************************************************** /* Post-constriction processing: resolvable, contextual /********************************************************** */ /** * We need to implement {@link ResolvableSerializer} to be able to * properly handle cyclic type references. */ // @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]; // let's start with null serializer resolution actually if (!prop.willSuppressNulls() && !prop.hasNullSerializer()) { JsonSerializer<Object> nullSer = provider.findNullValueSerializer(prop); if (nullSer != null) { prop.assignNullSerializer(nullSer); // also: remember to replace filtered property too? (see [JACKSON-364]) if (i < filteredCount) { BeanPropertyWriter w2 = _filteredProps[i]; if (w2 != null) { w2.assignNullSerializer(nullSer); } } } } 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 ContainerSerializer<?>) { // ugly casts... but necessary @SuppressWarnings("unchecked") JsonSerializer<Object> ser2 = (JsonSerializer<Object>)((ContainerSerializer<?>) ser).withValueTypeSerializer(typeSer); ser = ser2; } } } prop.assignSerializer(ser); // and maybe replace filtered property too? (see [JACKSON-364]) if (i < filteredCount) { BeanPropertyWriter w2 = _filteredProps[i]; if (w2 != null) { w2.assignSerializer(ser); } } } // also, any-getter may need to be resolved if (_anyGetterWriter != null) { _anyGetterWriter.resolve(provider); } } // @Override public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { ObjectIdWriter oiw = _objectIdWriter; String[] ignorals = null; final AnnotationIntrospector intr = provider.getAnnotationIntrospector(); final AnnotatedMember accessor = (property == null || intr == null) ? null : property.getMember(); // First: may have an override for Object Id: if (accessor != null) { ignorals = intr.findPropertiesToIgnore(accessor); ObjectIdInfo objectIdInfo = intr.findObjectIdInfo(accessor); if (objectIdInfo == null) { // no ObjectId override, but maybe ObjectIdRef? if (oiw != null) { objectIdInfo = intr.findObjectReferenceInfo(accessor, new ObjectIdInfo("", null, null)); oiw = _objectIdWriter.withAlwaysAsId(objectIdInfo.getAlwaysAsId()); } } else { /* Ugh: mostly copied from BeanSerializerBase: but can't easily * change it to be able to move to SerializerProvider (where it * really belongs) */ // 2.1: allow modifications by "id ref" annotations as well: objectIdInfo = intr.findObjectReferenceInfo(accessor, objectIdInfo); ObjectIdGenerator<?> gen; Class<?> implClass = objectIdInfo.getGeneratorType(); JavaType type = provider.constructType(implClass); JavaType idType = provider.getTypeFactory().findTypeParameters(type, ObjectIdGenerator.class)[0]; // Property-based generator is trickier if (implClass == ObjectIdGenerators.PropertyGenerator.class) { // most special one, needs extra work String propName = objectIdInfo.getPropertyName(); BeanPropertyWriter idProp = null; for (int i = 0, len = _props.length ;; ++i) { if (i == len) { throw new IllegalArgumentException("Invalid Object Id definition for "+_handledType.getName() +": can not find property with name '"+propName+"'"); } BeanPropertyWriter prop = _props[i]; if (propName.equals(prop.getName())) { idProp = prop; /* Let's force it to be the first property to output * (although it may still get rearranged etc) */ if (i > 0) { // note: must shuffle both regular properties and filtered System.arraycopy(_props, 0, _props, 1, i); _props[0] = idProp; if (_filteredProps != null) { BeanPropertyWriter fp = _filteredProps[i]; System.arraycopy(_filteredProps, 0, _filteredProps, 1, i); _filteredProps[0] = fp; } } break; } } idType = idProp.getType(); gen = new PropertyBasedObjectIdGenerator(objectIdInfo, idProp); oiw = ObjectIdWriter.construct(idType, null, gen, objectIdInfo.getAlwaysAsId()); } else { // other types need to be simpler gen = provider.objectIdGeneratorInstance(accessor, objectIdInfo); oiw = ObjectIdWriter.construct(idType, objectIdInfo.getPropertyName(), gen, objectIdInfo.getAlwaysAsId()); } } } // either way, need to resolve serializer: BeanSerializerBase contextual = this; if (oiw != null) { JsonSerializer<?> ser = provider.findValueSerializer(oiw.idType, property); oiw = oiw.withSerializer(ser); if (oiw != _objectIdWriter) { contextual = contextual.withObjectIdWriter(oiw); } } // And possibly add more properties to ignore if (ignorals != null && ignorals.length != 0) { contextual = contextual.withIgnorals(ignorals); } // One more thing: are we asked to serialize POJO as array? JsonFormat.Shape shape = null; if (accessor != null) { JsonFormat.Value format = intr.findFormat((Annotated) accessor); if (format != null) { shape = format.getShape(); } } if (shape == null) { shape = _serializationShape; } if (shape == JsonFormat.Shape.ARRAY) { contextual = contextual.asArraySerializer(); } return contextual; } /* /********************************************************** /* Partial JsonSerializer implementation /********************************************************** */ @Override public boolean usesObjectId() { return (_objectIdWriter != null); } // 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 { if (_objectIdWriter != null) { _serializeWithObjectId(bean, jgen, provider, typeSer); return; } String typeStr = (_typeId == null) ? null :_customTypeId(bean); if (typeStr == null) { typeSer.writeTypePrefixForObject(bean, jgen); } else { typeSer.writeCustomTypePrefixForObject(bean, jgen, typeStr); } if (_propertyFilterId != null) { serializeFieldsFiltered(bean, jgen, provider); } else { serializeFields(bean, jgen, provider); } if (typeStr == null) { typeSer.writeTypeSuffixForObject(bean, jgen); } else { typeSer.writeCustomTypeSuffixForObject(bean, jgen, typeStr); } } private final void _serializeWithObjectId(Object bean, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonGenerationException { final ObjectIdWriter w = _objectIdWriter; WritableObjectId oid = provider.findObjectId(bean, w.generator); Object id = oid.id; if (id != null) { // have seen before; serialize just id oid.serializer.serialize(id, jgen, provider); return; } // if not, bit more work: oid.serializer = w.serializer; oid.id = id = oid.generator.generateId(bean); String typeStr = (_typeId == null) ? null :_customTypeId(bean); if (typeStr == null) { typeSer.writeTypePrefixForObject(bean, jgen); } else { typeSer.writeCustomTypePrefixForObject(bean, jgen, typeStr); } // Very first thing: inject the id property SerializedString name = w.propertyName; if (name != null) { jgen.writeFieldName(name); w.serializer.serialize(id, jgen, provider); } if (_propertyFilterId != null) { serializeFieldsFiltered(bean, jgen, provider); } else { serializeFields(bean, jgen, provider); } if (typeStr == null) { typeSer.writeTypeSuffixForObject(bean, jgen); } else { typeSer.writeCustomTypeSuffixForObject(bean, jgen, typeStr); } } private final String _customTypeId(Object bean) { final Object typeId = _typeId.getValue(bean); if (typeId == null) { return ""; } return (typeId instanceof String) ? (String) typeId : typeId.toString(); } /* /********************************************************** /* 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) */ 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. */ 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); // [JACKSON-813]: Add optional JSON Schema id attribute, if found // NOTE: not optimal, does NOT go through AnnotationIntrospector etc: JsonSerializableSchema ann = _handledType.getAnnotation(JsonSerializableSchema.class); if (ann != null) { String id = ann.id(); if (id != null && id.length() > 0) { o.put("id", id); } } //todo: should the classname go in the title? //o.put("title", _className); ObjectNode propertiesNode = o.objectNode(); final BeanPropertyFilter filter; if (_propertyFilterId != null) { filter = findFilter(provider); } else { filter = null; } for (int i = 0; i < _props.length; i++) { BeanPropertyWriter prop = _props[i]; if (filter == null) { prop.depositSchemaProperty(propertiesNode, provider); } else { filter.depositSchemaProperty(prop, propertiesNode, provider); } } o.put("properties", propertiesNode); return o; } @Override public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { //deposit your output format JsonObjectFormatVisitor objectVisitor = visitor.expectObjectFormat(typeHint); if (_propertyFilterId != null) { BeanPropertyFilter filter = findFilter(visitor.getProvider()); for (int i = 0; i < _props.length; i++) { filter.depositSchemaProperty(_props[i], objectVisitor, visitor.getProvider()); } } else { for (int i = 0; i < _props.length; i++) { _props[i].depositSchemaProperty(objectVisitor); } } } }