/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2017 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.server; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.ws.rs.NameBinding; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.container.DynamicFeature; import javax.ws.rs.container.PreMatching; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.ReaderInterceptor; import javax.ws.rs.ext.WriterInterceptor; import org.glassfish.jersey.internal.BootstrapBag; import org.glassfish.jersey.internal.BootstrapConfigurator; import org.glassfish.jersey.internal.inject.InjectionManager; import org.glassfish.jersey.internal.inject.Providers; import org.glassfish.jersey.internal.util.ReflectionHelper; import org.glassfish.jersey.model.ContractProvider; import org.glassfish.jersey.model.internal.ComponentBag; import org.glassfish.jersey.model.internal.RankedProvider; import org.glassfish.jersey.server.internal.LocalizationMessages; import org.glassfish.jersey.server.internal.ProcessingProviders; /** * Configurator which initializes and register {@link ProcessingProviders} instance into {@link BootstrapBag}. * Instances of these interfaces are processed, configured and provided using this configurator: * <ul> * <li>{@link ContainerRequestFilter}</li> * <li>{@link ContainerResponseFilter}</li> * <li>{@link ReaderInterceptor}</li> * <li>{@link WriterInterceptor}</li> * <li>{@link DynamicFeature}</li> * </ul> * * @author Petr Bouda (petr.bouda at oracle.com) */ class ProcessingProvidersConfigurator implements BootstrapConfigurator { private static final Logger LOGGER = Logger.getLogger(ProcessingProvidersConfigurator.class.getName()); @Override public void init(InjectionManager injectionManager, BootstrapBag bootstrapBag) { } @Override public void postInit(InjectionManager injectionManager, BootstrapBag bootstrapBag) { ServerBootstrapBag serverBag = (ServerBootstrapBag) bootstrapBag; ComponentBag componentBag = serverBag.getRuntimeConfig().getComponentBag(); // scan for NameBinding annotations attached to the application class Collection<Class<? extends Annotation>> applicationNameBindings = ReflectionHelper.getAnnotationTypes( ResourceConfig.unwrapApplication(serverBag.getRuntimeConfig()).getClass(), NameBinding.class); MultivaluedMap<RankedProvider<ContainerResponseFilter>, Class<? extends Annotation>> nameBoundRespFiltersInverse = new MultivaluedHashMap<>(); MultivaluedMap<RankedProvider<ContainerRequestFilter>, Class<? extends Annotation>> nameBoundReqFiltersInverse = new MultivaluedHashMap<>(); MultivaluedMap<RankedProvider<ReaderInterceptor>, Class<? extends Annotation>> nameBoundReaderInterceptorsInverse = new MultivaluedHashMap<>(); MultivaluedMap<RankedProvider<WriterInterceptor>, Class<? extends Annotation>> nameBoundWriterInterceptorsInverse = new MultivaluedHashMap<>(); Iterable<RankedProvider<ContainerResponseFilter>> responseFilters = Providers.getAllRankedProviders(injectionManager, ContainerResponseFilter.class); MultivaluedMap<Class<? extends Annotation>, RankedProvider<ContainerResponseFilter>> nameBoundResponseFilters = filterNameBound(responseFilters, null, componentBag, applicationNameBindings, nameBoundRespFiltersInverse); Iterable<RankedProvider<ContainerRequestFilter>> requestFilters = Providers.getAllRankedProviders(injectionManager, ContainerRequestFilter.class); List<RankedProvider<ContainerRequestFilter>> preMatchFilters = new ArrayList<>(); MultivaluedMap<Class<? extends Annotation>, RankedProvider<ContainerRequestFilter>> nameBoundReqFilters = filterNameBound(requestFilters, preMatchFilters, componentBag, applicationNameBindings, nameBoundReqFiltersInverse); Iterable<RankedProvider<ReaderInterceptor>> readerInterceptors = Providers.getAllRankedProviders(injectionManager, ReaderInterceptor.class); MultivaluedMap<Class<? extends Annotation>, RankedProvider<ReaderInterceptor>> nameBoundReaderInterceptors = filterNameBound(readerInterceptors, null, componentBag, applicationNameBindings, nameBoundReaderInterceptorsInverse); Iterable<RankedProvider<WriterInterceptor>> writerInterceptors = Providers.getAllRankedProviders(injectionManager, WriterInterceptor.class); MultivaluedMap<Class<? extends Annotation>, RankedProvider<WriterInterceptor>> nameBoundWriterInterceptors = filterNameBound(writerInterceptors, null, componentBag, applicationNameBindings, nameBoundWriterInterceptorsInverse); Iterable<DynamicFeature> dynamicFeatures = Providers.getAllProviders(injectionManager, DynamicFeature.class); ProcessingProviders processingProviders = new ProcessingProviders(nameBoundReqFilters, nameBoundReqFiltersInverse, nameBoundResponseFilters, nameBoundRespFiltersInverse, nameBoundReaderInterceptors, nameBoundReaderInterceptorsInverse, nameBoundWriterInterceptors, nameBoundWriterInterceptorsInverse, requestFilters, preMatchFilters, responseFilters, readerInterceptors, writerInterceptors, dynamicFeatures); serverBag.setProcessingProviders(processingProviders); } /** * Takes collection of all filters/interceptors (either request/reader or response/writer) * and separates out all name-bound filters/interceptors, returns them as a separate MultivaluedMap, * mapping the name-bound annotation to the list of name-bound filters/interceptors. The same key values * are also added into the inverse map passed in {@code inverseNameBoundMap}. * <p/> * Note, the name-bound filters/interceptors are removed from the original filters/interceptors collection. * If non-null collection is passed in the postMatching parameter (applicable for filters only), * this method also removes all the global * postMatching filters from the original collection and adds them to the collection passed in the postMatching * parameter. * * @param all Collection of all filters to be processed. * @param preMatchingFilters Collection into which pre-matching filters should be added. * @param componentBag Component bag * @param applicationNameBindings Collection of name binding annotations attached to the JAX-RS application. * @param inverseNameBoundMap Inverse name bound map into which the name bound providers should be inserted. The keys * are providers (filters, interceptor) * @return {@link MultivaluedMap} of all name-bound filters. */ private static <T> MultivaluedMap<Class<? extends Annotation>, RankedProvider<T>> filterNameBound( final Iterable<RankedProvider<T>> all, final Collection<RankedProvider<ContainerRequestFilter>> preMatchingFilters, final ComponentBag componentBag, final Collection<Class<? extends Annotation>> applicationNameBindings, final MultivaluedMap<RankedProvider<T>, Class<? extends Annotation>> inverseNameBoundMap) { final MultivaluedMap<Class<? extends Annotation>, RankedProvider<T>> result = new MultivaluedHashMap<>(); for (final Iterator<RankedProvider<T>> it = all.iterator(); it.hasNext(); ) { final RankedProvider<T> provider = it.next(); Class<?> providerClass = provider.getProvider().getClass(); final Set<Type> contractTypes = provider.getContractTypes(); if (contractTypes != null && !contractTypes.contains(providerClass)) { providerClass = ReflectionHelper.theMostSpecificTypeOf(contractTypes); } ContractProvider model = componentBag.getModel(providerClass); if (model == null) { // the provider was (most likely) bound in HK2 externally model = ComponentBag.modelFor(providerClass); } final boolean preMatching = providerClass.getAnnotation(PreMatching.class) != null; if (preMatching && preMatchingFilters != null) { it.remove(); preMatchingFilters.add(new RankedProvider<>((ContainerRequestFilter) provider.getProvider(), model.getPriority(ContainerRequestFilter.class))); } boolean nameBound = model.isNameBound(); if (nameBound && !applicationNameBindings.isEmpty() && applicationNameBindings.containsAll(model.getNameBindings())) { // override the name-bound flag nameBound = false; } if (nameBound) { // not application-bound if (!preMatching) { it.remove(); for (final Class<? extends Annotation> binding : model.getNameBindings()) { result.add(binding, provider); inverseNameBoundMap.add(provider, binding); } } else { LOGGER.warning(LocalizationMessages.PREMATCHING_ALSO_NAME_BOUND(providerClass)); } } } return result; } }