package org.jboss.resteasy.plugins.providers.jackson;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterInjector;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterModifier;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.fasterxml.jackson.jaxrs.json.JsonEndpointConfig;
import com.fasterxml.jackson.jaxrs.util.ClassKey;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import org.jboss.resteasy.annotations.providers.jackson.Formatted;
import org.jboss.resteasy.microprofile.config.ResteasyConfig.SOURCE;
import org.jboss.resteasy.microprofile.config.ResteasyConfigFactory;
import org.jboss.resteasy.annotations.providers.NoJackson;
import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages;
import org.jboss.resteasy.util.DelegatingOutputStream;
import org.jboss.resteasy.util.FindAnnotation;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Arrays;

Only different from Jackson one is *+json in @Produces/@Consumes
Author:Bill Burke
Version:$Revision: 1 $
/** * Only different from Jackson one is *+json in @Produces/@Consumes * * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */
@Provider @Consumes({"application/json", "application/*+json", "text/json"}) @Produces({"application/json", "application/*+json", "text/json"}) public class ResteasyJackson2Provider extends JacksonJaxbJsonProvider { private static final boolean WRITE_DURATIONS_AS_TIMESTAMPS = getProperty( "resteasy.jackson.serialization.writeDurationsAsTimestamps", false); @Override public boolean isReadable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { if (FindAnnotation.findAnnotation(aClass, annotations, NoJackson.class) != null) return false; return super.isReadable(aClass, type, annotations, mediaType); } @Override public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { if (FindAnnotation.findAnnotation(aClass, annotations, NoJackson.class) != null) return false; return super.isWriteable(aClass, type, annotations, mediaType); } // Currently we need to override readFrom and writeTo because Jackson 2.2.1 does not cache correctly // It does not allow to have a ContextResolver that chooses different mappers per Java type. private static class ClassAnnotationKey { private AnnotationArrayKey annotations; private ClassKey classKey; private int hash; private ClassAnnotationKey(final Class<?> clazz, final Annotation[] annotations) { this.annotations = new AnnotationArrayKey(annotations); this.classKey = new ClassKey(clazz); hash = this.annotations.hashCode(); hash = 31 * hash + classKey.hashCode(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClassAnnotationKey that = (ClassAnnotationKey) o; if (!annotations.equals(that.annotations)) return false; if (!classKey.equals(that.classKey)) return false; return true; } @Override public int hashCode() { return hash; } } // Alternative to Jackson's AnnotationBundleKey that uses object equality // instead of referential equality (==) due to how parameter annotations are proxied and not cached. private static class AnnotationArrayKey { private static final Annotation[] NO_ANNOTATIONS = new Annotation[0]; private final Annotation[] annotations; private final int hash; private AnnotationArrayKey(final Annotation[] annotations) { if (annotations == null || annotations.length == 0) { this.annotations = NO_ANNOTATIONS; } else { this.annotations = annotations; } this.hash = calcHash(this.annotations); } private static int calcHash(Annotation[] annotations) { int result = annotations.length; result = 31 * result + Arrays.hashCode(annotations); return result; } @Override public int hashCode() { return hash; } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; AnnotationArrayKey that = (AnnotationArrayKey) object; return hash == that.hash && java.util.Arrays.equals(annotations, that.annotations); } } protected final ConcurrentHashMap<ClassAnnotationKey, JsonEndpointConfig> _readers = new ConcurrentHashMap<ClassAnnotationKey, JsonEndpointConfig>(); @Override public Object readFrom(Class<Object> type, final Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,String> httpHeaders, InputStream entityStream) throws IOException { LogMessages.LOGGER.debugf("Provider : %s, Method : readFrom", getClass().getName()); ClassAnnotationKey key = new ClassAnnotationKey(type, annotations); JsonEndpointConfig endpoint; endpoint = _readers.get(key); // not yet resolved (or not cached any more)? Resolve! if (endpoint == null) { ObjectMapper mapper = locateMapper(type, mediaType); PolymorphicTypeValidator ptv = mapper.getPolymorphicTypeValidator(); //the check is protected by test org.jboss.resteasy.test.providers.jackson2.whitelist.JacksonConfig, //be sure to keep that in synch if changing anything here. if (ptv == null || ptv instanceof LaissezFaireSubTypeValidator) { mapper.setPolymorphicTypeValidator(new WhiteListPolymorphicTypeValidatorBuilder().build()); } endpoint = _configForReading(mapper, annotations, null); _readers.put(key, endpoint); } final ObjectReader reader = endpoint.getReader(); final JsonParser jp = _createParser(reader, entityStream); // If null is returned, considered to be empty stream if (jp == null) { return null; } else if (jp.nextToken() == null) { jp.close(); return null; } // [Issue#1]: allow 'binding' to JsonParser if (((Class<?>) type) == JsonParser.class) { return jp; } Object result = null; try { if (System.getSecurityManager() == null) { result = reader.withType(genericType).readValue(jp); } else { result = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { return reader.withType(genericType).readValue(jp); } }); } } catch (PrivilegedActionException pae) { throw new IOException(pae); } finally { jp.close(); } return result; } protected final ConcurrentHashMap<ClassAnnotationKey, JsonEndpointConfig> _writers = new ConcurrentHashMap<ClassAnnotationKey, JsonEndpointConfig>(); @Override public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,Object> httpHeaders, OutputStream entityStream) throws IOException { LogMessages.LOGGER.debugf("Provider : %s, Method : writeTo", getClass().getName()); entityStream = new DelegatingOutputStream(entityStream) { @Override public void flush() throws IOException { // don't flush as this is a performance hit on Undertow. // and causes chunked encoding to happen. } }; ClassAnnotationKey key = new ClassAnnotationKey(type, annotations); JsonEndpointConfig endpoint; endpoint = _writers.get(key); // not yet resolved (or not cached any more)? Resolve! if (endpoint == null) { ObjectMapper mapper = locateMapper(type, mediaType); PolymorphicTypeValidator ptv = mapper.getPolymorphicTypeValidator(); //the check is protected by test org.jboss.resteasy.test.providers.jackson2.whitelist.JacksonConfig, //be sure to keep that in synch if changing anything here. if (ptv == null || ptv instanceof LaissezFaireSubTypeValidator) { mapper.setPolymorphicTypeValidator(new WhiteListPolymorphicTypeValidatorBuilder().build()); } mapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, WRITE_DURATIONS_AS_TIMESTAMPS); endpoint = _configForWriting(mapper, annotations, null); // and cache for future reuse _writers.put(key, endpoint); } ObjectWriter writer = endpoint.getWriter(); boolean withIndentOutput = false; // no way to replace _serializationConfig // we can't cache this. if (annotations != null) { for (Annotation annotation : annotations) { if (annotation.annotationType().equals(Formatted.class)) { withIndentOutput = true; break; } } } /* 27-Feb-2009, tatu: Where can we find desired encoding? Within * HTTP headers? */ JsonEncoding enc = findEncoding(mediaType, httpHeaders); final JsonGenerator jg = writer.getFactory().createGenerator(entityStream, enc); jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); try { // Want indentation? if (writer.isEnabled(SerializationFeature.INDENT_OUTPUT) || withIndentOutput) { 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); } value = endpoint.modifyBeforeWrite(value); ObjectWriterModifier mod = ObjectWriterInjector.getAndClear(); if (mod == null) { ClassLoader tccl; if (System.getSecurityManager() == null) { tccl = Thread.currentThread().getContextClassLoader(); } else { tccl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { @Override public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); } mod = ResteasyObjectWriterInjector.get(tccl); } if (mod != null) { writer = mod.modify(endpoint, httpHeaders, value, writer, jg); } if (System.getSecurityManager() == null) { writer.writeValue(jg, value); } else { final ObjectWriter smWriter = writer; final Object smValue = value; AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { smWriter.writeValue(jg, smValue); return null; } }); } } catch(PrivilegedActionException pae) { throw new IOException(pae); } finally { jg.close(); } } private static boolean getProperty(String propertyName, boolean def) { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { @Override public Boolean run() { String p = ResteasyConfigFactory.getConfig().getValue(propertyName, SOURCE.SYSTEM); return p != null ? Boolean.valueOf(p) : def; } }); } String p = ResteasyConfigFactory.getConfig().getValue(propertyName, SOURCE.SYSTEM); return p != null ? Boolean.valueOf(p) : def; } }