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 b4768d07f3e63b3577ae2d63cdae1002efe41b9b Author: Tatu Saloranta <[email protected]> Date: Fri Mar 15 22:46:34 2013 -0700 Refactoring --- .../fasterxml/jackson/jaxrs/base/ProviderBase.java | 534 ++++++++----------- .../jackson/jaxrs/base/cfg/EndpointConfigBase.java | 165 ++++++ .../jackson/jaxrs/json/JacksonJsonProvider.java | 591 ++------------------- .../jaxrs/json/annotation/EndpointConfig.java | 153 +----- 4 files changed, 439 insertions(+), 1004 deletions(-) diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java b/base/src/main/java/com/fasterxml/jackson/jaxrs/base/ProviderBase.java similarity index 65% copy from json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java copy to base/src/main/java/com/fasterxml/jackson/jaxrs/base/ProviderBase.java index 65ec884..61f59ff 100644 --- a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java +++ b/base/src/main/java/com/fasterxml/jackson/jaxrs/base/ProviderBase.java @@ -1,77 +1,39 @@ -package com.fasterxml.jackson.jaxrs.json; +package com.fasterxml.jackson.jaxrs.base; import java.io.*; import java.lang.annotation.Annotation; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; -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 javax.ws.rs.core.*; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.util.LRUMap; +import com.fasterxml.jackson.jaxrs.base.cfg.EndpointConfigBase; +import com.fasterxml.jackson.jaxrs.base.cfg.MapperConfiguratorBase; import com.fasterxml.jackson.jaxrs.base.util.AnnotationBundleKey; import com.fasterxml.jackson.jaxrs.base.util.ClassKey; -import com.fasterxml.jackson.jaxrs.json.annotation.EndpointConfig; -import com.fasterxml.jackson.jaxrs.json.cfg.MapperConfigurator; - -/** - * Basic implementation of JAX-RS abstractions ({@link MessageBodyReader}, - * {@link MessageBodyWriter}) needed for binding - * JSON ("application/json") content to and from Java Objects ("POJO"s). - *<p> - * Actual data binding functionality is implemented by {@link ObjectMapper}: - * mapper to use can be configured in multiple ways: - * <ul> - * <li>By explicitly passing mapper to use in constructor - * <li>By explictly setting mapper to use by {@link #setMapper} - * <li>By defining JAX-RS <code>Provider</code> that returns {@link ObjectMapper}s. - * <li>By doing none of above, in which case a default mapper instance is - * constructed (and configured if configuration methods are called) - * </ul> - * The last method ("do nothing specific") is often good enough; explicit passing - * of Mapper is simple and explicit; and Provider-based method may make sense - * with Depedency Injection frameworks, or if Mapper has to be configured differently - * for different media types. - *<p> - * Note that the default mapper instance will be automatically created if - * one of explicit configuration methods (like {@link #configure}) - * is called: if so, Provider-based introspection is <b>NOT</b> used, but the - * resulting Mapper is used as configured. - *<p> - * Note: version 1.3 added a sub-class ({@link JacksonJaxbJsonProvider}) which - * is configured by default to use both Jackson and JAXB annotations for configuration - * (base class when used as-is defaults to using just Jackson annotations) - * - * @author Tatu Saloranta - */ -@Provider -@Consumes(MediaType.WILDCARD) // NOTE: required to support "non-standard" JSON variants -@Produces(MediaType.WILDCARD) -public class JacksonJsonProvider +public abstract class ProviderBase< + THIS extends ProviderBase<THIS, MAPPER, ANN, EP_CONFIG, MAPPER_CONFIG>, + MAPPER extends ObjectMapper, + ANN extends Enum<ANN>, + EP_CONFIG extends EndpointConfigBase<EP_CONFIG>, + MAPPER_CONFIG extends MapperConfiguratorBase<MAPPER_CONFIG,MAPPER,ANN> +> 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 @@ -80,16 +42,16 @@ public class JacksonJsonProvider * (why ClassKey? since plain old Class has no hashCode() defined, * lookups are painfully slow) */ - public final static HashSet<ClassKey> _untouchables = new HashSet<ClassKey>(); + protected final static HashSet<ClassKey> DEFAULT_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)); + DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.InputStream.class)); + DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.Reader.class)); + DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.OutputStream.class)); + DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.Writer.class)); // then some primitive types - _untouchables.add(new ClassKey(char[].class)); + DEFAULT_UNTOUCHABLES.add(new ClassKey(char[].class)); /* 28-Jan-2012, tatu: 1.x excluded some additional types; * but let's relax these a bit: @@ -98,15 +60,15 @@ public class JacksonJsonProvider * [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)); + DEFAULT_UNTOUCHABLES.add(new ClassKey(String.class)); + DEFAULT_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<?>[] { + public final static Class<?>[] DEFAULT_UNREADABLES = new Class<?>[] { InputStream.class, Reader.class }; @@ -114,40 +76,16 @@ public class JacksonJsonProvider * These are classes that we never use for writing * (never try to serialize instances of these types). */ - public final static Class<?>[] _unwritableClasses = new Class<?>[] { + public final static Class<?>[] DEFAULT_UNWRITABLES = new Class<?>[] { OutputStream.class, Writer.class, StreamingOutput.class, Response.class }; /* /********************************************************** - /* Bit of caching - /********************************************************** - */ - - /** - * Cache for resolved endpoint configurations when reading JSON data - */ - protected final LRUMap<AnnotationBundleKey, EndpointConfig> _readers - = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120); - - /** - * Cache for resolved endpoint configurations when writing JSON data - */ - protected final LRUMap<AnnotationBundleKey, EndpointConfig> _writers - = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120); - - /* - /********************************************************** /* General configuration /********************************************************** */ - - /** - * Helper object used for encapsulating configuration aspects - * of {@link ObjectMapper} - */ - protected final MapperConfigurator _mapperConfig; /** * Set of types (classes) that provider should ignore for data binding @@ -155,34 +93,6 @@ public class JacksonJsonProvider protected HashSet<ClassKey> _cfgCustomUntouchables; /** - * JSONP function name to use for automatic JSONP wrapping, if any; - * if null, no JSONP wrapping is done. - * Note that this is the default value that can be overridden on - * per-endpoint basis. - */ - protected String _jsonpFunctionName; - - /* - /********************************************************** - /* Context configuration - /********************************************************** - */ - - /** - * Injectable context object used to locate configured - * instance of {@link ObjectMapper} to use for actual - * serialization. - */ - @Context - protected Providers _providers; - - /* - /********************************************************** - /* Configuration - /********************************************************** - */ - - /** * Whether we want to actually check that Jackson has * a serializer for given type. Since this should generally * be the case (due to auto-discovery) and since the call @@ -200,52 +110,48 @@ public class JacksonJsonProvider /* /********************************************************** - /* Construction + /* Excluded types /********************************************************** */ + + public final static HashSet<ClassKey> _untouchables = DEFAULT_UNTOUCHABLES; - /** - * Default constructor, usually used when provider is automatically - * configured to be used with JAX-RS implementation. + public final static Class<?>[] _unreadableClasses = DEFAULT_UNREADABLES; + + public final static Class<?>[] _unwritableClasses = DEFAULT_UNWRITABLES; + + /* + /********************************************************** + /* Bit of caching + /********************************************************** */ - public JacksonJsonProvider() - { - this(null, BASIC_ANNOTATIONS); - } /** - * @param annotationsToUse Annotation set(s) to use for configuring - * data binding + * Cache for resolved endpoint configurations when reading JSON data */ - public JacksonJsonProvider(Annotations... annotationsToUse) - { - this(null, annotationsToUse); - } + protected final LRUMap<AnnotationBundleKey, EP_CONFIG> _readers + = new LRUMap<AnnotationBundleKey, EP_CONFIG>(16, 120); - public JacksonJsonProvider(ObjectMapper mapper) - { - this(mapper, BASIC_ANNOTATIONS); - } - /** - * Constructor to use when a custom mapper (usually components - * like serializer/deserializer factories that have been configured) - * is to be used. - * - * @param annotationsToUse Sets of annotations (Jackson, JAXB) that provider should - * support + * Cache for resolved endpoint configurations when writing JSON data */ - public JacksonJsonProvider(ObjectMapper mapper, Annotations[] annotationsToUse) - { - _mapperConfig = new MapperConfigurator(mapper, annotationsToUse); - } - + protected final LRUMap<AnnotationBundleKey, EP_CONFIG> _writers + = new LRUMap<AnnotationBundleKey, EP_CONFIG>(16, 120); + + /* + /********************************************************** + /* General configuration + /********************************************************** + */ + /** - * Method that will return version information stored in and read from jar - * that contains this class. + * Helper object used for encapsulating configuration aspects + * of {@link ObjectMapper} */ - public Version version() { - return PackageVersion.VERSION; + protected final MAPPER_CONFIG _mapperConfig; + + protected ProviderBase(MAPPER_CONFIG mconfig) { + _mapperConfig = mconfig; } /* @@ -269,6 +175,23 @@ public class JacksonJsonProvider public void checkCanSerialize(boolean state) { _cfgCheckCanSerialize = state; } /** + * 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)); + } + + /** * 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. @@ -276,7 +199,7 @@ public class JacksonJsonProvider * @param annotationsToUse Ordered list of annotation sets to use; if null, * default */ - public void setAnnotationsToUse(Annotations[] annotationsToUse) { + public void setAnnotationsToUse(ANN[] annotationsToUse) { _mapperConfig.setAnnotationsToUse(annotationsToUse); } @@ -285,174 +208,96 @@ public class JacksonJsonProvider * for serialization and deserialization; if null, will use the standard * provider discovery from context instead. Default setting is null. */ - public void setMapper(ObjectMapper m) { + public void setMapper(MAPPER m) { _mapperConfig.setMapper(m); } - public JacksonJsonProvider configure(DeserializationFeature f, boolean state) { + public THIS configure(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, state); - return this; + return _this(); } - public JacksonJsonProvider configure(SerializationFeature f, boolean state) { + public THIS configure(SerializationFeature f, boolean state) { _mapperConfig.configure(f, state); - return this; + return _this(); } - public JacksonJsonProvider configure(JsonParser.Feature f, boolean state) { + public THIS configure(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, state); - return this; + return _this(); } - public JacksonJsonProvider configure(JsonGenerator.Feature f, boolean state) { + public THIS configure(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, state); - return this; + return _this(); } - public JacksonJsonProvider enable(DeserializationFeature f, boolean state) { + public THIS enable(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, true); - return this; + return _this(); } - public JacksonJsonProvider enable(SerializationFeature f, boolean state) { + public THIS enable(SerializationFeature f, boolean state) { _mapperConfig.configure(f, true); - return this; + return _this(); } - public JacksonJsonProvider enable(JsonParser.Feature f, boolean state) { + public THIS enable(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, true); - return this; + return _this(); } - public JacksonJsonProvider enable(JsonGenerator.Feature f, boolean state) { + public THIS enable(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, true); - return this; + return _this(); } - public JacksonJsonProvider disable(DeserializationFeature f, boolean state) { + public THIS disable(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, false); - return this; + return _this(); } - public JacksonJsonProvider disable(SerializationFeature f, boolean state) { + public THIS disable(SerializationFeature f, boolean state) { _mapperConfig.configure(f, false); - return this; + return _this(); } - public JacksonJsonProvider disable(JsonParser.Feature f, boolean state) { + public THIS disable(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, false); - return this; + return _this(); } - public JacksonJsonProvider disable(JsonGenerator.Feature f, boolean state) { + public THIS disable(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, false); - return this; + return _this(); } - /** - * Method for marking specified type as "untouchable", meaning that provider - * will not try to read or write values of this type (or its subtypes). - * - * @param type Type to consider untouchable; can be any kind of class, - * including abstract class or interface. No instance of this type - * (including subtypes, i.e. types assignable to this type) will - * be read or written by provider - */ - public void addUntouchable(Class<?> type) - { - if (_cfgCustomUntouchables == null) { - _cfgCustomUntouchables = new HashSet<ClassKey>(); - } - _cfgCustomUntouchables.add(new ClassKey(type)); - } - - public void setJSONPFunctionName(String fname) { - _jsonpFunctionName = fname; - } - /* /********************************************************** - /* MessageBodyReader impl + /* Abstract methods sub-classes need to implement /********************************************************** */ - - /** - * Method that JAX-RS container calls to try to check whether - * values of given type (and media type) can be deserialized by - * this provider. - * Implementation will first check that expected media type is - * a JSON type (via call to {@link #isJsonType}; then verify - * that type is not one of "untouchable" types (types we will never - * automatically handle), and finally that there is a deserializer - * for type (iff {@link #checkCanDeserialize} has been called with - * true argument -- otherwise assumption is there will be a handler) - */ - public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) - { - if (!isJsonType(mediaType)) { - return false; - } - - /* Ok: looks like we must weed out some core types here; ones that - * make no sense to try to bind from JSON: - */ - if (_untouchables.contains(new ClassKey(type))) { - return false; - } - // and there are some other abstract/interface types to exclude too: - for (Class<?> cls : _unreadableClasses) { - if (cls.isAssignableFrom(type)) { - return false; - } - } - // as well as possible custom exclusions - if (_containedIn(type, _cfgCustomUntouchables)) { - return false; - } - - // Finally: if we really want to verify that we can serialize, we'll check: - if (_cfgCheckCanSerialize) { - ObjectMapper mapper = locateMapper(type, mediaType); - if (!mapper.canDeserialize(mapper.constructType(type))) { - return false; - } - } - return true; - } /** - * Method that JAX-RS container calls to deserialize given value. + * Helper method used to check whether given media type + * is JSON type or sub type. + * Current implementation essentially checks to see whether + * {@link MediaType#getSubtype} returns "json" or something + * ending with "+json". + * + * @since 2.2 */ - public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,String> httpHeaders, InputStream entityStream) - throws IOException - { + protected abstract boolean hasMatchingMediaType(MediaType mediaType); - AnnotationBundleKey key = new AnnotationBundleKey(annotations); - EndpointConfig endpoint; - synchronized (_readers) { - endpoint = _readers.get(key); - } - // not yet resolved (or not cached any more)? Resolve! - if (endpoint == null) { - ObjectMapper mapper = locateMapper(type, mediaType); - endpoint = EndpointConfig.forReading(mapper, annotations); - // and cache for future reuse - synchronized (_readers) { - _readers.put(key.immutableKey(), endpoint); - } - } - ObjectReader reader = endpoint.getReader(); - - JsonParser jp = reader.getFactory().createParser(entityStream); - if (jp.nextToken() == null) { - return null; - } - return reader.withType(genericType).readValue(jp); - } + protected abstract MAPPER _locateMapperViaProvider(Class<?> type, MediaType mediaType); + protected abstract EP_CONFIG _configForReading(MAPPER mapper, Annotation[] annotations); + + protected abstract EP_CONFIG _configForWriting(MAPPER mapper, Annotation[] annotations); + /* /********************************************************** - /* MessageBodyWriter impl + /* Partial MessageBodyWriter impl /********************************************************** */ @@ -471,7 +316,7 @@ public class JacksonJsonProvider */ return -1; } - + /** * Method that JAX-RS container calls to try to check whether * given value (of specified type) can be serialized by @@ -485,7 +330,7 @@ public class JacksonJsonProvider */ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { - if (!isJsonType(mediaType)) { + if (!hasMatchingMediaType(mediaType)) { return false; } @@ -523,15 +368,14 @@ public class JacksonJsonProvider throws IOException { AnnotationBundleKey key = new AnnotationBundleKey(annotations); - EndpointConfig endpoint; + EP_CONFIG endpoint; synchronized (_writers) { endpoint = _writers.get(key); } // not yet resolved (or not cached any more)? Resolve! if (endpoint == null) { - ObjectMapper mapper = locateMapper(type, mediaType); - endpoint = EndpointConfig.forWriting(mapper, annotations, - this._jsonpFunctionName); + MAPPER mapper = locateMapper(type, mediaType); + endpoint = _configForWriting(mapper, annotations); // and cache for future reuse synchronized (_writers) { _writers.put(key.immutableKey(), endpoint); @@ -579,9 +423,7 @@ public class JacksonJsonProvider if (rootType != null) { writer = writer.withType(rootType); } - // and finally, JSONP wrapping, if any: - value = endpoint.applyJSONP(value); - + value = endpoint.modifyBeforeWrite(value); writer.writeValue(jg, value); } @@ -593,39 +435,92 @@ public class JacksonJsonProvider { return JsonEncoding.UTF8; } - + /* /********************************************************** - /* Public helper methods + /* MessageBodyReader impl /********************************************************** */ - + /** - * Helper method used to check whether given media type - * is JSON type or sub type. - * Current implementation essentially checks to see whether - * {@link MediaType#getSubtype} returns "json" or something - * ending with "+json". + * Method that JAX-RS container calls to try to check whether + * values of given type (and media type) can be deserialized by + * this provider. + * Implementation will first check that expected media type is + * a JSON type (via call to {@link #isJsonType}; then verify + * that type is not one of "untouchable" types (types we will never + * automatically handle), and finally that there is a deserializer + * for type (iff {@link #checkCanDeserialize} has been called with + * true argument -- otherwise assumption is there will be a handler) */ - protected boolean isJsonType(MediaType mediaType) + public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { - /* As suggested by Stephen D, there are 2 ways to check: either - * being as inclusive as possible (if subtype is "json"), or - * exclusive (major type "application", minor type "json"). - * Let's start with inclusive one, hard to know which major - * types we should cover aside from "application". - */ - if (mediaType != null) { - // Ok: there are also "xxx+json" subtypes, which count as well - String subtype = mediaType.getSubtype(); - return "json".equalsIgnoreCase(subtype) || subtype.endsWith("+json"); + if (!hasMatchingMediaType(mediaType)) { + return false; } - /* Not sure if this can happen; but it seems reasonable - * that we can at least produce json without media type? + + /* Ok: looks like we must weed out some core types here; ones that + * make no sense to try to bind from JSON: */ + if (_untouchables.contains(new ClassKey(type))) { + return false; + } + // and there are some other abstract/interface types to exclude too: + for (Class<?> cls : _unreadableClasses) { + if (cls.isAssignableFrom(type)) { + return false; + } + } + // as well as possible custom exclusions + if (_containedIn(type, _cfgCustomUntouchables)) { + return false; + } + + // Finally: if we really want to verify that we can serialize, we'll check: + if (_cfgCheckCanSerialize) { + ObjectMapper mapper = locateMapper(type, mediaType); + if (!mapper.canDeserialize(mapper.constructType(type))) { + return false; + } + } return true; } - + + /** + * Method that JAX-RS container calls to deserialize given value. + */ + public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,String> httpHeaders, InputStream entityStream) + throws IOException + { + AnnotationBundleKey key = new AnnotationBundleKey(annotations); + EP_CONFIG endpoint; + synchronized (_readers) { + endpoint = _readers.get(key); + } + // not yet resolved (or not cached any more)? Resolve! + if (endpoint == null) { + MAPPER mapper = locateMapper(type, mediaType); + endpoint = _configForReading(mapper, annotations); + // and cache for future reuse + synchronized (_readers) { + _readers.put(key.immutableKey(), endpoint); + } + } + ObjectReader reader = endpoint.getReader(); + + JsonParser jp = reader.getFactory().createParser(entityStream); + if (jp.nextToken() == null) { + return null; + } + return reader.withType(genericType).readValue(jp); + } + + /* + /********************************************************** + /* Private/sub-class helper methods + /********************************************************** + */ + /** * Method called to locate {@link ObjectMapper} to use for serialization * and deserialization. If an instance has been explicitly defined by @@ -646,26 +541,13 @@ public class JacksonJsonProvider * not used by this method, * but will be passed to {@link ContextResolver} as is. */ - public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) + public MAPPER locateMapper(Class<?> type, MediaType mediaType) { // First: were we configured with a specific instance? - ObjectMapper m = _mapperConfig.getConfiguredMapper(); + MAPPER 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); - } - } + m = _locateMapperViaProvider(type, mediaType); if (m == null) { // If not, let's get the fallback default instance m = _mapperConfig.getDefaultMapper(); @@ -673,13 +555,7 @@ public class JacksonJsonProvider } return m; } - - /* - /********************************************************** - /* Private/sub-class helper methods - /********************************************************** - */ - + protected static boolean _containedIn(Class<?> mainType, HashSet<ClassKey> set) { if (set != null) { @@ -695,18 +571,18 @@ public class JacksonJsonProvider return false; } - private static List<Class<?>> findSuperTypes(Class<?> cls, Class<?> endBefore) + protected 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) + protected 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) + protected static void _addSuperTypes(Class<?> cls, Class<?> endBefore, Collection<Class<?>> result, boolean addClassItself) { if (cls == endBefore || cls == null || cls == Object.class) { return; @@ -723,4 +599,8 @@ public class JacksonJsonProvider _addSuperTypes(cls.getSuperclass(), endBefore, result, true); } + @SuppressWarnings("unchecked") + private final THIS _this() { + return (THIS) this; + } } diff --git a/base/src/main/java/com/fasterxml/jackson/jaxrs/base/cfg/EndpointConfigBase.java b/base/src/main/java/com/fasterxml/jackson/jaxrs/base/cfg/EndpointConfigBase.java new file mode 100644 index 0000000..da7ec2a --- /dev/null +++ b/base/src/main/java/com/fasterxml/jackson/jaxrs/base/cfg/EndpointConfigBase.java @@ -0,0 +1,165 @@ +package com.fasterxml.jackson.jaxrs.base.cfg; + +import java.lang.annotation.Annotation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonView; + +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 abstract class EndpointConfigBase<THIS extends EndpointConfigBase<THIS>> +{ + // // 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 EndpointConfigBase() { } + + @SuppressWarnings("unchecked") + protected THIS add(Annotation[] annotations, boolean forWriting) + { + if (annotations != null) { + for (Annotation annotation : annotations) { + addAnnotation(annotation.annotationType(), annotation, forWriting); + } + } + return (THIS) this; + } + + protected void addAnnotation(Class<? extends Annotation> type, + Annotation annotation, boolean forWriting) + { + 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 == 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); + } + } + } + + @SuppressWarnings("unchecked") + protected THIS 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) this; + } + + @SuppressWarnings("unchecked") + protected THIS initWriter(ObjectMapper mapper) + { + // first common config + if (_activeView != null) { + _writer = mapper.writerWithView(_activeView); + } else { + _writer = mapper.writer(); + } + if (_rootName != null) { + _writer = _writer.withRootName(_rootName); + } + // Then features + if (_serEnable != null) { + _writer = _writer.withFeatures(_serEnable); + } + if (_serDisable != null) { + _writer = _writer.withoutFeatures(_serDisable); + } + // then others + + // Finally: couple of features we always set + + /* Important: we are NOT to close the underlying stream after + * mapping, so we need to instruct parser: + */ + _writer.getFactory().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); + + return (THIS) this; + } + + /* + /********************************************************** + /* Accessors + /********************************************************** + */ + + public final ObjectReader getReader() { + if (_reader == null) { // sanity check, should never happen + throw new IllegalStateException(); + } + return _reader; + } + + public final ObjectWriter getWriter() { + if (_writer == null) { // sanity check, should never happen + throw new IllegalStateException(); + } + return _writer; + } + + /* + /********************************************************** + /* Value modifications + /********************************************************** + */ + + public abstract Object modifyBeforeWrite(Object value); +} diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java index 65ec884..9a211db 100644 --- a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java +++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/JacksonJsonProvider.java @@ -1,24 +1,15 @@ package com.fasterxml.jackson.jaxrs.json; -import java.io.*; import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.util.*; import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.StreamingOutput; +import javax.ws.rs.core.*; 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.base.util.AnnotationBundleKey; -import com.fasterxml.jackson.jaxrs.base.util.ClassKey; +import com.fasterxml.jackson.jaxrs.base.ProviderBase; import com.fasterxml.jackson.jaxrs.json.annotation.EndpointConfig; import com.fasterxml.jackson.jaxrs.json.cfg.MapperConfigurator; @@ -57,10 +48,9 @@ import com.fasterxml.jackson.jaxrs.json.cfg.MapperConfigurator; @Consumes(MediaType.WILDCARD) // NOTE: required to support "non-standard" JSON variants @Produces(MediaType.WILDCARD) public class JacksonJsonProvider - implements - MessageBodyReader<Object>, - MessageBodyWriter<Object>, - Versioned + extends ProviderBase<JacksonJsonProvider, + ObjectMapper, Annotations, + EndpointConfig, MapperConfigurator> { /** * Default annotation sets to use, if not explicitly defined during @@ -70,89 +60,12 @@ public class JacksonJsonProvider public final static Annotations[] BASIC_ANNOTATIONS = { Annotations.JACKSON }; - - /** - * Looks like we need to worry about accidental - * data binding for types we shouldn't be handling. This is - * probably not a very good way to do it, but let's start by - * blacklisting things we are not to handle. - *<p> - * (why ClassKey? since plain old Class has no hashCode() defined, - * lookups are painfully slow) - */ - public final static HashSet<ClassKey> _untouchables = new HashSet<ClassKey>(); - static { - // First, I/O things (direct matches) - _untouchables.add(new ClassKey(java.io.InputStream.class)); - _untouchables.add(new ClassKey(java.io.Reader.class)); - _untouchables.add(new ClassKey(java.io.OutputStream.class)); - _untouchables.add(new ClassKey(java.io.Writer.class)); - - // then some primitive types - _untouchables.add(new ClassKey(char[].class)); - - /* 28-Jan-2012, tatu: 1.x excluded some additional types; - * but let's relax these a bit: - */ - /* 27-Apr-2012, tatu: Ugh. As per - * [https://github.com/FasterXML/jackson-jaxrs-json-provider/issues/12] - * better revert this back, to make them untouchable again. - */ - _untouchables.add(new ClassKey(String.class)); - _untouchables.add(new ClassKey(byte[].class)); - } - - /** - * These are classes that we never use for reading - * (never try to deserialize instances of these types). - */ - public final static Class<?>[] _unreadableClasses = new Class<?>[] { - InputStream.class, Reader.class - }; - - /** - * These are classes that we never use for writing - * (never try to serialize instances of these types). - */ - public final static Class<?>[] _unwritableClasses = new Class<?>[] { - OutputStream.class, Writer.class, - StreamingOutput.class, Response.class - }; - - /* - /********************************************************** - /* Bit of caching - /********************************************************** - */ - - /** - * Cache for resolved endpoint configurations when reading JSON data - */ - protected final LRUMap<AnnotationBundleKey, EndpointConfig> _readers - = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120); - - /** - * Cache for resolved endpoint configurations when writing JSON data - */ - protected final LRUMap<AnnotationBundleKey, EndpointConfig> _writers - = new LRUMap<AnnotationBundleKey, EndpointConfig>(16, 120); /* /********************************************************** /* General configuration /********************************************************** */ - - /** - * Helper object used for encapsulating configuration aspects - * of {@link ObjectMapper} - */ - protected final MapperConfigurator _mapperConfig; - - /** - * Set of types (classes) that provider should ignore for data binding - */ - protected HashSet<ClassKey> _cfgCustomUntouchables; /** * JSONP function name to use for automatic JSONP wrapping, if any; @@ -178,28 +91,6 @@ public class JacksonJsonProvider /* /********************************************************** - /* 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 /********************************************************** */ @@ -208,8 +99,7 @@ public class JacksonJsonProvider * Default constructor, usually used when provider is automatically * configured to be used with JAX-RS implementation. */ - public JacksonJsonProvider() - { + public JacksonJsonProvider() { this(null, BASIC_ANNOTATIONS); } @@ -217,13 +107,11 @@ public class JacksonJsonProvider * @param annotationsToUse Annotation set(s) to use for configuring * data binding */ - public JacksonJsonProvider(Annotations... annotationsToUse) - { + public JacksonJsonProvider(Annotations... annotationsToUse) { this(null, annotationsToUse); } - public JacksonJsonProvider(ObjectMapper mapper) - { + public JacksonJsonProvider(ObjectMapper mapper) { this(mapper, BASIC_ANNOTATIONS); } @@ -235,9 +123,8 @@ public class JacksonJsonProvider * @param annotationsToUse Sets of annotations (Jackson, JAXB) that provider should * support */ - public JacksonJsonProvider(ObjectMapper mapper, Annotations[] annotationsToUse) - { - _mapperConfig = new MapperConfigurator(mapper, annotationsToUse); + public JacksonJsonProvider(ObjectMapper mapper, Annotations[] annotationsToUse) { + super(new MapperConfigurator(mapper, annotationsToUse)); } /** @@ -250,364 +137,30 @@ public class JacksonJsonProvider /* /********************************************************** - /* Configuring + /* JSON-specific configuration /********************************************************** */ - /** - * Method for defining whether actual detection for existence of - * a deserializer for type should be done when {@link #isReadable} - * is called. - */ - public void checkCanDeserialize(boolean state) { _cfgCheckCanDeserialize = state; } - - /** - * Method for defining whether actual detection for existence of - * a serializer for type should be done when {@link #isWriteable} - * is called. - */ - public void checkCanSerialize(boolean state) { _cfgCheckCanSerialize = state; } - - /** - * Method for configuring which annotation sets to use (including none). - * Annotation sets are defined in order decreasing precedence; that is, - * first one has the priority over following ones. - * - * @param annotationsToUse Ordered list of annotation sets to use; if null, - * default - */ - public void setAnnotationsToUse(Annotations[] annotationsToUse) { - _mapperConfig.setAnnotationsToUse(annotationsToUse); - } - - /** - * Method that can be used to directly define {@link ObjectMapper} to use - * for serialization and deserialization; if null, will use the standard - * provider discovery from context instead. Default setting is null. - */ - public void setMapper(ObjectMapper m) { - _mapperConfig.setMapper(m); - } - - public JacksonJsonProvider configure(DeserializationFeature f, boolean state) { - _mapperConfig.configure(f, state); - return this; - } - - public JacksonJsonProvider configure(SerializationFeature f, boolean state) { - _mapperConfig.configure(f, state); - return this; - } - - public JacksonJsonProvider configure(JsonParser.Feature f, boolean state) { - _mapperConfig.configure(f, state); - return this; - } - - public JacksonJsonProvider configure(JsonGenerator.Feature f, boolean state) { - _mapperConfig.configure(f, state); - return this; - } - - public JacksonJsonProvider enable(DeserializationFeature f, boolean state) { - _mapperConfig.configure(f, true); - return this; - } - - public JacksonJsonProvider enable(SerializationFeature f, boolean state) { - _mapperConfig.configure(f, true); - return this; - } - - public JacksonJsonProvider enable(JsonParser.Feature f, boolean state) { - _mapperConfig.configure(f, true); - return this; - } - - public JacksonJsonProvider enable(JsonGenerator.Feature f, boolean state) { - _mapperConfig.configure(f, true); - return this; - } - - public JacksonJsonProvider disable(DeserializationFeature f, boolean state) { - _mapperConfig.configure(f, false); - return this; - } - - public JacksonJsonProvider disable(SerializationFeature f, boolean state) { - _mapperConfig.configure(f, false); - return this; - } - - public JacksonJsonProvider disable(JsonParser.Feature f, boolean state) { - _mapperConfig.configure(f, false); - return this; - } - - public JacksonJsonProvider disable(JsonGenerator.Feature f, boolean state) { - _mapperConfig.configure(f, false); - return this; - } - - /** - * Method for marking specified type as "untouchable", meaning that provider - * will not try to read or write values of this type (or its subtypes). - * - * @param type Type to consider untouchable; can be any kind of class, - * including abstract class or interface. No instance of this type - * (including subtypes, i.e. types assignable to this type) will - * be read or written by provider - */ - public void addUntouchable(Class<?> type) - { - if (_cfgCustomUntouchables == null) { - _cfgCustomUntouchables = new HashSet<ClassKey>(); - } - _cfgCustomUntouchables.add(new ClassKey(type)); - } - public void setJSONPFunctionName(String fname) { _jsonpFunctionName = fname; } - - /* - /********************************************************** - /* MessageBodyReader impl - /********************************************************** - */ - - /** - * Method that JAX-RS container calls to try to check whether - * values of given type (and media type) can be deserialized by - * this provider. - * Implementation will first check that expected media type is - * a JSON type (via call to {@link #isJsonType}; then verify - * that type is not one of "untouchable" types (types we will never - * automatically handle), and finally that there is a deserializer - * for type (iff {@link #checkCanDeserialize} has been called with - * true argument -- otherwise assumption is there will be a handler) - */ - public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) - { - if (!isJsonType(mediaType)) { - return false; - } - - /* Ok: looks like we must weed out some core types here; ones that - * make no sense to try to bind from JSON: - */ - if (_untouchables.contains(new ClassKey(type))) { - return false; - } - // and there are some other abstract/interface types to exclude too: - for (Class<?> cls : _unreadableClasses) { - if (cls.isAssignableFrom(type)) { - return false; - } - } - // as well as possible custom exclusions - if (_containedIn(type, _cfgCustomUntouchables)) { - return false; - } - - // Finally: if we really want to verify that we can serialize, we'll check: - if (_cfgCheckCanSerialize) { - ObjectMapper mapper = locateMapper(type, mediaType); - if (!mapper.canDeserialize(mapper.constructType(type))) { - return false; - } - } - return true; - } - - /** - * Method that JAX-RS container calls to deserialize given value. - */ - public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,String> httpHeaders, InputStream entityStream) - throws IOException - { - - AnnotationBundleKey key = new AnnotationBundleKey(annotations); - EndpointConfig endpoint; - synchronized (_readers) { - endpoint = _readers.get(key); - } - // not yet resolved (or not cached any more)? Resolve! - if (endpoint == null) { - ObjectMapper mapper = locateMapper(type, mediaType); - endpoint = EndpointConfig.forReading(mapper, annotations); - // and cache for future reuse - synchronized (_readers) { - _readers.put(key.immutableKey(), endpoint); - } - } - ObjectReader reader = endpoint.getReader(); - - JsonParser jp = reader.getFactory().createParser(entityStream); - if (jp.nextToken() == null) { - return null; - } - return reader.withType(genericType).readValue(jp); - } /* /********************************************************** - /* MessageBodyWriter impl + /* Abstract method impl /********************************************************** */ /** - * Method that JAX-RS container calls to try to figure out - * serialized length of given value. Since computation of - * this length is about as expensive as serialization itself, - * implementation will return -1 to denote "not known", so - * that container will determine length from actual serialized - * output (if needed). - */ - public long getSize(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) - { - /* In general figuring output size requires actual writing; usually not - * worth it to write everything twice. - */ - return -1; - } - - /** - * Method that JAX-RS container calls to try to check whether - * given value (of specified type) can be serialized by - * this provider. - * Implementation will first check that expected media type is - * a JSON type (via call to {@link #isJsonType}; then verify - * that type is not one of "untouchable" types (types we will never - * automatically handle), and finally that there is a serializer - * for type (iff {@link #checkCanSerialize} has been called with - * true argument -- otherwise assumption is there will be a handler) + * @deprecated Since 2.2 use {@link #hasMatchingMediaType(MediaType)} instead */ - public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) - { - if (!isJsonType(mediaType)) { - return false; - } - - /* Ok: looks like we must weed out some core types here; ones that - * make no sense to try to bind from JSON: - */ - if (_untouchables.contains(new ClassKey(type))) { - return false; - } - // but some are interface/abstract classes, so - for (Class<?> cls : _unwritableClasses) { - if (cls.isAssignableFrom(type)) { - return false; - } - } - // and finally, may have additional custom types to exclude - if (_containedIn(type, _cfgCustomUntouchables)) { - return false; - } - - // Also: if we really want to verify that we can deserialize, we'll check: - if (_cfgCheckCanSerialize) { - if (!locateMapper(type, mediaType).canSerialize(type)) { - return false; - } - } - return true; - } - - /** - * Method that JAX-RS container calls to serialize given value. - */ - public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, - MultivaluedMap<String,Object> httpHeaders, OutputStream entityStream) - throws IOException - { - AnnotationBundleKey key = new AnnotationBundleKey(annotations); - EndpointConfig endpoint; - synchronized (_writers) { - endpoint = _writers.get(key); - } - // not yet resolved (or not cached any more)? Resolve! - if (endpoint == null) { - ObjectMapper mapper = locateMapper(type, mediaType); - endpoint = EndpointConfig.forWriting(mapper, annotations, - this._jsonpFunctionName); - // and cache for future reuse - synchronized (_writers) { - _writers.put(key.immutableKey(), endpoint); - } - } - - ObjectWriter writer = endpoint.getWriter(); - - /* 27-Feb-2009, tatu: Where can we find desired encoding? Within - * HTTP headers? - */ - JsonEncoding enc = findEncoding(mediaType, httpHeaders); - JsonGenerator jg = writer.getFactory().createGenerator(entityStream, enc); - - // Want indentation? - if (writer.isEnabled(SerializationFeature.INDENT_OUTPUT)) { - jg.useDefaultPrettyPrinter(); - } - // 04-Mar-2010, tatu: How about type we were given? (if any) - JavaType rootType = null; - - if (genericType != null && value != null) { - /* 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root - * type since it prevents polymorphic type serialization. Since we really - * just need this for generics, let's only use generic type if it's truly - * generic. - */ - if (genericType.getClass() != Class.class) { // generic types are other impls of 'java.lang.reflect.Type' - /* This is still not exactly right; should root type be further - * specialized with 'value.getClass()'? Let's see how well this works before - * trying to come up with more complete solution. - */ - rootType = writer.getTypeFactory().constructType(genericType); - /* 26-Feb-2011, tatu: To help with [JACKSON-518], we better recognize cases where - * type degenerates back into "Object.class" (as is the case with plain TypeVariable, - * for example), and not use that. - */ - if (rootType.getRawClass() == Object.class) { - rootType = null; - } - } - } - // Most of the configuration now handled through EndpointConfig, ObjectWriter - // but we may need to force root type: - if (rootType != null) { - writer = writer.withType(rootType); - } - // and finally, JSONP wrapping, if any: - value = endpoint.applyJSONP(value); - - writer.writeValue(jg, value); - } - - /** - * Helper method to use for determining desired output encoding. - * For now, will always just use UTF-8... - */ - protected JsonEncoding findEncoding(MediaType mediaType, MultivaluedMap<String,Object> httpHeaders) - { - return JsonEncoding.UTF8; + @Deprecated + protected boolean isJsonType(MediaType mediaType) { + return hasMatchingMediaType(mediaType); } - /* - /********************************************************** - /* Public helper methods - /********************************************************** - */ - - /** - * Helper method used to check whether given media type - * is JSON type or sub type. - * Current implementation essentially checks to see whether - * {@link MediaType#getSubtype} returns "json" or something - * ending with "+json". - */ - protected boolean isJsonType(MediaType mediaType) + @Override + protected boolean hasMatchingMediaType(MediaType mediaType) { /* As suggested by Stephen D, there are 2 ways to check: either * being as inclusive as possible (if subtype is "json"), or @@ -626,101 +179,33 @@ public class JacksonJsonProvider 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) + @Override + protected ObjectMapper _locateMapperViaProvider(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(); + 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); } - } - 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; + if (resolver != null) { + return resolver.getContext(type); } } - return false; + return null; } - private static List<Class<?>> findSuperTypes(Class<?> cls, Class<?> endBefore) - { - return findSuperTypes(cls, endBefore, new ArrayList<Class<?>>(8)); + @Override + protected EndpointConfig _configForReading(ObjectMapper mapper, Annotation[] annotations) { + return EndpointConfig.forReading(mapper, annotations); } - 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); + @Override + protected EndpointConfig _configForWriting(ObjectMapper mapper, Annotation[] annotations) { + return EndpointConfig.forWriting(mapper, annotations, _jsonpFunctionName); } - } diff --git a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/EndpointConfig.java b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/EndpointConfig.java index ea7d099..ee10375 100644 --- a/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/EndpointConfig.java +++ b/json/src/main/java/com/fasterxml/jackson/jaxrs/json/annotation/EndpointConfig.java @@ -2,42 +2,23 @@ package com.fasterxml.jackson.jaxrs.json.annotation; import java.lang.annotation.Annotation; -import com.fasterxml.jackson.annotation.*; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.util.JSONPObject; import com.fasterxml.jackson.databind.util.JSONWrappedObject; +import com.fasterxml.jackson.jaxrs.base.cfg.EndpointConfigBase; + /** * Container class for figuring out annotation-based configuration * for JAX-RS end points. */ public class EndpointConfig + extends EndpointConfigBase<EndpointConfig> { - // // General configuration - - protected Class<?> _activeView; - - protected String _rootName; - - // // Deserialization-only config - - protected DeserializationFeature[] _deserEnable; - protected DeserializationFeature[] _deserDisable; - - protected ObjectReader _reader; - // // Serialization-only config protected JSONP.Def _jsonp; - protected SerializationFeature[] _serEnable; - protected SerializationFeature[] _serDisable; - - protected ObjectWriter _writer; - /* /********************************************************** /* Construction @@ -45,7 +26,7 @@ public class EndpointConfig */ protected EndpointConfig() { } - + public static EndpointConfig forReading(ObjectMapper mapper, Annotation[] annotations) { return new EndpointConfig() @@ -65,122 +46,46 @@ public class EndpointConfig .initWriter(mapper) ; } + + /* + /********************************************************** + /* Abstract method impls, overrides + /********************************************************** + */ - protected EndpointConfig add(Annotation[] annotations, boolean forWriting) + @Override + protected void addAnnotation(Class<? extends Annotation> type, + Annotation annotation, boolean forWriting) { - if (annotations != null) { - for (Annotation annotation : annotations) { - Class<?> type = annotation.annotationType(); - if (type == JSONP.class) { - if (forWriting) { - _jsonp = new JSONP.Def((JSONP) annotation); - } - } else if (type == JsonView.class) { - // Can only use one view; but if multiple defined, use first (no exception) - Class<?>[] views = ((JsonView) annotation).value(); - _activeView = (views.length > 0) ? views[0] : null; - } else if (type == JsonRootName.class) { - _rootName = ((JsonRootName) annotation).value(); - } else if (type == JacksonFeatures.class) { - JacksonFeatures feats = (JacksonFeatures) annotation; - if (forWriting) { - _serEnable = nullIfEmpty(feats.serializationEnable()); - _serDisable = nullIfEmpty(feats.serializationDisable()); - } else { - _deserEnable = nullIfEmpty(feats.deserializationEnable()); - _deserDisable = nullIfEmpty(feats.deserializationDisable()); - } - } else if (type == JacksonAnnotationsInside.class) { - // skip; processed below (in parent), so encountering here is of no use + if (type == JSONP.class) { + if (forWriting) { + _jsonp = new JSONP.Def((JSONP) annotation); + } + } else if (type == JacksonFeatures.class) { + JacksonFeatures feats = (JacksonFeatures) annotation; + if (forWriting) { + _serEnable = nullIfEmpty(feats.serializationEnable()); + _serDisable = nullIfEmpty(feats.serializationDisable()); } 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); - } + _deserEnable = nullIfEmpty(feats.deserializationEnable()); + _deserDisable = nullIfEmpty(feats.deserializationDisable()); } - } - } - return this; - } - - protected EndpointConfig initReader(ObjectMapper mapper) - { - // first common config - if (_activeView != null) { - _reader = mapper.readerWithView(_activeView); } else { - _reader = mapper.reader(); + super.addAnnotation(type, annotation, forWriting); } - - if (_rootName != null) { - _reader = _reader.withRootName(_rootName); - } - // Then deser features - if (_deserEnable != null) { - _reader = _reader.withFeatures(_deserEnable); - } - if (_deserDisable != null) { - _reader = _reader.withoutFeatures(_deserDisable); - } - /* Important: we are NOT to close the underlying stream after - * mapping, so we need to instruct parser: - */ - _reader.getFactory().disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); - - return this; } - - protected EndpointConfig initWriter(ObjectMapper mapper) - { - // first common config - if (_activeView != null) { - _writer = mapper.writerWithView(_activeView); - } else { - _writer = mapper.writer(); - } - if (_rootName != null) { - _writer = _writer.withRootName(_rootName); - } - // Then features - if (_serEnable != null) { - _writer = _writer.withFeatures(_serEnable); - } - if (_serDisable != null) { - _writer = _writer.withoutFeatures(_serDisable); - } - // then others - - // Finally: couple of features we always set - /* Important: we are NOT to close the underlying stream after - * mapping, so we need to instruct parser: - */ - _writer.getFactory().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); - - return this; + @Override + public Object modifyBeforeWrite(Object value) { + return applyJSONP(value); } - + /* /********************************************************** /* Accessors /********************************************************** */ - public ObjectReader getReader() { - if (_reader == null) { // sanity check, should never happen - throw new IllegalStateException(); - } - return _reader; - } - - public ObjectWriter getWriter() { - if (_writer == null) { // sanity check, should never happen - throw new IllegalStateException(); - } - return _writer; - } - /** * Method that will add JSONP wrapper object, if and as * configured by collected annotations. -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jackson-jaxrs-providers.git _______________________________________________ pkg-java-commits mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-java-commits

