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

org.apache.cxf.jaxrs.provider.ProviderFactory Maven / Gradle / Ivy

There is a newer version: 4.0.5
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.cxf.jaxrs.provider;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;

import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.Produces;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;

import org.apache.cxf.Bus;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.ClassHelper;
import org.apache.cxf.common.util.PropertyUtils;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.jaxrs.ext.ContextProvider;
import org.apache.cxf.jaxrs.impl.MetadataMap;
import org.apache.cxf.jaxrs.impl.ReaderInterceptorMBR;
import org.apache.cxf.jaxrs.impl.WriterInterceptorMBW;
import org.apache.cxf.jaxrs.impl.tl.ThreadLocalProxy;
import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
import org.apache.cxf.jaxrs.model.ApplicationInfo;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.FilterProviderInfo;
import org.apache.cxf.jaxrs.model.ProviderInfo;
import org.apache.cxf.jaxrs.utils.AnnotationUtils;
import org.apache.cxf.jaxrs.utils.InjectionUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.jaxrs.utils.ResourceUtils;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;

public abstract class ProviderFactory {
    public static final String DEFAULT_FILTER_NAME_BINDING = "org.apache.cxf.filter.binding";
    public static final String PROVIDER_SELECTION_PROPERTY_CHANGED = "provider.selection.property.changed";
    public static final String ACTIVE_JAXRS_PROVIDER_KEY = "active.jaxrs.provider";

    protected static final String SERVER_FACTORY_NAME = "org.apache.cxf.jaxrs.provider.ServerProviderFactory";
    protected static final String CLIENT_FACTORY_NAME = "org.apache.cxf.jaxrs.client.ClientProviderFactory";
    protected static final String IGNORE_TYPE_VARIABLES = "org.apache.cxf.jaxrs.providers.ignore.typevars";

    private static final Logger LOG = LogUtils.getL7dLogger(ProviderFactory.class);

    private static final String JAXB_PROVIDER_NAME = "org.apache.cxf.jaxrs.provider.JAXBElementProvider";
    private static final String JSON_PROVIDER_NAME = "org.apache.cxf.jaxrs.provider.json.JSONProvider";
    private static final String BUS_PROVIDERS_ALL = "org.apache.cxf.jaxrs.bus.providers";
    private static final String PROVIDER_CACHE_ALLOWED = "org.apache.cxf.jaxrs.provider.cache.allowed";
    private static final String PROVIDER_CACHE_CHECK_ALL = "org.apache.cxf.jaxrs.provider.cache.checkAllCandidates";


    static class LazyProviderClass {
        // class to Lazily call the ClassLoaderUtil.loadClass, but do it once
        // and cache the result.  Then use the class to create instances as needed.
        // This avoids calling loadClass every time a factory is initialized as
        // calling loadClass is super expensive, particularly if the class
        // cannot be found and particularly in osgi where the search is very complex.
        // This would record that the class is not found and prevent future
        // searches.
        final String className;
        volatile boolean initialized;
        Class cls;

        LazyProviderClass(String cn) {
            className = cn;
        }

        synchronized void loadClass() {
            if (!initialized) {
                try {
                    cls = ClassLoaderUtils.loadClass(className, ProviderFactory.class);
                } catch (final Throwable ex) {
                    LOG.fine(className + " not available, skipping");
                }
                initialized = true;
            }
        }

        public Object tryCreateInstance(Bus bus) {
            if (!initialized) {
                loadClass();
            }
            if (cls != null) {
                try {
                    for (Constructor c : cls.getConstructors()) {
                        if (c.getParameterTypes().length == 1 && c.getParameterTypes()[0] == Bus.class) {
                            return c.newInstance(bus);
                        }
                    }
                    return cls.getDeclaredConstructor().newInstance();
                } catch (Throwable ex) {
                    String message = "Problem with creating the provider " + className;
                    if (ex.getMessage() != null) {
                        message += ": " + ex.getMessage();
                    } else {
                        message += ", exception class : " + ex.getClass().getName();
                    }
                    LOG.fine(message);
                }
            }
            return null;
        }
    };

    private static final LazyProviderClass DATA_SOURCE_PROVIDER_CLASS =
        new LazyProviderClass("org.apache.cxf.jaxrs.provider.DataSourceProvider");
    private static final LazyProviderClass JAXB_PROVIDER_CLASS =
        new LazyProviderClass(JAXB_PROVIDER_NAME);
    private static final LazyProviderClass JAXB_ELEMENT_PROVIDER_CLASS =
        new LazyProviderClass("org.apache.cxf.jaxrs.provider.JAXBElementTypedProvider");
    private static final LazyProviderClass MULTIPART_PROVIDER_CLASS =
        new LazyProviderClass("org.apache.cxf.jaxrs.provider.MultipartProvider");

    protected Map> readerInterceptors =
        new NameKeyMap<>(true);
    protected Map> writerInterceptors =
        new NameKeyMap<>(true);

    private List>> messageReaders =
        new ArrayList<>();
    private List>> messageWriters =
        new ArrayList<>();
    private List>> contextResolvers =
        new ArrayList<>();
    private List>> contextProviders =
        new ArrayList<>();

    private List> paramConverters =
        new ArrayList<>(1);
    private boolean paramConverterContextsAvailable;
    // List of injected providers
    private Collection> injectedProviders =
        new HashSet<>();

    private Bus bus;

    private Comparator providerComparator;

    private ProviderCache providerCache;


    protected ProviderFactory(Bus bus) {
        this.bus = bus;
        providerCache = initCache(bus);
    }

    public Bus getBus() {
        return bus;
    }
    protected static ProviderCache initCache(Bus theBus) {
        Object allowProp = theBus.getProperty(PROVIDER_CACHE_ALLOWED);
        boolean allowed = allowProp == null || PropertyUtils.isTrue(allowProp);
        if (!allowed) {
            return null;
        }
        boolean checkAll = PropertyUtils.isTrue(theBus.getProperty(PROVIDER_CACHE_CHECK_ALL));
        return new ProviderCache(checkAll);
    }
    protected static void initFactory(ProviderFactory factory) {
        // ensure to not load providers not available in a module environment if not needed
        factory.setProviders(false,
                             false,
                     new BinaryDataProvider(),
                     new SourceProvider(),
                     DATA_SOURCE_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
                     new FormEncodingProvider(),
                     new StringTextProvider(),
                     new PrimitiveTextProvider(),
                     JAXB_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
                     JAXB_ELEMENT_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
                     MULTIPART_PROVIDER_CLASS.tryCreateInstance(factory.getBus()));
        Object prop = factory.getBus().getProperty("skip.default.json.provider.registration");
        if (!PropertyUtils.isTrue(prop)) {
            factory.setProviders(false, false, createProvider(JSON_PROVIDER_NAME, factory.getBus()));
        }
    }

    protected static Object createProvider(String className, Bus bus) {

        try {
            Class cls = ClassLoaderUtils.loadClass(className, ProviderFactory.class);
            for (Constructor c : cls.getConstructors()) {
                if (c.getParameterTypes().length == 1 && c.getParameterTypes()[0] == Bus.class) {
                    return c.newInstance(bus);
                }
            }
            return cls.getDeclaredConstructor().newInstance();
        } catch (Throwable ex) {
            String message = "Problem with creating the default provider " + className;
            if (ex.getMessage() != null) {
                message += ": " + ex.getMessage();
            } else {
                message += ", exception class : " + ex.getClass().getName();
            }
            LOG.fine(message);
        }
        return null;
    }

    public abstract Configuration getConfiguration(Message message);

    public  ContextResolver createContextResolver(Type contextType,
                                                        Message m) {
        boolean isRequestor = MessageUtils.isRequestor(m);
        Message requestMessage = isRequestor ? m.getExchange().getOutMessage()
                                             : m.getExchange().getInMessage();

        Message responseMessage = isRequestor ? m.getExchange().getInMessage()
                                              : m.getExchange().getOutMessage();
        final Object ctProperty;
        if (responseMessage != null) {
            ctProperty = responseMessage.get(Message.CONTENT_TYPE);
        } else {
            ctProperty = requestMessage.get(Message.CONTENT_TYPE);
        }
        MediaType mt = ctProperty != null ? JAXRSUtils.toMediaType(ctProperty.toString())
            : MediaType.WILDCARD_TYPE;
        return createContextResolver(contextType, m, mt);

    }

    @SuppressWarnings("unchecked")
    public  ContextResolver createContextResolver(Type contextType,
                                                        Message m,
                                                        MediaType type) {
        Class contextCls = InjectionUtils.getActualType(contextType);
        if (contextCls == null) {
            return null;
        }
        List> candidates = new LinkedList<>();
        for (ProviderInfo> cr : contextResolvers) {
            Type[] types = cr.getProvider().getClass().getGenericInterfaces();
            for (Type t : types) {
                if (t instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType)t;
                    Type[] args = pt.getActualTypeArguments();
                    if (args.length > 0) {
                        Class argCls = InjectionUtils.getActualType(args[0]);

                        if (argCls != null && argCls.isAssignableFrom(contextCls)) {
                            List mTypes = JAXRSUtils.getProduceTypes(
                                 cr.getProvider().getClass().getAnnotation(Produces.class));
                            if (JAXRSUtils.doMimeTypesIntersect(mTypes, type)) {
                                injectContextValues(cr, m);
                                candidates.add((ContextResolver)cr.getProvider());
                            }
                        }
                    }
                }
            }
        }
        if (candidates.isEmpty()) {
            return null;
        } else if (candidates.size() == 1) {
            return candidates.get(0);
        } else {
            Collections.sort(candidates, new PriorityBasedClassComparator());
            return new ContextResolverProxy(candidates);
        }

    }

    @SuppressWarnings("unchecked")
    public  ContextProvider createContextProvider(Type contextType,
                                                        Message m) {
        Class contextCls = InjectionUtils.getActualType(contextType);
        if (contextCls == null) {
            return null;
        }
        for (ProviderInfo> cr : contextProviders) {
            Type[] types = cr.getProvider().getClass().getGenericInterfaces();
            for (Type t : types) {
                if (t instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType)t;
                    Type[] args = pt.getActualTypeArguments();
                    if (args.length > 0) {
                        Class argCls = InjectionUtils.getActualType(args[0]);

                        if (argCls != null && argCls.isAssignableFrom(contextCls)) {
                            return (ContextProvider)cr.getProvider();
                        }
                    }
                }
            }
        }
        return null;
    }

    public  ParamConverter createParameterHandler(Class paramType,
                                                        Type genericType,
                                                        Annotation[] anns,
                                                        Message m) {

        anns = anns != null ? anns : new Annotation[]{};
        for (ProviderInfo pi : paramConverters) {
            injectContextValues(pi, m);
            ParamConverter converter = pi.getProvider().getConverter(paramType, genericType, anns);
            if (converter != null) {
                return converter;
            }
            pi.clearThreadLocalProxies();
        }
        return null;
    }

    protected  boolean handleMapper(ProviderInfo em,
                                       Class expectedType,
                                       Message m,
                                       Class providerClass,
                                       boolean injectContext) {
        return handleMapper(em, expectedType, m, providerClass, null, injectContext);
    }

    protected  boolean handleMapper(ProviderInfo em,
                                       Class expectedType,
                                       Message m,
                                       Class providerClass,
                                       Class commonBaseClass,
                                       boolean injectContext) {

        Class mapperClass = ClassHelper.getRealClass(bus, em.getProvider());
        Type[] types;
        if (m != null && MessageUtils.getContextualBoolean(m, IGNORE_TYPE_VARIABLES)) {
            types = new Type[]{mapperClass};
        } else {
            types = getGenericInterfaces(mapperClass, expectedType, commonBaseClass);
        }
        for (Type t : types) {
            if (t instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType)t;
                Type[] args = pt.getActualTypeArguments();
                for (Type arg : args) {
                    if (arg instanceof TypeVariable) {
                        TypeVariable var = (TypeVariable) arg;
                        Type[] bounds = var.getBounds();
                        boolean isResolved = false;
                        for (Type bound : bounds) {
                            Class cls = InjectionUtils.getRawType(bound);
                            if (cls != null && (cls == Object.class || cls.isAssignableFrom(expectedType))) {
                                isResolved = true;
                                break;
                            }
                        }
                        if (!isResolved) {
                            return false;
                        }
                        if (injectContext) {
                            injectContextValues(em, m);
                        }
                        return true;
                    }
                    Class actualClass = InjectionUtils.getRawType(arg);
                    if (actualClass == null) {
                        continue;
                    }
                    if (expectedType.isArray() && !actualClass.isArray()) {
                        expectedType = expectedType.getComponentType();
                    }
                    if (actualClass.isAssignableFrom(expectedType) || actualClass == Object.class) {
                        if (injectContext) {
                            injectContextValues(em, m);
                        }
                        return true;
                    }
                }
            } else if (t instanceof Class && providerClass.isAssignableFrom((Class)t)) {
                if (injectContext) {
                    injectContextValues(em, m);
                }
                return true;
            }
        }
        return false;
    }


    public  List createMessageBodyReaderInterceptor(Class bodyType,
                                                            Type parameterType,
                                                            Annotation[] parameterAnnotations,
                                                            MediaType mediaType,
                                                            Message m,
                                                            boolean checkMbrNow,
                                                            Set names) {
        MessageBodyReader mr = !checkMbrNow ? null : createMessageBodyReader(bodyType,
                                                      parameterType,
                                                      parameterAnnotations,
                                                      mediaType,
                                                      m);
        int size = readerInterceptors.size();
        if (mr != null || size > 0) {
            ReaderInterceptor mbrReader = new ReaderInterceptorMBR(mr, getResponseMessage(m));

            final List interceptors;
            if (size > 0) {
                interceptors = new ArrayList<>(size + 1);
                List> readers =
                    getBoundFilters(readerInterceptors, names);
                for (ProviderInfo p : readers) {
                    injectContextValues(p, m);
                    interceptors.add(p.getProvider());
                }
                interceptors.add(mbrReader);
            } else {
                interceptors = Collections.singletonList(mbrReader);
            }

            return interceptors;
        }
        return null;
    }

    public  List createMessageBodyWriterInterceptor(Class bodyType,
                                                                          Type parameterType,
                                                                          Annotation[] parameterAnnotations,
                                                                          MediaType mediaType,
                                                                          Message m,
                                                                          Set names) {
        MessageBodyWriter mw = createMessageBodyWriter(bodyType,
                                                      parameterType,
                                                      parameterAnnotations,
                                                      mediaType,
                                                      m);
        int size = writerInterceptors.size();
        if (mw != null || size > 0) {

            @SuppressWarnings({
                "unchecked", "rawtypes"
            })
            WriterInterceptor mbwWriter = new WriterInterceptorMBW((MessageBodyWriter)mw, m);

            final List interceptors;
            if (size > 0) {
                interceptors = new ArrayList<>(size + 1);
                List> writers =
                    getBoundFilters(writerInterceptors, names);
                for (ProviderInfo p : writers) {
                    injectContextValues(p, m);
                    interceptors.add(p.getProvider());
                }
                interceptors.add(mbwWriter);
            } else {
                interceptors = Collections.singletonList(mbwWriter);
            }

            return interceptors;
        }
        return null;
    }



    @SuppressWarnings("unchecked")
    public  MessageBodyReader createMessageBodyReader(Class type,
                                                            Type genericType,
                                                            Annotation[] annotations,
                                                            MediaType mediaType,
                                                            Message m) {
        // Step1: check the cache

        if (providerCache != null) {
            for (ProviderInfo> ep : providerCache.getReaders(type, mediaType)) {
                if (isReadable(ep, type, genericType, annotations, mediaType, m)) {
                    return (MessageBodyReader)ep.getProvider();
                }
            }
        }

        boolean checkAll = providerCache != null && providerCache.isCheckAllCandidates();
        List>> allCandidates =
            checkAll ? new LinkedList>>() : null;

        MessageBodyReader selectedReader = null;
        for (ProviderInfo> ep : messageReaders) {
            if (matchesReaderMediaTypes(ep, mediaType)
                && handleMapper(ep, type, m, MessageBodyReader.class, false)) {
                // This writer matches Media Type and Class
                if (checkAll) {
                    allCandidates.add(ep);
                } else if (providerCache != null && providerCache.getReaders(type, mediaType).isEmpty()) {
                    providerCache.putReaders(type, mediaType, Collections.singletonList(ep));
                }
                if (selectedReader == null
                    && isReadable(ep, type, genericType, annotations, mediaType, m)) {
                    // This writer is a selected candidate
                    selectedReader = (MessageBodyReader)ep.getProvider();
                    if (!checkAll) {
                        return selectedReader;
                    }
                }

            }
        }
        if (checkAll) {
            providerCache.putReaders(type, mediaType, allCandidates);
        }
        return selectedReader;
    }

    @SuppressWarnings("unchecked")
    public  MessageBodyWriter createMessageBodyWriter(Class type,
                                                            Type genericType,
                                                            Annotation[] annotations,
                                                            MediaType mediaType,
                                                            Message m) {

        // Step1: check the cache.
        if (providerCache != null) {
            for (ProviderInfo> ep : providerCache.getWriters(type, mediaType)) {
                if (isWriteable(ep, type, genericType, annotations, mediaType, m)) {
                    return (MessageBodyWriter)ep.getProvider();
                }
            }
        }

        // Step2: check all the registered writers

        // The cache, if enabled, may have been configured to keep the top candidate only
        boolean checkAll = providerCache != null && providerCache.isCheckAllCandidates();
        List>> allCandidates =
            checkAll ? new LinkedList>>() : null;

        MessageBodyWriter selectedWriter = null;
        for (ProviderInfo> ep : messageWriters) {
            if (matchesWriterMediaTypes(ep, mediaType)
                && handleMapper(ep, type, m, MessageBodyWriter.class, false)) {
                // This writer matches Media Type and Class
                if (checkAll) {
                    allCandidates.add(ep);
                } else if (providerCache != null && providerCache.getWriters(type, mediaType).isEmpty()) {
                    providerCache.putWriters(type, mediaType, Collections.singletonList(ep));
                }
                if (selectedWriter == null
                    && isWriteable(ep, type, genericType, annotations, mediaType, m)) {
                    // This writer is a selected candidate
                    selectedWriter = (MessageBodyWriter)ep.getProvider();
                    if (!checkAll) {
                        return selectedWriter;
                    }
                }

            }
        }
        if (checkAll) {
            providerCache.putWriters(type, mediaType, allCandidates);
        }
        return selectedWriter;

    }

    protected void setBusProviders() {
        List extensions = new LinkedList<>();
        addBusExtension(extensions,
                        MessageBodyReader.class,
                        MessageBodyWriter.class,
                        ExceptionMapper.class);
        if (!extensions.isEmpty()) {
            setProviders(true, true, extensions.toArray());
        }
    }


    private void addBusExtension(List extensions, Class... extClasses) {
        for (Class extClass : extClasses) {
            Object ext = bus.getProperty(extClass.getName());
            if (extClass.isInstance(ext)) {
                extensions.add(ext);
            }
        }
        Object allProp = bus.getProperty(BUS_PROVIDERS_ALL);
        if (allProp instanceof List) {
            @SuppressWarnings("unchecked")
            List all = (List)allProp;
            extensions.addAll(all);
        }
    }

    protected abstract void setProviders(boolean custom, boolean busGlobal, Object... providers);

    @SuppressWarnings("unchecked")
    protected void setCommonProviders(List> theProviders, RuntimeType type) {
        List> readInts =
            new LinkedList<>();
        List> writeInts =
            new LinkedList<>();
        for (ProviderInfo provider : theProviders) {
            Class providerCls = ClassHelper.getRealClass(bus, provider.getProvider());

            // Check if provider is constrained to runtime type
            if (!constrainedTo(providerCls, type)) {
                continue;
            }

            if (filterContractSupported(provider, providerCls, MessageBodyReader.class)) {
                addProviderToList(messageReaders, provider);
            }

            if (filterContractSupported(provider, providerCls, MessageBodyWriter.class)) {
                addProviderToList(messageWriters, provider);
            }

            if (filterContractSupported(provider, providerCls, ContextResolver.class)) {
                addProviderToList(contextResolvers, provider);
            }

            if (ContextProvider.class.isAssignableFrom(providerCls)) {
                addProviderToList(contextProviders, provider);
            }

            if (filterContractSupported(provider, providerCls, ReaderInterceptor.class)) {
                readInts.add((ProviderInfo)provider);
            }

            if (filterContractSupported(provider, providerCls, WriterInterceptor.class)) {
                writeInts.add((ProviderInfo)provider);
            }

            if (filterContractSupported(provider, providerCls, ParamConverterProvider.class)) {
                paramConverters.add((ProviderInfo)provider);
            }
        }
        sortReaders();
        sortWriters();
        sortContextResolvers();
        sortParamConverterProviders();

        mapInterceptorFilters(readerInterceptors, readInts, ReaderInterceptor.class, true);
        mapInterceptorFilters(writerInterceptors, writeInts, WriterInterceptor.class, true);

        injectContextProxies(messageReaders, messageWriters, contextResolvers, paramConverters,
            readerInterceptors.values(), writerInterceptors.values());
        checkParamConverterContexts();
    }

    private void checkParamConverterContexts() {
        for (ProviderInfo pi : paramConverters) {
            if (pi.contextsAvailable()) {
                paramConverterContextsAvailable = true;
            }
        }

    }
    public boolean isParamConverterContextsAvailable() {
        return paramConverterContextsAvailable;
    }



    protected void injectContextValues(ProviderInfo pi, Message m) {
        if (m != null) {
            InjectionUtils.injectContexts(pi.getProvider(), pi, m);
        }
    }

    protected void addProviderToList(List list, ProviderInfo provider) {
        List> list2 = CastUtils.cast(list);
        for (ProviderInfo pi : list2) {
            if (pi.getProvider() == provider.getProvider()) {
                return;
            }
        }
        list2.add(provider);
    }

    protected void injectContextProxies(Collection ... providerLists) {
        for (Collection list : providerLists) {
            Collection> l2 = CastUtils.cast(list);
            for (ProviderInfo pi : l2) {
                injectContextProxiesIntoProvider(pi);
            }
        }
    }

    protected void injectContextProxiesIntoProvider(ProviderInfo pi) {
        injectContextProxiesIntoProvider(pi, null);
    }

    void injectContextProxiesIntoProvider(ProviderInfo pi, Application app) {
        if (pi.contextsAvailable()) {
            InjectionUtils.injectContextProxiesAndApplication(pi, pi.getProvider(), app, this);
            injectedProviders.add(pi);
        }
    }

    /*
     * sorts the available providers according to the media types they declare
     * support for. Sorting of media types follows the general rule: x/y < * x < *,
     * i.e. a provider that explicitly lists a media types is sorted before a
     * provider that lists *. Quality parameter values are also used such that
     * x/y;q=1.0 < x/y;q=0.7.
     */
    private void sortReaders() {
        if (!customComparatorAvailable(MessageBodyReader.class)) {
            messageReaders.sort(new MessageBodyReaderComparator());
        } else {
            doCustomSort(messageReaders);
        }
    }
    private  void sortWriters() {
        if (!customComparatorAvailable(MessageBodyWriter.class)) {
            messageWriters.sort(new MessageBodyWriterComparator());
        } else {
            doCustomSort(messageWriters);
        }
    }
    
    private  void sortParamConverterProviders() {
        if (!customComparatorAvailable(ParamConverterProvider.class)) {
            paramConverters.sort(new ParamConverterProviderComparator(bus));
        } else {
            doCustomSort(paramConverters);
        }
    }

    private boolean customComparatorAvailable(Class providerClass) {
        if (providerComparator != null) {
            Type type = ((ParameterizedType)providerComparator.getClass()
                .getGenericInterfaces()[0]).getActualTypeArguments()[0];
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType)type;
                if (pt.getRawType() == ProviderInfo.class) {
                    Type type2 = pt.getActualTypeArguments()[0];
                    if (type2 == providerClass
                        || type2 instanceof WildcardType
                        || type2 instanceof ParameterizedType
                           && ((ParameterizedType)type2).getRawType() == providerClass) {
                        return true;
                    }
                }
            } else if (type == Object.class) {
                return true;
            }
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    private  void doCustomSort(List listOfProviders) {
        Comparator theProviderComparator = providerComparator;
        Type type = ((ParameterizedType)providerComparator.getClass()
            .getGenericInterfaces()[0]).getActualTypeArguments()[0];
        if (type == Object.class) {
            theProviderComparator =
                new ProviderInfoClassComparator((Comparator)theProviderComparator);
        }
        List theProviders = (List)listOfProviders;
        Comparator theComparator = (Comparator)theProviderComparator;
        theProviders.sort(theComparator);
    }

    private void sortContextResolvers() {
        contextResolvers.sort(new ContextResolverComparator());
    }





    private  boolean matchesReaderMediaTypes(ProviderInfo> pi,
                                                MediaType mediaType) {
        MessageBodyReader ep = pi.getProvider();
        List supportedMediaTypes = JAXRSUtils.getProviderConsumeTypes(ep);

        return JAXRSUtils.doMimeTypesIntersect(Collections.singletonList(mediaType), supportedMediaTypes);
    }

    private boolean isReadable(ProviderInfo> pi,
                               Class type,
                               Type genericType,
                               Annotation[] annotations,
                               MediaType mediaType,
                               Message m) {
        MessageBodyReader ep = pi.getProvider();
        if (m.get(ACTIVE_JAXRS_PROVIDER_KEY) != ep) {
            injectContextValues(pi, m);
        }
        return ep.isReadable(type, genericType, annotations, mediaType);
    }

    private  boolean matchesWriterMediaTypes(ProviderInfo> pi,
                                                MediaType mediaType) {
        MessageBodyWriter ep = pi.getProvider();
        List supportedMediaTypes = JAXRSUtils.getProviderProduceTypes(ep);

        return JAXRSUtils.doMimeTypesIntersect(Collections.singletonList(mediaType), supportedMediaTypes);
    }

    private boolean isWriteable(ProviderInfo> pi,
                               Class type,
                               Type genericType,
                               Annotation[] annotations,
                               MediaType mediaType,
                               Message m) {
        MessageBodyWriter ep = pi.getProvider();
        if (m.get(ACTIVE_JAXRS_PROVIDER_KEY) != ep) {
            injectContextValues(pi, m);
        }
        return ep.isWriteable(type, genericType, annotations, mediaType);
    }

    List>> getMessageReaders() {
        return Collections.unmodifiableList(messageReaders);
    }

    List>> getMessageWriters() {
        return Collections.unmodifiableList(messageWriters);
    }

    public List>> getContextResolvers() {
        return Collections.unmodifiableList(contextResolvers);
    }


    public void registerUserProvider(Object provider) {
        setUserProviders(Collections.singletonList(provider));
    }
    /**
     * Use for injection of entityProviders
     * @param userProviders the userProviders to set
     */
    public void setUserProviders(List userProviders) {
        setProviders(true, false, userProviders.toArray());
    }

    private static final class MessageBodyReaderComparator
        implements Comparator>> {

        public int compare(ProviderInfo> p1,
                           ProviderInfo> p2) {
            MessageBodyReader e1 = p1.getProvider();
            MessageBodyReader e2 = p2.getProvider();

            List types1 = JAXRSUtils.getProviderConsumeTypes(e1);
            types1 = JAXRSUtils.sortMediaTypes(types1, null);
            List types2 = JAXRSUtils.getProviderConsumeTypes(e2);
            types2 = JAXRSUtils.sortMediaTypes(types2, null);

            int result = JAXRSUtils.compareSortedMediaTypes(types1, types2, null);
            if (result != 0) {
                return result;
            }
            result = compareClasses(e1, e2);
            if (result != 0) {
                return result;
            }
            result = compareCustomStatus(p1, p2);
            if (result != 0) {
                return result;
            }
            return comparePriorityStatus(p1.getProvider().getClass(), p2.getProvider().getClass());
        }
    }

    private static final class MessageBodyWriterComparator
        implements Comparator>> {

        public int compare(ProviderInfo> p1,
                           ProviderInfo> p2) {
            MessageBodyWriter e1 = p1.getProvider();
            MessageBodyWriter e2 = p2.getProvider();

            int result = compareClasses(e1, e2);
            if (result != 0) {
                return result;
            }
            List types1 =
                JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(e1), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
            List types2 =
                JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(e2), JAXRSUtils.MEDIA_TYPE_QS_PARAM);

            result = JAXRSUtils.compareSortedMediaTypes(types1, types2, JAXRSUtils.MEDIA_TYPE_QS_PARAM);
            if (result != 0) {
                return result;
            }
            
            result = compareCustomStatus(p1, p2);
            if (result != 0) {
                return result;
            }

            return comparePriorityStatus(p1.getProvider().getClass(), p2.getProvider().getClass());
        }
    }

    private static final class ParamConverterProviderComparator
            implements Comparator> {
        private final Bus bus;
        
        ParamConverterProviderComparator(Bus bus) {
            this.bus = bus;
        }
        
        @Override
        public int compare(ProviderInfo p1, ProviderInfo p2) {
            final int result = compareCustomStatus(p1, p2);
            if (result != 0) {
                return result;
            }

            final Class cl1 = ClassHelper.getRealClass(bus, p1.getProvider());
            final Class cl2 = ClassHelper.getRealClass(bus, p2.getProvider());

            return comparePriorityStatus(cl1, cl2);
        }
    }

    protected static int compareCustomStatus(ProviderInfo p1, ProviderInfo p2) {
        boolean custom1 = p1.isCustom();
        int result = Boolean.compare(p2.isCustom(), custom1);
        if (result == 0 && custom1) {
            result = Boolean.compare(p1.isBusGlobal(), p2.isBusGlobal());
        }
        return result;
    }

    static int comparePriorityStatus(Class cl1, Class cl2) {
        return Integer.compare(AnnotationUtils.getBindingPriority(cl1), AnnotationUtils.getBindingPriority(cl2));
    }

    private static final class ContextResolverComparator
        implements Comparator>> {

        public int compare(ProviderInfo> p1,
                           ProviderInfo> p2) {
            ContextResolver e1 = p1.getProvider();
            ContextResolver e2 = p2.getProvider();

            List types1 =
                JAXRSUtils.sortMediaTypes(JAXRSUtils.getProduceTypes(
                     e1.getClass().getAnnotation(Produces.class)), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
            List types2 =
                JAXRSUtils.sortMediaTypes(JAXRSUtils.getProduceTypes(
                     e2.getClass().getAnnotation(Produces.class)), JAXRSUtils.MEDIA_TYPE_QS_PARAM);

            return JAXRSUtils.compareSortedMediaTypes(types1, types2, JAXRSUtils.MEDIA_TYPE_QS_PARAM);
        }
    }

    public void clearThreadLocalProxies() {
        clearProxies(injectedProviders);
    }

    void clearProxies(Collection ...lists) {
        for (Collection list : lists) {
            Collection> l2 = CastUtils.cast(list);
            for (ProviderInfo pi : l2) {
                pi.clearThreadLocalProxies();
            }
        }
    }

    public void clearProviders() {
        messageReaders.clear();
        messageWriters.clear();
        contextResolvers.clear();
        contextProviders.clear();
        readerInterceptors.clear();
        writerInterceptors.clear();
        paramConverters.clear();
    }

    public void setBus(Bus bus) {
        if (bus == null) {
            return;
        }
        for (ProviderInfo> r : messageReaders) {
            injectProviderProperty(r.getProvider(), "setBus", Bus.class, bus);
        }
    }

    private boolean injectProviderProperty(Object provider, String mName, Class pClass,
                                        Object pValue) {
        try {
            Method m = provider.getClass().getMethod(mName, new Class[]{pClass});
            m.invoke(provider, new Object[]{pValue});
            return true;
        } catch (Exception ex) {
            // ignore
        }
        return false;
    }

    public void setSchemaLocations(List schemas) {
        for (ProviderInfo> r : messageReaders) {
            injectProviderProperty(r.getProvider(), "setSchemaLocations", List.class, schemas);
        }
    }

    protected static  List> getBoundFilters(Map> boundFilters,
                                                                          Set names) {
        if (boundFilters.isEmpty()) {
            return Collections.emptyList();
        }
        names = names == null ? Collections.emptySet() : names;

        MultivaluedMap, String> map =
            new MetadataMap<>();
        for (Map.Entry> entry : boundFilters.entrySet()) {
            String entryName = entry.getKey().getName();
            ProviderInfo provider = entry.getValue();
            if (entryName.equals(DEFAULT_FILTER_NAME_BINDING)) {
                map.put(provider, Collections.emptyList());
            } else {
                if (provider instanceof FilterProviderInfo) {
                    FilterProviderInfo fpi = (FilterProviderInfo)provider;
                    if (fpi.isDynamic() && !names.containsAll(fpi.getNameBindings())) {
                        continue;
                    }
                }
                map.add(provider, entryName);
            }
        }
        List> list = new LinkedList<>();
        for (Map.Entry, List> entry : map.entrySet()) {
            List values = entry.getValue();
            if (names.containsAll(values)) {
                ProviderInfo provider = entry.getKey();
                list.add(provider);
            }
        }
        return list;
    }

    public void initProviders(List cris) {
        Set set = getReadersWriters();
        for (Object o : set) {
            Object provider = ((ProviderInfo)o).getProvider();
            if (provider instanceof AbstractConfigurableProvider) {
                ((AbstractConfigurableProvider)provider).init(cris);
            }
        }
    }

    Set getReadersWriters() {
        Set set = new HashSet<>();
        set.addAll(messageReaders);
        set.addAll(messageWriters);
        return set;
    }

    public static class ClassComparator implements
        Comparator {
        private Class expectedCls;
        public ClassComparator() {
        }
        public ClassComparator(Class expectedCls) {
            this.expectedCls = expectedCls;
        }

        public int compare(Object em1, Object em2) {
            return compareClasses(expectedCls, em1, em2);
        }
    }

    public static class ProviderInfoClassComparator implements Comparator> {
        private Comparator comp;
        private boolean defaultComp;
        public ProviderInfoClassComparator(Class expectedCls) {
            this.comp = new ClassComparator(expectedCls);
            this.defaultComp = true;
        }
        public ProviderInfoClassComparator(Comparator comp) {
            this.comp = comp;
        }
        public int compare(ProviderInfo p1, ProviderInfo p2) {
            int result = comp.compare(p1.getProvider(), p2.getProvider());
            if (result == 0 && defaultComp) {
                result = compareCustomStatus(p1, p2);
            }
            return result;
        }
    }

    static class PriorityBasedClassComparator extends ClassComparator {
        PriorityBasedClassComparator() {
            super();
        }

        PriorityBasedClassComparator(Class expectedCls) {
            super(expectedCls);
        }

        @Override
        public int compare(Object em1, Object em2) {
            int result = super.compare(em1, em2);
            if (result == 0) {
                result = comparePriorityStatus(em1.getClass(), em2.getClass());
            }
            return result;
        }
    }

    public static ProviderFactory getInstance(Message m) {
        Endpoint e = m.getExchange().getEndpoint();

        Message outM = m.getExchange().getOutMessage();
        boolean isClient = outM != null && MessageUtils.isRequestor(outM);
        String name = isClient ? CLIENT_FACTORY_NAME : SERVER_FACTORY_NAME;

        return (ProviderFactory)e.get(name);
    }
    protected static int compareClasses(Object o1, Object o2) {
        return compareClasses(null, o1, o2);
    }
    protected static int compareClasses(Class expectedCls, Object o1, Object o2) {
        Class cl1 = ClassHelper.getRealClass(o1);
        Class cl2 = ClassHelper.getRealClass(o2);
        Type[] types1 = getGenericInterfaces(cl1, expectedCls);
        Type[] types2 = getGenericInterfaces(cl2, expectedCls);
        if (types1.length == 0 && types2.length == 0) {
            return 0;
        } else if (types1.length == 0 && types2.length > 0) {
            return 1;
        } else if (types1.length > 0 && types2.length == 0) {
            return -1;
        }

        Class realClass1 = InjectionUtils.getActualType(types1[0]);
        Class realClass2 = InjectionUtils.getActualType(types2[0]);
        if (realClass1 == realClass2) {
            return 0;
        }
        if (realClass1.isAssignableFrom(realClass2)) {
            // subclass should go first
            return 1;
        } else if (realClass2.isAssignableFrom(realClass1)) {
            // superclass should go last
            return -1;
        }
        
        // there is no relation between the types returned by the providers
        return 0;
    }

    private static Type[] getGenericInterfaces(Class cls, Class expectedClass) {
        return getGenericInterfaces(cls, expectedClass, Object.class);
    }
    private static Type[] getGenericInterfaces(Class cls, Class expectedClass,
                                               Class commonBaseCls) {
        if (Object.class == cls) {
            return new Type[]{};
        }
        if (expectedClass != null) {
            Type genericSuperType = cls.getGenericSuperclass();
            if (genericSuperType instanceof ParameterizedType) {
                Class actualType = InjectionUtils.getActualType(genericSuperType);
                if (actualType != null && actualType.isAssignableFrom(expectedClass)) {
                    return new Type[]{genericSuperType};
                } else if (commonBaseCls != null && commonBaseCls != Object.class
                           && commonBaseCls.isAssignableFrom(expectedClass)
                           && commonBaseCls.isAssignableFrom(actualType)
                           || expectedClass.isAssignableFrom(actualType)) {
                    return new Type[]{};
                }
            }
        }
        Type[] types = cls.getGenericInterfaces();
        if (types.length > 0) {
            return types;
        }
        return getGenericInterfaces(cls.getSuperclass(), expectedClass, commonBaseCls);
    }

    protected static class AbstractPriorityComparator {

        private boolean ascending;

        protected AbstractPriorityComparator(boolean ascending) {
            this.ascending = ascending;
        }

        protected int compare(Integer b1Value, Integer b2Value) {
            int result = b1Value.compareTo(b2Value);
            return ascending ? result : result * -1;
        }

    }

    protected static class BindingPriorityComparator extends AbstractPriorityComparator
        implements Comparator> {
        private Class providerCls;

        public BindingPriorityComparator(Class providerCls, boolean ascending) {
            super(ascending);
            this.providerCls = providerCls;
        }

        public int compare(ProviderInfo p1, ProviderInfo p2) {
            return compare(getFilterPriority(p1, providerCls),
                           getFilterPriority(p2, providerCls));
        }

    }

    static class ContextResolverProxy implements ContextResolver {
        private List> candidates;
        ContextResolverProxy(List> candidates) {
            this.candidates = candidates;
        }
        public T getContext(Class cls) {
            for (ContextResolver resolver : candidates) {
                T context = resolver.getContext(cls);
                if (context != null) {
                    return context;
                }
            }
            return null;
        }

        public List> getResolvers() {
            return candidates;
        }
    }

    public static ProviderInfo createProviderFromConstructor(Constructor c,
                                                                 Map, Object> values,
                                                                 Bus theBus,
                                                                 boolean checkContexts,
                                                                 boolean custom) {


        Map, Map, ThreadLocalProxy>> proxiesMap =
            CastUtils.cast((Map)theBus.getProperty(AbstractResourceInfo.CONSTRUCTOR_PROXY_MAP));
        Map, ThreadLocalProxy> existingProxies = null;
        if (proxiesMap != null) {
            existingProxies = proxiesMap.get(c.getDeclaringClass());
        }
        Class[] paramTypes = c.getParameterTypes();
        Object[] cArgs = ResourceUtils.createConstructorArguments(c, null, false, values);
        if (existingProxies != null && existingProxies.size() <= paramTypes.length) {
            for (int i = 0; i < paramTypes.length; i++) {
                if (cArgs[i] instanceof ThreadLocalProxy) {
                    cArgs[i] = existingProxies.get(paramTypes[i]);
                }
            }
        }
        final Object instance;
        try {
            instance = c.newInstance(cArgs);
        } catch (Throwable ex) {
            throw new RuntimeException("Resource or provider class " + c.getDeclaringClass().getName()
                                       + " can not be instantiated", ex);
        }
        Map, ThreadLocalProxy> proxies =
            new LinkedHashMap<>();
        for (int i = 0; i < paramTypes.length; i++) {
            if (cArgs[i] instanceof ThreadLocalProxy) {
                @SuppressWarnings("unchecked")
                ThreadLocalProxy proxy = (ThreadLocalProxy)cArgs[i];
                proxies.put(paramTypes[i], proxy);
            }
        }
        boolean isApplication = Application.class.isAssignableFrom(c.getDeclaringClass());
        if (isApplication) {
            return new ApplicationInfo((Application)instance, proxies, theBus);
        }
        return new ProviderInfo(instance, proxies, theBus, checkContexts, custom);
    }

    private Message getResponseMessage(Message message) {
        Message responseMessage = message.getExchange().getInMessage();
        if (responseMessage == null) {
            responseMessage = message.getExchange().getInFaultMessage();
        }

        return responseMessage;
    }

    protected static class NameKey {
        private String name;
        private Integer priority;
        private Class providerCls;
        private ProviderInfo providerInfo;

        public NameKey(String name,
                       int priority,
                       Class providerCls) {

            this(name, priority, providerCls, null);
        }

        public NameKey(String name,
                       int priority,
                       Class providerCls,
                       ProviderInfo provider) {

            this.name = name;
            this.priority = priority;
            this.providerCls = providerCls;
            this.providerInfo = provider;
        }

        public String getName() {
            return name;
        }

        public Integer getPriority() {
            return priority;
        }

        public ProviderInfo getProviderInfo() {
            return providerInfo;
        }

        public boolean equals(Object o) {
            if (!(o instanceof NameKey)) {
                return false;
            }
            NameKey other = (NameKey)o;
            return name.equals(other.name) && priority.equals(other.priority)
                && providerCls == other.providerCls;
        }

        public int hashCode() {
            return super.hashCode();
        }

        public String toString() {
            return name + ":" + priority;
        }
    }

    protected static  void mapInterceptorFilters(Map> map,
                                                    List> filters,
                                                    Class providerCls,
                                                    boolean ascending) {

        for (ProviderInfo p : filters) {
            Set names = getFilterNameBindings(p);

            int priority = getFilterPriority(p, providerCls);

            for (String name : names) {
                map.put(new NameKey(name, priority, p.getClass(), p), p);
            }
        }

    }

    protected static Set getFilterNameBindings(ProviderInfo p) {
        if (p instanceof FilterProviderInfo) {
            return ((FilterProviderInfo)p).getNameBindings();
        } else {
            return getFilterNameBindings(p.getBus(), p.getProvider());
        }

    }
    protected static Set getFilterNameBindings(Bus bus, Object provider) {
        Set names = AnnotationUtils.getInstanceNameBindings(bus, provider);
        if (names.isEmpty()) {
            names = Collections.singleton(DEFAULT_FILTER_NAME_BINDING);
        }
        return names;
    }

    protected static int getFilterPriority(ProviderInfo p, Class providerCls) {
        return p instanceof FilterProviderInfo ? ((FilterProviderInfo)p).getPriority(providerCls)
            : AnnotationUtils.getBindingPriority(p.getProvider().getClass());
    }

    protected static class NameKeyComparator extends AbstractPriorityComparator
        implements Comparator {

        private final Comparator> comparator;

        public NameKeyComparator(boolean ascending) {
            this(null, ascending);
        }

        public NameKeyComparator(
            Comparator> comparator, boolean ascending) {

            super(ascending);
            this.comparator = comparator;
        }

        @Override
        public int compare(NameKey key1, NameKey key2) {
            int result = compare(key1.getPriority(), key2.getPriority());
            if (result != 0) {
                return result;
            }

            if (comparator != null) {
                result = comparator.compare(
                    key1.getProviderInfo(), key2.getProviderInfo());

                if (result != 0) {
                    return result;
                }
            }

            return compare(key1.hashCode(), key2.hashCode());
        }
    }

    protected static class NameKeyMap extends TreeMap {
        private static final long serialVersionUID = -4352258671270502204L;

        public NameKeyMap(
            Comparator> comparator, boolean ascending) {

            super(new NameKeyComparator(comparator, ascending));
        }

        public NameKeyMap(boolean ascending) {
            super(new NameKeyComparator(ascending));
        }
    }

    protected static boolean filterContractSupported(ProviderInfo provider,
                                                     Class providerCls,
                                                     Class contract) {
        boolean result = false;
        if (contract.isAssignableFrom(providerCls)) {
            Set> actualContracts = null;
            if (provider instanceof FilterProviderInfo) {
                actualContracts = ((FilterProviderInfo)provider).getSupportedContracts();
            }
            if (actualContracts != null) {
                result = actualContracts.contains(contract);
            } else {
                result = true;
            }
        }
        return result;
    }

    protected List> prepareProviders(boolean custom,
                                                                    boolean busGlobal,
                                                                    Object[] providers,
                                                                    ProviderInfo application) {
        List> theProviders =
            new ArrayList<>(providers.length);
        for (Object o : providers) {
            if (o == null) {
                continue;
            }
            Object provider = o;
            if (provider.getClass() == Class.class) {
                provider = ResourceUtils.createProviderInstance((Class)provider);
            }
            if (provider instanceof Constructor) {
                Map, Object> values = CastUtils.cast(application == null ? null
                    : Collections.singletonMap(Application.class, application.getProvider()));
                theProviders.add(
                    createProviderFromConstructor((Constructor)provider, values, getBus(), true, custom));
            } else if (provider instanceof ProviderInfo) {
                theProviders.add((ProviderInfo)provider);
            } else {
                ProviderInfo theProvider = new ProviderInfo<>(provider, getBus(), custom);
                theProvider.setBusGlobal(busGlobal);
                theProviders.add(theProvider);
            }
        }
        return theProviders;
    }

    public MessageBodyWriter getDefaultJaxbWriter() {
        for (ProviderInfo> pi : this.messageWriters) {
            Class cls = pi.getProvider().getClass();
            if (cls.getName().equals(JAXB_PROVIDER_NAME)) {
                return pi.getProvider();
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public void setProviderComparator(Comparator providerComparator) {
        this.providerComparator = providerComparator;

        sortReaders();
        sortWriters();
        sortParamConverterProviders();
        
        NameKeyMap> sortedReaderInterceptors =
            new NameKeyMap<>(
                (Comparator>) providerComparator, true);
        sortedReaderInterceptors.putAll(readerInterceptors);
        NameKeyMap> sortedWriterInterceptors =
            new NameKeyMap<>(
                (Comparator>) providerComparator, true);
        sortedWriterInterceptors.putAll(writerInterceptors);

        readerInterceptors = sortedReaderInterceptors;
        writerInterceptors = sortedWriterInterceptors;
    }

    /**
     * Checks the presence of {@link ConstrainedTo} annotation and, if present, applicability to 
     * the runtime type.
     * @param providerCls provider class
     * @param type runtime type
     * @return "true" if provider could be used with runtime type, "false" otherwise
     */
    protected static boolean constrainedTo(Class providerCls, RuntimeType type) {
        final ConstrainedTo constrained = AnnotationUtils.getClassAnnotation(providerCls, ConstrainedTo.class);
        return constrained == null || constrained.value() == type;
    }
}