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

org.codehaus.jackson.jaxrs.JacksonJsonProvider Maven / Gradle / Ivy

Go to download

Ehcache is an open source, standards-based cache used to boost performance, offload the database and simplify scalability. Ehcache is robust, proven and full-featured and this has made it the most widely-used Java-based cache.

There is a newer version: 2.10.9.2
Show newest version
package org.codehaus.jackson.jaxrs;

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 org.codehaus.jackson.*;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonView;
import org.codehaus.jackson.map.type.ClassKey;
import org.codehaus.jackson.map.util.ClassUtil;
import org.codehaus.jackson.map.util.JSONPObject;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.util.VersionUtil;

/**
 * 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 defining JAX-RS Provider that returns {@link ObjectMapper}s. *
  • By doing none of above, in which case a default 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 default 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 default to use both Jackson and JAXB annotations for configuration * (base class when used as-is defaults to using just Jackson annotations) * * @author Tatu Saloranta */ @Provider @Consumes({MediaType.APPLICATION_JSON, "text/json"}) @Produces({MediaType.APPLICATION_JSON, "text/json"}) public class JacksonJsonProvider implements MessageBodyReader, MessageBodyWriter, Versioned // since 1.6 { /** * Default annotation sets to use, if not explicitly defined 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() defined, * 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(byte[].class)); _untouchables.add(new ClassKey(char[].class)); // 24-Apr-2009, tatu: String is an edge case... let's leave it out _untouchables.add(new ClassKey(String.class)); // Then core JAX-RS things _untouchables.add(new ClassKey(StreamingOutput.class)); _untouchables.add(new ClassKey(Response.class)); } /** * These are classes that we never use for reading * (never try to deserialize 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 }; /** * 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 * * @since 1.5 */ protected HashSet _cfgCustomUntouchables; /** * JSONP function name to use for automatic JSONP wrapping, if any; * if null, no JSONP wrapping is done. */ 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, defaults to false. */ protected boolean _cfgCheckCanSerialize = false; /** * Whether we want to actually check that Jackson has * a deserializer 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, defaults 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 components * like serializer/deserializer factories that have been configured) * is to be used. * * @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. * * @since 1.6 */ @Override public Version version() { return VersionUtil.versionFor(getClass()); } /* /********************************************************** /* Configuring /********************************************************** */ /** * Method for defining whether actual detection for existence of * a deserializer for type should be done when {@link #isReadable} * is called. */ public void checkCanDeserialize(boolean state) { _cfgCheckCanDeserialize = state; } /** * Method for defining whether actual detection 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 defined in order decreasing precedence; that is, * first one has the priority over following ones. * * @param annotationsToUse Ordered list of annotation sets to use; if null, * default */ public void setAnnotationsToUse(Annotations[] annotationsToUse) { _mapperConfig.setAnnotationsToUse(annotationsToUse); } /** * Method that can be used to directly define {@link ObjectMapper} to use * for serialization and deserialization; 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(DeserializationConfig.Feature f, boolean state) { _mapperConfig.configure(f, state); return this; } public JacksonJsonProvider configure(SerializationConfig.Feature 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(DeserializationConfig.Feature f, boolean state) { _mapperConfig.configure(f, true); return this; } public JacksonJsonProvider enable(SerializationConfig.Feature 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(DeserializationConfig.Feature f, boolean state) { _mapperConfig.configure(f, false); return this; } public JacksonJsonProvider disable(SerializationConfig.Feature 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 * * @since 1.5 */ 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 deserialized 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 deserializer * for type (iff {@link #checkCanDeserialize} has been called with * true argument -- otherwise assumption is there will be a handler) */ @Override 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 deserialize given * value. */ @Override public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { ObjectMapper mapper = locateMapper(type, mediaType); JsonParser jp = mapper.getJsonFactory().createJsonParser(entityStream); /* Important: we are NOT to close the underlying stream after * mapping, so we need to instruct parser: */ jp.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); return mapper.readValue(jp, mapper.constructType(genericType)); } /* /********************************************************** /* MessageBodyWriter impl /********************************************************** */ /** * Method that JAX-RS container calls to try to figure out * serialized length of given value. Since computation of * this length is about as expensive as serialization itself, * implementation will return -1 to denote "not known", so * that container will determine length from actual serialized * output (if needed). */ @Override 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) */ @Override 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 deserialize, 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. */ @Override public void writeTo(Object value, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException { /* 27-Feb-2009, tatu: Where can we find desired encoding? Within * HTTP headers? */ ObjectMapper mapper = locateMapper(type, mediaType); JsonEncoding enc = findEncoding(mediaType, httpHeaders); JsonGenerator jg = mapper.getJsonFactory().createJsonGenerator(entityStream, enc); jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); // Want indentation? if (mapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.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 come up with more complete solution. */ rootType = mapper.getTypeFactory().constructType(genericType); /* 26-Feb-2011, tatu: To help with [JACKSON-518], we better recognize cases where * type degenerates back into "Object.class" (as is the case with plain TypeVariable, * for example), and not use that. */ if (rootType.getRawClass() == Object.class) { rootType = null; } } } // [JACKSON-578]: Allow use of @JsonView in resource methods. Class viewToUse = null; if (annotations != null && annotations.length > 0) { viewToUse = _findView(mapper, annotations); } if (viewToUse != null) { // TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer) ObjectWriter viewWriter = mapper.viewWriter(viewToUse); // [JACKSON-245] Allow automatic JSONP wrapping if (_jsonpFunctionName != null) { viewWriter.writeValue(jg, new JSONPObject(this._jsonpFunctionName, value, rootType)); } else if (rootType != null) { // TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer) mapper.typedWriter(rootType).withView(viewToUse).writeValue(jg, value); } else { viewWriter.writeValue(jg, value); } } else { // [JACKSON-245] Allow automatic JSONP wrapping if (_jsonpFunctionName != null) { mapper.writeValue(jg, new JSONPObject(this._jsonpFunctionName, value, rootType)); } else if (rootType != null) { // TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer) mapper.typedWriter(rootType).writeValue(jg, value); } else { mapper.writeValue(jg, value); } } } /** * Helper method to use for determining desired output encoding. * For now, will always just use UTF-8... * * @since 1.7.0 */ 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 deserialization. If an instance has been explicitly defined 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 default unconfigured * {@link ObjectMapper} instance (one constructed with default constructor * and not modified in any way) * * @param type Class of object being serialized or deserialized; * 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://jersey.dev.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 default 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 : ClassUtil.findSuperTypes(mainType, null)) { key.reset(cls); if (set.contains(key)) return true; } } return false; } protected Class _findView(ObjectMapper mapper, Annotation[] annotations) throws JsonMappingException { /* Ideally we would try to use AnnotationIntrospector here, but current * API expects to get a method/field, not bunch of annotations, * so we would need to change it a bit. * Let's not bother, then, since this works well for now. */ for (Annotation annotation : annotations) { if (annotation.annotationType().isAssignableFrom(JsonView.class)) { JsonView jsonView = (JsonView) annotation; Class[] views = jsonView.value(); if (views.length > 1) { StringBuilder s = new StringBuilder("Multiple @JsonView's can not be used on a JAX-RS method. Got "); s.append(views.length).append(" views: "); for (int i = 0; i < views.length; i++) { if (i > 0) { s.append(", "); } s.append(views[i].getName()); } throw new JsonMappingException(s.toString()); } return views[0]; } } return null; } }