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

org.glassfish.jersey.server.ResourceConfig Maven / Gradle / Ivy

There is a newer version: 4.0.0-M1
Show newest version
/*
 * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.server;

import java.io.IOException;
import java.io.InputStream;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Configurable;
import jakarta.ws.rs.core.Configuration;
import jakarta.ws.rs.core.Feature;

import org.glassfish.jersey.internal.Errors;
import org.glassfish.jersey.internal.config.ExternalPropertiesConfigurationFactory;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.spi.AutoDiscoverable;
import org.glassfish.jersey.internal.util.Producer;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.Tokenizer;
import org.glassfish.jersey.model.ContractProvider;
import org.glassfish.jersey.model.internal.CommonConfig;
import org.glassfish.jersey.model.internal.ComponentBag;
import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener;
import org.glassfish.jersey.server.internal.scanning.FilesScanner;
import org.glassfish.jersey.server.internal.scanning.PackageNamesScanner;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.uri.UriComponent;


/**
 * The resource configuration for configuring a web application.
 *
 * @author Paul Sandoz
 * @author Martin Matula
 * @author Michal Gajdos
 * @author Marek Potociar
 */
public class ResourceConfig extends Application implements Configurable, ServerConfig {

    private static final Logger LOGGER = Logger.getLogger(ResourceConfig.class.getName());

    private transient Set> cachedClasses = null;
    private transient Set> cachedClassesView = null;
    private transient Set cachedSingletons = null;
    private transient Set cachedSingletonsView = null;

    private transient boolean resetFinders = false;

    private volatile State state;

    private static class State extends CommonConfig implements ServerConfig {

        private final Set resourceFinders;

        private final Set resources;
        private final Set resourcesView;
        private volatile String applicationName;

        private volatile ClassLoader classLoader = null;

        public State() {
            super(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
            this.classLoader = AccessController.doPrivileged(ReflectionHelper.getContextClassLoaderPA());

            this.resourceFinders = new HashSet<>();

            this.resources = new HashSet<>();
            this.resourcesView = Collections.unmodifiableSet(this.resources);
        }

        public State(final State original) {
            super(original);
            this.classLoader = original.classLoader;
            this.applicationName = original.applicationName;

            this.resources = new HashSet<>(original.resources);
            this.resourcesView = Collections.unmodifiableSet(this.resources);

            this.resourceFinders = new HashSet<>(original.resourceFinders);
        }

        public void setClassLoader(final ClassLoader classLoader) {
            this.classLoader = classLoader;
        }

        public void setApplicationName(final String applicationName) {
            this.applicationName = applicationName;
        }

        public void registerResources(final Set resources) {
            this.resources.addAll(resources);
        }

        public void registerFinder(final ResourceFinder resourceFinder) {
            this.resourceFinders.add(resourceFinder);
        }

        @Override
        protected Inflector getModelEnhancer(final Class componentClass) {
            return builder -> {
                if (builder.getScope() == null && builder.getContracts().isEmpty() && Resource.getPath(componentClass) != null) {
                    builder.scope(RequestScoped.class);
                }

                return builder.build();
            };
        }

        @Override
        public State loadFrom(final Configuration config) {
            super.loadFrom(config);
            this.resourceFinders.clear();
            this.resources.clear();

            State other = null;
            if (config instanceof ResourceConfig) {
                other = ((ResourceConfig) config).state;
            }
            if (config instanceof State) {
                other = (State) config;
            }

            if (other != null) {
                this.resourceFinders.addAll(other.resourceFinders);
                this.resources.addAll(other.resources);
            }

            return this;
        }

        @Override
        public final Set getResources() {
            return resourcesView;
        }

        @Override
        public ServerConfig getConfiguration() {
            return this;
        }

        /**
         * Get the registered resource finders.
         *
         * @return registered resource finders.
         */
        public Set getResourceFinders() {
            return resourceFinders;
        }

        /**
         * Get resource and provider class loader.
         *
         * @return class loader to be used when looking up the resource classes and providers.
         */
        public ClassLoader getClassLoader() {
            return classLoader;
        }

        private String getApplicationName() {
            return applicationName;
        }
    }

    private static final class ImmutableState extends State {

        private ImmutableState(final State original) {
            super(original);
        }

        @Override
        public void setClassLoader(final ClassLoader classLoader) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void registerResources(final Set resources) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void registerFinder(final ResourceFinder resourceFinder) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State addProperties(final Map properties) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State property(final String name, final Object value) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class componentClass) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class componentClass, final int bindingPriority) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class componentClass, final Class... contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Class componentClass, final Map, Integer> contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component, final int bindingPriority) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component, final Class... contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State register(final Object component, final Map, Integer> contracts) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public State setProperties(final Map properties) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void configureAutoDiscoverableProviders(final InjectionManager injectionManager,
                final Collection autoDiscoverables, final boolean forcedOnly) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public void configureMetaProviders(final InjectionManager injectionManager, final ManagedObjectsFinalizer finalizer) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }
    }

    /**
     * Returns a {@code ResourceConfig} instance for the supplied application.
     * 

* If the application is an instance of {@code ResourceConfig} the method returns defensive copy of the resource config. * Otherwise it creates a new {@code ResourceConfig} from the application. * * @param application Application to provide the {@code ResourceConfig} instance for. * @return ResourceConfig instance for the supplied application. */ public static ResourceConfig forApplication(final Application application) { return application instanceof ResourceConfig ? ((ResourceConfig) application) : new WrappingResourceConfig(application, null, null); } /** * Returns a {@code ResourceConfig} instance wrapping the application of the supplied class. * * @param applicationClass Class representing a JAX-RS application. * @return ResourceConfig wrapping the JAX-RS application defined by the supplied class. */ public static ResourceConfig forApplicationClass(final Class applicationClass) { return new WrappingResourceConfig(null, applicationClass, null); } /** * Returns a {@code ResourceConfig} instance wrapping the application of the supplied class. *

* This method provides an option of supplying the set of classes that should be returned from {@link #getClasses()} * method if the application defined by the supplied application class returns empty sets from * {@link jakarta.ws.rs.core.Application#getClasses()} * and {@link jakarta.ws.rs.core.Application#getSingletons()} methods. * * @param applicationClass Class representing a JAX-RS application. * @param defaultClasses Default set of classes that should be returned from {@link #getClasses()} if the underlying * application does not provide any classes and singletons. * @return ResourceConfig wrapping the JAX-RS application defined by the supplied class. */ public static ResourceConfig forApplicationClass(final Class applicationClass, final Set> defaultClasses) { return new WrappingResourceConfig(null, applicationClass, defaultClasses); } /** * Create a new resource configuration without any custom properties or * resource and provider classes. */ public ResourceConfig() { this.state = new State(); } /** * Create a new resource configuration initialized with a given set of * resource/provider classes. * * @param classes application-specific resource and/or provider classes. */ public ResourceConfig(final Set> classes) { this(); this.registerClasses(classes); } /** * Create a new resource configuration initialized with a given set of * resource/provider classes. * * @param classes application-specific resource and/or provider classes. */ public ResourceConfig(final Class... classes) { this(Arrays.stream(classes).collect(Collectors.toSet())); } /** * Create a defensive resource configuration copy initialized with a given {@code ResourceConfig}. * * @param original resource configuration to createAndInitialize a defensive copy from. */ public ResourceConfig(final ResourceConfig original) { this.state = new State(original.state); } /** * Add properties to {@code ResourceConfig}. *

* If any of the added properties exists already, old values of existing * properties will be replaced by new values. * * @param properties properties to add. * @return updated resource configuration instance. */ public final ResourceConfig addProperties(final Map properties) { state.addProperties(properties); return this; } /** * Set new configuration properties replacing all previously set properties. * * @param properties new set of configuration properties. The content of * the map will replace any existing properties set on the configuration * instance. * @return the updated configuration instance. */ public ResourceConfig setProperties(final Map properties) { state.setProperties(properties); return this; } @Override public ResourceConfig property(final String name, final Object value) { state.property(name, value); return this; } @Override public ResourceConfig register(final Class componentClass) { invalidateCache(); state.register(componentClass); return this; } @Override public ResourceConfig register(final Class componentClass, final int bindingPriority) { invalidateCache(); state.register(componentClass, bindingPriority); return this; } @Override public ResourceConfig register(final Class componentClass, final Class... contracts) { invalidateCache(); state.register(componentClass, contracts); return this; } @Override public ResourceConfig register(final Class componentClass, final Map, Integer> contracts) { invalidateCache(); state.register(componentClass, contracts); return this; } @Override public ResourceConfig register(final Object component) { invalidateCache(); state.register(component); return this; } @Override public ResourceConfig register(final Object component, final int bindingPriority) { invalidateCache(); state.register(component, bindingPriority); return this; } @Override public ResourceConfig register(final Object component, final Class... contracts) { invalidateCache(); state.register(component, contracts); return this; } @Override public ResourceConfig register(final Object component, final Map, Integer> contracts) { invalidateCache(); state.register(component, contracts); return this; } /** * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider or JAX-RS feature * in the {@code ResourceConfig}. *

* Note that registered JAX-RS features are used to initialize and configure * the Jersey runtime {@link InjectionManager} instance during application deployment, but are * otherwise ignored by server-side runtime, unless they implement also another contract * recognized by Jersey runtime. *

*

* Also note that registration of {@link Binder binder} classes is note supported. Binders * must be {@link #registerInstances(Object...) registered as instances}. *

* * @param classes classes to register. * @return updated resource configuration instance. */ public final ResourceConfig registerClasses(final Set> classes) { if (classes == null) { return this; } for (final Class cls : classes) { register(cls); } return this; } /** * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider or JAX-RS feature * in the {@code ResourceConfig}. *

* Note that registered JAX-RS features are used to initialize and configure * the Jersey runtime {@link InjectionManager} instance during application deployment, but are * otherwise ignored by server-side runtime, unless they implement also another contract * recognized by Jersey runtime. *

*

* Also note that registration of {@link Binder binder} classes is note supported. Binders * must be {@link #registerInstances(Object...) registered as instances}. *

* * @param classes classes to register. * @return updated resource configuration instance. */ public final ResourceConfig registerClasses(final Class... classes) { if (classes == null) { return this; } return registerClasses(Arrays.stream(classes).collect(Collectors.toSet())); } /** * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider, JAX-RS feature * {@link Binder Jersey Binder} instances (singletons) in the {@code ResourceConfig}. *

* Note that registered binders and JAX-RS features are used to initialize and configure * the Jersey runtime {@link InjectionManager} instance during application deployment, but are * otherwise ignored by server-side runtime, unless they implement also another contract * recognized by Jersey runtime. *

* * @param instances instances to register. * @return updated resource configuration instance. */ public final ResourceConfig registerInstances(final Set instances) { if (instances == null) { return this; } for (final Object instance : instances) { register(instance); } return this; } /** * Register annotated JAX-RS resource, JAX-RS or Jersey contract provider, JAX-RS feature, * {@link Binder Jersey Binder} instances (singletons) in the {@code ResourceConfig}. *

* Note that registered binders and JAX-RS features are used to initialize and configure * the Jersey runtime {@link InjectionManager} instance during application deployment, but are * otherwise ignored by server-side runtime, unless they implement also another contract * recognized by Jersey runtime. *

* * @param instances instances to register. * @return updated resource configuration instance. */ public final ResourceConfig registerInstances(final Object... instances) { if (instances == null) { return this; } return registerInstances(Arrays.stream(instances).collect(Collectors.toSet())); } /** * Register new programmatic resource models in the {@code ResourceConfig}. * * @param resources resource models to register. * @return updated resource configuration instance. */ public final ResourceConfig registerResources(final Resource... resources) { if (resources == null) { return this; } return registerResources(Arrays.stream(resources).collect(Collectors.toSet())); } /** * Register new resource models in the {@code ResourceConfig}. * * @param resources resource models to register. * @return updated resource configuration instance. */ public final ResourceConfig registerResources(final Set resources) { if (resources == null) { return this; } this.state.registerResources(resources); return this; } /** * Add a {@link ResourceFinder} to {@code ResourceConfig}. * * @param resourceFinder {@link ResourceFinder} * @return updated resource configuration instance. */ public final ResourceConfig registerFinder(final ResourceFinder resourceFinder) { if (resourceFinder == null) { return this; } invalidateCache(); this.state.registerFinder(resourceFinder); return this; } /** * Set the name of the application. The name is an arbitrary user defined name * which is used to distinguish between Jersey applications in the case that more applications * are deployed on the same runtime (container). The name can be used for example for purposes * of monitoring by JMX when name identifies to which application deployed MBeans belong to. * The name should be unique in the runtime. * * @param applicationName Unique application name. * @return updated resource configuration instance. */ public final ResourceConfig setApplicationName(final String applicationName) { state.setApplicationName(applicationName); return this; } /** * Set {@link ClassLoader} which will be used for resource discovery. * * @param classLoader provided {@link ClassLoader}. * @return updated resource configuration instance. */ public final ResourceConfig setClassLoader(final ClassLoader classLoader) { this.state.setClassLoader(classLoader); return this; } /** * Adds array of package names which will be used to scan for components. *

* Package scanning ignores inheritance and therefore {@link Path} annotation * on parent classes and interfaces will be ignored. *

* Packages will be scanned recursively, including all nested packages. * * @param packages array of package names. * @return updated resource configuration instance. * @see #packages(boolean, String...) */ public final ResourceConfig packages(final String... packages) { return packages(true, packages); } /** * Adds array of package names which will be used to scan for components. *

* Package scanning ignores an inheritance and therefore {@link Path} annotation * on parent classes and interfaces will be ignored. *

* @param recursive defines whether any nested packages in the collection of specified * package names should be recursively scanned (value of {@code true}) * as part of the package scanning or not (value of {@code false}). * @param packages array of package names. * @return updated resource configuration instance. * @see #packages(String...) */ public final ResourceConfig packages(final boolean recursive, final String... packages) { if (packages == null || packages.length == 0) { return this; } return registerFinder(new PackageNamesScanner(packages, recursive)); } /** * Adds array of package names which will be used to scan for components. *

* Package scanning ignores an inheritance and therefore {@link Path} annotation * on parent classes and interfaces will be ignored. *

* @param recursive defines whether any nested packages in the collection of specified * package names should be recursively scanned (value of {@code true}) * as part of the package scanning or not (value of {@code false}). * @param classLoader defines the classloader used for scanning the packages and loading the classes. * @param packages array of package names. * @return updated resource configuration instance. * @see #packages(String...) */ public final ResourceConfig packages(final boolean recursive, final ClassLoader classLoader, final String... packages) { if (packages == null || packages.length == 0) { return this; } return registerFinder(new PackageNamesScanner(classLoader, packages, recursive)); } /** * Adds array of file and directory names to scan for components. *

* Any directories in the list will be scanned recursively, including their sub-directories. * * @param files array of file and directory names. * @return updated resource configuration instance. */ public final ResourceConfig files(final String... files) { return files(true, files); } /** * Adds array of file and directory names to scan for components. * * @param recursive defines whether any sub-directories of the directories specified * in the collection of file names should be recursively scanned (value of {@code true}) * as part of the file scanning or not (value of {@code false}). * @param files array of file and directory names. * @return updated resource configuration instance. */ public final ResourceConfig files(final boolean recursive, final String... files) { if (files == null || files.length == 0) { return this; } return registerFinder(new FilesScanner(files, recursive)); } /** * Invalidate cached component instances and classes. */ final void invalidateCache() { this.cachedClasses = null; this.cachedClassesView = null; this.cachedSingletons = null; this.cachedSingletonsView = null; // Reset ResourceFinders to make sure the next package scanning is successful. if (resetFinders) { for (final ResourceFinder finder : this.state.resourceFinders) { finder.reset(); } resetFinders = false; } } /** * Switches the ResourceConfig to read-only state. *

* Called by the WrappingResourceConfig if this ResourceConfig is set as the application. * Also called by ApplicationHandler on WrappingResourceConfig at the point when it is going * to build the resource model. *

* The method also sets the application name from properties if the name is not defined yer * and the property {@link ServerProperties#APPLICATION_NAME} is defined. */ final void lock() { final State current = state; if (!(current instanceof ImmutableState)) { setupApplicationName(); ExternalPropertiesConfigurationFactory.configure(state); state = new ImmutableState(current); } } @Override public final ServerConfig getConfiguration() { return this; } @Override public final Map getProperties() { return state.getProperties(); } @Override public final boolean hasProperty(final String name) { return state.hasProperty(name); } @Override public final Object getProperty(final String name) { return state.getProperty(name); } @Override public Collection getPropertyNames() { return state.getPropertyNames(); } @Override public final boolean isProperty(final String name) { return state.isProperty(name); } @Override public final Set> getClasses() { if (cachedClassesView == null) { cachedClasses = _getClasses(); cachedClassesView = Collections.unmodifiableSet(cachedClasses); } return cachedClassesView; } @Override public final Set getInstances() { return getSingletons(); } @Override public final Set getSingletons() { if (cachedSingletonsView == null) { cachedSingletons = _getSingletons(); cachedSingletonsView = Collections.unmodifiableSet(cachedSingletons == null ? new HashSet<>() : cachedSingletons); } return cachedSingletonsView; } /** * Get the internal component bag. * * @return internal component bag. */ final ComponentBag getComponentBag() { return state.getComponentBag(); } /** * Configure auto-discoverables. * * @param injectionManager injection manager to obtain auto-discoverables from. * @param autoDiscoverables list of registered auto discoverable components. */ final void configureAutoDiscoverableProviders(InjectionManager injectionManager, Collection autoDiscoverables) { state.configureAutoDiscoverableProviders(injectionManager, autoDiscoverables, false); } /** * Configure forced auto-discoverables. * * @param injectionManager injection manager to obtain auto-discoverables from. */ final void configureForcedAutoDiscoverableProviders(InjectionManager injectionManager) { state.configureAutoDiscoverableProviders(injectionManager, Collections.emptyList(), true); } final void configureMetaProviders(InjectionManager injectionManager, ManagedObjectsFinalizer finalizer) { state.configureMetaProviders(injectionManager, finalizer); } @Override public RuntimeType getRuntimeType() { return state.getRuntimeType(); } @Override public boolean isEnabled(final Feature feature) { return state.isEnabled(feature); } @Override public boolean isEnabled(final Class featureClass) { return state.isEnabled(featureClass); } @Override public boolean isRegistered(final Object component) { return state.isRegistered(component); } @Override public boolean isRegistered(final Class componentClass) { return state.isRegistered(componentClass); } @Override public Map, Integer> getContracts(final Class componentClass) { return state.getContracts(componentClass); } /** * Get configured resource and/or provider classes. The method is overridden * in a {@link WrappingResourceConfig private sub-type}. * * @return set of configured resource and/or provider classes. */ Set> _getClasses() { final Set> result = scanClasses(); result.addAll(state.getClasses()); return result; } private Set> scanClasses() { final Set> result = new HashSet<>(); final ResourceConfig.State _state = state; final Set rfs = new HashSet<>(_state.getResourceFinders()); // In case new entity is registered the available finders should be reset. resetFinders = true; // classes registered via configuration property final String[] classNames = parsePropertyValue(ServerProperties.PROVIDER_CLASSNAMES); if (classNames != null) { for (final String className : classNames) { try { result.add(_state.getClassLoader().loadClass(className)); } catch (final ClassNotFoundException e) { LOGGER.log(Level.CONFIG, LocalizationMessages.UNABLE_TO_LOAD_CLASS(className)); } } } final String[] packageNames = parsePropertyValue(ServerProperties.PROVIDER_PACKAGES); if (packageNames != null) { final Object p = getProperty(ServerProperties.PROVIDER_SCANNING_RECURSIVE); final boolean recursive = p == null || PropertiesHelper.isProperty(p); rfs.add(new PackageNamesScanner(packageNames, recursive)); } final String[] classPathElements = parsePropertyValue(ServerProperties.PROVIDER_CLASSPATH); if (classPathElements != null) { rfs.add(new FilesScanner(classPathElements, true)); } final AnnotationAcceptingListener parentAfl = AnnotationAcceptingListener.newJaxrsResourceAndProviderListener(_state.getClassLoader()); for (final ResourceFinder resourceFinder : rfs) { AnnotationAcceptingListener afl = parentAfl; if (resourceFinder instanceof PackageNamesScanner) { final ClassLoader classLoader = ((PackageNamesScanner) resourceFinder).getClassloader(); if (!getClassLoader().equals(classLoader)) { afl = AnnotationAcceptingListener.newJaxrsResourceAndProviderListener(classLoader); } } while (resourceFinder.hasNext()) { final String next = resourceFinder.next(); if (afl.accept(next)) { final InputStream in = resourceFinder.open(); try { afl.process(next, in); } catch (final IOException e) { LOGGER.log(Level.WARNING, LocalizationMessages.RESOURCE_CONFIG_UNABLE_TO_PROCESS(next)); } finally { try { in.close(); } catch (final IOException ex) { LOGGER.log(Level.FINER, "Error closing resource stream.", ex); } } } } if (afl != parentAfl) { result.addAll(afl.getAnnotatedClasses()); } } result.addAll(parentAfl.getAnnotatedClasses()); return result; } private String[] parsePropertyValue(final String propertyName) { String[] classNames = null; final Object o = state.getProperties().get(propertyName); if (o != null) { if (o instanceof String) { classNames = Tokenizer.tokenize((String) o); } else if (o instanceof String[]) { classNames = Tokenizer.tokenize((String[]) o); } } return classNames; } /** * Return classes which were registered by the user and not found by class path scanning (or any other scanning). * * @return Set of classes registered by the user. */ Set> getRegisteredClasses() { return state.getComponentBag().getRegistrations(); } /** * Get configured resource and/or provider instances. The method is overridden * in a {@link WrappingResourceConfig private sub-type}. * * @return set of configured resource and/or provider instances. */ Set _getSingletons() { final Set result = new HashSet<>(); result.addAll(state.getInstances()); return result; } @Override public final Set getResources() { return state.getResources(); } /** * Get resource and provider class loader. * * @return class loader to be used when looking up the resource classes and * providers. */ public final ClassLoader getClassLoader() { return state.getClassLoader(); } /** * Returns JAX-RS application corresponding with this ResourceConfig. * * @return JAX-RS application corresponding with this ResourceConfig. */ public final Application getApplication() { return _getApplication(); } /** * Returns encoded value of {@link ApplicationPath} annotation of the Application corresponding * with this ResourceConfig or {@code null} when the annotation is not present. * * @return Returns encoded value of {@link ApplicationPath} annotation of the Application * corresponding with this ResourceConfig. */ public final String getApplicationPath() { final Application application; if (ResourceConfig.class.isInstance(_getApplication())) { final Application unwrap = unwrapCustomRootApplication((ResourceConfig) _getApplication()); application = unwrap != null ? unwrap : _getApplication(); } else { application = _getApplication(); } final ApplicationPath appPath = application.getClass().getAnnotation(ApplicationPath.class); final String value; if (appPath != null && !appPath.value().isEmpty() && !appPath.value().trim().equals("/")) { final String val = appPath.value().trim(); value = UriComponent.encode(val.startsWith("/") ? val.substring(1) : val, UriComponent.Type.PATH); } else { value = null; } return value; } /** * Allows overriding the {@link #getApplication()} method functionality in {@link WrappingResourceConfig}. * * @return JAX-RS application corresponding with this ResourceConfig. */ Application _getApplication() { return this; } /** * Get the name of the Jersey application. * * @return Name of the application. * @see #setApplicationName(String) */ public String getApplicationName() { return state.getApplicationName(); } /** * Method used by ApplicationHandler to retrieve application class * (this method is overridden by {@link WrappingResourceConfig}). * * @return application class */ Class getApplicationClass() { return null; } /** * This method is used by ApplicationHandler to set application instance to the resource config (should * always be called on WrappingResourceConfig instance, never on plain instances of ResourceConfig * unless we have a bug in the code). * * @param app JAX-RS application * @return this ResourceConfig instance (for convenience) */ final ResourceConfig setApplication(final Application app) { return _setApplication(app); } /** * Allows overriding the setApplication() method functionality in WrappingResourceConfig. * * @param app application to be set for this ResourceConfig * @return this resource config instance */ ResourceConfig _setApplication(final Application app) { throw new UnsupportedOperationException(); } private static class WrappingResourceConfig extends ResourceConfig { private Application application; private Class applicationClass; private final Set> defaultClasses = new HashSet<>(); public WrappingResourceConfig( final Application application, final Class applicationClass, final Set> defaultClasses) { if (application == null && applicationClass == null) { throw new IllegalArgumentException(LocalizationMessages.RESOURCE_CONFIG_ERROR_NULL_APPLICATIONCLASS()); } this.application = application; this.applicationClass = applicationClass; if (defaultClasses != null) { this.defaultClasses.addAll(defaultClasses); } mergeApplications(application); } /** * Set the {@link jakarta.ws.rs.core.Application JAX-RS Application instance} * in the {@code ResourceConfig}. *

* This method is used by the {@link org.glassfish.jersey.server.ApplicationHandler} in case this resource * configuration instance was created with application class rather than application instance. * * @param application JAX-RS Application instance. * @return updated resource configuration instance. */ @Override ResourceConfig _setApplication(final Application application) { this.application = application; this.applicationClass = null; mergeApplications(application); return this; } /** * Get the original underlying JAX-RS {@link Application} instance used to * initialize the resource configuration instance. * * @return JAX-RS application instance. */ @Override Application _getApplication() { return application; } /** * Get the original JAX-RS {@link Application} class provided it was not * instantiated yet. A {@code null} is returned in case the class has been * instantiated already or was not configured at all. *

* This class will be used to initialize the resource configuration instance. * If there is no JAX-RS application class set, or if the class has been * instantiated already, the method will return {@code null}. *

* * @return original JAX-RS application class or {@code null} if there is no * such class configured or if the class has been already instantiated. */ @Override Class getApplicationClass() { return applicationClass; } /** * Merges fields (e.g. custom binders, properties) of the given application with this application. *

* The merging should be done because of the possibility of reloading this {@code ResourceConfig} in a container * so this resource config should know about custom binders and properties of the underlying application to ensure * the reload process will complete successfully. *

* * @param application the application which fields should be merged with this application. * @see org.glassfish.jersey.server.spi.Container#reload() * @see org.glassfish.jersey.server.spi.Container#reload(ResourceConfig) */ private void mergeApplications(final Application application) { if (application instanceof ResourceConfig) { // Merge custom binders. final ResourceConfig rc = (ResourceConfig) application; // Merge resources super.registerResources(rc.getResources()); // properties set on the wrapping resource config take precedence // (as those are retrieved from the web.xml, for example) rc.invalidateCache(); rc.addProperties(super.getProperties()); super.addProperties(rc.getProperties()); super.setApplicationName(rc.getApplicationName()); super.setClassLoader(rc.getClassLoader()); rc.lock(); } else if (application != null) { super.addProperties(application.getProperties()); } } @Override Set> _getClasses() { final Set> result = new HashSet<>(); final Set> applicationClasses = application.getClasses(); result.addAll(applicationClasses == null ? new HashSet>() : applicationClasses); if (result.isEmpty() && getSingletons().isEmpty()) { result.addAll(defaultClasses); } // if the application is not an instance of ResourceConfig, handle scanning triggered via properties if (!(application instanceof ResourceConfig)) { result.addAll(super._getClasses()); } return result; } @Override Set _getSingletons() { return application.getSingletons(); } } /** * Create runtime configuration initialized from a given deploy-time JAX-RS/Jersey * application configuration. * * @param application deploy-time JAX-RS/Jersey application configuration. * @return initialized run-time resource config. */ static ResourceConfig createRuntimeConfig(final Application application) { return (application instanceof ResourceConfig) ? new RuntimeConfig((ResourceConfig) application) : new RuntimeConfig(application); } private static class RuntimeConfig extends ResourceConfig { private final Set> originalRegistrations; private final Application application; private RuntimeConfig(final ResourceConfig original) { super(original); this.application = original; final Application customRootApp = ResourceConfig.unwrapCustomRootApplication(original); final Set rootSingletons = customRootApp != null ? registerComponentsOf(customRootApp) : null; originalRegistrations = Collections.newSetFromMap(new IdentityHashMap<>()); originalRegistrations.addAll(super.getRegisteredClasses()); // Do not call the same Application#getSingletons twice final Set origSingletons = customRootApp != null ? rootSingletons : original.getSingletons(); // Register externally provided instances. final Set externalInstances = origSingletons.stream() .filter(external -> !originalRegistrations.contains(external.getClass())) .collect(Collectors.toSet()); registerInstances(externalInstances); // Register externally provided classes. final Set> externalClasses = original.getClasses().stream() .filter(external -> !originalRegistrations.contains(external)) .collect(Collectors.toSet()); registerClasses(externalClasses); } private Set registerComponentsOf(final Application application) { return Errors.process(new Producer>() { @Override public Set call() { // First register instances that should take precedence over classes // in case of duplicate registrations final Set singletons = application.getSingletons(); if (singletons != null) { registerInstances( singletons.stream() .filter(input -> { if (input == null) { Errors.warning(application, LocalizationMessages.NON_INSTANTIABLE_COMPONENT(null)); } return input != null; }) .collect(Collectors.toSet())); } final Set> classes = application.getClasses(); if (classes != null) { registerClasses(classes.stream() .filter(input -> { if (input == null) { Errors.warning(application, LocalizationMessages.NON_INSTANTIABLE_COMPONENT(null)); } return input != null; }) .collect(Collectors.toSet())); } return singletons; } }); } private RuntimeConfig(final Application application) { super(); this.application = application; if (application != null) { registerComponentsOf(application); // Copy all available properties. addProperties(application.getProperties()); } originalRegistrations = super.getRegisteredClasses(); } @Override Set> _getClasses() { // Get only a read-only classes cached in internal state. return super.state.getClasses(); } @Override Set _getSingletons() { // Get only a read-only classes cached in internal state. return super.state.getInstances(); } @Override Set> getRegisteredClasses() { return originalRegistrations; } @Override Application _getApplication() { return application; } } private static Application unwrapCustomRootApplication(ResourceConfig resourceConfig) { Application app = null; while (resourceConfig != null) { app = resourceConfig.getApplication(); if (app == resourceConfig) { // resource config is the root application - return null return null; } else if (app instanceof ResourceConfig) { resourceConfig = (ResourceConfig) app; } else { break; } } return app; } /** * Get the most internal wrapped {@link Application application} class. *

* This method is similar to {@link ResourceConfig#getApplication()} except if provided application was * created by wrapping multiple {@code ResourceConfig} instances, this method will return the original (inner-most) * JAX-RS {@code Application} sub-class rather than a potentially intermediate {@code ResourceConfig} wrapper. *

* * @param application application that is potentially wrapped. * @return the original, inner-most {@link Application} subclass. May return the same instance directly, * in case the supplied {@code application} instance is not a wrapper {@code ResourceConfig} instance. */ static Application unwrapApplication(Application application) { while (application instanceof ResourceConfig) { final Application wrappedApplication = ((ResourceConfig) application).getApplication(); if (wrappedApplication == application) { break; } application = wrappedApplication; } return application; } private void setupApplicationName() { final String appName = ServerProperties.getValue(getProperties(), ServerProperties.APPLICATION_NAME, null, String.class); if (appName != null && getApplicationName() == null) { setApplicationName(appName); } } }