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);
}
}