[jackson-jaxrs-providers] 06/162: add skeletal xml provider as well
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 ff87bc9be6905091ad7d26e3764c173148cfb563
Author: Tatu Saloranta <tsaloranta at gmail.com>
Date: Mon Feb 4 14:38:52 2013 -0800
add skeletal xml provider as well
---
json/pom.xml | 35 +-
pom.xml | 39 +-
release-notes/VERSION | 9 +-
{json => smile}/pom.xml | 54 +-
.../fasterxml/jackson/jaxrs/smile/Annotations.java | 21 +
.../jaxrs/smile/JacksonJaxbSmileProvider.java | 63 ++
.../jackson/jaxrs/smile/JacksonSmileProvider.java | 710 ++++++++++++++++++++
.../jaxrs/smile/JsonMappingExceptionMapper.java | 18 +
.../jaxrs/smile/JsonParseExceptionMapper.java | 18 +
.../jackson/jaxrs/smile/ModuleVersion.java | 12 +
.../jackson/jaxrs/smile/SmileMediaTypes.java | 9 +
.../jaxrs/smile/annotation/EndpointConfig.java | 185 ++++++
.../jaxrs/smile/annotation/JacksonFeatures.java | 39 ++
.../jaxrs/smile/annotation/package-info.java | 5 +
.../jaxrs/smile/cfg/MapperConfigurator.java | 181 +++++
.../com/fasterxml/jackson/jaxrs/smile/package.info | 21 +
.../jaxrs/smile/util/AnnotationBundleKey.java | 110 ++++
.../jackson/jaxrs/smile/util/ClassKey.java | 92 +++
.../fasterxml/jackson/jaxrs/smile/util/LRUMap.java | 26 +
.../services/javax.ws.rs.ext.MessageBodyReader | 1 +
.../services/javax.ws.rs.ext.MessageBodyWriter | 1 +
.../jackson/jaxrs/smile/JaxrsTestBase.java | 91 +++
.../jackson/jaxrs/smile/TestCanDeserialize.java | 38 ++
{json => xml}/pom.xml | 54 +-
.../fasterxml/jackson/jaxrs/xml/Annotations.java | 21 +
.../jackson/jaxrs/xml/JacksonJaxbXMLProvider.java | 64 ++
.../jackson/jaxrs/xml/JacksonXMLProvider.java | 728 +++++++++++++++++++++
.../jaxrs/xml/JsonMappingExceptionMapper.java | 18 +
.../jaxrs/xml/JsonParseExceptionMapper.java | 18 +
.../fasterxml/jackson/jaxrs/xml/ModuleVersion.java | 12 +
.../jaxrs/xml/annotation/EndpointConfig.java | 185 ++++++
.../jaxrs/xml/annotation/JacksonFeatures.java | 39 ++
.../jackson/jaxrs/xml/annotation/package-info.java | 5 +
.../jackson/jaxrs/xml/cfg/MapperConfigurator.java | 200 ++++++
.../fasterxml/jackson/jaxrs/xml/package-info.java | 21 +
.../jaxrs/xml/util/AnnotationBundleKey.java | 110 ++++
.../fasterxml/jackson/jaxrs/xml/util/ClassKey.java | 92 +++
.../fasterxml/jackson/jaxrs/xml/util/LRUMap.java | 26 +
.../services/javax.ws.rs.ext.MessageBodyReader | 2 +
.../services/javax.ws.rs.ext.MessageBodyWriter | 1 +
.../fasterxml/jackson/jaxrs/xml/JaxrsTestBase.java | 91 +++
.../jackson/jaxrs/xml/TestCanDeserialize.java | 36 +
.../jackson/jaxrs/xml/TestCanSerialize.java | 46 ++
.../jackson/jaxrs/xml/TestJacksonFeatures.java | 112 ++++
.../fasterxml/jackson/jaxrs/xml/TestJsonView.java | 47 ++
.../fasterxml/jackson/jaxrs/xml/TestRootType.java | 40 ++
.../fasterxml/jackson/jaxrs/xml/TestSerialize.java | 69 ++
.../jackson/jaxrs/xml/TestUntouchables.java | 65 ++
.../fasterxml/jackson/jaxrs/xml/TestVersions.java | 42 ++
49 files changed, 3798 insertions(+), 124 deletions(-)
diff --git a/json/pom.xml b/json/pom.xml
index fd8ec40..bcf617e 100644
--- a/json/pom.xml
+++ b/json/pom.xml
@@ -70,40 +70,7 @@
<version>${version.jackson.jaxb}</version>
</dependency>
- <!-- and we need JAX-RS annotations as well; but usually provided by container
- (and app should definitely have direct dep too, when using annotations)
- -->
- <dependency>
- <groupId>javax.ws.rs</groupId>
- <artifactId>jsr311-api</artifactId>
- <version>1.1.1</version>
- <scope>provided</scope>
- </dependency>
-
- <!-- and for testing, JUnit (or TestNG?) is needed -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.10</version>
- <scope>test</scope>
- </dependency>
- <!-- tests require JAX-RS impl; otherwise components fail to load
- (some oddity with API classes; should NOT be needed...)
-
- NOTE: use 1.2 as that's JDK 1.5; later (1.8) versions need JDK 1.6
- -->
- <dependency>
- <groupId>com.sun.jersey</groupId>
- <artifactId>jersey-core</artifactId>
- <version>1.2</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.sun.jersey</groupId>
- <artifactId>jersey-server</artifactId>
- <version>1.2</version>
- <scope>test</scope>
- </dependency>
+ <!-- test deps should come from parent -->
</dependencies>
<build>
<plugins>
diff --git a/pom.xml b/pom.xml
index ebe0e34..161ca49 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,10 +31,8 @@
<modules>
<module>json</module>
-<!--
<module>smile</module>
<module>xml</module>
--->
</modules>
<url>http://wiki.fasterxml.com/JacksonHome</url>
<scm>
@@ -44,10 +42,14 @@
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <!-- core/databind should have same version; annotations may differ -->
+ <!-- core/databind should have same version; data formats, annotations may differ -->
<version.jackson.core>2.2.0-SNAPSHOT</version.jackson.core>
<version.jackson.annotations>2.2.0-SNAPSHOT</version.jackson.annotations>
- <version.jackson.jaxb>2.2.0-SNAPSHOT</version.jackson.jaxb>
+
+ <version.jackson.smile>${version.jackson.core}</version.jackson.smile>
+ <version.jackson.xml>${version.jackson.core}</version.jackson.xml>
+
+ <version.jackson.jaxb>${version.jackson.core}</version.jackson.jaxb>
</properties>
<dependencies>
@@ -58,5 +60,34 @@
<version>4.8.2</version>
<scope>test</scope>
</dependency>
+
+ <!-- and we need JAX-RS annotations as well; but usually provided by container
+ (and app should definitely have direct dep too, when using annotations)
+ -->
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>jsr311-api</artifactId>
+ <version>1.1.1</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- tests require JAX-RS impl; otherwise components fail to load
+ (some oddity with API classes; should NOT be needed...)
+
+ NOTE: use 1.2 as that's JDK 1.5; later (1.8) versions need JDK 1.6
+ -->
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-core</artifactId>
+ <version>1.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <version>1.2</version>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
</project>
diff --git a/release-notes/VERSION b/release-notes/VERSION
index 31cd42c..0291552 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -25,7 +25,9 @@ JSON
2.1.0 (08-Oct-2012)
-No fixes, updated dependencies.
+XML
+
+ * [Issue#4] Exception on empty content, should return null.
2.0.2 (14-May-2012)
@@ -38,9 +40,12 @@ JSON:
2.0.1 (23-Apr-2012)
-JSON:
+General:
* Changed 'jaxrs-311' dependency from 'compile' to 'provided'
+
+JSON:
+
* SPI (META-INF/services/) fixed so auto-registration should now work
(contributed by Simone T)
* Issue-10: NPE in EndpointConfig, if JAX-RS container passes null
diff --git a/json/pom.xml b/smile/pom.xml
similarity index 63%
copy from json/pom.xml
copy to smile/pom.xml
index fd8ec40..0c46065 100644
--- a/json/pom.xml
+++ b/smile/pom.xml
@@ -23,16 +23,16 @@
<artifactId>jackson-jaxrs-providers</artifactId>
<version>2.2.0-SNAPSHOT</version>
</parent>
- <artifactId>jackson-jaxrs-json-provider</artifactId>
- <name>Jackson-JAXRS-JSON</name>
- <description>Functionality to handle JSON input/output for JAX-RS implementations (like Jersey and RESTeasy) using standard Jackson data binding.
+ <artifactId>jackson-jaxrs-smile-provider</artifactId>
+ <name>Jackson-JAXRS-Smile</name>
+ <description>Functionality to handle Smile (binary JSON) input/output for JAX-RS implementations (like Jersey and RESTeasy) using standard Jackson data binding.
</description>
<properties>
<!-- Generate PackageVersion.java into this directory. -->
- <packageVersion.dir>com/fasterxml/jackson/jaxrs/json</packageVersion.dir>
- <packageVersion.package>${project.groupId}.json</packageVersion.package>
- <osgi.export>${project.groupId}.json.*;version=${project.version}</osgi.export>
+ <packageVersion.dir>com/fasterxml/jackson/jaxrs/smile</packageVersion.dir>
+ <packageVersion.package>${project.groupId}.smile</packageVersion.package>
+ <osgi.export>${project.groupId}.smile.*;version=${project.version}</osgi.export>
<!-- NOTE: JAXB annotations module is optional dependency, need to try to mark
as such here.
-->
@@ -63,47 +63,17 @@
<artifactId>jackson-databind</artifactId>
<version>${version.jackson.core}</version>
</dependency>
- <!-- also need JAXB annotation support -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-dataformat-smile</artifactId>
+ <version>${version.jackson.smile}</version>
+ </dependency>
+ <!-- may also need JAXB annotation support -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>${version.jackson.jaxb}</version>
</dependency>
-
- <!-- and we need JAX-RS annotations as well; but usually provided by container
- (and app should definitely have direct dep too, when using annotations)
- -->
- <dependency>
- <groupId>javax.ws.rs</groupId>
- <artifactId>jsr311-api</artifactId>
- <version>1.1.1</version>
- <scope>provided</scope>
- </dependency>
-
- <!-- and for testing, JUnit (or TestNG?) is needed -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.10</version>
- <scope>test</scope>
- </dependency>
- <!-- tests require JAX-RS impl; otherwise components fail to load
- (some oddity with API classes; should NOT be needed...)
-
- NOTE: use 1.2 as that's JDK 1.5; later (1.8) versions need JDK 1.6
- -->
- <dependency>
- <groupId>com.sun.jersey</groupId>
- <artifactId>jersey-core</artifactId>
- <version>1.2</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.sun.jersey</groupId>
- <artifactId>jersey-server</artifactId>
- <version>1.2</version>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
<plugins>
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/Annotations.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/Annotations.java
new file mode 100644
index 0000000..00322b6
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/Annotations.java
@@ -0,0 +1,21 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+/**
+ * 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
+ ;
+}
\ No newline at end of file
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JacksonJaxbSmileProvider.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JacksonJaxbSmileProvider.java
new file mode 100644
index 0000000..24a8d57
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JacksonJaxbSmileProvider.java
@@ -0,0 +1,63 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+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 JacksonSmileProvider}.
+ *<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(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
+ at Produces(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
+public class JacksonJaxbSmileProvider extends JacksonSmileProvider {
+ /**
+ * 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 JacksonJaxbSmileProvider()
+ {
+ this(null, DEFAULT_ANNOTATIONS);
+ }
+
+ /**
+ * @param annotationsToUse Annotation set(s) to use for configuring
+ * data binding
+ */
+ public JacksonJaxbSmileProvider(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 JacksonJaxbSmileProvider(ObjectMapper mapper, Annotations[] annotationsToUse)
+ {
+ super(mapper, annotationsToUse);
+ }
+}
\ No newline at end of file
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JacksonSmileProvider.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JacksonSmileProvider.java
new file mode 100644
index 0000000..68139cd
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JacksonSmileProvider.java
@@ -0,0 +1,710 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+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.smile.annotation.EndpointConfig;
+import com.fasterxml.jackson.jaxrs.smile.cfg.MapperConfigurator;
+import com.fasterxml.jackson.jaxrs.smile.util.AnnotationBundleKey;
+import com.fasterxml.jackson.jaxrs.smile.util.ClassKey;
+
+/**
+ * Basic implementation of JAX-RS abstractions ({@link MessageBodyReader},
+ * {@link MessageBodyWriter}) needed for binding
+ * Smile ("application/x-jackson-smile") 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 that there is also a sub-class -- ({@link JacksonJaxbSmileProvider}) -- that
+ * 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(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
+ at Produces(SmileMediaTypes.APPLICATION_JACKSON_SMILE)
+public class JacksonSmileProvider
+ 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 Smile data
+ */
+ protected final LRUMap<AnnotationBundleKey, EndpointConfig> _readers
+ = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120);
+
+ /**
+ * Cache for resolved endpoint configurations when writing Smile 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;
+
+ /*
+ /**********************************************************
+ /* 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 JacksonSmileProvider()
+ {
+ this(null, BASIC_ANNOTATIONS);
+ }
+
+ /**
+ * @param annotationsToUse Annotation set(s) to use for configuring
+ * data binding
+ */
+ public JacksonSmileProvider(Annotations... annotationsToUse)
+ {
+ this(null, annotationsToUse);
+ }
+
+ public JacksonSmileProvider(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 JacksonSmileProvider(ObjectMapper mapper, Annotations[] annotationsToUse)
+ {
+ _mapperConfig = new MapperConfigurator(mapper, annotationsToUse);
+ }
+
+ /**
+ * Method that will return version information stored in and read from jar
+ * that contains this class.
+ */
+ public Version version() {
+ return ModuleVersion.instance.version();
+ }
+
+ /*
+ /**********************************************************
+ /* Configuring
+ /**********************************************************
+ */
+
+ /**
+ * Method for 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 JacksonSmileProvider configure(DeserializationFeature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonSmileProvider configure(SerializationFeature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonSmileProvider configure(JsonParser.Feature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonSmileProvider configure(JsonGenerator.Feature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonSmileProvider enable(DeserializationFeature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonSmileProvider enable(SerializationFeature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonSmileProvider enable(JsonParser.Feature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonSmileProvider enable(JsonGenerator.Feature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonSmileProvider disable(DeserializationFeature f, boolean state) {
+ _mapperConfig.configure(f, false);
+ return this;
+ }
+
+ public JacksonSmileProvider disable(SerializationFeature f, boolean state) {
+ _mapperConfig.configure(f, false);
+ return this;
+ }
+
+ public JacksonSmileProvider disable(JsonParser.Feature f, boolean state) {
+ _mapperConfig.configure(f, false);
+ return this;
+ }
+
+ public JacksonSmileProvider 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));
+ }
+
+ /*
+ /**********************************************************
+ /* 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 Smile type (via call to {@link #isSmileType}; 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 (!isSmileType(mediaType)) {
+ return false;
+ }
+
+ /* Ok: looks like we must weed out some core types here; ones that
+ * make no sense to try to bind from Smile:
+ */
+ 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().createJsonParser(entityStream);
+ // [Issue#1]: should return null for empty/missing payload
+ // (note! Requires smile module v2.1.1)
+ 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 Smile type (via call to {@link #isSmileType}; 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 (!isSmileType(mediaType)) {
+ return false;
+ }
+
+ /* Ok: looks like we must weed out some core types here; ones that
+ * make no sense to try to bind from Smile:
+ */
+ 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);
+ // 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.getJsonFactory().createJsonGenerator(entityStream, enc);
+
+ // Want indentation?
+ if (writer.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
+ jg.useDefaultPrettyPrinter();
+ }
+ // 04-Mar-2010, tatu: How about type we were given? (if any)
+ JavaType rootType = null;
+
+ if (genericType != null && value != null) {
+ /* 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root
+ * type since it prevents polymorphic type serialization. Since we really
+ * just need this for generics, let's only use generic type if it's truly
+ * generic.
+ */
+ if (genericType.getClass() != Class.class) { // generic types are other impls of 'java.lang.reflect.Type'
+ /* This is still not exactly right; should root type be further
+ * specialized with 'value.getClass()'? Let's see how well this works before
+ * trying to 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);
+ }
+
+ 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 Smile type or sub type.
+ * Current implementation essentially checks to see whether
+ * {@link MediaType#getSubtype} returns
+ * "smile" or something ending with "+smile".
+ */
+ protected boolean isSmileType(MediaType mediaType)
+ {
+ /* As suggested by Stephen D, there are 2 ways to check: either
+ * being as inclusive as possible (if subtype is "smile"), or
+ * exclusive (major type "application", minor type "smile").
+ * 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+smile" subtypes, which count as well
+ String subtype = mediaType.getSubtype();
+ return SmileMediaTypes.APPLICATION_JACKSON_SMILE_TYPE.getSubtype().equalsIgnoreCase(subtype) ||
+ "smile".equalsIgnoreCase(subtype) || subtype.endsWith("+smile");
+ }
+ /* Not sure if this can happen; but it seems reasonable
+ * that we can at least produce smile 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/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JsonMappingExceptionMapper.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JsonMappingExceptionMapper.java
new file mode 100644
index 0000000..d03d9a3
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JsonMappingExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+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 Smile 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();
+ }
+}
\ No newline at end of file
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JsonParseExceptionMapper.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JsonParseExceptionMapper.java
new file mode 100644
index 0000000..c3037ee
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/JsonParseExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+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 Smile 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/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/ModuleVersion.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/ModuleVersion.java
new file mode 100644
index 0000000..cd2efca
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/ModuleVersion.java
@@ -0,0 +1,12 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+* Helper class used for finding and caching version information
+* for this module.
+*/
+class ModuleVersion extends VersionUtil
+{
+ public final static ModuleVersion instance = new ModuleVersion();
+}
\ No newline at end of file
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/SmileMediaTypes.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/SmileMediaTypes.java
new file mode 100644
index 0000000..fd00fb0
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/SmileMediaTypes.java
@@ -0,0 +1,9 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+import javax.ws.rs.core.MediaType;
+
+public class SmileMediaTypes {
+
+ public static final String APPLICATION_JACKSON_SMILE = "application/x-jackson-smile";
+ public static final MediaType APPLICATION_JACKSON_SMILE_TYPE = MediaType.valueOf(APPLICATION_JACKSON_SMILE);
+}
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/EndpointConfig.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/EndpointConfig.java
new file mode 100644
index 0000000..03d9acd
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/EndpointConfig.java
@@ -0,0 +1,185 @@
+package com.fasterxml.jackson.jaxrs.smile.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.*;
+
+/**
+ * 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 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)
+ {
+ EndpointConfig config = new EndpointConfig();
+ return config
+ .add(annotations, true)
+ .initWriter(mapper)
+ ;
+ }
+
+ protected EndpointConfig add(Annotation[] annotations, boolean forWriting)
+ {
+ // Same as [issue-10] with JSON provider; must check for null:
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ Class<?> type = annotation.annotationType();
+ 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.getJsonFactory().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;
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private static <T> T[] nullIfEmpty(T[] arg) {
+ if (arg == null || arg.length == 0) {
+ return null;
+ }
+ return arg;
+ }
+}
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/JacksonFeatures.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/JacksonFeatures.java
new file mode 100644
index 0000000..689729d
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/JacksonFeatures.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.jaxrs.smile.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/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/package-info.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/package-info.java
new file mode 100644
index 0000000..281224c
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/annotation/package-info.java
@@ -0,0 +1,5 @@
+/**
+* Package that contains utility classes and methods for
+* the JAX-RS Smile provider module.
+*/
+package com.fasterxml.jackson.jaxrs.smile.annotation;
\ No newline at end of file
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/cfg/MapperConfigurator.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/cfg/MapperConfigurator.java
new file mode 100644
index 0000000..fcebffc
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/cfg/MapperConfigurator.java
@@ -0,0 +1,181 @@
+package com.fasterxml.jackson.jaxrs.smile.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.dataformat.smile.SmileFactory;
+import com.fasterxml.jackson.jaxrs.smile.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(new SmileFactory());
+ _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(new SmileFactory());
+ _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/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/package.info b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/package.info
new file mode 100644
index 0000000..f832b45
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/package.info
@@ -0,0 +1,21 @@
+/**
+ * Jackson-based JAX-RS provider that can automatically
+ * serialize and deserialize resources for
+ * Smile 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.smile.JacksonSmileProvider} is the basic
+ * provider configured to use Jackson annotations
+ * </li>
+ * <li>{@link com.fasterxml.jackson.jaxrs.smile.JacksonJaxbSmileProvider} 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.smile;
\ No newline at end of file
diff --git a/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/AnnotationBundleKey.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/AnnotationBundleKey.java
new file mode 100644
index 0000000..d1e299e
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/AnnotationBundleKey.java
@@ -0,0 +1,110 @@
+package com.fasterxml.jackson.jaxrs.smile.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/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/ClassKey.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/ClassKey.java
new file mode 100644
index 0000000..274f6b4
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/ClassKey.java
@@ -0,0 +1,92 @@
+package com.fasterxml.jackson.jaxrs.smile.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/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/LRUMap.java b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/LRUMap.java
new file mode 100644
index 0000000..aae4d8b
--- /dev/null
+++ b/smile/src/main/java/com/fasterxml/jackson/jaxrs/smile/util/LRUMap.java
@@ -0,0 +1,26 @@
+package com.fasterxml.jackson.jaxrs.smile.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/smile/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader b/smile/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
new file mode 100644
index 0000000..1c1dcd5
--- /dev/null
+++ b/smile/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
@@ -0,0 +1 @@
+com.fasterxml.jackson.jaxrs.smile.JacksonSmileProvider
diff --git a/smile/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter b/smile/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
new file mode 100644
index 0000000..1c1dcd5
--- /dev/null
+++ b/smile/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
@@ -0,0 +1 @@
+com.fasterxml.jackson.jaxrs.smile.JacksonSmileProvider
diff --git a/smile/src/test/java/com/fasterxml/jackson/jaxrs/smile/JaxrsTestBase.java b/smile/src/test/java/com/fasterxml/jackson/jaxrs/smile/JaxrsTestBase.java
new file mode 100644
index 0000000..fc977b6
--- /dev/null
+++ b/smile/src/test/java/com/fasterxml/jackson/jaxrs/smile/JaxrsTestBase.java
@@ -0,0 +1,91 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+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/smile/src/test/java/com/fasterxml/jackson/jaxrs/smile/TestCanDeserialize.java b/smile/src/test/java/com/fasterxml/jackson/jaxrs/smile/TestCanDeserialize.java
new file mode 100644
index 0000000..f9d3a33
--- /dev/null
+++ b/smile/src/test/java/com/fasterxml/jackson/jaxrs/smile/TestCanDeserialize.java
@@ -0,0 +1,38 @@
+package com.fasterxml.jackson.jaxrs.smile;
+
+import java.io.*;
+import java.lang.annotation.Annotation;
+
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Unit test to check [JACKSON-540]
+ */
+public class TestCanDeserialize extends JaxrsTestBase
+{
+ static class Bean {
+ public int x;
+ }
+
+ /*
+ public void testCanSerialize() throws IOException
+ {
+ JacksonSmileProvider prov = new JacksonSmileProvider();
+ byte[] smile = ...;
+ Bean b = (Bean) prov.readFrom(Object.class, Bean.class, new Annotation[0],
+ MediaType.APPLICATION_XML_TYPE, null,
+ new ByteArrayInputStream(smile);
+ assertNotNull(b);
+ assertEquals(3, b.x);
+ }
+ */
+
+ // [Issue#1]: exception for no content
+ public void testCanSerializeEmpty() throws IOException
+ {
+ JacksonSmileProvider prov = new JacksonSmileProvider();
+ Bean b = (Bean) prov.readFrom(Object.class, Bean.class, new Annotation[0],
+ MediaType.APPLICATION_XML_TYPE, null, new ByteArrayInputStream(new byte[0]));
+ assertNull(b);
+ }
+}
diff --git a/json/pom.xml b/xml/pom.xml
similarity index 63%
copy from json/pom.xml
copy to xml/pom.xml
index fd8ec40..d378668 100644
--- a/json/pom.xml
+++ b/xml/pom.xml
@@ -23,16 +23,16 @@
<artifactId>jackson-jaxrs-providers</artifactId>
<version>2.2.0-SNAPSHOT</version>
</parent>
- <artifactId>jackson-jaxrs-json-provider</artifactId>
- <name>Jackson-JAXRS-JSON</name>
- <description>Functionality to handle JSON input/output for JAX-RS implementations (like Jersey and RESTeasy) using standard Jackson data binding.
+ <artifactId>jackson-jaxrs-xml-provider</artifactId>
+ <name>Jackson-JAXRS-XML</name>
+ <description>Functionality to handle Smile XML input/output for JAX-RS implementations (like Jersey and RESTeasy) using standard Jackson data binding.
</description>
<properties>
<!-- Generate PackageVersion.java into this directory. -->
- <packageVersion.dir>com/fasterxml/jackson/jaxrs/json</packageVersion.dir>
- <packageVersion.package>${project.groupId}.json</packageVersion.package>
- <osgi.export>${project.groupId}.json.*;version=${project.version}</osgi.export>
+ <packageVersion.dir>com/fasterxml/jackson/jaxrs/xml</packageVersion.dir>
+ <packageVersion.package>${project.groupId}.xml</packageVersion.package>
+ <osgi.export>${project.groupId}.xml.*;version=${project.version}</osgi.export>
<!-- NOTE: JAXB annotations module is optional dependency, need to try to mark
as such here.
-->
@@ -63,47 +63,17 @@
<artifactId>jackson-databind</artifactId>
<version>${version.jackson.core}</version>
</dependency>
- <!-- also need JAXB annotation support -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-dataformat-xml</artifactId>
+ <version>${version.jackson.xml}</version>
+ </dependency>
+ <!-- may also need JAXB annotation support -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>${version.jackson.jaxb}</version>
</dependency>
-
- <!-- and we need JAX-RS annotations as well; but usually provided by container
- (and app should definitely have direct dep too, when using annotations)
- -->
- <dependency>
- <groupId>javax.ws.rs</groupId>
- <artifactId>jsr311-api</artifactId>
- <version>1.1.1</version>
- <scope>provided</scope>
- </dependency>
-
- <!-- and for testing, JUnit (or TestNG?) is needed -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.10</version>
- <scope>test</scope>
- </dependency>
- <!-- tests require JAX-RS impl; otherwise components fail to load
- (some oddity with API classes; should NOT be needed...)
-
- NOTE: use 1.2 as that's JDK 1.5; later (1.8) versions need JDK 1.6
- -->
- <dependency>
- <groupId>com.sun.jersey</groupId>
- <artifactId>jersey-core</artifactId>
- <version>1.2</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.sun.jersey</groupId>
- <artifactId>jersey-server</artifactId>
- <version>1.2</version>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
<plugins>
diff --git a/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/Annotations.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/Annotations.java
new file mode 100644
index 0000000..b8dc022
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/Annotations.java
@@ -0,0 +1,21 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+/**
+ * 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/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JacksonJaxbXMLProvider.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JacksonJaxbXMLProvider.java
new file mode 100644
index 0000000..4a15c63
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JacksonJaxbXMLProvider.java
@@ -0,0 +1,64 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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.dataformat.xml.XmlMapper;
+
+/**
+ * JSON content type provider automatically configured to use both Jackson
+ * and JAXB annotations (in that order of priority). Otherwise functionally
+ * same as {@link JacksonXMLProvider}.
+ *<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.TEXT_XML, MediaType.APPLICATION_XML})
+ at Produces({MediaType.TEXT_XML, MediaType.APPLICATION_XML})
+public class JacksonJaxbXMLProvider extends JacksonXMLProvider {
+ /**
+ * 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 JacksonJaxbXMLProvider()
+ {
+ this(null, DEFAULT_ANNOTATIONS);
+ }
+
+ /**
+ * @param annotationsToUse Annotation set(s) to use for configuring
+ * data binding
+ */
+ public JacksonJaxbXMLProvider(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 JacksonJaxbXMLProvider(XmlMapper mapper, Annotations[] annotationsToUse)
+ {
+ super(mapper, annotationsToUse);
+ }
+}
diff --git a/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JacksonXMLProvider.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JacksonXMLProvider.java
new file mode 100644
index 0000000..01dd4e7
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JacksonXMLProvider.java
@@ -0,0 +1,728 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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.dataformat.xml.XmlMapper;
+import com.fasterxml.jackson.jaxrs.xml.annotation.EndpointConfig;
+import com.fasterxml.jackson.jaxrs.xml.cfg.MapperConfigurator;
+import com.fasterxml.jackson.jaxrs.xml.util.AnnotationBundleKey;
+import com.fasterxml.jackson.jaxrs.xml.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 XmlMapper}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 that there is also a sub-class -- ({@link JacksonJaxbXMLProvider}) -- that
+ * 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.TEXT_XML, MediaType.APPLICATION_XML})
+ at Produces({MediaType.TEXT_XML, MediaType.APPLICATION_XML})
+public class JacksonXMLProvider
+ 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));
+
+ /* 27-Apr-2012, tatu: Grrrh. Looks like we need to consider these
+ * untouchables, still, to allow users to access "raw" responses
+ * if and as necessary. Latter is unfortunate in a way as it
+ * prevents use of automatic base64 decoding but...
+ */
+ _untouchables.add(new ClassKey(String.class));
+ _untouchables.add(new ClassKey(byte[].class));
+
+ // Then core JAX-RS things
+ _untouchables.add(new ClassKey(StreamingOutput.class));
+ _untouchables.add(new ClassKey(Response.class));
+ }
+
+ /**
+ * These are classes that we never use for reading
+ * (never try to deserialize instances of these types).
+ */
+ public final static Class<?>[] _unreadableClasses = new Class<?>[] {
+ InputStream.class, Reader.class
+ };
+
+ /**
+ * These are classes that we never use for writing
+ * (never try to serialize instances of these types).
+ */
+ public final static Class<?>[] _unwritableClasses = new Class<?>[] {
+ OutputStream.class, Writer.class,
+ StreamingOutput.class, Response.class
+ };
+
+ /*
+ /**********************************************************
+ /* Bit of caching
+ /**********************************************************
+ */
+
+ /**
+ * Cache for resolved endpoint configurations when reading XML
+ */
+ protected final LRUMap<AnnotationBundleKey, EndpointConfig> _readers
+ = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120);
+
+ /**
+ * Cache for resolved endpoint configurations when writing XML
+ */
+ protected final LRUMap<AnnotationBundleKey, EndpointConfig> _writers
+ = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120);
+
+ /*
+ /**********************************************************
+ /* General configuration
+ /**********************************************************
+ */
+
+ /**
+ * Helper object used for encapsulating configuration aspects
+ * of {@link XmlMapper}
+ */
+ protected final MapperConfigurator _mapperConfig;
+
+ /**
+ * Set of types (classes) that provider should ignore for data binding
+ */
+ protected HashSet<ClassKey> _cfgCustomUntouchables;
+
+ /*
+ /**********************************************************
+ /* Context configuration
+ /**********************************************************
+ */
+
+ /**
+ * Injectable context object used to locate configured
+ * instance of {@link XmlMapper} 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 JacksonXMLProvider()
+ {
+ this(null, BASIC_ANNOTATIONS);
+ }
+
+ /**
+ * @param annotationsToUse Annotation set(s) to use for configuring
+ * data binding
+ */
+ public JacksonXMLProvider(Annotations... annotationsToUse)
+ {
+ this(null, annotationsToUse);
+ }
+
+ public JacksonXMLProvider(XmlMapper 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 JacksonXMLProvider(XmlMapper mapper, Annotations[] annotationsToUse)
+ {
+ _mapperConfig = new MapperConfigurator(mapper, annotationsToUse);
+ }
+
+ /**
+ * Method that will return version information stored in and read from jar
+ * that contains this class.
+ */
+ public Version version() {
+ return ModuleVersion.instance.version();
+ }
+
+ /*
+ /**********************************************************
+ /* Configuring
+ /**********************************************************
+ */
+
+ /**
+ * Method for 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 XmlMapper} to use
+ * for serialization and deserialization; if null, will use the standard
+ * provider discovery from context instead. Default setting is null.
+ */
+ public void setMapper(XmlMapper m) {
+ _mapperConfig.setMapper(m);
+ }
+
+ public JacksonXMLProvider configure(DeserializationFeature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonXMLProvider configure(SerializationFeature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonXMLProvider configure(JsonParser.Feature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonXMLProvider configure(JsonGenerator.Feature f, boolean state) {
+ _mapperConfig.configure(f, state);
+ return this;
+ }
+
+ public JacksonXMLProvider enable(DeserializationFeature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonXMLProvider enable(SerializationFeature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonXMLProvider enable(JsonParser.Feature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonXMLProvider enable(JsonGenerator.Feature f, boolean state) {
+ _mapperConfig.configure(f, true);
+ return this;
+ }
+
+ public JacksonXMLProvider disable(DeserializationFeature f, boolean state) {
+ _mapperConfig.configure(f, false);
+ return this;
+ }
+
+ public JacksonXMLProvider disable(SerializationFeature f, boolean state) {
+ _mapperConfig.configure(f, false);
+ return this;
+ }
+
+ public JacksonXMLProvider disable(JsonParser.Feature f, boolean state) {
+ _mapperConfig.configure(f, false);
+ return this;
+ }
+
+ public JacksonXMLProvider 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));
+ }
+
+ /*
+ /**********************************************************
+ /* 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
+ * an XML type (via call to {@link #isXMLType}; 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 (!isXMLType(mediaType) || !isReadableType(type)) {
+ return false;
+ }
+ // if we really want to verify that we can serialize, we'll check:
+ if (_cfgCheckCanSerialize) {
+ XmlMapper 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) {
+ XmlMapper 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();
+ // Fix for [Issue#4]: note, can not try to advance parser, XML parser complains
+ PushbackInputStream wrappedStream = new PushbackInputStream(entityStream);
+ int firstByte = wrappedStream.read();
+ if (firstByte == -1) {
+ return null;
+ }
+ wrappedStream.unread(firstByte);
+ JsonParser jp = reader.getJsonFactory().createJsonParser(wrappedStream);
+ 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
+ * an XML type (via call to {@link #isXMLType}; 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 (!isXMLType(mediaType) || !isWritableType(type)) {
+ 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) {
+ XmlMapper mapper = locateMapper(type, mediaType);
+ endpoint = EndpointConfig.forWriting(mapper, annotations);
+ // 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.getJsonFactory().createJsonGenerator(entityStream, enc);
+
+ // Want indentation?
+ if (writer.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
+ jg.useDefaultPrettyPrinter();
+ }
+ // 04-Mar-2010, tatu: How about type we were given? (if any)
+ JavaType rootType = null;
+
+ if (genericType != null && value != null) {
+ /* 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root
+ * type since it prevents polymorphic type serialization. Since we really
+ * just need this for generics, let's only use generic type if it's truly
+ * generic.
+ */
+ if (genericType.getClass() != Class.class) { // generic types are other impls of 'java.lang.reflect.Type'
+ /* This is still not exactly right; should root type be further
+ * specialized with 'value.getClass()'? Let's see how well this works before
+ * trying to 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);
+ }
+ 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;
+ }
+
+ /*
+ /**********************************************************
+ /* Overridable helper methods
+ /**********************************************************
+ */
+
+ /**
+ * Helper method used to check whether given media type
+ * is XML type or sub type.
+ * Current implementation essentially checks to see whether
+ * {@link MediaType#getSubtype} returns "xml" or something
+ * ending with "+xml".
+ */
+ protected boolean isXMLType(MediaType mediaType)
+ {
+ /* As suggested by Stephen D, there are 2 ways to check: either
+ * being as inclusive as possible (if subtype is "xml"), or
+ * exclusive (major type "application", minor type "xml").
+ * 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+xml" subtypes, which count as well
+ String subtype = mediaType.getSubtype();
+ return "xml".equalsIgnoreCase(subtype) || subtype.endsWith("+xml");
+ }
+ /* Not sure if this can happen; but it seems reasonable
+ * that we can at least produce XML without media type?
+ */
+ return true;
+ }
+
+ /**
+ * Helper method called to see if given type is readable, that is,
+ * acceptable Java type to bind from XML
+ */
+ protected boolean isReadableType(Class<?> type)
+ {
+ /* Ok: looks like we must weed out some core types here; ones that
+ * make no sense to try to bind from XML
+ */
+ 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;
+ }
+ return true;
+ }
+
+
+ /**
+ * Helper method called to see if given type is writable, that is,
+ * acceptable Java type to write out as XML.
+ */
+ protected boolean isWritableType(Class<?> type)
+ {
+ /* Ok: looks like we must weed out some core types here; ones that
+ * make no sense to try to write out as XML:
+ */
+ 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;
+ }
+ return true;
+ }
+
+ /**
+ * Method called to locate {@link XmlMapper} 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 XmlMapper locateMapper(Class<?> type, MediaType mediaType)
+ {
+ // First: were we configured with a specific instance?
+ XmlMapper m = _mapperConfig.getConfiguredMapper();
+ if (m == null) {
+ // If not, maybe we can get one configured via context?
+ if (_providers != null) {
+ ContextResolver<XmlMapper> resolver = _providers.getContextResolver(XmlMapper.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(XmlMapper.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/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JsonMappingExceptionMapper.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JsonMappingExceptionMapper.java
new file mode 100644
index 0000000..83c26eb
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JsonMappingExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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 unprocessable XML 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/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JsonParseExceptionMapper.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JsonParseExceptionMapper.java
new file mode 100644
index 0000000..784ea3a
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/JsonParseExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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 unprocessable XML 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/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/ModuleVersion.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/ModuleVersion.java
new file mode 100644
index 0000000..c7ca123
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/ModuleVersion.java
@@ -0,0 +1,12 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+/**
+ * Helper class used for finding and caching version information
+ * for this module.
+ */
+class ModuleVersion extends VersionUtil
+{
+ public final static ModuleVersion instance = new ModuleVersion();
+}
diff --git a/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/EndpointConfig.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/EndpointConfig.java
new file mode 100644
index 0000000..8816aec
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/EndpointConfig.java
@@ -0,0 +1,185 @@
+package com.fasterxml.jackson.jaxrs.xml.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.*;
+
+/**
+ * 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 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)
+ {
+ EndpointConfig config = new EndpointConfig();
+ return config
+ .add(annotations, true)
+ .initWriter(mapper)
+ ;
+ }
+
+ protected EndpointConfig add(Annotation[] annotations, boolean forWriting)
+ {
+ // Same as [issue-10] with JSON provider; must check for null:
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ Class<?> type = annotation.annotationType();
+ 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.getJsonFactory().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.getJsonFactory().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;
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private static <T> T[] nullIfEmpty(T[] arg) {
+ if (arg == null || arg.length == 0) {
+ return null;
+ }
+ return arg;
+ }
+}
diff --git a/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/JacksonFeatures.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/JacksonFeatures.java
new file mode 100644
index 0000000..e128a41
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/JacksonFeatures.java
@@ -0,0 +1,39 @@
+package com.fasterxml.jackson.jaxrs.xml.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/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/package-info.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/package-info.java
new file mode 100644
index 0000000..ed19ecd
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/annotation/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Package that contains utility classes and methods for
+ * the JAX-RS XML provider module.
+ */
+package com.fasterxml.jackson.jaxrs.xml.annotation;
diff --git a/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/cfg/MapperConfigurator.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/cfg/MapperConfigurator.java
new file mode 100644
index 0000000..67f2749
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/cfg/MapperConfigurator.java
@@ -0,0 +1,200 @@
+package com.fasterxml.jackson.jaxrs.xml.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.dataformat.xml.JacksonXmlAnnotationIntrospector;
+import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.fasterxml.jackson.dataformat.xml.jaxb.XmlJaxbAnnotationIntrospector;
+
+import com.fasterxml.jackson.jaxrs.xml.Annotations;
+
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
+
+/**
+ * Helper class used to encapsulate details of configuring an
+ * {@link XmlMapper} 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 XmlMapper _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 XmlMapper _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(XmlMapper mapper, Annotations[] defAnnotations)
+ {
+ _mapper = mapper;
+ _defaultAnnotationsToUse = defAnnotations;
+ }
+
+ /**
+ * Method that locates, configures and returns {@link XmlMapper} to use
+ */
+ public synchronized XmlMapper getConfiguredMapper() {
+ /* important: should NOT call mapper(); needs to return null
+ * if no instance has been passed or constructed
+ */
+ return _mapper;
+ }
+
+ public synchronized XmlMapper getDefaultMapper()
+ {
+ if (_defaultMapper == null) {
+ // 10-Oct-2012, tatu: Better do things explicitly...
+ JacksonXmlModule module = getConfiguredModule();
+ _defaultMapper = (module == null) ? new XmlMapper() : new XmlMapper(module);
+ _setAnnotations(_defaultMapper, _defaultAnnotationsToUse);
+ }
+ return _defaultMapper;
+ }
+
+ protected JacksonXmlModule getConfiguredModule()
+ {
+ return new JacksonXmlModule();
+ }
+
+ /*
+ ***********************************************************
+ * Configuration methods
+ ***********************************************************
+ */
+
+ public synchronized void setMapper(XmlMapper 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 XmlMapper mapper()
+ {
+ if (_mapper == null) {
+ _mapper = new XmlMapper();
+ _setAnnotations(_mapper, _defaultAnnotationsToUse);
+ }
+ return _mapper;
+ }
+
+ protected void _setAnnotations(XmlMapper 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)
+ {
+ /* 11-Oct-2012, tatu: IMPORTANT: we MUST override choices here,
+ * since XML module provides extended versions of BOTH standard
+ * AnnotationIntrospectors.
+ */
+
+ switch (ann) {
+ case JACKSON:
+ return new JacksonXmlAnnotationIntrospector();
+ 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 = XmlJaxbAnnotationIntrospector.class;
+ }
+ return _jaxbIntrospectorClass.newInstance();
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to instantiate XmlJaxbAnnotationIntrospector: "+e.getMessage(), e);
+ }
+ default:
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/package-info.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/package-info.java
new file mode 100644
index 0000000..3105d18
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/package-info.java
@@ -0,0 +1,21 @@
+/**
+ * Jackson-based JAX-RS provider that can automatically
+ * serialize and deserialize resources for
+ * XML 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.xml.JacksonXMLProvider} is the basic
+ * provider configured to use Jackson annotations
+ * </li>
+ * <li>{@link com.fasterxml.jackson.jaxrs.xml.JacksonJaxbXMLProvider} 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.xml;
diff --git a/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/AnnotationBundleKey.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/AnnotationBundleKey.java
new file mode 100644
index 0000000..7596429
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/AnnotationBundleKey.java
@@ -0,0 +1,110 @@
+package com.fasterxml.jackson.jaxrs.xml.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/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/ClassKey.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/ClassKey.java
new file mode 100644
index 0000000..c76bf8d
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/ClassKey.java
@@ -0,0 +1,92 @@
+package com.fasterxml.jackson.jaxrs.xml.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/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/LRUMap.java b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/LRUMap.java
new file mode 100644
index 0000000..a7313aa
--- /dev/null
+++ b/xml/src/main/java/com/fasterxml/jackson/jaxrs/xml/util/LRUMap.java
@@ -0,0 +1,26 @@
+package com.fasterxml.jackson.jaxrs.xml.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/xml/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader b/xml/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
new file mode 100644
index 0000000..53cb534
--- /dev/null
+++ b/xml/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyReader
@@ -0,0 +1,2 @@
+com.fasterxml.jackson.jaxrs.xml.JacksonXMLProvider
+
diff --git a/xml/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter b/xml/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
new file mode 100644
index 0000000..9595ec8
--- /dev/null
+++ b/xml/src/main/resources/META-INF/services/javax.ws.rs.ext.MessageBodyWriter
@@ -0,0 +1 @@
+com.fasterxml.jackson.jaxrs.xml.JacksonXMLProvider
diff --git a/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/JaxrsTestBase.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/JaxrsTestBase.java
new file mode 100644
index 0000000..99dfc44
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/JaxrsTestBase.java
@@ -0,0 +1,91 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestCanDeserialize.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestCanDeserialize.java
new file mode 100644
index 0000000..8303c06
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestCanDeserialize.java
@@ -0,0 +1,36 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+import java.io.*;
+import java.lang.annotation.Annotation;
+
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Unit test to check [JACKSON-540]
+ */
+public class TestCanDeserialize extends JaxrsTestBase
+{
+ static class Bean {
+ public int x;
+ }
+
+ public void testCanSerialize() throws IOException
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ String XML = "<Bean><x>3</x></Bean>";
+ InputStream stream = new ByteArrayInputStream(XML.getBytes());
+ Bean b = (Bean) prov.readFrom(Object.class, Bean.class, new Annotation[0],
+ MediaType.APPLICATION_XML_TYPE, null, stream);
+ assertNotNull(b);
+ assertEquals(3, b.x);
+ }
+
+ // [Issue#4]: exception for no content
+ public void testCanSerializeEmpty() throws IOException
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ Bean b = (Bean) prov.readFrom(Object.class, Bean.class, new Annotation[0],
+ MediaType.APPLICATION_XML_TYPE, null, new ByteArrayInputStream(new byte[0]));
+ assertNull(b);
+ }
+}
diff --git a/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestCanSerialize.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestCanSerialize.java
new file mode 100644
index 0000000..832066f
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestCanSerialize.java
@@ -0,0 +1,46 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestJacksonFeatures.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestJacksonFeatures.java
new file mode 100644
index 0000000..cce1705
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestJacksonFeatures.java
@@ -0,0 +1,112 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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.xml.JacksonXMLProvider;
+import com.fasterxml.jackson.jaxrs.xml.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
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ 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 (extra) wrapping enabled, we get:
+ prov.writeTo(bean, bean.getClass(), bean.getClass(), new Annotation[] { feats },
+ MediaType.APPLICATION_JSON_TYPE, null, out);
+ assertEquals("<Bean><Bean><a>3</a></Bean></Bean>", 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("<Bean><a>3</a></Bean>", out.toString("UTF-8"));
+ }
+
+ public void testWriteConfigsViaBundle() throws Exception
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ 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><Bean><a>3</a></Bean></Bean>", out.toString("UTF-8"));
+ }
+
+ // [Issue-2], deserialization
+ public void testReadConfigs() throws Exception
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ 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("<Bean><foobar>3</foobar></Bean>".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("<Bean><foobar>3</foobar></Bean>".getBytes("UTF-8")));
+ fail("Should have caught an exception");
+ } catch (JsonMappingException e) {
+ verifyException(e, "Unrecognized field");
+ }
+ }
+
+}
diff --git a/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestJsonView.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestJsonView.java
new file mode 100644
index 0000000..f8e556c
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestJsonView.java
@@ -0,0 +1,47 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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;
+import com.fasterxml.jackson.jaxrs.xml.JacksonXMLProvider;
+
+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
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ 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("<Bean><value1>1</value1></Bean>", out.toString("UTF-8"));
+ }
+}
diff --git a/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestRootType.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestRootType.java
new file mode 100644
index 0000000..a5664c2
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestRootType.java
@@ -0,0 +1,40 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+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;
+import com.fasterxml.jackson.jaxrs.xml.JacksonXMLProvider;
+
+public class TestRootType
+ extends JaxrsTestBase
+{
+ @JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT, property="type")
+ @JsonTypeName("bean")
+ static class Bean {
+ public int a = 3;
+ }
+
+ public void testRootType() throws Exception
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ 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 xml = out.toString("UTF-8");
+ assertEquals("<List><bean><a>3</a></bean></List>", xml);
+ }
+}
diff --git a/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestSerialize.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestSerialize.java
new file mode 100644
index 0000000..ff26f2a
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestSerialize.java
@@ -0,0 +1,69 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.lang.annotation.Annotation;
+import java.util.*;
+
+import javax.ws.rs.core.MediaType;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonRootName;
+
+public class TestSerialize extends JaxrsTestBase
+{
+ // for [Issue-1]
+ public class Message {
+ public String text;
+
+ public Message() { }
+ public Message(String s) { text = s; }
+ }
+
+ @JsonRootName("m")
+ @JsonPropertyOrder({ "pageNumber", "messages" })
+ public class Messages {
+ protected List<Message> messages = new ArrayList<Message>();
+ protected int pageNumber;
+
+ protected Messages() {}
+
+ public Messages(List<Message> messages, int pageNumber) {
+ this.messages = messages;
+ this.pageNumber = pageNumber;
+ }
+
+ public List<Message> getMessages() {
+ return messages;
+ }
+
+ public int getPageNumber() {
+ return pageNumber;
+ }
+ }
+
+ /*
+ /**********************************************************************
+ /* Unit tests
+ /**********************************************************************
+ */
+
+ // [Issue-1]
+ public void testSimpleWriteTo() throws Exception
+ {
+ Messages msgs = new Messages();
+ msgs.pageNumber = 3;
+ msgs.messages.add(new Message("foo"));
+ msgs.messages.add(new Message("bar"));
+
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ MediaType mt = MediaType.APPLICATION_JSON_TYPE;
+ prov.writeTo(msgs, Messages.class, Messages.class, new Annotation[0], mt, null, out);
+
+ String xml = out.toString("UTF-8");
+
+ assertEquals("<m><pageNumber>3</pageNumber><messages><messages><text>foo</text></messages>"
+ +"<messages><text>bar</text></messages></messages></m>",
+ xml);
+ }
+}
diff --git a/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestUntouchables.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestUntouchables.java
new file mode 100644
index 0000000..dcad7c3
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestUntouchables.java
@@ -0,0 +1,65 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+import java.util.*;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.StreamingOutput;
+
+import com.fasterxml.jackson.jaxrs.xml.JacksonXMLProvider;
+
+/**
+ * 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 "isXMLType"
+ * remains overridable.
+ */
+ public static class MyJacksonProvider extends JacksonXMLProvider {
+ // 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 isXMLType(MediaType mediaType) { return super.isXMLType(mediaType); }
+ }
+
+ /*
+ /**********************************************************
+ /* Unit tests
+ /**********************************************************
+ */
+
+ public void testDefaultUntouchables() throws Exception
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ // By default, no reason to exclude, say, this test class...
+ assertTrue(prov.isReadable(getClass(), getClass(), null, null));
+ assertTrue(prov.isWriteable(getClass(), getClass(), null, null));
+
+ // but some types should be ignored (set of ignorable may change over time tho!)
+ assertFalse(prov.isWriteable(StreamingOutput.class, StreamingOutput.class, null, null));
+
+ // and then on-the-fence things (see [Issue-1])
+ assertFalse(prov.isReadable(String.class, getClass(), null, null));
+ assertFalse(prov.isReadable(byte[].class, getClass(), null, null));
+ }
+
+ public void testCustomUntouchables() throws Exception
+ {
+ JacksonXMLProvider prov = new JacksonXMLProvider();
+ // can mark this as ignorable...
+ prov.addUntouchable(getClass());
+ // and then it shouldn't be processable
+ assertFalse(prov.isReadable(getClass(), getClass(), null, null));
+ assertFalse(prov.isWriteable(getClass(), getClass(), null, null));
+
+ // Same for interfaces, like:
+ prov.addUntouchable(Collection.class);
+ assertFalse(prov.isReadable(ArrayList.class, ArrayList.class, null, null));
+ assertFalse(prov.isWriteable(HashSet.class, HashSet.class, null, null));
+ }
+}
+
\ No newline at end of file
diff --git a/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestVersions.java b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestVersions.java
new file mode 100644
index 0000000..c97586d
--- /dev/null
+++ b/xml/src/test/java/com/fasterxml/jackson/jaxrs/xml/TestVersions.java
@@ -0,0 +1,42 @@
+package com.fasterxml.jackson.jaxrs.xml;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.core.Versioned;
+import com.fasterxml.jackson.jaxrs.xml.JacksonXMLProvider;
+
+public class TestVersions extends JaxrsTestBase
+{
+ /**
+ * Not a good to do this, but has to do, for now...
+ */
+ private final static int MAJOR_VERSION = 2;
+ private final static int MINOR_VERSION = 1;
+
+ // could inject using Maven filters as well...
+ private final static String GROUP_ID = "com.fasterxml.jackson.jaxrs";
+ private final static String ARTIFACT_ID = "jackson-jaxrs-xml-provider";
+
+ public void testMapperVersions()
+ {
+ assertVersion(new JacksonXMLProvider());
+ }
+
+ /*
+ /**********************************************************
+ /* Helper methods
+ /**********************************************************
+ */
+
+ private void assertVersion(Versioned vers)
+ {
+ final Version v = vers.version();
+ assertFalse("Should find version information (got "+v+")", v.isUknownVersion());
+ assertEquals(MAJOR_VERSION, v.getMajorVersion());
+ assertEquals(MINOR_VERSION, v.getMinorVersion());
+ // Check patch level initially, comment out for maint versions
+// assertEquals(0, v.getPatchLevel());
+ assertEquals(GROUP_ID, v.getGroupId());
+ assertEquals(ARTIFACT_ID, v.getArtifactId());
+ }
+}
+
--
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