/*
 * 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 MessageBodyReader, MessageBodyWriter instances.
Author:Paul Sandoz, Marek Potociar, Jakub Podlesak
/** * 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 MessageBodyWorkers instance into InjectionManager and BootstrapBag.
Author:Petr Bouda
/** * 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<MessageBodyFactory> 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.
/** * Media type comparator. */
public static final KeyComparator<MediaType> MEDIA_TYPE_KEY_COMPARATOR = new KeyComparator<MediaType>() { 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.
/** * 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<AbstractEntityProviderModel<?>> WORKER_BY_TYPE_COMPARATOR = new Comparator<AbstractEntityProviderModel<?>>() { @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<MediaType> mediaTypeList1, List<MediaType> 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<?>> CLASS_BY_NAME_COMPARATOR = Comparator.comparing(Class::getName); private InjectionManager injectionManager; private final Boolean legacyProviderOrdering; private List<ReaderModel> readers; private List<WriterModel> writers; private final Map<MediaType, List<MessageBodyReader>> readersCache = new KeyComparatorHashMap<>(MEDIA_TYPE_KEY_COMPARATOR); private final Map<MediaType, List<MessageBodyWriter>> 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<Class<?>, List<ReaderModel>> mbrTypeLookupCache = new ConcurrentHashMap<>( LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL); private final Map<Class<?>, List<WriterModel>> mbwTypeLookupCache = new ConcurrentHashMap<>( LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL); private final Map<Class<?>, List<MediaType>> typeToMediaTypeReadersCache = new ConcurrentHashMap<>( LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL); private final Map<Class<?>, List<MediaType>> typeToMediaTypeWritersCache = new ConcurrentHashMap<>( LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL); private final Map<ModelLookupKey, List<ReaderModel>> mbrLookupCache = new ConcurrentHashMap<>( LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL); private final Map<ModelLookupKey, List<WriterModel>> mbwLookupCache = new ConcurrentHashMap<>( LOOKUP_CACHE_INITIAL_CAPACITY, LOOKUP_CACHE_LOAD_FACTOR, DataStructures.DEFAULT_CONCURENCY_LEVEL);
Create a new message body factory.
Params:
  • configuration – configuration. Optional - can be null.
/** * 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 InjectionManager.
Params:
  • injectionManager – completed injection manager.
/** * 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<MessageBodyReader> customMbrs = Providers.getCustomProviders(injectionManager, MessageBodyReader.class); final Set<MessageBodyReader> 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<MessageBodyReader> 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<MessageBodyWriter> customMbws = Providers.getCustomProviders(injectionManager, MessageBodyWriter.class); final Set<MessageBodyWriter> 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<MessageBodyWriter> model : writers) { for (final MediaType mt : model.declaredTypes()) { List<MessageBodyWriter> 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.
Type parameters:
  • <T> – common super-type used for computing the declaration distance and comparing instances.
/** * 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. * <p/> * The comparator is optimized to cache results of the previous distance declaration * computations. * * @param <T> common super-type used for computing the declaration distance and * comparing instances. */
private static class DeclarationDistanceComparator<T> implements Comparator<T> { private final Class<T> declared; private final Map<Class, Integer> distanceMap = new HashMap<>(); DeclarationDistanceComparator(final Class<T> 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; } }
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).
Type parameters:
  • <T> – MessageBodyReader or MessageBodyWriter.
See Also:
/** * {@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 <T> MessageBodyReader or MessageBodyWriter. * @see DeclarationDistanceComparator * @see #MEDIA_TYPE_KEY_COMPARATOR */
private static class WorkerComparator<T> implements Comparator<AbstractEntityProviderModel<T>> { 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<T> modelA, final AbstractEntityProviderModel<T> 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<MediaType> 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<Class<?>> it1 = getClassHierarchyIterator(tmp1); final Iterator<Class<?>> 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<Class<?>> getClassHierarchyIterator(final Class<?> classParam) { if (classParam == null) { return Collections.<Class<?>>emptyList().iterator(); } final ArrayList<Class<?>> classes = new ArrayList<>(); final LinkedList<Class<?>> 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(); } }
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.
Type parameters:
  • <T> – MessageBodyReader or MessageBodyWriter.
See Also:
/** * {@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 <T> MessageBodyReader or MessageBodyWriter. * @see DeclarationDistanceComparator * @see #MEDIA_TYPE_KEY_COMPARATOR */
private static class LegacyWorkerComparator<T> implements Comparator<AbstractEntityProviderModel<T>> { final DeclarationDistanceComparator<T> distanceComparator; private LegacyWorkerComparator(final Class<T> type) { distanceComparator = new DeclarationDistanceComparator<>(type); } @Override public int compare(final AbstractEntityProviderModel<T> modelA, final AbstractEntityProviderModel<T> 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<ReaderModel> models, final Set<MessageBodyReader> readers, final boolean custom) { for (final MessageBodyReader provider : readers) { final List<MediaType> values = MediaTypes.createFrom(provider.getClass().getAnnotation(Consumes.class)); models.add(new ReaderModel(provider, values, custom)); } } private static void addWriters(final List<WriterModel> models, final Set<MessageBodyWriter> writers, final boolean custom) { for (final MessageBodyWriter provider : writers) { final List<MediaType> values = MediaTypes.createFrom(provider.getClass().getAnnotation(Produces.class)); models.add(new WriterModel(provider, values, custom)); } } // MessageBodyWorkers @Override public Map<MediaType, List<MessageBodyReader>> getReaders(final MediaType mediaType) { final Map<MediaType, List<MessageBodyReader>> subSet = new KeyComparatorLinkedHashMap<>(MEDIA_TYPE_KEY_COMPARATOR); getCompatibleProvidersMap(mediaType, readers, subSet); return subSet; } @Override public Map<MediaType, List<MessageBodyWriter>> getWriters(final MediaType mediaType) { final Map<MediaType, List<MessageBodyWriter>> subSet = new KeyComparatorLinkedHashMap<>(MEDIA_TYPE_KEY_COMPARATOR); getCompatibleProvidersMap(mediaType, writers, subSet); return subSet; } @Override public String readersToString(final Map<MediaType, List<MessageBodyReader>> readers) { return toString(readers); } @Override public String writersToString(final Map<MediaType, List<MessageBodyWriter>> writers) { return toString(writers); } private <T> String toString(final Map<MediaType, List<T>> set) { final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); for (final Map.Entry<MediaType, List<T>> 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 <T> MessageBodyReader<T> getMessageBodyReader(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType) { return getMessageBodyReader(c, t, as, mediaType, null); } @Override public <T> MessageBodyReader<T> getMessageBodyReader(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType, final PropertiesDelegate propertiesDelegate) { MessageBodyReader<T> 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<MediaType> getMessageBodyReaderMediaTypes(final Class<?> type, final Type genericType, final Annotation[] annotations) { final Set<MediaType> 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<MediaType> mtl = new ArrayList<>(readableMediaTypes); mtl.sort(MediaTypes.PARTIAL_ORDER_COMPARATOR); return mtl; } @SuppressWarnings("unchecked") private <T> boolean isCompatible(final AbstractEntityProviderModel<T> 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 <T> MessageBodyReader<T> _getMessageBodyReader(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType, final List<ReaderModel> 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<ReaderModel> 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<T> selected = null; final Iterator<ReaderModel> iterator = readers.iterator(); while (iterator.hasNext()) { final ReaderModel model = iterator.next(); if (model.isReadable(c, t, as, mediaType)) { selected = (MessageBodyReader<T>) 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 <T> MessageBodyReader<T> _getMessageBodyReader(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType, final MediaType lookup, final PropertiesDelegate propertiesDelegate) { final List<MessageBodyReader> readers = readersCache.get(lookup); if (readers == null) { return null; } final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate); MessageBodyReader<T> selected = null; final Iterator<MessageBodyReader> iterator = readers.iterator(); while (iterator.hasNext()) { final MessageBodyReader p = iterator.next(); if (isReadable(p, c, t, as, mediaType)) { selected = (MessageBodyReader<T>) 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 <T> MessageBodyWriter<T> getMessageBodyWriter(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType) { return getMessageBodyWriter(c, t, as, mediaType, null); } @Override public <T> MessageBodyWriter<T> getMessageBodyWriter(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType, final PropertiesDelegate propertiesDelegate) { MessageBodyWriter<T> 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 <T> MessageBodyWriter<T> _getMessageBodyWriter(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType, final List<WriterModel> 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<WriterModel> 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<T> selected = null; final Iterator<WriterModel> iterator = writers.iterator(); while (iterator.hasNext()) { final WriterModel model = iterator.next(); if (model.isWriteable(c, t, as, mediaType)) { selected = (MessageBodyWriter<T>) 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 <T> MessageBodyWriter<T> _getMessageBodyWriter(final Class<T> c, final Type t, final Annotation[] as, final MediaType mediaType, final MediaType lookup, final PropertiesDelegate propertiesDelegate) { final List<MessageBodyWriter> writers = writersCache.get(lookup); if (writers == null) { return null; } final TracingLogger tracingLogger = TracingLogger.getInstance(propertiesDelegate); MessageBodyWriter<T> selected = null; final Iterator<MessageBodyWriter> iterator = writers.iterator(); while (iterator.hasNext()) { final MessageBodyWriter p = iterator.next(); if (isWriteable(p, c, t, as, mediaType)) { selected = (MessageBodyWriter<T>) 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 <T> void getCompatibleProvidersMap( final MediaType mediaType, final List<? extends AbstractEntityProviderModel<T>> set, final Map<MediaType, List<T>> 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 <T> void getCompatibleProvidersList( final MediaType mediaType, final List<? extends AbstractEntityProviderModel<T>> set, final Map<MediaType, List<T>> subSet) { final List<T> 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<MediaType> getMessageBodyWriterMediaTypes(final Class<?> c, final Type t, final Annotation[] as) { final Set<MediaType> 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<MediaType> mtl = new ArrayList<>(writeableMediaTypes); mtl.sort(MediaTypes.PARTIAL_ORDER_COMPARATOR); return mtl; } private static final Function<WriterModel, MessageBodyWriter> MODEL_TO_WRITER = AbstractEntityProviderModel::provider; @Override public List<MessageBodyWriter> getMessageBodyWritersForType(final Class<?> type) { return getWritersModelsForType(type).stream().map(MODEL_TO_WRITER).collect(Collectors.toList()); } @Override public List<WriterModel> getWritersModelsForType(final Class<?> type) { final List<WriterModel> writerModels = mbwTypeLookupCache.get(type); if (writerModels != null) { return writerModels; } return processMessageBodyWritersForType(type); } private List<WriterModel> processMessageBodyWritersForType(final Class<?> clazz) { final List<WriterModel> 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<MediaType> getMessageBodyWriterMediaTypesByType(final Class<?> type) { if (!typeToMediaTypeWritersCache.containsKey(type)) { processMessageBodyWritersForType(type); } return typeToMediaTypeWritersCache.get(type); } @Override public List<MediaType> getMessageBodyReaderMediaTypesByType(final Class<?> type) { if (!typeToMediaTypeReadersCache.containsKey(type)) { processMessageBodyReadersForType(type); } return typeToMediaTypeReadersCache.get(type); } @SuppressWarnings("unchecked") private static <T> List<MediaType> getMessageBodyWorkersMediaTypesByType( final List<? extends AbstractEntityProviderModel<T>> workerModels) { final Set<MediaType> mediaTypeSet = new HashSet<>(); for (final AbstractEntityProviderModel<T> model : workerModels) { mediaTypeSet.addAll(model.declaredTypes()); } final List<MediaType> mediaTypes = new ArrayList<>(mediaTypeSet); mediaTypes.sort(MediaTypes.PARTIAL_ORDER_COMPARATOR); return mediaTypes; } private static final Function<ReaderModel, MessageBodyReader> MODEL_TO_READER = AbstractEntityProviderModel::provider; @Override public List<MessageBodyReader> getMessageBodyReadersForType(final Class<?> type) { return getReaderModelsForType(type).stream().map(MODEL_TO_READER).collect(Collectors.toList()); } @Override public List<ReaderModel> getReaderModelsForType(final Class<?> type) { if (!mbrTypeLookupCache.containsKey(type)) { processMessageBodyReadersForType(type); } return mbrTypeLookupCache.get(type); } private List<ReaderModel> processMessageBodyReadersForType(final Class<?> clazz) { final List<ReaderModel> 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<MediaType> 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<String, String> httpHeaders, final PropertiesDelegate propertiesDelegate, final InputStream entityStream, final Iterable<ReaderInterceptor> 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<String, Object> httpHeaders, final PropertiesDelegate propertiesDelegate, final OutputStream entityStream, final Iterable<WriterInterceptor> 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 isWriteable method on the supplied provider. Any exceptions will be logged at finer level.
Params:
  • provider – message body writer on which the isWriteable should be invoked.
  • type – the class of instance that is to be written.
  • 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. GenericEntity provides a way to specify this information at runtime.
  • annotations – an array of the annotations attached to the message entity instance.
  • mediaType – the media type of the HTTP entity.
Returns:true if the type is supported, otherwise false.
/** * 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 isReadable method on the supplied provider. Any exceptions will be logged at finer level.
Params:
  • provider – message body reader on which the isReadable should be invoked. Safely invokes isReadable method on the underlying provider.
  • type – the class of instance to be produced.
  • 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 Method.getGenericParameterTypes.
  • 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 Method.getParameterAnnotations.
  • mediaType – the media type of the HTTP entity, if one is not specified in the request then application/octet-stream is used.
Returns:true if the type is supported, otherwise 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; } }