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

import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.core.Application;

import org.glassfish.jersey.Config;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.Tokenizer;
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.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.Binder;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * The resource configuration for configuring a web application.
 *
 * @author Paul Sandoz
 * @author Martin Matula (martin.matula at oracle.com)
 */
public class ResourceConfig extends Application implements Config {

    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 final Set> classes;
    private final Set singletons;
    private final Set resourceFinders;
    //
    private final Set resources;
    private final Set resourcesView;
    private final Map properties;
    private final Map propertiesView;
    //
    private final Set customBinders;
    //
    private ClassLoader classLoader = null;
    //
    private InternalState internalState = new Mutable();

    /**
     * Create a new resource configuration without any custom properties or
     * resource and provider classes.
     */
    public ResourceConfig() {
        this.classLoader = ReflectionHelper.getContextClassLoader();

        this.classes = Sets.newHashSet();
        this.singletons = Sets.newHashSet();
        this.resources = Sets.newHashSet();
        this.resourcesView = Collections.unmodifiableSet(this.resources);

        this.properties = Maps.newHashMap();
        this.propertiesView = Collections.unmodifiableMap(this.properties);

        this.resourceFinders = Sets.newHashSet();
        this.customBinders = Sets.newHashSet();
    }

    /**
     * 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(Set> classes) {
        this();
        this.addClasses(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(Class... classes) {
        this(Sets.newHashSet(classes));
    }

    /**
     * Returns a {@link ResourceConfig} instance for the supplied application.
     *
     * If the application is an instance of {@link ResourceConfig} the method simply returns the application.
     * Otherwise it creates a new {@link ResourceConfig} wrapping the application.
     *
     * @param application Application to provide the {@link ResourceConfig} instance for.
     * @return ResourceConfig instance for the supplied application.
     */
    public static ResourceConfig forApplication(Application application) {
        return (application instanceof ResourceConfig) ? ((ResourceConfig) application) : new WrappingResourceConfig(application, null, null);
    }

    /**
     * Returns a {@link 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(Class applicationClass) {
        return new WrappingResourceConfig(null, applicationClass, null);
    }

    /**
     * Returns a {@link 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 javax.ws.rs.core.Application#getClasses()}
     * and {@link javax.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(Class applicationClass, Set> defaultClasses) {
        return new WrappingResourceConfig(null, applicationClass, defaultClasses);
    }

    /**
     * Add classes to {@code ResourceConfig}.
     *
     * @param classes list of classes to add.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addClasses(Set> classes) {
        return internalState.addClasses(classes);
    }

    /**
     * Add classes to {@code ResourceConfig}.
     *
     * @param classes {@link Set} of classes to add.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addClasses(Class... classes) {
        return addClasses(Sets.newHashSet(classes));
    }

    /**
     * Add singletons to {@code ResourceConfig}.
     *
     * @param singletons {@link Set} of instances to add.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addSingletons(Set singletons) {
        return internalState.addSingletons(singletons);
    }

    /**
     * Add singletons to {@code ResourceConfig}.
     *
     * @param singletons list of instances to add.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addSingletons(Object... singletons) {
        return addSingletons(Sets.newHashSet(singletons));
    }

    /**
     * Add new resource models to the configuration.
     *
     * @param resources resource models.
     * @return updated resource configuration.
     */
    public final ResourceConfig addResources(Resource... resources) {
        return addResources(Sets.newHashSet(resources));
    }

    /**
     * Add new resource models to the configuration.
     *
     * @param resources resource models.
     * @return updated resource configuration.
     */
    public final ResourceConfig addResources(Set resources) {
        return internalState.addResources(resources);
    }

    /**
     * Set a {@code ResourceConfig} property.
     *
     * @param name  property name.
     * @param value property value.
     * @return updated resource configuration instance.
     */
    public ResourceConfig setProperty(String name, Object value) {
        return internalState.setProperty(name, value);
    }

    /**
     * Add properties to {@code ResourceConfig}.
     *
     * If any of the added properties exists already, he values of the existing
     * properties will be replaced with new values.
     *
     * @param properties properties to add.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addProperties(Map properties) {
        return internalState.addProperties(properties);
    }

    /**
     * Add a {@link ResourceFinder} to {@code ResourceConfig}.
     *
     * @param resourceFinder {@link ResourceFinder}
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addFinder(ResourceFinder resourceFinder) {
        return internalState.addFinder(resourceFinder);
    }

    /**
     * Add {@link Binder HK2 binders} to {@code ResourceConfig}.
     *
     * These binders will be added when creating {@link ServiceLocator} instance.
     *
     * @param binders custom binders.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addBinders(Set binders) {
        return internalState.addBinders(binders);
    }

    /**
     * Add {@link Binder HK2 binders} to {@code ResourceConfig}.
     *
     * These binders will be added when creating {@link ServiceLocator} instance.
     *
     * @param binders custom binders.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig addBinders(Binder... binders) {
        return addBinders(Sets.newHashSet(binders));
    }

    /**
     * Set {@link ClassLoader} which will be used for resource discovery.
     *
     * @param classLoader provided {@link ClassLoader}.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig setClassLoader(ClassLoader classLoader) {
        return internalState.setClassLoader(classLoader);
    }

    /**
     * Adds array of package names which will be used to scan for
     * providers.
     *
     * @param packages array of package names
     * @return updated resource configuration instance.
     */
    public final ResourceConfig packages(String... packages) {
        return addFinder(new PackageNamesScanner(packages));
    }

    /**
     * Adds array of file names to scan for providers.
     *
     * @param files array of file names.
     * @return updated resource configuration instance.
     */
    public final ResourceConfig files(String... files) {
        return addFinder(new FilesScanner(files));
    }


    private void invalidateCache() {
        this.cachedClasses = null;
        this.cachedClassesView = null;
        this.cachedSingletons = null;
        this.cachedSingletonsView = null;
    }

    /**
     * Returns binders declared during {@code ResourceConfig} creation.
     *
     * @return set of custom HK2 binders.
     */
    final Set getCustomBinders() {
        return customBinders;
    }

    /**
     * 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.
     */
    void lock() {
        if (!(internalState instanceof Immutable)) {
            internalState = new Immutable();
        }
    }

    /**
     * Unmodifiable {@link Set} of current resource and provider classes.
     *
     * @return Unmodifiable {@link Set} of resource and provider classes.
     */
    @Override
    public final Set> getClasses() {
        if (cachedClassesView == null) {
            cachedClasses = _getClasses();
            cachedClassesView = Collections.unmodifiableSet(cachedClasses);
        }
        return cachedClassesView;
    }

    /**
     * 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() {
        Set> result = Sets.newHashSet();

        Set rfs = Sets.newHashSet(resourceFinders);

        // classes registered via configuration property
        String[] classNames = parsePropertyValue(ServerProperties.PROVIDER_CLASSNAMES);
        if (classNames != null) {
            for (String className : classNames) {
                try {
                    result.add(classLoader.loadClass(className));
                } catch (ClassNotFoundException e) {
                    LOGGER.log(Level.CONFIG, LocalizationMessages.UNABLE_TO_LOAD_CLASS(className));
                }
            }
        }

        String[] packageNames = parsePropertyValue(ServerProperties.PROVIDER_PACKAGES);
        if (packageNames != null) {
            rfs.add(new PackageNamesScanner(packageNames));
        }

        String[] classPathElements = parsePropertyValue(ServerProperties.PROVIDER_CLASSPATH);
        if (classPathElements != null) {
            rfs.add(new FilesScanner(classPathElements));
        }

        AnnotationAcceptingListener afl = AnnotationAcceptingListener.newJaxrsResourceAndProviderListener(classLoader);
        for (ResourceFinder resourceFinder : rfs) {
            while (resourceFinder.hasNext()) {
                final String next = resourceFinder.next();
                if (afl.accept(next)) {
                    try {
                        afl.process(next, resourceFinder.open());
                    } catch (IOException e) {
                        // TODO L10N
                        LOGGER.log(Level.WARNING, "Unable to process {0}", next);
                    }
                }
            }
        }

        result.addAll(afl.getAnnotatedClasses());
        result.addAll(classes);
        return result;
    }

    private String[] parsePropertyValue(String propertyName) {
        String[] classNames = null;
        final Object o = properties.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;
    }

    /**
     * Unmodifiable {@link Set} of singletons.
     *
     * @return Unmodifiable {@link Set} of singletons.
     */
    @Override
    public final Set getSingletons() {
        if (cachedSingletonsView == null) {
            cachedSingletons = _getSingletons();
            cachedSingletonsView = Collections.unmodifiableSet(cachedSingletons);
        }

        return cachedSingletonsView;
    }

    /**
     * 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() {
        Set result = Sets.newHashSet();
        result.addAll(singletons);
        return result;
    }

    /**
     * Get programmatically modeled resources.
     *
     * @return programmatically modeled resources.
     */
    public final Set getResources() {
        return resourcesView;
    }

    /**
     * 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 classLoader;
    }

    @Override
    public final Map getProperties() {
        return propertiesView;
    }

    @Override
    public final Object getProperty(String name) {
        return properties.get(name);
    }

    @Override
    public final boolean isProperty(String name) {
        if (properties.containsKey(name)) {
            Object value = properties.get(name);
            if (value instanceof Boolean) {
                return Boolean.class.cast(value);
            } else {
                return Boolean.parseBoolean(value.toString());
            }
        }

        return false;
    }

    /**
     * Returns JAX-RS application corresponding with this ResourceConfig.
     *
     * @return JAX-RS application corresponding with this ResourceConfig.
     */
    public final Application getApplication() {
        return _getApplication();
    }

    /**
     * Allows overriding the {@link #getApplication()} method functionality in {@link WrappingResourceConfig}.
     *
     * @return JAX-RS application corresponding with this ResourceConfig.
     */
    Application _getApplication() {
        return this;
    }

    /**
     * Method used by ApplicationHandler to retrieve application class (this method is overriden by 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(Application app) {
        internalState.setApplication(app);
        return this;
    }

    /**
     * Allows overriding the setApplication() method functionality in WrappingResourceConfig.
     *
     * @param app application to be set for this ResourceConfig
     * @return this resource config instance
     */
    ResourceConfig _setApplication(Application app) {
        throw new UnsupportedOperationException();
    }

    private interface InternalState {
        ResourceConfig addClasses(Set> classes);

        ResourceConfig addResources(Set resources);

        ResourceConfig addFinder(ResourceFinder resourceFinder);

        ResourceConfig addBinders(Set binders);

        ResourceConfig addProperties(Map properties);

        ResourceConfig addSingletons(Set singletons);

        ResourceConfig setClassLoader(ClassLoader classLoader);

        ResourceConfig setProperty(String name, Object value);

        ResourceConfig setApplication(Application application);
    }

    private class Immutable implements InternalState {
        @Override
        public ResourceConfig addClasses(Set> classes) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public ResourceConfig addResources(Set resources) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public ResourceConfig addFinder(ResourceFinder resourceFinder) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public ResourceConfig addBinders(Set binders) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

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

        @Override
        public ResourceConfig addSingletons(Set singletons) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

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

        @Override
        public ResourceConfig setProperty(String name, Object value) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }

        @Override
        public ResourceConfig setApplication(Application application) {
            throw new IllegalStateException(LocalizationMessages.RC_NOT_MODIFIABLE());
        }
    }

    private class Mutable implements InternalState {
        @Override
        public ResourceConfig addClasses(Set> classes) {
            invalidateCache();
            ResourceConfig.this.classes.addAll(classes);
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig addResources(Set resources) {
            ResourceConfig.this.resources.addAll(resources);
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig addFinder(ResourceFinder resourceFinder) {
            invalidateCache();
            ResourceConfig.this.resourceFinders.add(resourceFinder);
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig addBinders(Set binders) {
            ResourceConfig.this.customBinders.addAll(binders);
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig addProperties(Map properties) {
            invalidateCache();
            ResourceConfig.this.properties.putAll(properties);
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig addSingletons(Set singletons) {
            invalidateCache();
            ResourceConfig.this.singletons.addAll(singletons);
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig setClassLoader(ClassLoader classLoader) {
            invalidateCache();
            ResourceConfig.this.classLoader = classLoader;
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig setProperty(String name, Object value) {
            invalidateCache();
            ResourceConfig.this.properties.put(name, value);
            return ResourceConfig.this;
        }

        @Override
        public ResourceConfig setApplication(Application application) {
            invalidateCache();
            return ResourceConfig.this._setApplication(application);
        }
    }

    private static class WrappingResourceConfig extends ResourceConfig {
        private Application application;
        private Class applicationClass;
        private final Set> defaultClasses = Sets.newHashSet();

        public WrappingResourceConfig(Application application, Class applicationClass,
                                      Set> defaultClasses) {
            if (application == null && applicationClass == null) {
                throw new IllegalArgumentException("Both application and applicationClass can't be null.");
            }
            this.application = application;
            this.applicationClass = applicationClass;
            if (defaultClasses != null) {
                this.defaultClasses.addAll(defaultClasses);
            }
            mergeApplications(application);
        }

        /**
         * Set the {@link javax.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(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. ResourceConfig rc = (ResourceConfig) application; super.customBinders.addAll(rc.customBinders); // Merge resources super.resources.addAll(rc.resources); // properties set on the wrapping resource config take precedence (as those are retrieved from the web.xml for example) rc.invalidateCache(); rc.properties.putAll(super.properties); super.properties.putAll(rc.properties); rc.lock(); } } @Override Set> _getClasses() { Set> result = Sets.newHashSet(); result.addAll(application.getClasses()); if (result.isEmpty() && getSingletons().isEmpty()) { result.addAll(defaultClasses); } // if the application is not an instance of ResourceConfig, handle scanning triggered by the way of properties if (!(application instanceof ResourceConfig)) { result.addAll(super._getClasses()); } return result; } @Override Set _getSingletons() { return application.getSingletons(); } } }