All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.jersey.message.internal.MessageBodyFactory Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.1.9
Show newest version
/*
 * 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> 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> 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> 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy