[jackson-jaxrs-providers] 05/162: first version with full json provider
Timo Aaltonen
tjaalton at moszumanska.debian.org
Mon Sep 8 22:16:22 UTC 2014
This is an automated email from the git hooks/post-receive script.
tjaalton pushed a commit to branch master
in repository jackson-jaxrs-providers.
commit e8d9040cb51c5649a886c97a78d0a8554b24714b
Author: Tatu Saloranta <tsaloranta at gmail.com>
Date: Mon Feb 4 14:26:31 2013 -0800
first version with full json provider
---
.../fasterxml/jackson/jaxrs/json/Annotations.java | 21 +
.../jaxrs/json/JacksonJaxbJsonProvider.java | 64 ++
.../jackson/jaxrs/json/JacksonJsonProvider.java | 724 +++++++++++++++++++++
.../jaxrs/json/JsonMappingExceptionMapper.java | 18 +
.../jaxrs/json/JsonParseExceptionMapper.java | 18 +
.../jackson/jaxrs/json/PackageVersion.java.in | 20 +
.../jaxrs/json/annotation/EndpointConfig.java | 214 ++++++
.../jackson/jaxrs/json/annotation/JSONP.java | 94 +++
.../jaxrs/json/annotation/JacksonFeatures.java | 39 ++
.../jaxrs/json/annotation/package-info.java | 5 +
.../jackson/jaxrs/json/cfg/MapperConfigurator.java | 180 +++++
.../fasterxml/jackson/jaxrs/json/package-info.java | 21 +
.../jaxrs/json/util/AnnotationBundleKey.java | 110 ++++
.../jackson/jaxrs/json/util/ClassKey.java | 92 +++
.../fasterxml/jackson/jaxrs/json/util/LRUMap.java | 26 +
.../services/javax.ws.rs.ext.MessageBodyReader | 1 +
.../services/javax.ws.rs.ext.MessageBodyWriter | 1 +
.../jackson/jaxrs/json/JaxrsTestBase.java | 92 +++
.../jackson/jaxrs/json/TestCanDeserialize.java | 43 ++
.../jackson/jaxrs/json/TestCanSerialize.java | 46 ++
.../jackson/jaxrs/json/TestJacksonFeatures.java | 111 ++++
.../fasterxml/jackson/jaxrs/json/TestJsonView.java | 46 ++
.../jackson/jaxrs/json/TestJsonpWrapping.java | 27 +
.../fasterxml/jackson/jaxrs/json/TestRootType.java | 45 ++
.../jackson/jaxrs/json/TestStreamingOutput.java | 33 +
.../jackson/jaxrs/json/TestUntouchables.java | 80 +++
.../fasterxml/jackson/jaxrs/json/TestVersions.java | 28 +
.../jaxrs/json/util/TestAnnotationBundleKey.java | 56 ++
28 files changed, 2255 insertions(+)
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/Annotations.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/Annotations.java
new file mode 100644
index 0000000..cd536d8
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/Annotations.java
@@ -0,0 +1,21 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+/**
+ * Enumeration that defines standard annotation sets available for configuring
+ * data binding aspects.
+ */
+public enum Annotations {
+ /**
+ * Standard Jackson annotations, defined in Jackson core and databind
+ * packages
+ */
+ JACKSON,
+
+ /**
+ * Standard JAXB annotations, used in a way that approximates expected
+ * definitions (since JAXB defines XML aspects, not all features map
+ * well to JSON handling)
+ */
+ JAXB
+ ;
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJaxbJsonProvider.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJaxbJsonProvider.java
new file mode 100644
index 0000000..d6b2473
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJaxbJsonProvider.java
@@ -0,0 +1,64 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.ext.Provider;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * JSON content type provider automatically configured to use both Jackson
+ * and JAXB annotations (in that order of priority). Otherwise functionally
+ * same as {@link JacksonJsonProvider}.
+ *<p>
+ * Typical usage pattern is to just instantiate instance of this
+ * provider for JAX-RS and use as is: this will use both Jackson and
+ * JAXB annotations (with Jackson annotations having priority).
+ *<p>
+ * Note: class annotations are duplicated from super class, since it
+ * is not clear whether JAX-RS implementations are required to
+ * check settings of super-classes. It is important to keep annotations
+ * in sync if changed.
+ */
+ at Provider
+ at Consumes(MediaType.WILDCARD) // NOTE: required to support "non-standard" JSON variants
+ at Produces(MediaType.WILDCARD)
+public class JacksonJaxbJsonProvider extends JacksonJsonProvider {
+ /**
+ * Default annotation sets to use, if not explicitly defined during
+ * construction: use Jackson annotations if found; if not, use
+ * JAXB annotations as fallback.
+ */
+ public final static Annotations[] DEFAULT_ANNOTATIONS = {
+ Annotations.JACKSON, Annotations.JAXB
+ };
+
+ /**
+ * Default constructor, usually used when provider is automatically
+ * configured to be used with JAX-RS implementation.
+ */
+ public JacksonJaxbJsonProvider()
+ {
+ this(null, DEFAULT_ANNOTATIONS);
+ }
+
+ /**
+ * @param annotationsToUse Annotation set(s) to use for configuring
+ * data binding
+ */
+ public JacksonJaxbJsonProvider(Annotations... annotationsToUse)
+ {
+ this(null, annotationsToUse);
+ }
+
+ /**
+ * Constructor to use when a custom mapper (usually components
+ * like serializer/deserializer factories that have been configured)
+ * is to be used.
+ */
+ public JacksonJaxbJsonProvider(ObjectMapper mapper, Annotations[] annotationsToUse)
+ {
+ super(mapper, annotationsToUse);
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java
new file mode 100644
index 0000000..a388b3f
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java
@@ -0,0 +1,724 @@
+package com.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.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.util.LRUMap;
+import com.fasterxml.jackson.jaxrs.json.annotation.EndpointConfig;
+import com.fasterxml.jackson.jaxrs.json.cfg.MapperConfigurator;
+import com.fasterxml.jackson.jaxrs.json.util.AnnotationBundleKey;
+import com.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).
+ *<p>
+ * Actual data binding functionality is implemented by {@link ObjectMapper}:
+ * mapper to use can be configured in multiple ways:
+ * <ul>
+ * <li>By explicitly passing mapper to use in constructor
+ * <li>By explictly setting mapper to use by {@link #setMapper}
+ * <li>By defining JAX-RS <code>Provider</code> that returns {@link ObjectMapper}s.
+ * <li>By doing none of above, in which case a default mapper instance is
+ * constructed (and configured if configuration methods are called)
+ * </ul>
+ * 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.
+ *<p>
+ * 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 <b>NOT</b> used, but the
+ * resulting Mapper is used as configured.
+ *<p>
+ * 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
+ */
+ at Provider
+ at Consumes(MediaType.WILDCARD) // NOTE: required to support "non-standard" JSON variants
+ at Produces(MediaType.WILDCARD)
+public class JacksonJsonProvider
+ implements
+ MessageBodyReader<Object>,
+ MessageBodyWriter<Object>,
+ Versioned
+{
+ /**
+ * 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.
+ *<p>
+ * (why ClassKey? since plain old Class has no hashCode() defined,
+ * lookups are painfully slow)
+ */
+ public final static HashSet<ClassKey> _untouchables = new HashSet<ClassKey>();
+ 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/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 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
+ };
+
+ /*
+ /**********************************************************
+ /* Bit of caching
+ /**********************************************************
+ */
+
+ /**
+ * Cache for resolved endpoint configurations when reading JSON data
+ */
+ protected final LRUMap<AnnotationBundleKey, EndpointConfig> _readers
+ = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120);
+
+ /**
+ * Cache for resolved endpoint configurations when writing JSON data
+ */
+ protected final LRUMap<AnnotationBundleKey, EndpointConfig> _writers
+ = new LRUMap<AnnotationBundleKey, EndpointConfig>(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<ClassKey> _cfgCustomUntouchables;
+
+ /**
+ * JSONP function name to use for automatic JSONP wrapping, if any;
+ * if null, no JSONP wrapping is done.
+ * Note that this is the default 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, 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.
+ *
+ * @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 PackageVersion.VERSION;
+ }
+
+ /*
+ /**********************************************************
+ /* 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(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<ClassKey>();
+ }
+ _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)
+ */
+ 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.
+ */
+ public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,String> 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.getFactory().createParser(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 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).
+ */
+ 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 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.
+ */
+ public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
+ MultivaluedMap<String,Object> 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 desired encoding? Within
+ * HTTP headers?
+ */
+ JsonEncoding enc = findEncoding(mediaType, httpHeaders);
+ JsonGenerator jg = writer.getFactory().createGenerator(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 come up with more complete solution.
+ */
+ rootType = writer.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;
+ }
+ }
+ }
+ // 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 determining desired output encoding.
+ * For now, will always just use UTF-8...
+ */
+ protected JsonEncoding findEncoding(MediaType mediaType, MultivaluedMap<String,Object> 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<ObjectMapper> 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<ClassKey> 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<Class<?>> findSuperTypes(Class<?> cls, Class<?> endBefore)
+ {
+ return findSuperTypes(cls, endBefore, new ArrayList<Class<?>>(8));
+ }
+
+ private static List<Class<?>> findSuperTypes(Class<?> cls, Class<?> endBefore, List<Class<?>> result)
+ {
+ _addSuperTypes(cls, endBefore, result, false);
+ return result;
+ }
+
+ private static void _addSuperTypes(Class<?> cls, Class<?> endBefore, Collection<Class<?>> 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);
+ }
+
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JsonMappingExceptionMapper.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JsonMappingExceptionMapper.java
new file mode 100644
index 0000000..7d07679
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JsonMappingExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import com.fasterxml.jackson.databind.JsonMappingException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Implementation if {@link ExceptionMapper} to send down a "400 Bad Request"
+ * response in the event that unmappable JSON is received.
+ */
+ at Provider
+public class JsonMappingExceptionMapper implements ExceptionMapper<JsonMappingException> {
+ public Response toResponse(JsonMappingException exception) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(exception.getMessage()).type("text/plain").build();
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JsonParseExceptionMapper.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JsonParseExceptionMapper.java
new file mode 100644
index 0000000..429caf1
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JsonParseExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import com.fasterxml.jackson.core.JsonParseException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Implementation of {@link ExceptionMapper} to send down a "400 Bad Request"
+ * in the event unparsable JSON is received.
+ */
+ at Provider
+public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
+ public Response toResponse(JsonParseException exception) {
+ return Response.status(Response.Status.BAD_REQUEST).entity(exception.getMessage()).type("text/plain").build();
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/PackageVersion.java.in b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/PackageVersion.java.in
new file mode 100644
index 0000000..7860aa1
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/PackageVersion.java.in
@@ -0,0 +1,20 @@
+package @package@;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Automatically generated from PackageVersion.java.in during
+ * packageVersion-generate execution of maven-replacer-plugin in
+ * pom.xml.
+ */
+public final class PackageVersion implements Versioned {
+ public final static Version VERSION = VersionUtil.parseVersion(
+ "@projectversion@", "@projectgroupid@", "@projectartifactid@");
+
+ @Override
+ public Version version() {
+ return VERSION;
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/EndpointConfig.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/EndpointConfig.java
new file mode 100644
index 0000000..ea7d099
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/EndpointConfig.java
@@ -0,0 +1,214 @@
+package com.fasterxml.jackson.jaxrs.json.annotation;
+
+import java.lang.annotation.Annotation;
+
+import com.fasterxml.jackson.annotation.*;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.util.JSONPObject;
+import com.fasterxml.jackson.databind.util.JSONWrappedObject;
+
+/**
+ * Container class for figuring out annotation-based configuration
+ * for JAX-RS end points.
+ */
+public class EndpointConfig
+{
+ // // General configuration
+
+ protected Class<?> _activeView;
+
+ protected String _rootName;
+
+ // // Deserialization-only config
+
+ protected DeserializationFeature[] _deserEnable;
+ protected DeserializationFeature[] _deserDisable;
+
+ protected ObjectReader _reader;
+
+ // // Serialization-only config
+
+ protected JSONP.Def _jsonp;
+
+ protected SerializationFeature[] _serEnable;
+ protected SerializationFeature[] _serDisable;
+
+ protected ObjectWriter _writer;
+
+ /*
+ /**********************************************************
+ /* Construction
+ /**********************************************************
+ */
+
+ protected EndpointConfig() { }
+
+ public static EndpointConfig forReading(ObjectMapper mapper, Annotation[] annotations)
+ {
+ return new EndpointConfig()
+ .add(annotations, false)
+ .initReader(mapper);
+ }
+
+ public static EndpointConfig forWriting(ObjectMapper mapper, Annotation[] annotations,
+ String defaultJsonpMethod)
+ {
+ EndpointConfig config = new EndpointConfig();
+ if (defaultJsonpMethod != null) {
+ config._jsonp = new JSONP.Def(defaultJsonpMethod);
+ }
+ return config
+ .add(annotations, true)
+ .initWriter(mapper)
+ ;
+ }
+
+ protected EndpointConfig add(Annotation[] annotations, boolean forWriting)
+ {
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ Class<?> type = annotation.annotationType();
+ if (type == JSONP.class) {
+ if (forWriting) {
+ _jsonp = new JSONP.Def((JSONP) annotation);
+ }
+ } else if (type == JsonView.class) {
+ // Can only use one view; but if multiple defined, use first (no exception)
+ Class<?>[] views = ((JsonView) annotation).value();
+ _activeView = (views.length > 0) ? views[0] : null;
+ } else if (type == JsonRootName.class) {
+ _rootName = ((JsonRootName) annotation).value();
+ } else if (type == JacksonFeatures.class) {
+ JacksonFeatures feats = (JacksonFeatures) annotation;
+ if (forWriting) {
+ _serEnable = nullIfEmpty(feats.serializationEnable());
+ _serDisable = nullIfEmpty(feats.serializationDisable());
+ } else {
+ _deserEnable = nullIfEmpty(feats.deserializationEnable());
+ _deserDisable = nullIfEmpty(feats.deserializationDisable());
+ }
+ } else if (type == JacksonAnnotationsInside.class) {
+ // skip; processed below (in parent), so encountering here is of no use
+ } else {
+ // For all unrecognized types, check meta-annotation(s) to see if they are bundles
+ JacksonAnnotationsInside inside = type.getAnnotation(JacksonAnnotationsInside.class);
+ if (inside != null) {
+ add(type.getAnnotations(), forWriting);
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ protected EndpointConfig initReader(ObjectMapper mapper)
+ {
+ // first common config
+ if (_activeView != null) {
+ _reader = mapper.readerWithView(_activeView);
+ } else {
+ _reader = mapper.reader();
+ }
+
+ if (_rootName != null) {
+ _reader = _reader.withRootName(_rootName);
+ }
+ // Then deser features
+ if (_deserEnable != null) {
+ _reader = _reader.withFeatures(_deserEnable);
+ }
+ if (_deserDisable != null) {
+ _reader = _reader.withoutFeatures(_deserDisable);
+ }
+ /* Important: we are NOT to close the underlying stream after
+ * mapping, so we need to instruct parser:
+ */
+ _reader.getFactory().disable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
+
+ return this;
+ }
+
+ protected EndpointConfig initWriter(ObjectMapper mapper)
+ {
+ // first common config
+ if (_activeView != null) {
+ _writer = mapper.writerWithView(_activeView);
+ } else {
+ _writer = mapper.writer();
+ }
+ if (_rootName != null) {
+ _writer = _writer.withRootName(_rootName);
+ }
+ // Then features
+ if (_serEnable != null) {
+ _writer = _writer.withFeatures(_serEnable);
+ }
+ if (_serDisable != null) {
+ _writer = _writer.withoutFeatures(_serDisable);
+ }
+ // then others
+
+ // Finally: couple of features we always set
+
+ /* Important: we are NOT to close the underlying stream after
+ * mapping, so we need to instruct parser:
+ */
+ _writer.getFactory().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
+
+ return this;
+ }
+
+ /*
+ /**********************************************************
+ /* Accessors
+ /**********************************************************
+ */
+
+ public ObjectReader getReader() {
+ if (_reader == null) { // sanity check, should never happen
+ throw new IllegalStateException();
+ }
+ return _reader;
+ }
+
+ public ObjectWriter getWriter() {
+ if (_writer == null) { // sanity check, should never happen
+ throw new IllegalStateException();
+ }
+ return _writer;
+ }
+
+ /**
+ * Method that will add JSONP wrapper object, if and as
+ * configured by collected annotations.
+ */
+ public Object applyJSONP(Object value)
+ {
+ if (_jsonp != null) {
+ // full prefix+suffix?
+ if (_jsonp.prefix != null || _jsonp.suffix != null) {
+ return new JSONWrappedObject(_jsonp.prefix, _jsonp.suffix, value);
+ }
+ if (_jsonp.method != null) {
+ return new JSONPObject(_jsonp.method, value);
+ }
+ }
+ return value;
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private static <T> T[] nullIfEmpty(T[] arg) {
+ if (arg == null || arg.length == 0) {
+ return null;
+ }
+ return arg;
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/JSONP.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/JSONP.java
new file mode 100644
index 0000000..620292c
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/JSONP.java
@@ -0,0 +1,94 @@
+package com.fasterxml.jackson.jaxrs.json.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/* Note: applicable to annotations to allow bundling (if support added
+ * to JAX-RS bundle itself), as well as methods to indicate that return
+ * type is to be wrapped.
+ * Other types are not allowed, since there is no current usage for those;
+ * input can't be wrapped (so no need for parameters); fields are not
+ * exposed through JAX-RS; and we do not allow 'default wrapping' for
+ * types.
+ *<p>
+ * Note on properties: if either {@link #prefix()} or {@link #suffix()}
+ * is non-empty, they are used as literal prefix and suffix to use.
+ * Otherwise {@link #value()} is used as the function name, followed
+ * by opening parenthesis, value, and closing parenthesis.
+ *<p>
+ * Example usage:
+ *<pre>
+ * class Wrapper {
+ * @JSONP("myFunc") public int value = 3;
+ * }
+ *</pre>
+ * would serialize as:
+ *<pre>
+ * myFunc({"value":3})
+ *<pre>
+ * whereas
+ *</pre>
+ *<pre>
+ * class Wrapper {
+ * @JSONP(prefix="call(", suffix=")+3") public int value = 1;
+ * }
+ *</pre>
+ * would serialize as:
+ *<pre>
+ * call({"value":1})+3
+ *<pre>
+ */
+ at Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD })
+ at Retention(RetentionPolicy.RUNTIME)
+ at com.fasterxml.jackson.annotation.JacksonAnnotation
+public @interface JSONP
+{
+ /**
+ * Method used for JSONP, unless {@link #prefix()} or
+ * {@link #suffix()} return non-empty Strings.
+ */
+ public String value() default "";
+
+ /**
+ * Prefix String used for JSONP if not empty: will be included
+ * verbatim before JSON value.
+ */
+ public String prefix() default "";
+
+ /**
+ * Suffix String used for JSONP if not empty: will be included
+ * verbatim after JSON value.
+ */
+ public String suffix() default "";
+
+ /**
+ * Helper class for encapsulating information from {@link JSONP}
+ * annotation instance.
+ */
+ public static class Def {
+ public final String method;
+ public final String prefix;
+ public final String suffix;
+
+ public Def(String m) {
+ method = m;
+ prefix = null;
+ suffix = null;
+ }
+
+ public Def(JSONP json) {
+ method = emptyAsNull(json.value());
+ prefix = emptyAsNull(json.prefix());
+ suffix = emptyAsNull(json.suffix());
+ }
+
+ private final static String emptyAsNull(String str) {
+ if (str == null || str.length() == 0) {
+ return null;
+ }
+ return str;
+ }
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/JacksonFeatures.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/JacksonFeatures.java
new file mode 100644
index 0000000..c5d0c9c
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/JacksonFeatures.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.jaxrs.json.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+/**
+ * Annotation that can be used enable and/or disable various
+ * features for <code>ObjectReader</code>s and <code>ObjectWriter</code>s.
+ */
+ at Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD })
+ at Retention(RetentionPolicy.RUNTIME)
+ at com.fasterxml.jackson.annotation.JacksonAnnotation
+public @interface JacksonFeatures
+{
+ /**
+ * Deserialization features to enable.
+ */
+ public DeserializationFeature[] deserializationEnable() default { };
+
+ /**
+ * Deserialization features to disable.
+ */
+ public DeserializationFeature[] deserializationDisable() default { };
+
+ /**
+ * Serialization features to enable.
+ */
+ public SerializationFeature[] serializationEnable() default { };
+
+ /**
+ * Serialization features to disable.
+ */
+ public SerializationFeature[] serializationDisable() default { };
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/package-info.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/package-info.java
new file mode 100644
index 0000000..62461ef
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Package that contains utility classes and methods for
+ * the JAX-RS JSON provider module.
+ */
+package com.fasterxml.jackson.jaxrs.json.annotation;
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/cfg/MapperConfigurator.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/cfg/MapperConfigurator.java
new file mode 100644
index 0000000..f23dd2a
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/cfg/MapperConfigurator.java
@@ -0,0 +1,180 @@
+package com.fasterxml.jackson.jaxrs.json.cfg;
+
+import java.util.*;
+
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
+import com.fasterxml.jackson.jaxrs.json.Annotations;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
+
+/**
+ * Helper class used to encapsulate details of configuring an
+ * {@link ObjectMapper} instance to be used for data binding, as
+ * well as accessing it.
+ */
+public class MapperConfigurator
+{
+ /**
+ * Mapper provider was constructed with if any, or that was constructed
+ * due to a call to explicitly configure mapper.
+ * If defined (explicitly or implicitly) it will be used, instead
+ * of using provider-based lookup.
+ */
+ protected ObjectMapper _mapper;
+
+ /**
+ * If no mapper was specified when constructed, and no configuration
+ * calls are made, a default mapper is constructed. The difference
+ * between default mapper and regular one is that default mapper
+ * is only used if no mapper is found via provider lookup.
+ */
+ protected ObjectMapper _defaultMapper;
+
+ /**
+ * Annotations set to use by default; overridden by explicit call
+ * to {@link #setAnnotationsToUse}
+ */
+ protected Annotations[] _defaultAnnotationsToUse;
+
+ /**
+ * To support optional dependency to Jackson JAXB annotations module
+ * (needed iff JAXB annotations are used for configuration)
+ */
+ protected Class<? extends AnnotationIntrospector> _jaxbIntrospectorClass;
+
+ /*
+ /**********************************************************
+ /* Construction
+ /**********************************************************
+ */
+
+ public MapperConfigurator(ObjectMapper mapper, Annotations[] defAnnotations)
+ {
+ _mapper = mapper;
+ _defaultAnnotationsToUse = defAnnotations;
+ }
+
+ /**
+ * Method that locates, configures and returns {@link ObjectMapper} to use
+ */
+ public synchronized ObjectMapper getConfiguredMapper() {
+ /* important: should NOT call mapper(); needs to return null
+ * if no instance has been passed or constructed
+ */
+ return _mapper;
+ }
+
+ public synchronized ObjectMapper getDefaultMapper() {
+ if (_defaultMapper == null) {
+ _defaultMapper = new ObjectMapper();
+ _setAnnotations(_defaultMapper, _defaultAnnotationsToUse);
+ }
+ return _defaultMapper;
+ }
+
+ /*
+ ***********************************************************
+ * Configuration methods
+ ***********************************************************
+ */
+
+ public synchronized void setMapper(ObjectMapper m) {
+ _mapper = m;
+ }
+
+ public synchronized void setAnnotationsToUse(Annotations[] annotationsToUse) {
+ _setAnnotations(mapper(), annotationsToUse);
+ }
+
+ public synchronized void configure(DeserializationFeature f, boolean state) {
+ mapper().configure(f, state);
+ }
+
+ public synchronized void configure(SerializationFeature f, boolean state) {
+ mapper().configure(f, state);
+ }
+
+ public synchronized void configure(JsonParser.Feature f, boolean state) {
+ mapper().configure(f, state);
+ }
+
+ public synchronized void configure(JsonGenerator.Feature f, boolean state) {
+ mapper().configure(f, state);
+ }
+
+ /*
+ ***********************************************************
+ * Internal methods
+ ***********************************************************
+ */
+
+ /**
+ * Helper method that will ensure that there is a configurable non-default
+ * mapper (constructing an instance if one didn't yet exit), and return
+ * that mapper.
+ */
+ protected ObjectMapper mapper()
+ {
+ if (_mapper == null) {
+ _mapper = new ObjectMapper();
+ _setAnnotations(_mapper, _defaultAnnotationsToUse);
+ }
+ return _mapper;
+ }
+
+ protected void _setAnnotations(ObjectMapper mapper, Annotations[] annotationsToUse)
+ {
+ AnnotationIntrospector intr;
+ if (annotationsToUse == null || annotationsToUse.length == 0) {
+ intr = AnnotationIntrospector.nopInstance();
+ } else {
+ intr = _resolveIntrospectors(annotationsToUse);
+ }
+ mapper.setAnnotationIntrospector(intr);
+ }
+
+
+ protected AnnotationIntrospector _resolveIntrospectors(Annotations[] annotationsToUse)
+ {
+ // Let's ensure there are no dups there first, filter out nulls
+ ArrayList<AnnotationIntrospector> intr = new ArrayList<AnnotationIntrospector>();
+ for (Annotations a : annotationsToUse) {
+ if (a != null) {
+ intr.add(_resolveIntrospector(a));
+ }
+ }
+ int count = intr.size();
+ if (count == 0) {
+ return AnnotationIntrospector.nopInstance();
+ }
+ AnnotationIntrospector curr = intr.get(0);
+ for (int i = 1, len = intr.size(); i < len; ++i) {
+ curr = AnnotationIntrospector.pair(curr, intr.get(i));
+ }
+ return curr;
+ }
+
+ protected AnnotationIntrospector _resolveIntrospector(Annotations ann)
+ {
+ switch (ann) {
+ case JACKSON:
+ return new JacksonAnnotationIntrospector();
+ case JAXB:
+ /* For this, need to use indirection just so that error occurs
+ * when we get here, and not when this class is being loaded
+ */
+ try {
+ if (_jaxbIntrospectorClass == null) {
+ _jaxbIntrospectorClass = JaxbAnnotationIntrospector.class;
+ }
+ return _jaxbIntrospectorClass.newInstance();
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to instantiate JaxbAnnotationIntrospector: "+e.getMessage(), e);
+ }
+ default:
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/package-info.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/package-info.java
new file mode 100644
index 0000000..72248c3
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/package-info.java
@@ -0,0 +1,21 @@
+/**
+ * Jackson-based JAX-RS provider that can automatically
+ * serialize and deserialize resources for
+ * JSON content type (MediaType).
+ *<p>
+ * Also continues supporting functionality, such as
+ * exception mappers that can simplify handling of
+ * error conditions.
+ *<p>
+ * There are two default provider classes:
+ *<ul>
+ * <li>{@link com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider} is the basic
+ * provider configured to use Jackson annotations
+ * </li>
+ * <li>{@link com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider} is extension
+ * of the basic provider, configured to additionally use JAXB annotations,
+ * in addition to (or in addition of, if so configured) Jackson annotations.
+ * </li>
+ * </ul>
+ */
+package com.fasterxml.jackson.jaxrs.json;
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/AnnotationBundleKey.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/AnnotationBundleKey.java
new file mode 100644
index 0000000..e06e01f
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/AnnotationBundleKey.java
@@ -0,0 +1,110 @@
+package com.fasterxml.jackson.jaxrs.json.util;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Helper class used to allow efficient caching of information,
+ * given a sequence of Annotations.
+ * This is mostly used for reusing introspected information on
+ * JAX-RS end points.
+ */
+public final class AnnotationBundleKey
+{
+ private final static Annotation[] NO_ANNOTATIONS = new Annotation[0];
+
+ private final Annotation[] _annotations;
+
+ private final boolean _annotationsCopied;
+
+ private final int _hashCode;
+
+ /*
+ /**********************************************************
+ /* Construction
+ /**********************************************************
+ */
+
+ public AnnotationBundleKey(Annotation[] annotations)
+ {
+ if (annotations == null || annotations.length == 0) {
+ annotations = NO_ANNOTATIONS;
+ _annotationsCopied = true;
+ _hashCode = -1;
+ } else {
+ _annotationsCopied = false;
+ _hashCode = calcHash(annotations);
+ }
+ _annotations = annotations;
+ }
+
+ private AnnotationBundleKey(Annotation[] annotations, int hashCode)
+ {
+ _annotations = annotations;
+ _annotationsCopied = true;
+ _hashCode = hashCode;
+ }
+
+ private final static int calcHash(Annotation[] annotations)
+ {
+ /* hmmh. Can't just base on Annotation type; chances are that Annotation
+ * instances use identity hash, which has to do.
+ */
+ final int len = annotations.length;
+ int hash = len;
+ for (int i = 0; i < len; ++i) {
+ hash = (hash * 31) + annotations[i].hashCode();
+ }
+ return hash;
+ }
+
+ /**
+ * Method called to create a safe immutable copy of the key; used when
+ * adding entry with this key -- lookups are ok without calling the method.
+ */
+ public AnnotationBundleKey immutableKey() {
+ if (_annotationsCopied) {
+ return this;
+ }
+ int len = _annotations.length;
+ Annotation[] newAnnotations = new Annotation[len];
+ System.arraycopy(_annotations, 0, newAnnotations, 0, len);
+ return new AnnotationBundleKey(newAnnotations, _hashCode);
+ }
+
+ /*
+ /**********************************************************
+ /* Overridden methods
+ /**********************************************************
+ */
+
+ @Override
+ public String toString() {
+ return "[Annotations: "+_annotations.length+", hash 0x"+Integer.toHexString(_hashCode)
+ +", copied: "+_annotationsCopied+"]";
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == this) return true;
+ if (o == null) return false;
+ if (o.getClass() != getClass()) return false;
+ AnnotationBundleKey other = (AnnotationBundleKey) o;
+ if (other._hashCode != _hashCode) return false;
+ return _equals(other._annotations);
+ }
+
+ private final boolean _equals(Annotation[] otherAnn)
+ {
+ final int len = _annotations.length;
+ if (otherAnn.length != len) {
+ return false;
+ }
+ for (int i = 0; i < len; ++i) {
+ if (_annotations[i] != otherAnn[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/ClassKey.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/ClassKey.java
new file mode 100644
index 0000000..7251774
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/ClassKey.java
@@ -0,0 +1,92 @@
+package com.fasterxml.jackson.jaxrs.json.util;
+
+/**
+ * Efficient key class, used instead of using <code>Class</code>.
+ * The reason for having a separate key class instead of
+ * directly using {@link Class} as key is mostly
+ * to allow for redefining <code>hashCode</code> method --
+ * for some strange reason, {@link Class} does not
+ * redefine {@link Object#hashCode} and thus uses identity
+ * hash, which is pretty slow. This makes key access using
+ * {@link Class} unnecessarily slow.
+ *<p>
+ * Note: since class is not strictly immutable, caller must
+ * know what it is doing, if changing field values.
+ *<p>
+ * NOTE: cut'n pasted from 'databind' package for 2.0, to reduce
+ * tight coupling
+ */
+public final class ClassKey
+ implements Comparable<ClassKey>
+{
+ private String _className;
+
+ private Class<?> _class;
+
+ /**
+ * Let's cache hash code straight away, since we are
+ * almost certain to need it.
+ */
+ private int _hashCode;
+
+ public ClassKey()
+ {
+ _class = null;
+ _className = null;
+ _hashCode = 0;
+ }
+
+ public ClassKey(Class<?> clz)
+ {
+ _class = clz;
+ _className = clz.getName();
+ _hashCode = _className.hashCode();
+ }
+
+ public void reset(Class<?> clz)
+ {
+ _class = clz;
+ _className = clz.getName();
+ _hashCode = _className.hashCode();
+ }
+
+ /*
+ /**********************************************************
+ /* Comparable
+ /**********************************************************
+ */
+
+ // Just need to sort by name, ok to collide (unless used in TreeMap/Set!)
+ //@Override
+ public int compareTo(ClassKey other) {
+ return _className.compareTo(other._className);
+ }
+
+ /*
+ /**********************************************************
+ /* Standard methods
+ /**********************************************************
+ */
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == this) return true;
+ if (o == null) return false;
+ if (o.getClass() != getClass()) return false;
+ ClassKey other = (ClassKey) o;
+
+ /* Is it possible to have different Class object for same name + class loader combo?
+ * Let's assume answer is no: if this is wrong, will need to uncomment following functionality
+ */
+ /*
+ return (other._className.equals(_className))
+ && (other._class.getClassLoader() == _class.getClassLoader());
+ */
+ return other._class == _class;
+ }
+
+ @Override public int hashCode() { return _hashCode; }
+
+ @Override public String toString() { return _className; }
+}
diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/LRUMap.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/LRUMap.java
new file mode 100644
index 0000000..fa4a956
--- /dev/null
+++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/util/LRUMap.java
@@ -0,0 +1,26 @@
+package com.fasterxml.jackson.jaxrs.json.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Helper for simple bounded LRU maps used for reusing lookup values.
+ */
+ at SuppressWarnings("serial")
+public class LRUMap<K,V> extends LinkedHashMap<K,V>
+{
+ protected final int _maxEntries;
+
+ public LRUMap(int initialEntries, int maxEntries)
+ {
+ super(initialEntries, 0.8f, true);
+ _maxEntries = maxEntries;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
+ {
+ return size() > _maxEntries;
+ }
+
+}
diff --git a/json/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader b/json/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
new file mode 100644
index 0000000..4abbe18
--- /dev/null
+++ b/json/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
@@ -0,0 +1 @@
+com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider
diff --git a/json/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter b/json/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
new file mode 100644
index 0000000..4abbe18
--- /dev/null
+++ b/json/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
@@ -0,0 +1 @@
+com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/JaxrsTestBase.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/JaxrsTestBase.java
new file mode 100644
index 0000000..34d56ab
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/JaxrsTestBase.java
@@ -0,0 +1,92 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.junit.Assert;
+
+import com.fasterxml.jackson.core.*;
+
+public abstract class JaxrsTestBase
+ extends junit.framework.TestCase
+{
+
+ /*
+ /**********************************************************
+ /* Additional assertion methods
+ /**********************************************************
+ */
+
+ protected void assertToken(JsonToken expToken, JsonToken actToken)
+ {
+ if (actToken != expToken) {
+ fail("Expected token "+expToken+", current token "+actToken);
+ }
+ }
+
+ protected void assertToken(JsonToken expToken, JsonParser jp)
+ {
+ assertToken(expToken, jp.getCurrentToken());
+ }
+
+ protected void assertType(Object ob, Class<?> expType)
+ {
+ if (ob == null) {
+ fail("Expected an object of type "+expType.getName()+", got null");
+ }
+ Class<?> cls = ob.getClass();
+ if (!expType.isAssignableFrom(cls)) {
+ fail("Expected type "+expType.getName()+", got "+cls.getName());
+ }
+ }
+
+ protected void verifyException(Throwable e, String... matches)
+ {
+ String msg = e.getMessage();
+ String lmsg = (msg == null) ? "" : msg.toLowerCase();
+ for (String match : matches) {
+ String lmatch = match.toLowerCase();
+ if (lmsg.indexOf(lmatch) >= 0) {
+ return;
+ }
+ }
+ fail("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\"");
+ }
+
+ protected void _verifyBytes(byte[] actBytes, byte... expBytes)
+ {
+ Assert.assertArrayEquals(expBytes, actBytes);
+ }
+
+ /**
+ * Method that gets textual contents of the current token using
+ * available methods, and ensures results are consistent, before
+ * returning them
+ */
+ protected String getAndVerifyText(JsonParser jp)
+ throws IOException, JsonParseException
+ {
+ // Ok, let's verify other accessors
+ int actLen = jp.getTextLength();
+ char[] ch = jp.getTextCharacters();
+ String str2 = new String(ch, jp.getTextOffset(), actLen);
+ String str = jp.getText();
+
+ if (str.length() != actLen) {
+ fail("Internal problem (jp.token == "+jp.getCurrentToken()+"): jp.getText().length() ['"+str+"'] == "+str.length()+"; jp.getTextLength() == "+actLen);
+ }
+ assertEquals("String access via getText(), getTextXxx() must be the same", str, str2);
+
+ return str;
+ }
+
+ /*
+ /**********************************************************
+ /* Other helper methods
+ /**********************************************************
+ */
+
+ public String quote(String str) {
+ return '"'+str+'"';
+ }
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanDeserialize.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanDeserialize.java
new file mode 100644
index 0000000..dafb45c
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanDeserialize.java
@@ -0,0 +1,43 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Unit test to check [JACKSON-540]
+ */
+public class TestCanDeserialize extends JaxrsTestBase {
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void testCanSerialize() throws IOException {
+ Map<String, Object> object = new LinkedHashMap<String, Object>();
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+
+ String json = "{\"foo\":\"bar\"}";
+ InputStream stream = new ByteArrayInputStream(json.getBytes());
+
+ object = (Map) prov.readFrom(Object.class, object.getClass(), new Annotation[0],
+ MediaType.APPLICATION_JSON_TYPE, null, stream);
+
+ assertEquals("bar", object.get("foo"));
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void testCanSerializeEmpty() throws IOException {
+ Map<String, Object> object = new LinkedHashMap<String, Object>();
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+
+ InputStream stream = new ByteArrayInputStream(new byte[0]);
+
+ object = (Map) prov.readFrom(Object.class, object.getClass(), new Annotation[0],
+ MediaType.APPLICATION_JSON_TYPE, null, stream);
+
+ assertNull(object);
+ }
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanSerialize.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanSerialize.java
new file mode 100644
index 0000000..7d57a4b
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestCanSerialize.java
@@ -0,0 +1,46 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.*;
+import java.util.*;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Unit test to check [JACKSON-540]
+ */
+public class TestCanSerialize extends JaxrsTestBase
+{
+ static class Simple {
+ protected List<String> list;
+
+ public List<String> getList( ) { return list; }
+ public void setList(List<String> l) { list = l; }
+ }
+
+ public void testCanSerialize() throws IOException
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
+
+ // construct test object
+ List<String> l = new ArrayList<String>();
+ l.add("foo");
+ l.add("bar");
+
+ Simple s = new Simple();
+ s.setList(l);
+
+ // this is fine:
+ boolean can = mapper.canSerialize(Simple.class);
+ assertTrue(can);
+
+ // but with problem of [JACKSON-540], we get nasty surprise here...
+ String json = mapper.writeValueAsString(s);
+
+ Simple result = mapper.readValue(json, Simple.class);
+ assertNotNull(result.list);
+ assertEquals(2, result.list.size());
+ }
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJacksonFeatures.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJacksonFeatures.java
new file mode 100644
index 0000000..9c3dc12
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJacksonFeatures.java
@@ -0,0 +1,111 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.*;
+import java.lang.annotation.*;
+import java.lang.reflect.Method;
+
+import javax.ws.rs.core.MediaType;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+import com.fasterxml.jackson.jaxrs.json.annotation.JacksonFeatures;
+
+/**
+ * Tests for [Issue-2], Addition of {@link JacksonFeatures}.
+ */
+public class TestJacksonFeatures extends JaxrsTestBase
+{
+ static class Bean {
+ public int a = 3;
+ }
+
+ @JacksonFeatures(serializationEnable={ SerializationFeature.WRAP_ROOT_VALUE })
+ public void writeConfig() { }
+
+ @JacksonFeatures(deserializationDisable={ DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES })
+ public void readConfig() { }
+
+ // Also, let's check that we can bundle annotations
+ @JacksonAnnotationsInside
+ @JacksonFeatures(serializationEnable={ SerializationFeature.WRAP_ROOT_VALUE })
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface FeatureBundle { }
+
+ @FeatureBundle // should work as if all annotations from FeatureBundle were directly added
+ public void writeConfig2() { }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ // [Issue-2], serialization
+ public void testWriteConfigs() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Bean bean = new Bean();
+ Method m = getClass().getDeclaredMethod("writeConfig");
+ JacksonFeatures feats = m.getAnnotation(JacksonFeatures.class);
+ assertNotNull(feats); // just a sanity check
+
+ // when wrapping enabled, we get:
+ prov.writeTo(bean, bean.getClass(), bean.getClass(), new Annotation[] { feats },
+ MediaType.APPLICATION_JSON_TYPE, null, out);
+ assertEquals("{\"Bean\":{\"a\":3}}", out.toString("UTF-8"));
+
+ // but without, not:
+ out.reset();
+ prov.writeTo(bean, bean.getClass(), bean.getClass(), new Annotation[] { },
+ MediaType.APPLICATION_JSON_TYPE, null, out);
+ assertEquals("{\"a\":3}", out.toString("UTF-8"));
+ }
+
+ public void testWriteConfigsViaBundle() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Bean bean = new Bean();
+ Method m = getClass().getDeclaredMethod("writeConfig2");
+ // should still enable root-wrapping
+ prov.writeTo(bean, bean.getClass(), bean.getClass(), m.getAnnotations(),
+ MediaType.APPLICATION_JSON_TYPE, null, out);
+ assertEquals("{\"Bean\":{\"a\":3}}", out.toString("UTF-8"));
+ }
+
+ // [Issue-2], deserialization
+ public void testReadConfigs() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ Method m = getClass().getDeclaredMethod("readConfig");
+ JacksonFeatures feats = m.getAnnotation(JacksonFeatures.class);
+ assertNotNull(feats); // just a sanity check
+
+ // ok: here let's verify that we can disable exception throwing unrecognized things
+ @SuppressWarnings("unchecked")
+ Class<Object> raw = (Class<Object>)(Class<?>)Bean.class;
+ Object ob = prov.readFrom(raw, raw,
+ new Annotation[] { feats },
+ MediaType.APPLICATION_JSON_TYPE, null,
+ new ByteArrayInputStream("{ \"foobar\" : 3 }".getBytes("UTF-8")));
+ assertNotNull(ob);
+
+ // but without setting, get the exception
+ try {
+ prov.readFrom(raw, raw,
+ new Annotation[] { },
+ MediaType.APPLICATION_JSON_TYPE, null,
+ new ByteArrayInputStream("{ \"foobar\" : 3 }".getBytes("UTF-8")));
+ fail("Should have caught an exception");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Unrecognized field");
+ }
+ }
+
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJsonView.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJsonView.java
new file mode 100644
index 0000000..1567351
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJsonView.java
@@ -0,0 +1,46 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.ByteArrayOutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import javax.ws.rs.core.MediaType;
+
+import com.fasterxml.jackson.annotation.JsonView;
+
+public class TestJsonView extends JaxrsTestBase
+{
+ static class MyView1 { }
+ static class MyView2 { }
+
+ static class Bean {
+ @JsonView(MyView1.class)
+ public int value1 = 1;
+
+ @JsonView(MyView2.class)
+ public int value2 = 2;
+ }
+
+ @JsonView({ MyView1.class })
+ public void bogus() { }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ // [JACKSON-578]
+ public void testViews() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Bean bean = new Bean();
+ Method m = getClass().getDeclaredMethod("bogus");
+ JsonView view = m.getAnnotation(JsonView.class);
+ assertNotNull(view); // just a sanity check
+ prov.writeTo(bean, bean.getClass(), bean.getClass(), new Annotation[] { view },
+ MediaType.APPLICATION_JSON_TYPE, null, out);
+ assertEquals("{\"value1\":1}", out.toString("UTF-8"));
+ }
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJsonpWrapping.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJsonpWrapping.java
new file mode 100644
index 0000000..121427c
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestJsonpWrapping.java
@@ -0,0 +1,27 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.*;
+import java.lang.annotation.Annotation;
+
+import javax.ws.rs.core.MediaType;
+
+public class TestJsonpWrapping
+ extends JaxrsTestBase
+{
+ public void testSimple() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ Object bean = new Integer[] { 1, 2, 3 };
+
+ // First: no JSONP wrapping:
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ prov.writeTo(bean, bean.getClass(), bean.getClass(), new Annotation[0], MediaType.APPLICATION_JSON_TYPE, null, out);
+ assertEquals("[1,2,3]", out.toString("UTF-8"));
+
+ // then with wrapping:
+ prov.setJSONPFunctionName("addAll");
+ out = new ByteArrayOutputStream();
+ prov.writeTo(bean, bean.getClass(), bean.getClass(), new Annotation[0], MediaType.APPLICATION_JSON_TYPE, null, out);
+ assertEquals("addAll([1,2,3])", out.toString("UTF-8"));
+ }
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestRootType.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestRootType.java
new file mode 100644
index 0000000..f89fdaa
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestRootType.java
@@ -0,0 +1,45 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.ByteArrayOutputStream;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.MediaType;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+public class TestRootType
+ extends JaxrsTestBase
+{
+ @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT, property="type")
+ @JsonTypeName("bean")
+ static class Bean {
+ public int a = 3;
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ public void testRootType() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ TypeReference<?> ref = new TypeReference<List<Bean>>(){};
+
+ Bean bean = new Bean();
+ ArrayList<Bean> list = new ArrayList<Bean>();
+ list.add(bean);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ MediaType mt = MediaType.APPLICATION_JSON_TYPE;
+ prov.writeTo(list, List.class, ref.getType(), new Annotation[0], mt, null, out);
+
+ String json = out.toString("UTF-8");
+ assertEquals("[{\"bean\":{\"a\":3}}]", json);
+ }
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestStreamingOutput.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestStreamingOutput.java
new file mode 100644
index 0000000..52aa90d
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestStreamingOutput.java
@@ -0,0 +1,33 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.*;
+import java.lang.annotation.Annotation;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.StreamingOutput;
+
+public class TestStreamingOutput extends JaxrsTestBase
+{
+ static class StreamingSubtype implements StreamingOutput
+ {
+ // important: this can trick "canSerialize()" to include it:
+ public int getFoo() { return 3; }
+
+ public void write(OutputStream out) throws IOException {
+ out.write("OK".getBytes("UTF-8"));
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Test methods
+ /**********************************************************************
+ */
+
+ public void testSimpleSubtype() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ assertFalse(prov.isWriteable(StreamingSubtype.class, StreamingSubtype.class,
+ new Annotation[] { }, MediaType.APPLICATION_JSON_TYPE));
+ }
+}
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestUntouchables.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestUntouchables.java
new file mode 100644
index 0000000..c111939
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestUntouchables.java
@@ -0,0 +1,80 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.util.*;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.StreamingOutput;
+
+/**
+ * Unit tests for verifying that certain JDK base types will be
+ * ignored by default Jackson JAX-RS conversion provider.
+ */
+public class TestUntouchables
+ extends JaxrsTestBase
+{
+ /**
+ * Test type added for [JACKSON-460]... just to ensure that "isJsonType"
+ * remains overridable.
+ */
+ public static class MyJacksonJsonProvider extends JacksonJsonProvider {
+ // ensure isJsonType remains "protected" � this is a compile-time check.
+ // Some users of JacksonJsonProvider override this method;
+ // changing to "private" would regress them.
+ @Override
+ protected boolean isJsonType(MediaType mediaType) { return super.isJsonType(mediaType); }
+ }
+
+ static class StreamingSubType implements StreamingOutput {
+ public void write(OutputStream output) { }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ public void testDefaultUntouchables() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ // By default, no reason to exclude, say, this test class...
+ assertTrue(prov.isReadable(getClass(), getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+ assertTrue(prov.isWriteable(getClass(), getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+
+ // but some types should be ignored (set of ignorable may change over time tho!)
+ assertFalse(prov.isWriteable(StreamingOutput.class, StreamingOutput.class,
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+ assertFalse(prov.isWriteable(StreamingSubType.class, StreamingSubType.class,
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+
+ // and then on-the-fence things
+ assertFalse(prov.isReadable(String.class, getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+ assertFalse(prov.isReadable(byte[].class, getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+ }
+
+ public void testCustomUntouchables() throws Exception
+ {
+ JacksonJsonProvider prov = new JacksonJsonProvider();
+ // can mark this as ignorable...
+ prov.addUntouchable(getClass());
+ // and then it shouldn't be processable
+ assertFalse(prov.isReadable(getClass(), getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+ assertFalse(prov.isWriteable(getClass(), getClass(),
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+
+ // Same for interfaces, like:
+ prov.addUntouchable(Collection.class);
+ assertFalse(prov.isReadable(ArrayList.class, ArrayList.class,
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+ assertFalse(prov.isWriteable(HashSet.class, HashSet.class,
+ new Annotation[0], MediaType.APPLICATION_JSON_TYPE));
+ }
+}
+
\ No newline at end of file
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestVersions.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestVersions.java
new file mode 100644
index 0000000..be30cff
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/TestVersions.java
@@ -0,0 +1,28 @@
+package com.fasterxml.jackson.jaxrs.json;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+
+public class TestVersions extends JaxrsTestBase
+{
+ public void testMapperVersions()
+ {
+ assertVersion(new JacksonJsonProvider());
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void assertVersion(Versioned vers)
+ {
+ final Version v = vers.version();
+ assertFalse("Should find version information (got "+v+")", v.isUknownVersion());
+ Version exp = PackageVersion.VERSION;
+ assertEquals(exp.toFullString(), v.toFullString());
+ assertEquals(exp, v);
+ }
+}
+
diff --git a/json/src/test/java/com/fasterxml/jackson/jaxrs/json/util/TestAnnotationBundleKey.java b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/util/TestAnnotationBundleKey.java
new file mode 100644
index 0000000..459b29f
--- /dev/null
+++ b/json/src/test/java/com/fasterxml/jackson/jaxrs/json/util/TestAnnotationBundleKey.java
@@ -0,0 +1,56 @@
+package com.fasterxml.jackson.jaxrs.json.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+import com.fasterxml.jackson.jaxrs.json.JaxrsTestBase;
+import com.fasterxml.jackson.jaxrs.json.annotation.JSONP;
+
+public class TestAnnotationBundleKey extends JaxrsTestBase
+{
+ @JSONP("foo")
+ public void annotated1() { }
+
+ @JSONP("foo")
+ public void annotated2() { }
+
+ /*
+ /**********************************************************
+ /* Test methods
+ /**********************************************************
+ */
+
+ public void testKeys() throws Exception
+ {
+ Method m1 = getClass().getDeclaredMethod("annotated1");
+ Method m2 = getClass().getDeclaredMethod("annotated2");
+
+ assertNotSame(m1, m2);
+
+ Annotation[] ann1 = m1.getAnnotations();
+ assertEquals(1, ann1.length);
+ Annotation[] ann2 = m2.getAnnotations();
+ assertEquals(1, ann2.length);
+
+ AnnotationBundleKey key1 = new AnnotationBundleKey(ann1);
+ AnnotationBundleKey key2 = new AnnotationBundleKey(ann2);
+ AnnotationBundleKey key1dup = new AnnotationBundleKey(ann1);
+ AnnotationBundleKey key1immutable = key1.immutableKey();
+
+ // identity checks first
+ assertEquals(key1, key1);
+ assertEquals(key2, key2);
+ assertEquals(key1dup, key1dup);
+ assertEquals(key1immutable, key1immutable);
+
+ // then inequality by content (even though both have 1 JSONP annotation)
+ assertFalse(key1.equals(key2));
+ assertFalse(key2.equals(key1));
+
+ // but safe copy ought to be equal
+ assertTrue(key1.equals(key1dup)); // from same method
+ assertTrue(key1dup.equals(key1));
+ assertTrue(key1.equals(key1immutable)); // and immutable variant
+ assertTrue(key1immutable.equals(key1));
+ }
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jackson-jaxrs-providers.git
More information about the pkg-java-commits
mailing list