All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.jackson.map.ser.StdSerializerProvider Maven / Gradle / Ivy

Go to download

Data Mapper package is a high-performance data binding package built on Jackson JSON processor

There is a newer version: 1.9.13
Show newest version
package org.codehaus.jackson.map.ser;

import java.io.IOException;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.Date;

import org.codehaus.jackson.*;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.node.ObjectNode;
import org.codehaus.jackson.schema.JsonSchema;
import org.codehaus.jackson.schema.SchemaAware;
import org.codehaus.jackson.type.JavaType;

import org.codehaus.jackson.map.*;

/**
 * Default {@link SerializerProvider} implementation. Handles
 * caching aspects of serializer handling; all construction details
 * are delegated to {@link SerializerFactory} instance.
 *

* One note about implementation: the main instance constructed will * be so-called "blueprint" object, and will NOT be used during actual * serialization. Rather, an "instance" instance is created so that * state can be carried along, as well as to avoid synchronization * during serializer access. Because of this, if sub-classing, one * must override method {@link #createInstance}: if this is not done, * an exception will get thrown as base class verifies that the * instance has same class as the blueprint * (instance.getClass() == blueprint.getClass()). * Check is done to prevent weird bugs that would otherwise occur. *

* Starting with version 1.5, provider is also responsible for * some parts of type serialization; specifically for locating * proper type serializers to use for types. */ public class StdSerializerProvider extends SerializerProvider { /** * Setting for determining whether mappings for "unknown classes" should be * cached for faster resolution. Usually this isn't needed, but maybe it * is in some cases? *

* TODO: make configurable? */ final static boolean CACHE_UNKNOWN_MAPPINGS = false; public final static JsonSerializer DEFAULT_NULL_KEY_SERIALIZER = new FailingSerializer("Null key for a Map not allowed in Json (use a converting NullKeySerializer?)"); public final static JsonSerializer DEFAULT_KEY_SERIALIZER = new StdKeySerializer(); public final static JsonSerializer DEFAULT_UNKNOWN_SERIALIZER = new SerializerBase(Object.class) { @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonMappingException { // 27-Nov-2009, tatu: As per [JACKSON-201] may or may not fail... if (provider.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS)) { failForEmpty(value); } // But if it's fine, we'll just output empty JSON Object: jgen.writeStartObject(); jgen.writeEndObject(); } // since 1.6.2; needed to retain type information @Override public final void serializeWithType(Object value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonGenerationException { if (provider.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS)) { failForEmpty(value); } typeSer.writeTypePrefixForObject(value, jgen); typeSer.writeTypeSuffixForObject(value, jgen); } @Override public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException { return null; } protected void failForEmpty(Object value) throws JsonMappingException { throw new JsonMappingException("No serializer found for class "+value.getClass().getName()+" and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )"); } }; /* /********************************************************** /* Configuration, factories /********************************************************** */ final protected SerializerFactory _serializerFactory; final protected SerializerCache _serializerCache; /* /********************************************************** /* Configuration, specialized serializers /********************************************************** */ /** * Serializer that gets called for values of types for which no * serializers can be constructed. *

* The default serializer will simply thrown an exception; a possible * alternative that can be used would be * {@link ToStringSerializer}. */ protected JsonSerializer _unknownTypeSerializer = DEFAULT_UNKNOWN_SERIALIZER; /** * Serializer used to output non-null keys of Maps (which will get * output as Json Objects). */ protected JsonSerializer _keySerializer = DEFAULT_KEY_SERIALIZER; /** * Serializer used to output a null value. Default implementation * writes nulls using {@link JsonGenerator#writeNull}. */ protected JsonSerializer _nullValueSerializer = NullSerializer.instance; /** * Serializer used to (try to) output a null key, due to an entry of * {@link java.util.Map} having null key. * The default implementation will throw an exception if this happens; * alternative implementation (like one that would write an Empty String) * can be defined. */ protected JsonSerializer _nullKeySerializer = DEFAULT_NULL_KEY_SERIALIZER; /* /********************************************************** /* State, for non-blueprint instances /********************************************************** */ /** * For fast lookups, we will have a local non-shared read-only * map that contains serializers previously fetched. */ protected final ReadOnlyClassToSerializerMap _knownSerializers; /** * Lazily acquired and instantiated formatter object: initialized * first time it is needed, reused afterwards. Used via instances * (not blueprints), so that access need not be thread-safe. */ protected DateFormat _dateFormat; /* /********************************************************** /* Life-cycle /********************************************************** */ /** * Constructor for creating master (or "blue-print") provider object, * which is only used as the template for constructing per-binding * instances. */ public StdSerializerProvider() { super(null); _serializerFactory = null; _serializerCache = new SerializerCache(); // Blueprints doesn't have access to any serializers... _knownSerializers = null; } /** * "Copy-constructor", used from {@link #createInstance} (or by * sub-classes) * * @param src Blueprint object used as the baseline for this instance */ protected StdSerializerProvider(SerializationConfig config, StdSerializerProvider src, SerializerFactory f) { super(config); if (config == null) { throw new NullPointerException(); } _serializerFactory = f; _serializerCache = src._serializerCache; _unknownTypeSerializer = src._unknownTypeSerializer; _keySerializer = src._keySerializer; _nullValueSerializer = src._nullValueSerializer; _nullKeySerializer = src._nullKeySerializer; /* Non-blueprint instances do have a read-only map; one that doesn't * need synchronization for lookups. */ _knownSerializers = _serializerCache.getReadOnlyLookupMap(); } /** * Overridable method, used to create a non-blueprint instances from the blueprint. * This is needed to retain state during serialization. */ protected StdSerializerProvider createInstance(SerializationConfig config, SerializerFactory jsf) { return new StdSerializerProvider(config, this, jsf); } /* /********************************************************** /* Methods to be called by ObjectMapper /********************************************************** */ @Override public final void serializeValue(SerializationConfig config, JsonGenerator jgen, Object value, SerializerFactory jsf) throws IOException, JsonGenerationException { if (jsf == null) { throw new IllegalArgumentException("Can not pass null serializerFactory"); } /* First: we need a separate instance, which will hold a copy of the * non-shared ("local") read-only lookup Map for fast * class-to-serializer lookup */ StdSerializerProvider inst = createInstance(config, jsf); // sanity check to avoid weird errors; to ensure sub-classes do override createInstance if (inst.getClass() != getClass()) { throw new IllegalStateException("Broken serializer provider: createInstance returned instance of type "+inst.getClass()+"; blueprint of type "+getClass()); } // And then we can do actual serialization, through the instance inst._serializeValue(jgen, value); } @Override public final void serializeValue(SerializationConfig config, JsonGenerator jgen, Object value, JavaType rootType, SerializerFactory jsf) throws IOException, JsonGenerationException { if (jsf == null) { throw new IllegalArgumentException("Can not pass null serializerFactory"); } StdSerializerProvider inst = createInstance(config, jsf); if (inst.getClass() != getClass()) { throw new IllegalStateException("Broken serializer provider: createInstance returned instance of type "+inst.getClass()+"; blueprint of type "+getClass()); } inst._serializeValue(jgen, value, rootType); } @Override public JsonSchema generateJsonSchema(Class type, SerializationConfig config, SerializerFactory jsf) throws JsonMappingException { if (type == null) { throw new IllegalArgumentException("A class must be provided."); } /* First: we need a separate instance, which will hold a copy of the * non-shared ("local") read-only lookup Map for fast * class-to-serializer lookup */ StdSerializerProvider inst = createInstance(config, jsf); // sanity check to avoid weird errors; to ensure sub-classes do override createInstance if (inst.getClass() != getClass()) { throw new IllegalStateException("Broken serializer provider: createInstance returned instance of type "+inst.getClass()+"; blueprint of type "+getClass()); } /* no need for embedded type information for JSON schema generation (all * type information it needs is accessible via "untyped" serializer) */ JsonSerializer ser = inst.findValueSerializer(type); JsonNode schemaNode = (ser instanceof SchemaAware) ? ((SchemaAware) ser).getSchema(inst, null) : JsonSchema.getDefaultSchemaNode(); if (!(schemaNode instanceof ObjectNode)) { throw new IllegalArgumentException("Class " + type.getName() + " would not be serialized as a JSON object and therefore has no schema."); } return new JsonSchema((ObjectNode) schemaNode); } @Override public boolean hasSerializerFor(SerializationConfig config, Class cls, SerializerFactory jsf) { return createInstance(config, jsf)._findExplicitUntypedSerializer(cls) != null; } /** * Method called on the actual non-blueprint provider instance object, * to kick off the serialization. */ protected void _serializeValue(JsonGenerator jgen, Object value) throws IOException, JsonProcessingException { JsonSerializer ser; if (value == null) { ser = getNullValueSerializer(); } else { Class cls = value.getClass(); // true, since we do want to cache root-level typed serializers ser = findTypedValueSerializer(cls, true); } try { ser.serialize(value, jgen, this); } catch (IOException ioe) { /* As per [JACKSON-99], should not wrap IOException or its * sub-classes (like JsonProcessingException, JsonMappingException) */ throw ioe; } catch (Exception e) { // but others are wrapped String msg = e.getMessage(); if (msg == null) { msg = "[no message for "+e.getClass().getName()+"]"; } throw new JsonMappingException(msg, e); } } /** * Method called on the actual non-blueprint provider instance object, * to kick off the serialization, when root type is explicitly * specified and not determined from value. */ protected void _serializeValue(JsonGenerator jgen, Object value, JavaType rootType) throws IOException, JsonProcessingException { JsonSerializer ser; if (value == null) { ser = getNullValueSerializer(); } else { // Let's ensure types are compatible at this point if (!rootType.getRawClass().isAssignableFrom(value.getClass())) { throw new JsonMappingException("Incompatible types: declared root type ("+rootType+") vs " +value.getClass().getName()); } ser = findTypedValueSerializer(rootType, true); } try { ser.serialize(value, jgen, this); } catch (IOException ioe) { // no wrapping for IO (and derived) throw ioe; } catch (Exception e) { // but others do need to be, to get path etc String msg = e.getMessage(); if (msg == null) { msg = "[no message for "+e.getClass().getName()+"]"; } throw new JsonMappingException(msg, e); } } /* /********************************************************** /* Configuration methods /********************************************************** */ public void setKeySerializer(JsonSerializer ks) { if (ks == null) { throw new IllegalArgumentException("Can not pass null JsonSerializer"); } _keySerializer = ks; } public void setNullValueSerializer(JsonSerializer nvs) { if (nvs == null) { throw new IllegalArgumentException("Can not pass null JsonSerializer"); } _nullValueSerializer = nvs; } public void setNullKeySerializer(JsonSerializer nks) { if (nks == null) { throw new IllegalArgumentException("Can not pass null JsonSerializer"); } _nullKeySerializer = nks; } @Override public int cachedSerializersCount() { return _serializerCache.size(); } @Override public void flushCachedSerializers() { _serializerCache.flush(); } /* /********************************************************** /* Abstract method implementations, value/type serializers /********************************************************** */ @Override public JsonSerializer findValueSerializer(Class valueType) throws JsonMappingException { // Fast lookup from local lookup thingy works? JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); if (ser != null) return ser; // If not, maybe shared map already has it? ser = _serializerCache.untypedValueSerializer(valueType); if (ser != null) return ser; // ... possibly as fully typed? ser = _serializerCache.untypedValueSerializer(TypeFactory.type(valueType)); if (ser != null) return ser; // If neither, must create ser = _createAndCacheUntypedSerializer(valueType); // Not found? Must use the unknown type serializer /* Couldn't create? Need to return the fallback serializer, which * most likely will report an error: but one question is whether * we should cache it? */ if (ser == null) { ser = getUnknownTypeSerializer(valueType); // Should this be added to lookups? if (CACHE_UNKNOWN_MAPPINGS) { _serializerCache.addNonTypedSerializer(valueType, ser); } } return ser; } /** * This variant was added in 1.5, to allow for efficient access using full * structured types, not just classes. This is necessary for accurate * handling of external type information, to handle polymorphic types. */ @Override public JsonSerializer findValueSerializer(JavaType valueType) throws JsonMappingException { // Fast lookup from local lookup thingy works? JsonSerializer ser = _knownSerializers.untypedValueSerializer(valueType); if (ser != null) { return ser; } // If not, maybe shared map already has it? ser = _serializerCache.untypedValueSerializer(valueType); if (ser != null) { return ser; } // If neither, must create ser = _createAndCacheUntypedSerializer(valueType); // Not found? Must use the unknown type serializer /* Couldn't create? Need to return the fallback serializer, which * most likely will report an error: but one question is whether * we should cache it? */ if (ser == null) { ser = getUnknownTypeSerializer(valueType.getRawClass()); // Should this be added to lookups? if (CACHE_UNKNOWN_MAPPINGS) { _serializerCache.addNonTypedSerializer(valueType, ser); } } return ser; } /** * @param cache Whether resulting value serializer should be cached or not; this is just * a hint */ @Override public JsonSerializer findTypedValueSerializer(Class valueType, boolean cache) throws JsonMappingException { // Two-phase lookups; local non-shared cache, then shared: JsonSerializer ser = _knownSerializers.typedValueSerializer(valueType); if (ser != null) { return ser; } // If not, maybe shared map already has it? ser = _serializerCache.typedValueSerializer(valueType); if (ser != null) { return ser; } // Well, let's just compose from pieces: ser = findValueSerializer(valueType); TypeSerializer typeSer = _serializerFactory.createTypeSerializer(TypeFactory.type(valueType), _config); if (typeSer != null) { ser = new WrappedSerializer(typeSer, ser); } if (cache) { _serializerCache.addTypedSerializer(valueType, ser); } return ser; } @Override public JsonSerializer findTypedValueSerializer(JavaType valueType, boolean cache) throws JsonMappingException { // Two-phase lookups; local non-shared cache, then shared: /* 10-Mar-2010, tatu: This looks suspicious; could this not lead to problems for * generic type variations? */ JsonSerializer ser = _knownSerializers.typedValueSerializer(valueType); if (ser != null) { return ser; } // If not, maybe shared map already has it? ser = _serializerCache.typedValueSerializer(valueType); if (ser != null) { return ser; } // Well, let's just compose from pieces: ser = findValueSerializer(valueType); TypeSerializer typeSer = _serializerFactory.createTypeSerializer(valueType, _config); if (typeSer != null) { ser = new WrappedSerializer(typeSer, ser); } if (cache) { _serializerCache.addTypedSerializer(valueType, ser); } return ser; } /* /********************************************************** /* Abstract method implementations, other serializers /********************************************************** */ @Override public JsonSerializer getKeySerializer() { return _keySerializer; } @Override public JsonSerializer getNullKeySerializer() { return _nullKeySerializer; } @Override public JsonSerializer getNullValueSerializer() { return _nullValueSerializer; } @Override public JsonSerializer getUnknownTypeSerializer(Class unknownType) { return _unknownTypeSerializer; } /* /********************************************************** /* Abstract method impls, convenience methods /********************************************************** */ /** * @param timestamp Millisecond timestamp that defines date, if available; */ @Override public final void defaultSerializeDateValue(long timestamp, JsonGenerator jgen) throws IOException, JsonProcessingException { // [JACKSON-87]: Support both numeric timestamps and textual if (isEnabled(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS)) { jgen.writeNumber(timestamp); } else { if (_dateFormat == null) { // must create a clone since Formats are not thread-safe: _dateFormat = (DateFormat)_config.getDateFormat().clone(); } jgen.writeString(_dateFormat.format(new Date(timestamp))); } } @Override public final void defaultSerializeDateValue(Date date, JsonGenerator jgen) throws IOException, JsonProcessingException { // [JACKSON-87]: Support both numeric timestamps and textual if (isEnabled(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS)) { jgen.writeNumber(date.getTime()); } else { if (_dateFormat == null) { DateFormat blueprint = _config.getDateFormat(); // must create a clone since Formats are not thread-safe: _dateFormat = (DateFormat)blueprint.clone(); } jgen.writeString(_dateFormat.format(date)); } } /* /********************************************************** /* Helper methods: can be overridden by sub-classes /********************************************************** */ /** * Method that will try to find a serializer, either from cache * or by constructing one; but will not return an "unknown" serializer * if this can not be done but rather returns null. * * @return Serializer if one can be found, null if not. */ protected JsonSerializer _findExplicitUntypedSerializer(Class runtimeType) { // Fast lookup from local lookup thingy works? JsonSerializer ser = _knownSerializers.untypedValueSerializer(runtimeType); if (ser != null) { return ser; } // If not, maybe shared map already has it? ser = _serializerCache.untypedValueSerializer(runtimeType); if (ser != null) { return ser; } try { return _createAndCacheUntypedSerializer(runtimeType); } catch (Exception e) { return null; } } /** * Method that will try to construct a value serializer; and if * one is succesfully created, cache it for reuse. */ protected JsonSerializer _createAndCacheUntypedSerializer(Class type) throws JsonMappingException { JsonSerializer ser; try { ser = _createUntypedSerializer(TypeFactory.type(type)); } catch (IllegalArgumentException iae) { /* We better only expose checked exceptions, since those * are what caller is expected to handle */ throw new JsonMappingException(iae.getMessage(), null, iae); } if (ser != null) { _serializerCache.addNonTypedSerializer(type, ser); /* Finally: some serializers want to do post-processing, after * getting registered (to handle cyclic deps). */ if (ser instanceof ResolvableSerializer) { _resolveSerializer((ResolvableSerializer)ser); } } return ser; } /** * @since 1.5 ] */ protected JsonSerializer _createAndCacheUntypedSerializer(JavaType type) throws JsonMappingException { JsonSerializer ser; try { ser = _createUntypedSerializer(type); } catch (IllegalArgumentException iae) { /* We better only expose checked exceptions, since those * are what caller is expected to handle */ throw new JsonMappingException(iae.getMessage(), null, iae); } if (ser != null) { _serializerCache.addNonTypedSerializer(type, ser); /* Finally: some serializers want to do post-processing, after * getting registered (to handle cyclic deps). */ if (ser instanceof ResolvableSerializer) { _resolveSerializer((ResolvableSerializer)ser); } } return ser; } protected JsonSerializer _createUntypedSerializer(JavaType type) throws JsonMappingException { /* 10-Dec-2008, tatu: Is there a possibility of infinite loops * here? Shouldn't be, given that we do not pass back-reference * to this provider. But if there is, we'd need to sync calls, * and keep track of creation chain to look for loops -- fairly * easy to do, but won't add yet since it seems unnecessary. */ return (JsonSerializer)_serializerFactory.createSerializer(type, _config); } protected void _resolveSerializer(ResolvableSerializer ser) throws JsonMappingException { ser.resolve(this); } /* /********************************************************** /* Helper classes /********************************************************** */ /** * Simple serializer that will call configured type serializer, passing * in configured data serializer, and exposing it all as a simple * serializer. */ private final static class WrappedSerializer extends JsonSerializer { final protected TypeSerializer _typeSerializer; final protected JsonSerializer _serializer; public WrappedSerializer(TypeSerializer typeSer, JsonSerializer ser) { super(); _typeSerializer = typeSer; _serializer = ser; } @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { _serializer.serializeWithType(value, jgen, provider, _typeSerializer); } @Override public void serializeWithType(Object value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonProcessingException { /* Is this an erroneous call? For now, let's assume it is not, and * that type serializer is just overridden if so */ _serializer.serializeWithType(value, jgen, provider, typeSer); } @Override public Class handledType() { return Object.class; } } }