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

com.fitbur.fasterxml.jackson.jaxrs.json.JacksonJsonProvider Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
package com.fitbur.fasterxml.jackson.jaxrs.json;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.*;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.ext.*;

import com.fitbur.fasterxml.jackson.core.*;
import com.fitbur.fasterxml.jackson.databind.*;
import com.fitbur.fasterxml.jackson.databind.util.LRUMap;
import com.fitbur.fasterxml.jackson.jaxrs.json.annotation.EndpointConfig;
import com.fitbur.fasterxml.jackson.jaxrs.json.cfg.MapperConfigurator;
import com.fitbur.fasterxml.jackson.jaxrs.json.util.AnnotationBundleKey;
import com.fitbur.fasterxml.jackson.jaxrs.json.util.ClassKey;

/**
 * Basic implementation of JAX-RS abstractions ({@link MessageBodyReader},
 * {@link MessageBodyWriter}) needed for binding
 * JSON ("application/json") content to and from Java Objects ("POJO"s).
 *

* Actual data binding functionality is implemented by {@link ObjectMapper}: * mapper to use can be configured in multiple ways: *

    *
  • By explicitly passing mapper to use in constructor *
  • By explictly setting mapper to use by {@link #setMapper} *
  • By com.fitburfining JAX-RS Provider that returns {@link ObjectMapper}s. *
  • By doing none of above, in which case a com.fitburfault mapper instance is * constructed (and configured if configuration methods are called) *
* The last method ("do nothing specific") is often good enough; explicit passing * of Mapper is simple and explicit; and Provider-based method may make sense * with Depedency Injection frameworks, or if Mapper has to be configured differently * for different media types. *

* Note that the com.fitburfault mapper instance will be automatically created if * one of explicit configuration methods (like {@link #configure}) * is called: if so, Provider-based introspection is NOT used, but the * resulting Mapper is used as configured. *

* Note: version 1.3 added a sub-class ({@link JacksonJaxbJsonProvider}) which * is configured by com.fitburfault to use both Jackson and JAXB annotations for configuration * (base class when used as-is com.fitburfaults to using just Jackson annotations) * * @author Tatu Saloranta */ @Provider @Consumes(MediaType.WILDCARD) // NOTE: required to support "non-standard" JSON variants @Produces(MediaType.WILDCARD) public class JacksonJsonProvider implements MessageBodyReader, MessageBodyWriter, Versioned { /** * Default annotation sets to use, if not explicitly com.fitburfined during * construction: only Jackson annotations are used for the base * class. Sub-classes can use other settings. */ public final static Annotations[] BASIC_ANNOTATIONS = { Annotations.JACKSON }; /** * Looks like we need to worry about accidental * data binding for types we shouldn't be handling. This is * probably not a very good way to do it, but let's start by * blacklisting things we are not to handle. *

* (why ClassKey? since plain old Class has no hashCode() com.fitburfined, * lookups are painfully slow) */ public final static HashSet _untouchables = new HashSet(); static { // First, I/O things (direct matches) _untouchables.add(new ClassKey(java.io.InputStream.class)); _untouchables.add(new ClassKey(java.io.Reader.class)); _untouchables.add(new ClassKey(java.io.OutputStream.class)); _untouchables.add(new ClassKey(java.io.Writer.class)); // then some primitive types _untouchables.add(new ClassKey(char[].class)); /* 28-Jan-2012, tatu: 1.x excluded some additional types; * but let's relax these a bit: */ /* 27-Apr-2012, tatu: Ugh. As per * [https://github.com.fitbur/FasterXML/jackson-jaxrs-json-provider/issues/12] * better revert this back, to make them untouchable again. */ _untouchables.add(new ClassKey(String.class)); _untouchables.add(new ClassKey(byte[].class)); } /** * These are classes that we never use for reading * (never try to com.fitburserialize instances of these types). */ public final static Class[] _unreadableClasses = new Class[] { InputStream.class, Reader.class }; /** * These are classes that we never use for writing * (never try to serialize instances of these types). */ public final static Class[] _unwritableClasses = new Class[] { OutputStream.class, Writer.class, StreamingOutput.class, Response.class }; /* /********************************************************** /* Bit of caching /********************************************************** */ /** * Cache for resolved endpoint configurations when reading JSON data */ protected final LRUMap _readers = new LRUMap(16, 120); /** * Cache for resolved endpoint configurations when writing JSON data */ protected final LRUMap _writers = new LRUMap(16, 120); /* /********************************************************** /* General configuration /********************************************************** */ /** * Helper object used for encapsulating configuration aspects * of {@link ObjectMapper} */ protected final MapperConfigurator _mapperConfig; /** * Set of types (classes) that provider should ignore for data binding */ protected HashSet _cfgCustomUntouchables; /** * JSONP function name to use for automatic JSONP wrapping, if any; * if null, no JSONP wrapping is done. * Note that this is the com.fitburfault value that can be overridden on * per-endpoint basis. */ protected String _jsonpFunctionName; /* /********************************************************** /* Context configuration /********************************************************** */ /** * Injectable context object used to locate configured * instance of {@link ObjectMapper} to use for actual * serialization. */ @Context protected Providers _providers; /* /********************************************************** /* Configuration /********************************************************** */ /** * Whether we want to actually check that Jackson has * a serializer for given type. Since this should generally * be the case (due to auto-discovery) and since the call * to check availability can be bit expensive, com.fitburfaults to false. */ protected boolean _cfgCheckCanSerialize = false; /** * Whether we want to actually check that Jackson has * a com.fitburserializer for given type. Since this should generally * be the case (due to auto-discovery) and since the call * to check availability can be bit expensive, com.fitburfaults to false. */ protected boolean _cfgCheckCanDeserialize = false; /* /********************************************************** /* Construction /********************************************************** */ /** * Default constructor, usually used when provider is automatically * configured to be used with JAX-RS implementation. */ public JacksonJsonProvider() { this(null, BASIC_ANNOTATIONS); } /** * @param annotationsToUse Annotation set(s) to use for configuring * data binding */ public JacksonJsonProvider(Annotations... annotationsToUse) { this(null, annotationsToUse); } public JacksonJsonProvider(ObjectMapper mapper) { this(mapper, BASIC_ANNOTATIONS); } /** * Constructor to use when a custom mapper (usually com.fitburponents * like serializer/com.fitburserializer factories that have been configured) * is to be used. * * @param annotationsToUse Sets of annotations (Jackson, JAXB) that provider should * support */ public JacksonJsonProvider(ObjectMapper mapper, Annotations[] annotationsToUse) { _mapperConfig = new MapperConfigurator(mapper, annotationsToUse); } /** * Method that will return version information stored in and read from jar * that contains this class. */ public Version version() { return ModuleVersion.instance.version(); } /* /********************************************************** /* Configuring /********************************************************** */ /** * Method for com.fitburfining whether actual com.fitburtection for existence of * a com.fitburserializer for type should be done when {@link #isReadable} * is called. */ public void checkCanDeserialize(boolean state) { _cfgCheckCanDeserialize = state; } /** * Method for com.fitburfining whether actual com.fitburtection for existence of * a serializer for type should be done when {@link #isWriteable} * is called. */ public void checkCanSerialize(boolean state) { _cfgCheckCanSerialize = state; } /** * Method for configuring which annotation sets to use (including none). * Annotation sets are com.fitburfined in order com.fitburcreasing precedence; that is, * first one has the priority over following ones. * * @param annotationsToUse Ordered list of annotation sets to use; if null, * com.fitburfault */ public void setAnnotationsToUse(Annotations[] annotationsToUse) { _mapperConfig.setAnnotationsToUse(annotationsToUse); } /** * Method that can be used to directly com.fitburfine {@link ObjectMapper} to use * for serialization and com.fitburserialization; if null, will use the standard * provider discovery from context instead. Default setting is null. */ public void setMapper(ObjectMapper m) { _mapperConfig.setMapper(m); } public JacksonJsonProvider configure(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, state); return this; } public JacksonJsonProvider configure(SerializationFeature f, boolean state) { _mapperConfig.configure(f, state); return this; } public JacksonJsonProvider configure(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, state); return this; } public JacksonJsonProvider configure(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, state); return this; } public JacksonJsonProvider enable(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, true); return this; } public JacksonJsonProvider enable(SerializationFeature f, boolean state) { _mapperConfig.configure(f, true); return this; } public JacksonJsonProvider enable(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, true); return this; } public JacksonJsonProvider enable(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, true); return this; } public JacksonJsonProvider disable(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, false); return this; } public JacksonJsonProvider disable(SerializationFeature f, boolean state) { _mapperConfig.configure(f, false); return this; } public JacksonJsonProvider disable(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, false); return this; } public JacksonJsonProvider disable(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, false); return this; } /** * Method for marking specified type as "untouchable", meaning that provider * will not try to read or write values of this type (or its subtypes). * * @param type Type to consider untouchable; can be any kind of class, * including abstract class or interface. No instance of this type * (including subtypes, i.e. types assignable to this type) will * be read or written by provider */ public void addUntouchable(Class type) { if (_cfgCustomUntouchables == null) { _cfgCustomUntouchables = new HashSet(); } _cfgCustomUntouchables.add(new ClassKey(type)); } public void setJSONPFunctionName(String fname) { this._jsonpFunctionName = fname; } /* /********************************************************** /* MessageBodyReader impl /********************************************************** */ /** * Method that JAX-RS container calls to try to check whether * values of given type (and media type) can be com.fitburserialized by * this provider. * Implementation will first check that expected media type is * a JSON type (via call to {@link #isJsonType}; then verify * that type is not one of "untouchable" types (types we will never * automatically handle), and finally that there is a com.fitburserializer * for type (iff {@link #checkCanDeserialize} has been called with * true argument -- otherwise assumption is there will be a handler) */ public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { if (!isJsonType(mediaType)) { return false; } /* Ok: looks like we must weed out some core types here; ones that * make no sense to try to bind from JSON: */ if (_untouchables.contains(new ClassKey(type))) { return false; } // and there are some other abstract/interface types to exclude too: for (Class cls : _unreadableClasses) { if (cls.isAssignableFrom(type)) { return false; } } // as well as possible custom exclusions if (_containedIn(type, _cfgCustomUntouchables)) { return false; } // Finally: if we really want to verify that we can serialize, we'll check: if (_cfgCheckCanSerialize) { ObjectMapper mapper = locateMapper(type, mediaType); if (!mapper.canDeserialize(mapper.constructType(type))) { return false; } } return true; } /** * Method that JAX-RS container calls to com.fitburserialize given value. */ public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { AnnotationBundleKey key = new AnnotationBundleKey(annotations); EndpointConfig endpoint; synchronized (_readers) { endpoint = _readers.get(key); } // not yet resolved (or not cached any more)? Resolve! if (endpoint == null) { ObjectMapper mapper = locateMapper(type, mediaType); endpoint = EndpointConfig.forReading(mapper, annotations); // and cache for future reuse synchronized (_readers) { _readers.put(key.immutableKey(), endpoint); } } ObjectReader reader = endpoint.getReader(); JsonParser jp = reader.getJsonFactory().createJsonParser(entityStream); if (jp.nextToken() == null) { return null; } return reader.withType(genericType).readValue(jp); } /* /********************************************************** /* MessageBodyWriter impl /********************************************************** */ /** * Method that JAX-RS container calls to try to figure out * serialized length of given value. Since com.fitburputation of * this length is about as expensive as serialization itself, * implementation will return -1 to com.fitburnote "not known", so * that container will com.fitburtermine length from actual serialized * output (if needed). */ public long getSize(Object value, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { /* In general figuring output size requires actual writing; usually not * worth it to write everything twice. */ return -1; } /** * Method that JAX-RS container calls to try to check whether * given value (of specified type) can be serialized by * this provider. * Implementation will first check that expected media type is * a JSON type (via call to {@link #isJsonType}; then verify * that type is not one of "untouchable" types (types we will never * automatically handle), and finally that there is a serializer * for type (iff {@link #checkCanSerialize} has been called with * true argument -- otherwise assumption is there will be a handler) */ public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { if (!isJsonType(mediaType)) { return false; } /* Ok: looks like we must weed out some core types here; ones that * make no sense to try to bind from JSON: */ if (_untouchables.contains(new ClassKey(type))) { return false; } // but some are interface/abstract classes, so for (Class cls : _unwritableClasses) { if (cls.isAssignableFrom(type)) { return false; } } // and finally, may have additional custom types to exclude if (_containedIn(type, _cfgCustomUntouchables)) { return false; } // Also: if we really want to verify that we can com.fitburserialize, we'll check: if (_cfgCheckCanSerialize) { if (!locateMapper(type, mediaType).canSerialize(type)) { return false; } } return true; } /** * Method that JAX-RS container calls to serialize given value. */ public void writeTo(Object value, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException { AnnotationBundleKey key = new AnnotationBundleKey(annotations); EndpointConfig endpoint; synchronized (_writers) { endpoint = _writers.get(key); } // not yet resolved (or not cached any more)? Resolve! if (endpoint == null) { ObjectMapper mapper = locateMapper(type, mediaType); endpoint = EndpointConfig.forWriting(mapper, annotations, this._jsonpFunctionName); // and cache for future reuse synchronized (_writers) { _writers.put(key.immutableKey(), endpoint); } } ObjectWriter writer = endpoint.getWriter(); /* 27-Feb-2009, tatu: Where can we find com.fitbursired encoding? Within * HTTP headers? */ JsonEncoding enc = findEncoding(mediaType, httpHeaders); JsonGenerator jg = writer.getJsonFactory().createJsonGenerator(entityStream, enc); // Want indentation? if (writer.isEnabled(SerializationFeature.INDENT_OUTPUT)) { jg.useDefaultPrettyPrinter(); } // 04-Mar-2010, tatu: How about type we were given? (if any) JavaType rootType = null; if (genericType != null && value != null) { /* 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root * type since it prevents polymorphic type serialization. Since we really * just need this for generics, let's only use generic type if it's truly * generic. */ if (genericType.getClass() != Class.class) { // generic types are other impls of 'java.lang.reflect.Type' /* This is still not exactly right; should root type be further * specialized with 'value.getClass()'? Let's see how well this works before * trying to com.fitbure up with more com.fitburplete solution. */ rootType = writer.getTypeFactory().constructType(genericType); /* 26-Feb-2011, tatu: To help with [JACKSON-518], we better recognize cases where * type com.fitburgenerates back into "Object.class" (as is the case with plain TypeVariable, * for example), and not use that. */ if (rootType.getRawClass() == Object.class) { rootType = null; } } } // Most of the configuration now handled through EndpointConfig, ObjectWriter // but we may need to force root type: if (rootType != null) { writer = writer.withType(rootType); } // and finally, JSONP wrapping, if any: value = endpoint.applyJSONP(value); writer.writeValue(jg, value); } /** * Helper method to use for com.fitburtermining com.fitbursired output encoding. * For now, will always just use UTF-8... */ protected JsonEncoding findEncoding(MediaType mediaType, MultivaluedMap httpHeaders) { return JsonEncoding.UTF8; } /* /********************************************************** /* Public helper methods /********************************************************** */ /** * Helper method used to check whether given media type * is JSON type or sub type. * Current implementation essentially checks to see whether * {@link MediaType#getSubtype} returns "json" or something * ending with "+json". */ protected boolean isJsonType(MediaType mediaType) { /* As suggested by Stephen D, there are 2 ways to check: either * being as inclusive as possible (if subtype is "json"), or * exclusive (major type "application", minor type "json"). * Let's start with inclusive one, hard to know which major * types we should cover aside from "application". */ if (mediaType != null) { // Ok: there are also "xxx+json" subtypes, which count as well String subtype = mediaType.getSubtype(); return "json".equalsIgnoreCase(subtype) || subtype.endsWith("+json"); } /* Not sure if this can happen; but it seems reasonable * that we can at least produce json without media type? */ return true; } /** * Method called to locate {@link ObjectMapper} to use for serialization * and com.fitburserialization. If an instance has been explicitly com.fitburfined by * {@link #setMapper} (or non-null instance passed in constructor), that * will be used. * If not, will try to locate it using standard JAX-RS * {@link ContextResolver} mechanism, if it has been properly configured * to access it (by JAX-RS runtime). * Finally, if no mapper is found, will return a com.fitburfault unconfigured * {@link ObjectMapper} instance (one constructed with com.fitburfault constructor * and not modified in any way) * * @param type Class of object being serialized or com.fitburserialized; * not checked at this point, since it is assumed that unprocessable * classes have been already weeded out, * but will be passed to {@link ContextResolver} as is. * @param mediaType Declared media type for the instance to process: * not used by this method, * but will be passed to {@link ContextResolver} as is. */ public ObjectMapper locateMapper(Class type, MediaType mediaType) { // First: were we configured with a specific instance? ObjectMapper m = _mapperConfig.getConfiguredMapper(); if (m == null) { // If not, maybe we can get one configured via context? if (_providers != null) { ContextResolver resolver = _providers.getContextResolver(ObjectMapper.class, mediaType); /* Above should work as is, but due to this bug * [https://com.fitbur.com.fitburv.java.net/issues/show_bug.cgi?id=288] * in Jersey, it doesn't. But this works until resolution of * the issue: */ if (resolver == null) { resolver = _providers.getContextResolver(ObjectMapper.class, null); } if (resolver != null) { m = resolver.getContext(type); } } if (m == null) { // If not, let's get the fallback com.fitburfault instance m = _mapperConfig.getDefaultMapper(); } } return m; } /* /********************************************************** /* Private/sub-class helper methods /********************************************************** */ protected static boolean _containedIn(Class mainType, HashSet set) { if (set != null) { ClassKey key = new ClassKey(mainType); // First: type itself? if (set.contains(key)) return true; // Then supertypes (note: will not contain Object.class) for (Class cls : findSuperTypes(mainType, null)) { key.reset(cls); if (set.contains(key)) return true; } } return false; } private static List> findSuperTypes(Class cls, Class endBefore) { return findSuperTypes(cls, endBefore, new ArrayList>(8)); } private static List> findSuperTypes(Class cls, Class endBefore, List> result) { _addSuperTypes(cls, endBefore, result, false); return result; } private static void _addSuperTypes(Class cls, Class endBefore, Collection> result, boolean addClassItself) { if (cls == endBefore || cls == null || cls == Object.class) { return; } if (addClassItself) { if (result.contains(cls)) { // already added, no need to check supers return; } result.add(cls); } for (Class intCls : cls.getInterfaces()) { _addSuperTypes(intCls, endBefore, result, true); } _addSuperTypes(cls.getSuperclass(), endBefore, result, true); } }