org.glassfish.jersey.message.internal.MessageBodyFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaxrs-ri Show documentation
Show all versions of jaxrs-ri Show documentation
A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle
(jaxrs-ri.jar).
Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and
contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external
RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source
bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external
RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI
sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from
the command line.
/*
* Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.message.internal;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;
import javax.xml.transform.Source;
import org.glassfish.jersey.internal.BootstrapBag;
import org.glassfish.jersey.internal.BootstrapConfigurator;
import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.PropertiesDelegate;
import org.glassfish.jersey.internal.guava.Primitives;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.ReflectionHelper.DeclaringClassInterfacePair;
import org.glassfish.jersey.internal.util.collection.DataStructures;
import org.glassfish.jersey.internal.util.collection.KeyComparator;
import org.glassfish.jersey.internal.util.collection.KeyComparatorHashMap;
import org.glassfish.jersey.internal.util.collection.KeyComparatorLinkedHashMap;
import org.glassfish.jersey.message.AbstractEntityProviderModel;
import org.glassfish.jersey.message.MessageBodyWorkers;
import org.glassfish.jersey.message.MessageProperties;
import org.glassfish.jersey.message.ReaderModel;
import org.glassfish.jersey.message.WriterModel;
/**
* A factory for managing {@link MessageBodyReader}, {@link MessageBodyWriter} instances.
*
* @author Paul Sandoz
* @author Marek Potociar
* @author Jakub Podlesak
*/
public class MessageBodyFactory implements MessageBodyWorkers {
private static final Logger LOGGER = Logger.getLogger(MessageBodyFactory.class.getName());
/**
* Configurator which initializes and register {@link MessageBodyWorkers} instance into {@link InjectionManager} and
* {@link BootstrapBag}.
*
* @author Petr Bouda
*/
public static class MessageBodyWorkersConfigurator implements BootstrapConfigurator {
private MessageBodyFactory messageBodyFactory;
@Override
public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) {
messageBodyFactory = new MessageBodyFactory(bootstrapBag.getConfiguration());
InstanceBinding binding =
Bindings.service(messageBodyFactory)
.to(MessageBodyWorkers.class);
injectionManager.register(binding);
}
@Override
public void postInit(InjectionManager injectionManager, BootstrapBag bootstrapBag) {
messageBodyFactory.initialize(injectionManager);
bootstrapBag.setMessageBodyWorkers(messageBodyFactory);
}
}
/**
* Media type comparator.
*/
public static final KeyComparator MEDIA_TYPE_KEY_COMPARATOR =
new KeyComparator() {
private static final long serialVersionUID = 1616819828630827763L;
@Override
public boolean equals(final MediaType mt1, final MediaType mt2) {
// treat compatible types as equal
return mt1.isCompatible(mt2);
}
@Override
public int hash(final MediaType mt) {
// treat compatible types as equal
return mt.getType().toLowerCase(Locale.ROOT).hashCode() + mt.getSubtype().toLowerCase(Locale.ROOT).hashCode();
}
};
/**
* Compares entity providers by the provided class (most specific first)
* and then by the declared supported media types, if the provided classes
* are the same.
*/
static final Comparator> WORKER_BY_TYPE_COMPARATOR =
new Comparator>() {
@Override
public int compare(final AbstractEntityProviderModel> o1, final AbstractEntityProviderModel> o2) {
final Class> o1ProviderClassParam = o1.providedType();
final Class> o2ProviderClassParam = o2.providedType();
if (o1ProviderClassParam == o2ProviderClassParam) {
// Compare producible media types.
return compare(o2.declaredTypes(), o1.declaredTypes());
} else if (o1ProviderClassParam.isAssignableFrom(o2ProviderClassParam)) {
return 1;
} else if (o2ProviderClassParam.isAssignableFrom(o1ProviderClassParam)) {
return -1;
}
// Fallback to comparing provided class name.
return CLASS_BY_NAME_COMPARATOR.compare(o1ProviderClassParam, o2ProviderClassParam);
}
private int compare(List mediaTypeList1, List mediaTypeList2) {
mediaTypeList1 = mediaTypeList1.isEmpty() ? MediaTypes.WILDCARD_TYPE_SINGLETON_LIST : mediaTypeList1;
mediaTypeList2 = mediaTypeList2.isEmpty() ? MediaTypes.WILDCARD_TYPE_SINGLETON_LIST : mediaTypeList2;
return MediaTypes.MEDIA_TYPE_LIST_COMPARATOR.compare(mediaTypeList2, mediaTypeList1);
}
};
private static final Comparator> CLASS_BY_NAME_COMPARATOR = Comparator.comparing(Class::getName);
private InjectionManager injectionManager;
private final Boolean legacyProviderOrdering;
private List readers;
private List writers;
private final Map> readersCache = new KeyComparatorHashMap<>(MEDIA_TYPE_KEY_COMPARATOR);
private final Map> writersCache = new KeyComparatorHashMap<>(MEDIA_TYPE_KEY_COMPARATOR);
private static final int LOOKUP_CACHE_INITIAL_CAPACITY = 32;
private static final float LOOKUP_CACHE_LOAD_FACTOR = 0.75f;
private final Map, List> mbrTypeLookupCache = new ConcurrentHashMap<>(
LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL);
private final Map, List> mbwTypeLookupCache = new ConcurrentHashMap<>(
LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL);
private final Map, List> typeToMediaTypeReadersCache = new ConcurrentHashMap<>(
LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL);
private final Map, List> typeToMediaTypeWritersCache = new ConcurrentHashMap<>(
LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL);
private final Map> mbrLookupCache = new ConcurrentHashMap<>(
LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL);
private final Map> mbwLookupCache = new ConcurrentHashMap<>(
LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL);
/**
* Create a new message body factory.
*
* @param configuration configuration. Optional - can be null.
*/
public MessageBodyFactory(Configuration configuration) {
this.legacyProviderOrdering = configuration != null
&& PropertiesHelper.isProperty(configuration.getProperty(MessageProperties.LEGACY_WORKERS_ORDERING));
}
/**
* Must be initialize at the time of completed populated {@link InjectionManager}.
*
* @param injectionManager completed injection manager.
*/
public void initialize(InjectionManager injectionManager) {
this.injectionManager = injectionManager;
// Initialize readers
this.readers = new ArrayList<>();
final Set customMbrs = Providers.getCustomProviders(injectionManager, MessageBodyReader.class);
final Set mbrs = Providers.getProviders(injectionManager, MessageBodyReader.class);
addReaders(readers, customMbrs, true);
mbrs.removeAll(customMbrs);
addReaders(readers, mbrs, false);
if (legacyProviderOrdering) {
readers.sort(new LegacyWorkerComparator<>(MessageBodyReader.class));
for (final ReaderModel model : readers) {
for (final MediaType mt : model.declaredTypes()) {
List readerList = readersCache.get(mt);
if (readerList == null) {
readerList = new ArrayList<>();
readersCache.put(mt, readerList);
}
readerList.add(model.provider());
}
}
}
// Initialize writers
this.writers = new ArrayList<>();
final Set customMbws = Providers.getCustomProviders(injectionManager, MessageBodyWriter.class);
final Set mbws = Providers.getProviders(injectionManager, MessageBodyWriter.class);
addWriters(writers, customMbws, true);
mbws.removeAll(customMbws);
addWriters(writers, mbws, false);
if (legacyProviderOrdering) {
writers.sort(new LegacyWorkerComparator<>(MessageBodyWriter.class));
for (final AbstractEntityProviderModel model : writers) {
for (final MediaType mt : model.declaredTypes()) {
List writerList = writersCache.get(mt);
if (writerList == null) {
writerList = new ArrayList<>();
writersCache.put(mt, writerList);
}
writerList.add(model.provider());
}
}
}
}
/**
* Compares 2 instances implementing/inheriting the same super-type and returns
* which of the two instances has the super-type declaration closer in it's
* inheritance hierarchy tree.
*
* The comparator is optimized to cache results of the previous distance declaration
* computations.
*
* @param common super-type used for computing the declaration distance and
* comparing instances.
*/
private static class DeclarationDistanceComparator implements Comparator {
private final Class declared;
private final Map distanceMap = new HashMap<>();
DeclarationDistanceComparator(final Class declared) {
this.declared = declared;
}
@Override
public int compare(final T o1, final T o2) {
final int d1 = getDistance(o1);
final int d2 = getDistance(o2);
return d2 - d1;
}
private int getDistance(final T t) {
Integer distance = distanceMap.get(t.getClass());
if (distance != null) {
return distance;
}
final DeclaringClassInterfacePair p = ReflectionHelper.getClass(
t.getClass(), declared);
final Class[] as = ReflectionHelper.getParameterizedClassArguments(p);
Class a = (as != null) ? as[0] : null;
distance = 0;
while (a != null && a != Object.class) {
distance++;
a = a.getSuperclass();
}
distanceMap.put(t.getClass(), distance);
return distance;
}
}
/**
* {@link AbstractEntityProviderModel} comparator
* which works as it is described in JAX-RS 2.x specification.
*
* Pairs are sorted by distance from required type, media type and custom/provided (provided goes first).
*
* @param MessageBodyReader or MessageBodyWriter.
* @see DeclarationDistanceComparator
* @see #MEDIA_TYPE_KEY_COMPARATOR
*/
private static class WorkerComparator implements Comparator> {
final Class wantedType;
final MediaType wantedMediaType;
private WorkerComparator(final Class wantedType, final MediaType wantedMediaType) {
this.wantedType = wantedType;
this.wantedMediaType = wantedMediaType;
}
@Override
public int compare(final AbstractEntityProviderModel modelA, final AbstractEntityProviderModel modelB) {
final int distance = compareTypeDistances(modelA.providedType(), modelB.providedType());
if (distance != 0) {
return distance;
}
final int mediaTypeComparison = getMediaTypeDistance(wantedMediaType, modelA.declaredTypes())
- getMediaTypeDistance(wantedMediaType, modelB.declaredTypes());
if (mediaTypeComparison != 0) {
return mediaTypeComparison;
}
if (modelA.isCustom() ^ modelB.isCustom()) {
return (modelA.isCustom()) ? -1 : 1;
}
return 0;
}
private int getMediaTypeDistance(final MediaType wanted, final List mtl) {
if (wanted == null) {
return 0;
}
int distance = 2;
for (final MediaType mt : mtl) {
if (MediaTypes.typeEqual(wanted, mt)) {
return 0;
}
if (distance > 1 && MediaTypes.typeEqual(MediaTypes.getTypeWildCart(wanted), mt)) {
distance = 1;
}
}
return distance;
}
private int compareTypeDistances(final Class> providerClassParam1, final Class> providerClassParam2) {
return getTypeDistance(providerClassParam1) - getTypeDistance(providerClassParam2);
}
private int getTypeDistance(final Class> classParam) {
// cache?
Class> tmp1 = wantedType;
Class> tmp2 = classParam;
final Iterator> it1 = getClassHierarchyIterator(tmp1);
final Iterator> it2 = getClassHierarchyIterator(tmp2);
int distance = 0;
while (!wantedType.equals(tmp2) && !classParam.equals(tmp1)) {
distance++;
if (!wantedType.equals(tmp2)) {
tmp2 = it2.hasNext() ? it2.next() : null;
}
if (!classParam.equals(tmp1)) {
tmp1 = it1.hasNext() ? it1.next() : null;
}
if (tmp2 == null && tmp1 == null) {
return Integer.MAX_VALUE;
}
}
return distance;
}
private Iterator> getClassHierarchyIterator(final Class> classParam) {
if (classParam == null) {
return Collections.>emptyList().iterator();
}
final ArrayList> classes = new ArrayList<>();
final LinkedList> unprocessed = new LinkedList<>();
// Object is special - needs to be always the furthest type.
boolean objectFound = false;
unprocessed.add(classParam);
while (!unprocessed.isEmpty()) {
final Class> clazz = unprocessed.removeFirst();
if (Object.class.equals(clazz)) {
objectFound = true;
} else {
classes.add(clazz);
}
unprocessed.addAll(Arrays.asList(clazz.getInterfaces()));
final Class> superclazz = clazz.getSuperclass();
if (superclazz != null) {
unprocessed.add(superclazz);
}
}
if (objectFound) {
classes.add(Object.class);
}
return classes.iterator();
}
}
/**
* {@link AbstractEntityProviderModel} comparator which
* works as it is described in JAX-RS 1.x specification.
*
* Pairs are sorted by custom/provided (custom goes first), media type and declaration distance.
*
* @param MessageBodyReader or MessageBodyWriter.
* @see DeclarationDistanceComparator
* @see #MEDIA_TYPE_KEY_COMPARATOR
*/
private static class LegacyWorkerComparator implements Comparator> {
final DeclarationDistanceComparator distanceComparator;
private LegacyWorkerComparator(final Class type) {
distanceComparator = new DeclarationDistanceComparator<>(type);
}
@Override
public int compare(final AbstractEntityProviderModel modelA, final AbstractEntityProviderModel modelB) {
if (modelA.isCustom() ^ modelB.isCustom()) {
return (modelA.isCustom()) ? -1 : 1;
}
final MediaType mtA = modelA.declaredTypes().get(0);
final MediaType mtB = modelB.declaredTypes().get(0);
final int mediaTypeComparison = MediaTypes.PARTIAL_ORDER_COMPARATOR.compare(mtA, mtB);
if (mediaTypeComparison != 0 && !mtA.isCompatible(mtB)) {
return mediaTypeComparison;
}
return distanceComparator.compare(modelA.provider(), modelB.provider());
}
}
private static class ModelLookupKey {
final Class> clazz;
final MediaType mediaType;
private ModelLookupKey(final Class> clazz, final MediaType mediaType) {
this.clazz = clazz;
this.mediaType = mediaType;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final ModelLookupKey that = (ModelLookupKey) o;
return !(clazz != null ? !clazz.equals(that.clazz) : that.clazz != null)
&& !(mediaType != null ? !mediaType.equals(that.mediaType) : that.mediaType != null);
}
@Override
public int hashCode() {
int result = clazz != null ? clazz.hashCode() : 0;
result = 31 * result + (mediaType != null ? mediaType.hashCode() : 0);
return result;
}
}
private static void addReaders(final List models, final Set readers, final boolean custom) {
for (final MessageBodyReader provider : readers) {
final List values = MediaTypes.createFrom(provider.getClass().getAnnotation(Consumes.class));
models.add(new ReaderModel(provider, values, custom));
}
}
private static void addWriters(final List models, final Set writers, final boolean custom) {
for (final MessageBodyWriter provider : writers) {
final List values = MediaTypes.createFrom(provider.getClass().getAnnotation(Produces.class));
models.add(new WriterModel(provider, values, custom));
}
}
// MessageBodyWorkers
@Override
public Map> getReaders(final MediaType mediaType) {
final Map> subSet = new KeyComparatorLinkedHashMap<>(MEDIA_TYPE_KEY_COMPARATOR);
getCompatibleProvidersMap(mediaType, readers, subSet);
return subSet;
}
@Override
public Map> getWriters(final MediaType mediaType) {
final Map> subSet = new KeyComparatorLinkedHashMap<>(MEDIA_TYPE_KEY_COMPARATOR);
getCompatibleProvidersMap(mediaType, writers, subSet);
return subSet;
}
@Override
public String readersToString(final Map> readers) {
return toString(readers);
}
@Override
public String writersToString(final Map> writers) {
return toString(writers);
}
private String toString(final Map> set) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw);
for (final Map.Entry> e : set.entrySet()) {
pw.append(e.getKey().toString()).println(" ->");
for (final T t : e.getValue()) {
pw.append(" ").println(t.getClass().getName());
}
}
pw.flush();
return sw.toString();
}
@Override
public MessageBodyReader getMessageBodyReader(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType) {
return getMessageBodyReader(c, t, as, mediaType, null);
}
@Override
public MessageBodyReader getMessageBodyReader(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType,
final PropertiesDelegate propertiesDelegate) {
MessageBodyReader p = null;
if (legacyProviderOrdering) {
if (mediaType != null) {
p = _getMessageBodyReader(c, t, as, mediaType, mediaType, propertiesDelegate);
if (p == null) {
p = _getMessageBodyReader(c, t, as, mediaType, MediaTypes.getTypeWildCart(mediaType), propertiesDelegate);
}
}
if (p == null) {
p = _getMessageBodyReader(c, t, as, mediaType, MediaType.WILDCARD_TYPE, propertiesDelegate);
}
} else {
p = _getMessageBodyReader(c, t, as, mediaType, readers, propertiesDelegate);
}
return p;
}
@Override
@SuppressWarnings("unchecked")
public List getMessageBodyReaderMediaTypes(final Class> type,
final Type genericType,
final Annotation[] annotations) {
final Set readableMediaTypes = new LinkedHashSet<>();
for (final ReaderModel model : readers) {
boolean readableWorker = false;
for (final MediaType mt : model.declaredTypes()) {
if (model.isReadable(type, genericType, annotations, mt)) {
readableMediaTypes.add(mt);
readableWorker = true;
}
if (!readableMediaTypes.contains(MediaType.WILDCARD_TYPE)
&& readableWorker
&& model.declaredTypes().contains(MediaType.WILDCARD_TYPE)) {
readableMediaTypes.add(MediaType.WILDCARD_TYPE);
}
}
}
final List mtl = new ArrayList<>(readableMediaTypes);
mtl.sort(MediaTypes.PARTIAL_ORDER_COMPARATOR);
return mtl;
}
@SuppressWarnings("unchecked")
private boolean isCompatible(final AbstractEntityProviderModel model, final Class c, final MediaType mediaType) {
if (model.providedType().equals(Object.class)
|| // looks weird. Could/(should?) be separated to Writer/Reader check
model.providedType().isAssignableFrom(c)
|| c.isAssignableFrom(model.providedType())
) {
for (final MediaType mt : model.declaredTypes()) {
if (mediaType == null) {
return true;
}
if (mediaType.isCompatible(mt)) {
return true;
}
}
}
return false;
}
@SuppressWarnings("unchecked")
private MessageBodyReader _getMessageBodyReader(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType,
final List models,
final PropertiesDelegate propertiesDelegate) {
// Ensure a parameter-less lookup type to prevent excessive memory consumption
// reported in JERSEY-2297
final MediaType lookupType = mediaType == null || mediaType.getParameters().isEmpty()
? mediaType
: new MediaType(mediaType.getType(), mediaType.getSubtype());
final ModelLookupKey lookupKey = new ModelLookupKey(c, lookupType);
List readers = mbrLookupCache.get(lookupKey);
if (readers == null) {
readers = new ArrayList<>();
for (final ReaderModel model : models) {
if (isCompatible(model, c, mediaType)) {
readers.add(model);
}
}
readers.sort(new WorkerComparator<>(c, mediaType));
mbrLookupCache.put(lookupKey, readers);
}
if (readers.isEmpty()) {
return null;
}
final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate);
MessageBodyReader selected = null;
final Iterator iterator = readers.iterator();
while (iterator.hasNext()) {
final ReaderModel model = iterator.next();
if (model.isReadable(c, t, as, mediaType)) {
selected = (MessageBodyReader) model.provider();
tracingLogger.log(MsgTraceEvent.MBR_SELECTED, selected);
break;
}
tracingLogger.log(MsgTraceEvent.MBR_NOT_READABLE, model.provider());
}
if (tracingLogger.isLogEnabled(MsgTraceEvent.MBR_SKIPPED)) {
while (iterator.hasNext()) {
final ReaderModel model = iterator.next();
tracingLogger.log(MsgTraceEvent.MBR_SKIPPED, model.provider());
}
}
return selected;
}
@SuppressWarnings("unchecked")
private MessageBodyReader _getMessageBodyReader(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType, final MediaType lookup,
final PropertiesDelegate propertiesDelegate) {
final List readers = readersCache.get(lookup);
if (readers == null) {
return null;
}
final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate);
MessageBodyReader selected = null;
final Iterator iterator = readers.iterator();
while (iterator.hasNext()) {
final MessageBodyReader p = iterator.next();
if (isReadable(p, c, t, as, mediaType)) {
selected = (MessageBodyReader) p;
tracingLogger.log(MsgTraceEvent.MBR_SELECTED, selected);
break;
}
tracingLogger.log(MsgTraceEvent.MBR_NOT_READABLE, p);
}
if (tracingLogger.isLogEnabled(MsgTraceEvent.MBR_SKIPPED)) {
while (iterator.hasNext()) {
final MessageBodyReader p = iterator.next();
tracingLogger.log(MsgTraceEvent.MBR_SKIPPED, p);
}
}
return selected;
}
@Override
public MessageBodyWriter getMessageBodyWriter(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType) {
return getMessageBodyWriter(c, t, as, mediaType, null);
}
@Override
public MessageBodyWriter getMessageBodyWriter(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType,
final PropertiesDelegate propertiesDelegate) {
MessageBodyWriter p = null;
if (legacyProviderOrdering) {
if (mediaType != null) {
p = _getMessageBodyWriter(c, t, as, mediaType, mediaType, propertiesDelegate);
if (p == null) {
p = _getMessageBodyWriter(c, t, as, mediaType, MediaTypes.getTypeWildCart(mediaType), propertiesDelegate);
}
}
if (p == null) {
p = _getMessageBodyWriter(c, t, as, mediaType, MediaType.WILDCARD_TYPE, propertiesDelegate);
}
} else {
p = _getMessageBodyWriter(c, t, as, mediaType, writers, propertiesDelegate);
}
return p;
}
@SuppressWarnings("unchecked")
private MessageBodyWriter _getMessageBodyWriter(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType,
final List models,
final PropertiesDelegate propertiesDelegate) {
// Ensure a parameter-less lookup type to prevent excessive memory consumption
// reported in JERSEY-2297
final MediaType lookupType = mediaType == null || mediaType.getParameters().isEmpty()
? mediaType
: new MediaType(mediaType.getType(), mediaType.getSubtype());
final ModelLookupKey lookupKey = new ModelLookupKey(c, lookupType);
List writers = mbwLookupCache.get(lookupKey);
if (writers == null) {
writers = new ArrayList<>();
for (final WriterModel model : models) {
if (isCompatible(model, c, mediaType)) {
writers.add(model);
}
}
writers.sort(new WorkerComparator<>(c, mediaType));
mbwLookupCache.put(lookupKey, writers);
}
if (writers.isEmpty()) {
return null;
}
final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate);
MessageBodyWriter selected = null;
final Iterator iterator = writers.iterator();
while (iterator.hasNext()) {
final WriterModel model = iterator.next();
if (model.isWriteable(c, t, as, mediaType)) {
selected = (MessageBodyWriter) model.provider();
tracingLogger.log(MsgTraceEvent.MBW_SELECTED, selected);
break;
}
tracingLogger.log(MsgTraceEvent.MBW_NOT_WRITEABLE, model.provider());
}
if (tracingLogger.isLogEnabled(MsgTraceEvent.MBW_SKIPPED)) {
while (iterator.hasNext()) {
final WriterModel model = iterator.next();
tracingLogger.log(MsgTraceEvent.MBW_SKIPPED, model.provider());
}
}
return selected;
}
@SuppressWarnings("unchecked")
private MessageBodyWriter _getMessageBodyWriter(final Class c, final Type t,
final Annotation[] as,
final MediaType mediaType, final MediaType lookup,
final PropertiesDelegate propertiesDelegate) {
final List writers = writersCache.get(lookup);
if (writers == null) {
return null;
}
final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate);
MessageBodyWriter selected = null;
final Iterator iterator = writers.iterator();
while (iterator.hasNext()) {
final MessageBodyWriter p = iterator.next();
if (isWriteable(p, c, t, as, mediaType)) {
selected = (MessageBodyWriter) p;
tracingLogger.log(MsgTraceEvent.MBW_SELECTED, selected);
break;
}
tracingLogger.log(MsgTraceEvent.MBW_NOT_WRITEABLE, p);
}
if (tracingLogger.isLogEnabled(MsgTraceEvent.MBW_SKIPPED)) {
while (iterator.hasNext()) {
final MessageBodyWriter p = iterator.next();
tracingLogger.log(MsgTraceEvent.MBW_SKIPPED, p);
}
}
return selected;
}
private static void getCompatibleProvidersMap(
final MediaType mediaType,
final List extends AbstractEntityProviderModel> set,
final Map> subSet) {
if (mediaType.isWildcardType()) {
getCompatibleProvidersList(mediaType, set, subSet);
} else if (mediaType.isWildcardSubtype()) {
getCompatibleProvidersList(mediaType, set, subSet);
getCompatibleProvidersList(MediaType.WILDCARD_TYPE, set, subSet);
} else {
getCompatibleProvidersList(mediaType, set, subSet);
getCompatibleProvidersList(
MediaTypes.getTypeWildCart(mediaType),
set, subSet);
getCompatibleProvidersList(MediaType.WILDCARD_TYPE, set, subSet);
}
}
private static void getCompatibleProvidersList(
final MediaType mediaType,
final List extends AbstractEntityProviderModel> set,
final Map> subSet) {
final List providers = set.stream()
.filter(model -> model.declaredTypes().contains(mediaType))
.map(AbstractEntityProviderModel::provider)
.collect(Collectors.toList());
if (!providers.isEmpty()) {
subSet.put(mediaType, Collections.unmodifiableList(providers));
}
}
@Override
@SuppressWarnings("unchecked")
public List getMessageBodyWriterMediaTypes(final Class> c, final Type t, final Annotation[] as) {
final Set writeableMediaTypes = new LinkedHashSet<>();
for (final WriterModel model : writers) {
boolean writeableWorker = false;
for (final MediaType mt : model.declaredTypes()) {
if (model.isWriteable(c, t, as, mt)) {
writeableMediaTypes.add(mt);
writeableWorker = true;
}
if (!writeableMediaTypes.contains(MediaType.WILDCARD_TYPE)
&& writeableWorker
&& model.declaredTypes().contains(MediaType.WILDCARD_TYPE)) {
writeableMediaTypes.add(MediaType.WILDCARD_TYPE);
}
}
}
final List mtl = new ArrayList<>(writeableMediaTypes);
mtl.sort(MediaTypes.PARTIAL_ORDER_COMPARATOR);
return mtl;
}
private static final Function MODEL_TO_WRITER = AbstractEntityProviderModel::provider;
@Override
public List getMessageBodyWritersForType(final Class> type) {
return getWritersModelsForType(type).stream().map(MODEL_TO_WRITER).collect(Collectors.toList());
}
@Override
public List getWritersModelsForType(final Class> type) {
final List writerModels = mbwTypeLookupCache.get(type);
if (writerModels != null) {
return writerModels;
}
return processMessageBodyWritersForType(type);
}
private List processMessageBodyWritersForType(final Class> clazz) {
final List suitableWriters = new ArrayList<>();
if (Response.class.isAssignableFrom(clazz)) {
suitableWriters.addAll(writers);
} else {
final Class> wrapped = Primitives.wrap(clazz);
for (final WriterModel model : writers) {
if (model.providedType() == null
|| model.providedType() == clazz
|| model.providedType().isAssignableFrom(wrapped)) {
suitableWriters.add(model);
}
}
}
// Type -> Writer.
suitableWriters.sort(WORKER_BY_TYPE_COMPARATOR);
mbwTypeLookupCache.put(clazz, suitableWriters);
// Type -> MediaType.
typeToMediaTypeWritersCache.put(clazz, getMessageBodyWorkersMediaTypesByType(suitableWriters));
return suitableWriters;
}
@Override
public List getMessageBodyWriterMediaTypesByType(final Class> type) {
if (!typeToMediaTypeWritersCache.containsKey(type)) {
processMessageBodyWritersForType(type);
}
return typeToMediaTypeWritersCache.get(type);
}
@Override
public List getMessageBodyReaderMediaTypesByType(final Class> type) {
if (!typeToMediaTypeReadersCache.containsKey(type)) {
processMessageBodyReadersForType(type);
}
return typeToMediaTypeReadersCache.get(type);
}
@SuppressWarnings("unchecked")
private static List getMessageBodyWorkersMediaTypesByType(
final List extends AbstractEntityProviderModel> workerModels) {
final Set mediaTypeSet = new HashSet<>();
for (final AbstractEntityProviderModel model : workerModels) {
mediaTypeSet.addAll(model.declaredTypes());
}
final List mediaTypes = new ArrayList<>(mediaTypeSet);
mediaTypes.sort(MediaTypes.PARTIAL_ORDER_COMPARATOR);
return mediaTypes;
}
private static final Function MODEL_TO_READER = AbstractEntityProviderModel::provider;
@Override
public List getMessageBodyReadersForType(final Class> type) {
return getReaderModelsForType(type).stream().map(MODEL_TO_READER).collect(Collectors.toList());
}
@Override
public List getReaderModelsForType(final Class> type) {
if (!mbrTypeLookupCache.containsKey(type)) {
processMessageBodyReadersForType(type);
}
return mbrTypeLookupCache.get(type);
}
private List processMessageBodyReadersForType(final Class> clazz) {
final List suitableReaders = new ArrayList<>();
final Class> wrapped = Primitives.wrap(clazz);
for (final ReaderModel reader : readers) {
if (reader.providedType() == null
|| reader.providedType() == clazz
|| reader.providedType().isAssignableFrom(wrapped)) {
suitableReaders.add(reader);
}
}
// Type -> Writer.
suitableReaders.sort(WORKER_BY_TYPE_COMPARATOR);
mbrTypeLookupCache.put(clazz, suitableReaders);
// Type -> MediaType.
typeToMediaTypeReadersCache.put(clazz, getMessageBodyWorkersMediaTypesByType(suitableReaders));
return suitableReaders;
}
@Override
@SuppressWarnings("unchecked")
public MediaType getMessageBodyWriterMediaType(
final Class> c, final Type t, final Annotation[] as, final List acceptableMediaTypes) {
for (final MediaType acceptable : acceptableMediaTypes) {
for (final WriterModel model : writers) {
for (final MediaType mt : model.declaredTypes()) {
if (mt.isCompatible(acceptable) && model.isWriteable(c, t, as, acceptable)) {
return MediaTypes.mostSpecific(mt, acceptable);
}
}
}
}
return null;
}
@Override
public Object readFrom(final Class> rawType,
final Type type,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap httpHeaders,
final PropertiesDelegate propertiesDelegate,
final InputStream entityStream,
final Iterable readerInterceptors,
final boolean translateNce) throws WebApplicationException, IOException {
final ReaderInterceptorExecutor executor = new ReaderInterceptorExecutor(
rawType,
type,
annotations,
mediaType,
httpHeaders,
propertiesDelegate,
entityStream,
this,
readerInterceptors,
translateNce, injectionManager);
final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate);
final long timestamp = tracingLogger.timestamp(MsgTraceEvent.RI_SUMMARY);
try {
final Object instance = executor.proceed();
if (!(instance instanceof Closeable) && !(instance instanceof Source)) {
final InputStream stream = executor.getInputStream();
if (stream != entityStream && stream != null) {
// We only close stream if it differs from the received entity stream,
// otherwise we let the caller close the stream.
ReaderWriter.safelyClose(stream);
}
}
return instance;
} finally {
tracingLogger.logDuration(MsgTraceEvent.RI_SUMMARY, timestamp, executor.getProcessedCount());
}
}
@Override
public OutputStream writeTo(final Object t,
final Class> rawType,
final Type type,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap httpHeaders,
final PropertiesDelegate propertiesDelegate,
final OutputStream entityStream,
final Iterable writerInterceptors)
throws IOException, WebApplicationException {
final WriterInterceptorExecutor executor = new WriterInterceptorExecutor(
t,
rawType,
type,
annotations,
mediaType,
httpHeaders,
propertiesDelegate,
entityStream,
this,
writerInterceptors, injectionManager);
final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate);
final long timestamp = tracingLogger.timestamp(MsgTraceEvent.WI_SUMMARY);
try {
executor.proceed();
} finally {
tracingLogger.logDuration(MsgTraceEvent.WI_SUMMARY, timestamp, executor.getProcessedCount());
}
return executor.getOutputStream();
}
/**
* Safely invokes {@link javax.ws.rs.ext.MessageBodyWriter#isWriteable isWriteable} method on the supplied provider.
*
* Any exceptions will be logged at finer level.
*
* @param provider message body writer on which the {@code isWriteable} should be invoked.
* @param type the class of instance that is to be written.
* @param genericType the type of instance to be written, obtained either
* by reflection of a resource method return type or via inspection
* of the returned instance. {@link javax.ws.rs.core.GenericEntity}
* provides a way to specify this information at runtime.
* @param annotations an array of the annotations attached to the message entity instance.
* @param mediaType the media type of the HTTP entity.
* @return {@code true} if the type is supported, otherwise {@code false}.
*/
public static boolean isWriteable(
final MessageBodyWriter> provider,
final Class> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
try {
return provider.isWriteable(type, genericType, annotations, mediaType);
} catch (final Exception ex) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, LocalizationMessages.ERROR_MBW_ISWRITABLE(provider.getClass().getName()), ex);
}
}
return false;
}
/**
* Safely invokes {@link javax.ws.rs.ext.MessageBodyReader#isReadable isReadable} method on the supplied provider.
*
* Any exceptions will be logged at finer level.
*
* @param provider message body reader on which the {@code isReadable} should be invoked.
* Safely invokes {@link javax.ws.rs.ext.MessageBodyReader#isReadable isReadable} method on the underlying
* provider.
* @param type the class of instance to be produced.
* @param genericType the type of instance to be produced. E.g. if the
* message body is to be converted into a method parameter, this will be
* the formal type of the method parameter as returned by
* {@code Method.getGenericParameterTypes}.
* @param annotations an array of the annotations on the declaration of the
* artifact that will be initialized with the produced instance. E.g. if the
* message body is to be converted into a method parameter, this will be
* the annotations on that parameter returned by
* {@code Method.getParameterAnnotations}.
* @param mediaType the media type of the HTTP entity, if one is not
* specified in the request then {@code application/octet-stream} is
* used.
* @return {@code true} if the type is supported, otherwise {@code false}.
*/
public static boolean isReadable(final MessageBodyReader> provider,
final Class> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
try {
return provider.isReadable(type, genericType, annotations, mediaType);
} catch (final Exception ex) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, LocalizationMessages.ERROR_MBR_ISREADABLE(provider.getClass().getName()), ex);
}
}
return false;
}
}