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

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

There is a newer version: 2.0-rc1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2012 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.glassfish.jersey.message.internal;

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.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
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.inject.Inject;
import javax.inject.Singleton;

import org.glassfish.jersey.Config;
import org.glassfish.jersey.internal.PropertiesDelegate;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.KeyComparator;
import org.glassfish.jersey.internal.util.KeyComparatorHashMap;
import org.glassfish.jersey.internal.util.KeyComparatorLinkedHashMap;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.ReflectionHelper.DeclaringClassInterfacePair;
import org.glassfish.jersey.message.MessageBodyWorkers;
import org.glassfish.jersey.message.MessageProperties;
import org.glassfish.jersey.process.internal.PriorityComparator;
import org.glassfish.jersey.process.internal.PriorityComparator.Order;

import org.glassfish.hk2.api.ServiceLocator;

import org.jvnet.hk2.annotations.Optional;

/**
 * A factory for managing {@link MessageBodyReader} and {@link MessageBodyWriter}
 * instances.
 *
 * @author Paul Sandoz
 * @author Marek Potociar (marek.potociar at oracle.com)
 * @author Jakub Podlesak (jakub.podlesak at oracle.com)
 */
public class MessageBodyFactory implements MessageBodyWorkers {

    /**
     * Message body factory injection binder.
     */
    public static class Binder extends AbstractBinder {
        @Override
        protected void configure() {
            bindAsContract(MessageBodyFactory.class).to(MessageBodyWorkers.class).in(Singleton.class);
        }
    }

    /**
     * Media type comparator.
     */
    public static final KeyComparator MEDIA_TYPE_COMPARATOR =
            new KeyComparator() {

                private static final long serialVersionUID = 2727819828630827763L;

                @Override
                public boolean equals(MediaType x, MediaType y) {
                    return x.getType().equalsIgnoreCase(y.getType())
                            && x.getSubtype().equalsIgnoreCase(y.getSubtype());
                }

                @Override
                public int hash(MediaType k) {
                    return k.getType().toLowerCase().hashCode()
                            + k.getSubtype().toLowerCase().hashCode();
                }

                // move to separate comparator?
                @Override
                public int compare(MediaType o1, MediaType o2) {
                    if (equals(o1, o2)) {
                        return 0;
                    } else if (o1.isWildcardType() ^ o2.isWildcardType()) {
                        return (o1.isWildcardType()) ? 1 : -1;
                    } else if (o1.isWildcardSubtype() ^ o2.isWildcardSubtype()) {
                        return (o1.isWildcardSubtype()) ? 1 : -1;
                    }
                    return 0;
                }
            };

    private final ServiceLocator locator;
    private final Boolean legacyProviderOrdering;

    private List readerInterceptors;
    private List writerInterceptors;

    private List> readers;
    private List> writers;

    private final Map> readersCache =
            new KeyComparatorHashMap>(MEDIA_TYPE_COMPARATOR);
    private final Map> writersCache =
            new KeyComparatorHashMap>(MEDIA_TYPE_COMPARATOR);

    private final Map>> mbrLookupCache =
            new ConcurrentHashMap>>();
    private final Map>> mbwLookupCache =
            new ConcurrentHashMap>>();


    @Override
    public List getReaderInterceptors() {
        return readerInterceptors;
    }

    @Override
    public List getWriterInterceptors() {
        return writerInterceptors;
    }

    private static class MessageBodyWorkerPair {
        final T provider;
        final List types;
        final Boolean custom;
        Class providerClassParam = null;

        private MessageBodyWorkerPair(T provider, List types, Boolean custom) {
            this.provider = provider;
            this.types = types;
            this.custom = custom;
        }
    }

    /**
     * Create new message body workers factory.
     *
     * @param locator service locator.
     * @param config configuration. Optional - can be null.
     */
    @Inject
    public MessageBodyFactory(ServiceLocator locator, @Optional Config config) {
        this.locator = locator;
        this.legacyProviderOrdering = config != null && config.isProperty(MessageProperties.LEGACY_WORKERS_ORDERING);

        initReaders();
        initWriters();
        initInterceptors();
    }


    /**
     * 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(Class declared) { this.declared = declared; } @Override public int compare(T o1, T o2) { int d1 = getDistance(o1); int d2 = getDistance(o2); return d2 - d1; } private int getDistance(T t) { Integer distance = distanceMap.get(t.getClass()); if (distance != null) { return distance; } DeclaringClassInterfacePair p = ReflectionHelper.getClass( t.getClass(), declared); 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 MessageBodyWorkerPair} 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_COMPARATOR */ private static class WorkerComparator implements Comparator> { final Class wantedType; final MediaType wantedMediaType; private WorkerComparator(Class wantedType, MediaType wantedMediaType) { this.wantedType = wantedType; this.wantedMediaType = wantedMediaType; } @Override public int compare(MessageBodyWorkerPair mbwp1, MessageBodyWorkerPair mbwp2) { final int distance = compareTypeDistances(mbwp1.providerClassParam, mbwp2.providerClassParam); if (distance != 0) { return distance; } final int mediaTypeComparison = getMediaTypeDistance(wantedMediaType, mbwp1.types) - getMediaTypeDistance(wantedMediaType, mbwp2.types); if (mediaTypeComparison != 0) { return mediaTypeComparison; } if (mbwp1.custom ^ mbwp2.custom) { return (mbwp1.custom) ? 1 : -1; } return 0; } private int getMediaTypeDistance(MediaType wanted, List mtl) { if(wanted == null) { return 0; } int distance = 2; for(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(Class providerClassParam1, Class providerClassParam2) { return getTypeDistance(providerClassParam1) - getTypeDistance(providerClassParam2); } private int getTypeDistance(Class classParam) { // cache? Class tmp1 = wantedType; Class tmp2 = classParam; int distance = 0; while (!wantedType.equals(tmp2) && !classParam.equals(tmp1)) { distance++; if(tmp2 != null) tmp2 = tmp2.getSuperclass(); if(tmp1 != null) tmp1 = tmp1.getSuperclass(); if(tmp2 == null && tmp1 == null) { return Integer.MAX_VALUE; } } return distance; } } /** * {@link MessageBodyWorkerPair} 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_COMPARATOR */ private static class LegacyWorkerComparator implements Comparator> { final DeclarationDistanceComparator distanceComparator; private LegacyWorkerComparator(Class type) { distanceComparator = new DeclarationDistanceComparator(type); } @Override public int compare(MessageBodyWorkerPair mbwp1, MessageBodyWorkerPair mbwp2) { if (mbwp1.custom ^ mbwp2.custom) { return (mbwp1.custom) ? -1 : 1; } final int mediaTypeComparison = MEDIA_TYPE_COMPARATOR.compare(mbwp1.types.get(0), mbwp2.types.get(0)); if (mediaTypeComparison != 0) { return mediaTypeComparison; } return distanceComparator.compare(mbwp1.provider, mbwp2.provider); } } private static class TypeMediaTypePair { final Class clazz; final MediaType mediaType; private TypeMediaTypePair(Class clazz, MediaType mediaType) { this.clazz = clazz; this.mediaType = mediaType; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TypeMediaTypePair that = (TypeMediaTypePair) 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 void initInterceptors() { // TODO: only "global" interceptors should be taken into account here ? final List _readerInterceptors = locator.getAllServices(ReaderInterceptor.class); Collections.sort(_readerInterceptors, new PriorityComparator(Order.ASCENDING)); this.readerInterceptors = Collections.unmodifiableList(_readerInterceptors); final List _writerInterceptors = locator.getAllServices(WriterInterceptor.class); Collections.sort(_writerInterceptors, new PriorityComparator(Order.ASCENDING)); this.writerInterceptors = Collections.unmodifiableList(_writerInterceptors); } private void initReaders() { this.readers = new ArrayList>(); final Set customProviders = Providers.getCustomProviders(locator, MessageBodyReader.class); final Set providers = Providers.getProviders(locator, MessageBodyReader.class); initReaders(readers, customProviders, true); providers.removeAll(customProviders); initReaders(readers, providers, false); if(legacyProviderOrdering) { Collections.sort(readers, new LegacyWorkerComparator(MessageBodyReader.class)); for(MessageBodyWorkerPair messageBodyWorkerPair : readers) { for(MediaType mt : messageBodyWorkerPair.types) { List readerList = readersCache.get(mt); if(readerList == null) { readerList = new ArrayList(); readersCache.put(mt, readerList); } readerList.add(messageBodyWorkerPair.provider); } } } } private void initReaders(List> readers, Set providersSet, boolean custom) { for (MessageBodyReader provider : providersSet) { List values = MediaTypes.createFrom( provider.getClass().getAnnotation(Consumes.class)); final MessageBodyWorkerPair readerPair = new MessageBodyWorkerPair(provider, values, custom); readers.add(readerPair); } } private void initWriters() { this.writers = new ArrayList>(); final Set customProviders = Providers.getCustomProviders(locator, MessageBodyWriter.class); final Set providers = Providers.getProviders(locator, MessageBodyWriter.class); initWriters(writers, customProviders, true); providers.removeAll(customProviders); initWriters(writers, providers, false); if(legacyProviderOrdering) { Collections.sort(writers, new LegacyWorkerComparator(MessageBodyWriter.class)); for(MessageBodyWorkerPair messageBodyWorkerPair : writers) { for(MediaType mt : messageBodyWorkerPair.types) { List writerList = writersCache.get(mt); if(writerList == null) { writerList = new ArrayList(); writersCache.put(mt, writerList); } writerList.add(messageBodyWorkerPair.provider); } } } } private void initWriters(List> writers, Set providersSet, boolean custom) { for (MessageBodyWriter provider : providersSet) { List values = MediaTypes.createFrom( provider.getClass().getAnnotation(Produces.class)); final MessageBodyWorkerPair workerPair = new MessageBodyWorkerPair(provider, values, custom); writers.add(workerPair); } } // MessageBodyWorkers @Override public Map> getReaders(MediaType mediaType) { Map> subSet = new KeyComparatorLinkedHashMap>( MEDIA_TYPE_COMPARATOR); getCompatibleProvidersMap(mediaType, readers, subSet); return subSet; } @Override public Map> getWriters(MediaType mediaType) { Map> subSet = new KeyComparatorLinkedHashMap>( MEDIA_TYPE_COMPARATOR); getCompatibleProvidersMap(mediaType, writers, subSet); return subSet; } @Override public String readersToString(Map> readers) { return toString(readers); } @Override public String writersToString(Map> writers) { return toString(writers); } private String toString(Map> set) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); for (Map.Entry> e : set.entrySet()) { pw.append(e.getKey().toString()).println(" ->"); for (T t : e.getValue()) { pw.append(" ").println(t.getClass().getName()); } } pw.flush(); return sw.toString(); } @Override public MessageBodyReader getMessageBodyReader(Class c, Type t, Annotation[] as, MediaType mediaType) { MessageBodyReader p = null; if(legacyProviderOrdering) { if (mediaType != null) { p = _getMessageBodyReader(c, t, as, mediaType, mediaType); if (p == null) { p = _getMessageBodyReader(c, t, as, mediaType, MediaTypes.getTypeWildCart(mediaType)); } } if (p == null) { p = _getMessageBodyReader(c, t, as, mediaType, MediaTypes.GENERAL_MEDIA_TYPE); } } else { p = _getMessageBodyReader(c, t, as, mediaType, readers); } return p; } @Override public List getMessageBodyReaderMediaTypes(Class type, Type genericType, Annotation[] annotations) { List mtl = new ArrayList(); for (MessageBodyWorkerPair mbrp : readers) { for (MediaType mt : mbrp.types) { if (mbrp.provider.isReadable(type, genericType, annotations, mt)) { mtl.addAll(mbrp.types); } } } Collections.sort(mtl, MediaTypes.MEDIA_TYPE_COMPARATOR); return mtl; } private boolean isCompatible(Class workerClass, MessageBodyWorkerPair messageBodyWorkerPair, Class c, MediaType mediaType) { if(messageBodyWorkerPair.providerClassParam == null) { DeclaringClassInterfacePair p = ReflectionHelper.getClass( messageBodyWorkerPair.provider.getClass(), workerClass); Class[] classArgs = ReflectionHelper.getParameterizedClassArguments(p); messageBodyWorkerPair.providerClassParam = (classArgs != null) ? classArgs[0] : null; } if(messageBodyWorkerPair.providerClassParam == null) { messageBodyWorkerPair.providerClassParam = Object.class; } if(messageBodyWorkerPair.providerClassParam.equals(Object.class) || // looks weird. Could/(should?) be separated to Writer/Reader check messageBodyWorkerPair.providerClassParam.isAssignableFrom(c) || c.isAssignableFrom(messageBodyWorkerPair.providerClassParam) ) { for(MediaType mt : messageBodyWorkerPair.types) { if(mediaType == null) { return true; } if(MediaTypes.typeEqual(mediaType, mt) || MediaTypes.typeEqual(MediaTypes.getTypeWildCart(mediaType), mt) || MediaTypes.typeEqual(MediaTypes.GENERAL_MEDIA_TYPE, mt)) { return true; } } } return false; } @SuppressWarnings("unchecked") private MessageBodyReader _getMessageBodyReader(Class c, Type t, Annotation[] as, MediaType mediaType, List> workers) { List> readers = mbrLookupCache.get(new TypeMediaTypePair(c, mediaType)); if(readers == null) { readers = new ArrayList>(); for(MessageBodyWorkerPair mbwp : workers) { if(isCompatible(MessageBodyReader.class, mbwp, c, mediaType)) { readers.add(mbwp); } } Collections.sort(readers, new WorkerComparator(c, mediaType)); mbrLookupCache.put(new TypeMediaTypePair(c, mediaType), readers); } if(readers.isEmpty()) { return null; } for(MessageBodyWorkerPair mbwp : readers) { if(mbwp.provider.isReadable(c, t, as, mediaType)) { return mbwp.provider; } } return null; } @SuppressWarnings("unchecked") private MessageBodyReader _getMessageBodyReader(Class c, Type t, Annotation[] as, MediaType mediaType, MediaType lookup) { List readers = readersCache.get(lookup); if(readers == null) { return null; } for (MessageBodyReader p : readers) { if (p.isReadable(c, t, as, mediaType)) { return (MessageBodyReader) p; } } return null; } @Override public MessageBodyWriter getMessageBodyWriter(Class c, Type t, Annotation[] as, MediaType mediaType) { MessageBodyWriter p = null; if(legacyProviderOrdering) { if (mediaType != null) { p = _getMessageBodyWriter(c, t, as, mediaType, mediaType); if (p == null) { p = _getMessageBodyWriter(c, t, as, mediaType, MediaTypes.getTypeWildCart(mediaType)); } } if (p == null) { p = _getMessageBodyWriter(c, t, as, mediaType, MediaTypes.GENERAL_MEDIA_TYPE); } } else { p = _getMessageBodyWriter(c, t, as, mediaType, writers); } return p; } @SuppressWarnings("unchecked") private MessageBodyWriter _getMessageBodyWriter(Class c, Type t, Annotation[] as, MediaType mediaType, List> workers) { List> writers = mbwLookupCache.get(new TypeMediaTypePair(c, mediaType)); if(writers == null) { writers = new ArrayList>(); for(MessageBodyWorkerPair mbwp : workers) { if(isCompatible(MessageBodyWriter.class, mbwp, c, mediaType)) { writers.add(mbwp); } } Collections.sort(writers, new WorkerComparator(c, mediaType)); mbwLookupCache.put(new TypeMediaTypePair(c, mediaType), writers); } if(writers.isEmpty()) { return null; } for(MessageBodyWorkerPair mbwp : writers) { if(mbwp.provider.isWriteable(c, t, as, mediaType)) { return mbwp.provider; } } return null; } @SuppressWarnings("unchecked") private MessageBodyWriter _getMessageBodyWriter(Class c, Type t, Annotation[] as, MediaType mediaType, MediaType lookup) { List writers = writersCache.get(lookup); if(writers == null) { return null; } for (MessageBodyWriter p : writers) { if (p.isWriteable(c, t, as, mediaType)) { return (MessageBodyWriter) p; } } return null; } private void getCompatibleProvidersMap(MediaType mediaType, List> set, Map> subSet) { if (mediaType.isWildcardType()) { getCompatibleProvidersList(mediaType, set, subSet); } else if (mediaType.isWildcardSubtype()) { getCompatibleProvidersList(mediaType, set, subSet); getCompatibleProvidersList(MediaTypes.GENERAL_MEDIA_TYPE, set, subSet); } else { getCompatibleProvidersList(mediaType, set, subSet); getCompatibleProvidersList( MediaTypes.getTypeWildCart(mediaType), set, subSet); getCompatibleProvidersList(MediaTypes.GENERAL_MEDIA_TYPE, set, subSet); } } private void getCompatibleProvidersList(MediaType mediaType, List> set, Map> subSet) { List providers = new ArrayList(); for(MessageBodyWorkerPair mbpp : set) { if(mbpp.types.contains(mediaType)) { providers.add(mbpp.provider); } } if (!providers.isEmpty()) { subSet.put(mediaType, Collections.unmodifiableList(providers)); } } @Override public List getMessageBodyWriterMediaTypes(Class c, Type t, Annotation[] as) { List mtl = new ArrayList(); for (MessageBodyWorkerPair mbwp : writers) { for (MediaType mt : mbwp.types) { if (mbwp.provider.isWriteable(c, t, as, mt)) { mtl.addAll(mbwp.types); } } } Collections.sort(mtl, MediaTypes.MEDIA_TYPE_COMPARATOR); return mtl; } @Override public MediaType getMessageBodyWriterMediaType(Class c, Type t, Annotation[] as, List acceptableMediaTypes) { for (MediaType acceptable : acceptableMediaTypes) { for (MessageBodyWorkerPair mbwp : writers) { for (MediaType mt : mbwp.types) { if (mt.isCompatible(acceptable) && mbwp.provider.isWriteable(c, t, as, acceptable)) { return MediaTypes.mostSpecific(mt, acceptable); } } } } return null; } @Override public Object readFrom(Class rawType, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, PropertiesDelegate propertiesDelegate, InputStream entityStream, boolean intercept) throws WebApplicationException, IOException { ReaderInterceptorExecutor executor = new ReaderInterceptorExecutor(rawType, type, annotations, mediaType, httpHeaders, propertiesDelegate, entityStream, this, intercept); return executor.proceed(); } @Override public OutputStream writeTo(Object t, Class rawType, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, PropertiesDelegate propertiesDelegate, OutputStream entityStream, MessageBodySizeCallback sizeCallback, boolean intercept) throws IOException, WebApplicationException { return writeTo(t, rawType, type, annotations, mediaType, httpHeaders, propertiesDelegate, entityStream, sizeCallback, intercept, true); } @Override public OutputStream writeTo(Object t, Class rawType, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, PropertiesDelegate propertiesDelegate, OutputStream entityStream, MessageBodySizeCallback sizeCallback, boolean intercept, boolean writeEntity) throws IOException, WebApplicationException { WriterInterceptorExecutor executor = new WriterInterceptorExecutor(t, rawType, type, annotations, mediaType, httpHeaders, propertiesDelegate, entityStream, this, sizeCallback, intercept, writeEntity); executor.proceed(); return executor.getOutputStream(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy