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;
@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);
}
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;
}
}
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);
if (endpoint == null) {
ObjectMapper mapper = locateMapper(type, mediaType);
PolymorphicTypeValidator ptv = mapper.getPolymorphicTypeValidator();
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 (jp == null) {
return null;
} else if (jp.nextToken() == null) {
jp.close();
return null;
}
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 {
}
};
ClassAnnotationKey key = new ClassAnnotationKey(type, annotations);
JsonEndpointConfig endpoint;
endpoint = _writers.get(key);
if (endpoint == null) {
ObjectMapper mapper = locateMapper(type, mediaType);
PolymorphicTypeValidator ptv = mapper.getPolymorphicTypeValidator();
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);
_writers.put(key, endpoint);
}
ObjectWriter writer = endpoint.getWriter();
boolean withIndentOutput = false;
if (annotations != null) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(Formatted.class)) {
withIndentOutput = true;
break;
}
}
}
JsonEncoding enc = findEncoding(mediaType, httpHeaders);
final JsonGenerator jg = writer.getFactory().createGenerator(entityStream, enc);
jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
try {
if (writer.isEnabled(SerializationFeature.INDENT_OUTPUT) || withIndentOutput) {
jg.useDefaultPrettyPrinter();
}
JavaType rootType = null;
if (genericType != null && value != null) {
if (genericType.getClass() != Class.class) {
rootType = writer.getTypeFactory().constructType(genericType);
if (rootType.getRawClass() == Object.class) {
rootType = null;
}
}
}
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;
}
}