[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