/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2014 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 com.sun.jersey.spi.container.servlet;

import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sun.jersey.api.container.ContainerException;
import com.sun.jersey.api.core.ClasspathResourceConfig;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.api.core.servlet.WebAppResourceConfig;
import com.sun.jersey.api.uri.UriComponent;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
import com.sun.jersey.server.impl.application.DeferredResourceConfig;
import com.sun.jersey.spi.container.ContainerNotifier;
import com.sun.jersey.spi.container.ReloadListener;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.WebApplicationFactory;
import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider;
import com.sun.jersey.spi.service.ServiceFinder;


/**
 * A {@link Servlet} or {@link Filter} for deploying root resource classes.
 * <p>
 * If this class is declared as a filter and the initialization parameter
 * {@link #PROPERTY_WEB_PAGE_CONTENT_REGEX} is not set
 * or {@link #FEATURE_FILTER_FORWARD_ON_404} is not set to true then the filter
 * must be declared at the last position in the filter chain as the filter will
 * not forward any request to a next filter (if any) in the chain.
 * <p>
 * The following sections make reference to initialization parameters. Unless
 * otherwise specified the initialization parameters apply to both server
 * and filter initialization parameters.
 * <p>
 * The servlet or filter may be configured to have an initialization
 * parameter "com.sun.jersey.config.property.resourceConfigClass" or
 * "javax.ws.rs.Application" and whose value is a
 * fully qualified name of a class that implements {@link ResourceConfig} or
 * {@link Application}.
 * If the concrete class has a constructor that takes a single parameter of the
 * type Map then the class is instantiated with that constructor and an instance
 * of Map that contains all the initialization parameters is passed as the
 * parameter. Otherwise, the class is instantiated as a singleton component
 * managed by the runtime, and injection may be performed (the artifacts that
 * may be injected are limited to injectable providers registered when
 * the servlet or filter is configured).
 * <p>
 * If the initialization parameter
 * "com.sun.jersey.config.property.resourceConfigClass" or
 * "javax.ws.rs.Application" is not present and a
 * initialization parameter "com.sun.jersey.config.property.packages" is present
 * (see {@link PackagesResourceConfig#PROPERTY_PACKAGES}) a new instance of
 * {@link PackagesResourceConfig} is created. The initialization parameter
 * "com.sun.jersey.config.property.packages" MUST be set to provide one or
 * more package names. Each package name MUST be separated by ';'.
 *
 * The package names are added as a property value to a Map instance using
 * the property name {@value com.sun.jersey.api.core.PackagesResourceConfig#PROPERTY_PACKAGES}.
 * Any additional initialization parameters are then added to the Map instance.
 * Then that Map instance is passed to the constructor of
 * {@link PackagesResourceConfig}.
 *
 * <p>
 * If none of the above resource configuration related initialization parameters
 * are present a new instance of {@link WebAppResourceConfig} is created. The
 * initialization parameter "com.sun.jersey.config.property.classpath" MAY be
 * set to provide one or more resource paths. Each path MUST be separated by ';'.
 *
 * The resource paths are added as a property value to a Map instance using
 * the property name {@value com.sun.jersey.api.core.ClasspathResourceConfig#PROPERTY_CLASSPATH}.
 * Any additional initialization parameters are then added to the Map instance.
 * Then that Map instance is passed to the constructor of
 * {@link WebAppResourceConfig}.
 *
 * If the initialization parameter is not present then the following resource
 * paths are utilized:
 * "/WEB-INF/lib" and "/WEB-INF/classes".
 * <p>
 * All initialization parameters are added as properties of the created
 * {@link ResourceConfig}.
 * <p>
 * A new {@link WebApplication} instance will be created and configured such
 * that the following classes may be injected onto a root resource, provider
 * and {@link Application} classes using {@link javax.ws.rs.core.Context}:
 * {@link HttpServletRequest}, {@link HttpServletResponse},
 * {@link ServletContext}, {@link ServletConfig} and {@link WebConfig}.
 * If this class is used as a Servlet then the {@link ServletConfig} class may
 * be injected. If this class is used as a Filter then the {@link FilterConfig}
 * class may be injected. {@link WebConfig} may be injected to abstract
 * servlet or filter deployment.
 * <p>
 * A {@link IoCComponentProviderFactory} instance may be registered by extending this class
 * and overriding the method {@link #initiate(ResourceConfig, WebApplication)}
 * to initiate the {@link WebApplication} with the {@link IoCComponentProviderFactory}
 * instance.
 *
 */
public class ServletContainer extends HttpServlet implements Filter {
    /**
     * The servlet initialization property whose boolean value determines
     * if GlassFish default error pages will be returned or not.
     * <p>
     * The default value is true.
     * <p>
     * If false then GlassFish will not return default error pages.
     * <p>
     * This property is supported on GlassFish version 3.1 or greater.
     * @since 1.5
     */
    public static final String GLASSFISH_DEFAULT_ERROR_PAGE_RESPONSE = "org.glassfish.web.isDefaultErrorPageEnabled";

    /**
     * The servlet initialization property whose value is a fully qualified
     * class name of a class that implements {@link ResourceConfig} or
     * {@link Application}.
     */
    public static final String APPLICATION_CONFIG_CLASS =
            "javax.ws.rs.Application";

    /**
     * The servlet initialization property whose value is a fully qualified
     * class name of a class that implements {@link ResourceConfig} or
     * {@link Application}.
     */
    public static final String RESOURCE_CONFIG_CLASS =
            "com.sun.jersey.config.property.resourceConfigClass";

    /**
     * The base path in the Web Pages where JSP templates, associated with
     * viewables of resource classes, are located.
     * <p>
     * If this property is not set then the base path will be the root path
     * of the Web Pages.
     */
    public static final String JSP_TEMPLATES_BASE_PATH =
            "com.sun.jersey.config.property.JSPTemplatesBasePath";

    /**
     * If set the regular expression used to match an incoming servlet path URI
     * to some web page content such as static resources or JSPs to be handled
     * by the underlying servlet engine.
     * <p>
     * The type of this property must be a String and the value must be a valid
     * regular expression.
     * <p>
     * This property is only applicable when this class is used as a
     * {@link Filter}, otherwise this property will be ignored and not
     * processed.
     * <p>
     * If a servlet path matches this regular expression then the filter
     * forwards the request to the next filter in the filter chain so that the
     * underlying servlet engine can process the request otherwise Jersey
     * will process the request.
     * <p>
     * For example if you set the value to
     * <code>/(image|css)/.*</code>
     * then you can serve up images and CSS files for your Implicit or Explicit
     * Views while still processing your JAX-RS resources.
     */
    public static final String PROPERTY_WEB_PAGE_CONTENT_REGEX
            = "com.sun.jersey.config.property.WebPageContentRegex";

    /**
     * If true and a 404 response with no entity body is returned from either
     * the runtime or the application then the runtime forwards the request to
     * the next filter in the filter chain. This enables another filter or
     * the underlying servlet engine to process the request.
     * Before the request is forwarded the response status is set to 200.
     * <p>
     * This property is only applicable when this class is used as a
     * {@link Filter}, otherwise this property will be ignored and not
     * processed.
     * <p>
     * Application code, such as methods corresponding to sub-resource locators
     * may be invoked when this feature is enabled.
     * <p>
     * This feature is an alternative to setting
     * {@link #PROPERTY_WEB_PAGE_CONTENT_REGEX} and requires less configuration.
     * However, application code, such as methods corresponding to sub-resource
     * locators, may be invoked when this feature is enabled.
     * <p>
     * The default value is false.
     */
    public static final String FEATURE_FILTER_FORWARD_ON_404
            = "com.sun.jersey.config.feature.FilterForwardOn404";

    /**
     * The filter context path.
     * <p>
     * If the URL pattern of a filter is set to a base path and a wildcard,
     * such as "/base/*", then this property can be used to declare a filter
     * context path that behaves in the same manner as the Servlet context
     * path for determining the base URI of the application. (Note that with
     * the Servlet 2.x API it is not possible to determine the URL pattern
     * without parsing the web.xml, hence why this property is necessary.)
     * <p>
     * This property is only applicable when this class is used as a
     * {@link Filter}, otherwise this property will be ignored and not
     * processed.
     * <p>
     * This property may consist of one or more path segments separate by '/'.
     */
    public static final String PROPERTY_FILTER_CONTEXT_PATH
            = "com.sun.jersey.config.feature.FilterContextPath";

    /**
     * If set to true then the internal Jersey CDI component provider
     * will be used for managed bean types,
     * that are not annotated with {@link ManagedBean} annotation (neither with any other
     * bean defining annotation) and CDI bean manager
     * evaluates them as dependent scoped beans.
     * <p/>
     * This feature is only applicable when CDI processing is enabled,
     * i.e. when <code>beans.xml</code> file is present.
     * <p/>
     * This feature is an alternative to adding {@link ManagedBean} annotation
     * to managed bean types directly.
     * <p/>
     * The default value is false.
     */
    public static final String FEATURE_ALLOW_RAW_MANAGED_BEANS
            = "com.sun.jersey.config.feature.AllowRawManagedBeans";

    /**
     * A helper class for creating an injectable provider that supports
     * {@link Context} with a type and constant value.
     *
     * @param <T> the type of the constant value.
     */
    protected static class ContextInjectableProvider<T> extends
            SingletonTypeInjectableProvider<Context, T> {

        /**
         * Create a new instance.
         *
         * @param type the type of the constant value.
         * @param instance the constant value.
         */
        protected ContextInjectableProvider(Type type, T instance) {
            super(type, instance);
        }
    }

    private transient WebComponent webComponent;

    private transient FilterConfig filterConfig;

    private transient Pattern staticContentPattern;

    private transient boolean forwardOn404;

    private class InternalWebComponent extends WebComponent {

        InternalWebComponent() {
        }

        InternalWebComponent(Application app) {
            super(app);
        }

        @Override
        protected WebApplication create() {
            return ServletContainer.this.create();
        }

        @Override
        protected void configure(WebConfig wc, ResourceConfig rc, WebApplication wa) {
            super.configure(wc, rc, wa);

            ServletContainer.this.configure(wc, rc, wa);
        }

        @Override
        protected void initiate(ResourceConfig rc, WebApplication wa) {
            ServletContainer.this.initiate(rc, wa);
        }

        @Override
        protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props,
                                                          WebConfig wc) throws ServletException  {
            return ServletContainer.this.getDefaultResourceConfig(props, wc);
        }
    }

    private transient final Application app;

    public ServletContainer() {
        this.app = null;
    }

    public ServletContainer(Class<? extends Application> appClass) {
        this.app = new DeferredResourceConfig(appClass);
    }

    public ServletContainer(Application app) {
        this.app = app;
    }

    // GenericServlet

    /**
     * Get the servlet context for the servlet or filter, depending on
     * how this class is registered.
     * <p>
     * It is recommended that the {@link WebConfig} be utilized,
     * see the method {@link #getWebConfig() }, to obtain the servlet context
     * and initialization parameters for a servlet or filter.
     *
     * @return the servlet context for the servlet or filter.
     */
    @Override
    public ServletContext getServletContext() {
        if (filterConfig != null)
            return filterConfig.getServletContext();

        return super.getServletContext();
    }

    /**
     * Initiate the Web component.
     *
     * @param webConfig the Web configuration.
     *
     * @throws javax.servlet.ServletException in case of an initialization failure
     */
    protected void init(WebConfig webConfig) throws ServletException {
        webComponent = (app == null)
                ? new InternalWebComponent()
                : new InternalWebComponent(app);
        webComponent.init(webConfig);
    }

    /**
     * Get the Web configuration.
     *
     * @return the Web configuration.
     */
    protected WebConfig getWebConfig() {
        return webComponent.getWebConfig();
    }

    /**
     * Create a new instance of a {@link WebApplication}.
     *
     * @return the {@link WebApplication} instance.
     */
    protected WebApplication create() {
        return WebApplicationFactory.createWebApplication();
    }

    /**
     * Get the default resource configuration if one is not declared in the
     * web.xml.
     * <p>
     * This implementation returns an instance of {@link WebAppResourceConfig}
     * that scans in files and directories as declared by the
     * {@link ClasspathResourceConfig#PROPERTY_CLASSPATH} if present, otherwise
     * in the "WEB-INF/lib" and "WEB-INF/classes" directories.
     * <p>
     * An inheriting class may override this method to supply a different
     * default resource configuration implementation.
     *
     * @param props the properties to pass to the resource configuration.
     * @param wc the web configuration.
     * @return the default resource configuration.
     *
     * @throws javax.servlet.ServletException in case there was an error while retrieving the default resource config
     */
    protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props,
                                                      WebConfig wc) throws ServletException  {
        return webComponent.getWebAppResourceConfig(props, wc);
    }

    /**
     * Configure the {@link ResourceConfig}.
     * <p>
     * The {@link ResourceConfig} is configured such that the following classes
     * may be injected onto the field of a root resource class or a parameter
     * of a method of root resource class that is annotated with
     * {@link javax.ws.rs.core.Context}: {@link HttpServletRequest}, {@link HttpServletResponse}
     * , {@link ServletContext} and {@link WebConfig}.
     * <p>
     * Any root resource class in registered in the resource configuration
     * that is an interface is processed as follows.
     * If the class is an interface and there exists a JNDI named object
     * with the fully qualified class name as the JNDI name then that named
     * object is added as a singleton root resource and the class is removed
     * from the set of root resource classes.
     * <p>
     * An inheriting class may override this method to configure the
     * {@link ResourceConfig} to provide alternative or additional instances
     * that are resource or provider classes or instances, and may modify the
     * features and properties of the {@link ResourceConfig}. For an inheriting
     * class to extend configuration behaviour the overriding method MUST call
     * <code>super.configure(servletConfig, rc, wa)</code> as the first statement
     * of that method.
     * <p>
     * This method will be called only once at initiation. Subsequent
     * reloads of the Web application will not result in subsequence calls to
     * this method.
     *
     * @param wc the Web configuration
     * @param rc the Resource configuration
     * @param wa the Web application
     */
    protected void configure(WebConfig wc, ResourceConfig rc, WebApplication wa) {
        if (getServletConfig() != null)
            configure(getServletConfig(), rc, wa);
        else if (filterConfig != null)
            configure(filterConfig, rc, wa);

        if (rc instanceof ReloadListener) {
            List<ContainerNotifier> notifiers = new ArrayList<ContainerNotifier>();

            Object o = rc.getProperties().get(ResourceConfig.PROPERTY_CONTAINER_NOTIFIER);

            if (o instanceof ContainerNotifier)
                notifiers.add((ContainerNotifier) o);
            else if (o instanceof List)
                for (Object elem : (List) o)
                    if (elem instanceof ContainerNotifier)
                        notifiers.add((ContainerNotifier) elem);

            for (ContainerNotifier cn : ServiceFinder.find(ContainerNotifier.class)) {
                notifiers.add(cn);
            }

            rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_NOTIFIER, notifiers);
        }
    }

    /**
     * Initiate the {@link WebApplication}.
     * <p>
     * This method will be called once at initiation and for
     * each reload of the Web application.
     * <p>
     * An inheriting class may override this method to initiate the
     * Web application with different parameters.
     *
     * @param rc the Resource configuration
     * @param wa the Web application
     */
    protected void initiate(ResourceConfig rc, WebApplication wa) {
        wa.initiate(rc);
    }

    /**
     * Load the Web application. This will create, configure and initiate
     * the web application.
     */
    public void load() {
        webComponent.load();
    }

    /**
     * Reload the Web application. This will create and initiate the web
     * application using the same {@link ResourceConfig} implementation
     * that was used to load the Web application.
     * <p>
     * This method may be called at runtime, more than once, to reload the
     * Web application. For example, if a {@link ResourceConfig} implementation
     * is capable of detecting changes to resource classes (addition or removal)
     * or providers then this method may be invoked to reload the web
     * application for such changes to take effect.
     * <p>
     * If this method is called when there are pending requests then such
     * requests will be processed using the previously loaded web application.
     */
    public void reload() {
        webComponent.onReload();
    }

    /**
     * Dispatch client requests to a resource class.
     *
     * @param baseUri the base URI of the request.
     * @param requestUri the URI of the request.
     * @param request the {@link HttpServletRequest} object that
     *        contains the request the client made to
     *	      the Web component.
     * @param response the {@link HttpServletResponse} object that
     *        contains the response the Web component returns
     *        to the client.
     * @return the status code of the response.
     * @exception IOException if an input or output error occurs
     *            while the Web component is handling the
     *            HTTP request.
     * @exception ServletException if the HTTP request cannot
     *            be handled.
     */
    public int service(URI baseUri, URI requestUri, final HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        return webComponent.service(baseUri, requestUri, request, response);
    }

    /**
     * Destroy this Servlet or Filter.
     *
     */
    @Override
    public void destroy() {
        if (webComponent != null) {
            webComponent.destroy();
        }
    }


    // Servlet

    @Override
    public void init() throws ServletException {
        init(new WebServletConfig(this));
    }

    /**
     * Get the default resource configuration if one is not declared in the
     * web.xml.
     * <p>
     * This implementation returns an instance of {@link WebAppResourceConfig}
     * that scans in files and directories as declared by the
     * {@link ClasspathResourceConfig#PROPERTY_CLASSPATH} if present, otherwise
     * in the "WEB-INF/lib" and "WEB-INF/classes" directories.
     * <p>
     * An inheriting class may override this method to supply a different
     * default resource configuration implementation.
     *
     * @param props the properties to pass to the resource configuration.
     * @param servletConfig the servlet configuration.
     * @return the default resource configuration.
     *
     * @throws javax.servlet.ServletException in case there was an error while retrieving the default resource config
     * @deprecated methods should implement {@link #getDefaultResourceConfig(java.util.Map, com.sun.jersey.spi.container.servlet.WebConfig) }.
     */
    @Deprecated
    protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props,
                                                      ServletConfig servletConfig) throws ServletException  {
        return getDefaultResourceConfig(props, getWebConfig());
    }

    /**
     * Configure the {@link ResourceConfig} for a Servlet.
     * <p>
     * The {@link ResourceConfig} is configured such that the following classes
     * may be injected onto the field of a root resource class or a parameter
     * of a method of root resource class that is annotated with
     * {@link javax.ws.rs.core.Context}: {@link ServletConfig}.
     * <p>
     * An inheriting class may override this method to configure the
     * {@link ResourceConfig} to provide alternative or additional instances
     * that are resource or provider classes or instances, and may modify the
     * features and properties of the {@link ResourceConfig}. For an inheriting
     * class to extend configuration behaviour the overriding method MUST call
     * <code>super.configure(servletConfig, rc, wa)</code> as the first statement of that
     * method.
     * <p>
     * This method will be called only once at servlet initiation. Subsequent
     * reloads of the Web application will not result in subsequence calls to
     * this method.
     *
     * @param sc the Servlet configuration
     * @param rc the Resource configuration
     * @param wa the Web application
     */
    protected void configure(final ServletConfig sc, ResourceConfig rc, WebApplication wa) {
        rc.getSingletons().add(new ContextInjectableProvider<ServletConfig>(
                ServletConfig.class, sc));
    }

    /**
     * Dispatches client requests to the {@link #service(java.net.URI, java.net.URI, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)  }
     * method.
     *
     * @param request the {@link HttpServletRequest} object that
     *        contains the request the client made to
     *	      the servlet.
     * @param response the {@link HttpServletResponse} object that
     *        contains the response the servlet returns
     *        to the client.
     * @exception IOException if an input or output error occurs
     *            while the servlet is handling the
     *            HTTP request.
     * @exception ServletException if the HTTP request cannot
     *            be handled.
     */
    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /**
         * There is an annoying edge case where the service method is
         * invoked for the case when the URI is equal to the deployment URL
         * minus the '/', for example http://locahost:8080/HelloWorldWebApp
         */
        final String servletPath = request.getServletPath();
        String pathInfo = request.getPathInfo();
        StringBuffer requestURL = request.getRequestURL();
        String requestURI = request.getRequestURI();
        final boolean checkPathInfo = pathInfo == null || pathInfo.isEmpty() || pathInfo.equals("/");

        /**
         * The HttpServletRequest.getRequestURL() contains the complete URI
         * minus the query and fragment components.
         */
        UriBuilder absoluteUriBuilder;
        try {
            absoluteUriBuilder = UriBuilder.fromUri(requestURL.toString());
        } catch (IllegalArgumentException iae) {
            final Response.Status badRequest = Response.Status.BAD_REQUEST;
            response.sendError(badRequest.getStatusCode(), badRequest.getReasonPhrase());
            return;
        }

        if (checkPathInfo && !request.getRequestURI().endsWith("/")) {
            // Only do this if the last segment of the servlet path does not contain '.'
            // This handles the case when the extension mapping is used with the servlet
            // see issue 506
            // This solution does not require parsing the deployment descriptor,
            // however still leaves it broken for the very rare case if a standard path
            // servlet mapping would include dot in the last segment (e.g. /.webresources/*)
            // and somebody would want to hit the root resource without the trailing slash
            int i = servletPath.lastIndexOf("/");
            if (servletPath.substring(i + 1).indexOf('.') < 0) {
                if (webComponent.getResourceConfig().getFeature(ResourceConfig.FEATURE_REDIRECT)) {
                    URI l = absoluteUriBuilder.
                            path("/").
                            replaceQuery(request.getQueryString()).build();

                    response.setStatus(307);
                    response.setHeader("Location", l.toASCIIString());
                    return;
                } else {
                    requestURL.append("/");
                    requestURI += "/";
                }
            }
        }

        /**
         * The HttpServletRequest.getPathInfo() and
         * HttpServletRequest.getServletPath() are in decoded form.
         *
         * On some servlet implementations the getPathInfo() removed
         * contiguous '/' characters. This is problematic if URIs
         * are embedded, for example as the last path segment.
         * We need to work around this and not use getPathInfo
         * for the decodedPath.
         */
        final String decodedBasePath = request.getContextPath() + servletPath + "/";

        final String encodedBasePath = UriComponent.encode(decodedBasePath,
                UriComponent.Type.PATH);

        if (!decodedBasePath.equals(encodedBasePath)) {
            throw new ContainerException("The servlet context path and/or the " +
                    "servlet path contain characters that are percent encoded");
        }

        final URI baseUri = absoluteUriBuilder.replacePath(encodedBasePath).
                build();

        String queryParameters = request.getQueryString();
        if (queryParameters == null) {
            queryParameters = "";
        }

        final URI requestUri = absoluteUriBuilder.replacePath(requestURI).
                replaceQuery(queryParameters).
                build();

        service(baseUri, requestUri, request, response);
    }


    // Filter

    private String filterContextPath = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;

        init(new WebFilterConfig(filterConfig));
    }

    /**
     * @return the {@link Pattern} compiled from a regular expression that is
     * the property value of {@link #PROPERTY_WEB_PAGE_CONTENT_REGEX}.
     * A <code>null</code> value will be returned if the property is not present
     * is or an empty String.
     */
    public Pattern getStaticContentPattern() {
        return staticContentPattern;
    }

    /**
     * Configure the {@link ResourceConfig} for a Filter.
     * <p>
     * The {@link ResourceConfig} is configured such that the following classes
     * may be injected onto the field of a root resource class or a parameter
     * of a method of root resource class that is annotated with
     * {@link javax.ws.rs.core.Context}: {@link FilterConfig}.
     * <p>
     * An inheriting class may override this method to configure the
     * {@link ResourceConfig} to provide alternative or additional instances
     * that are resource or provider classes or instances, and may modify the
     * features and properties of the {@link ResourceConfig}. For an inheriting
     * class to extend configuration behaviour the overriding method MUST call
     * <code>super.configure(servletConfig, rc, wa)</code> as the first statement of that
     * method.
     * <p>
     * This method will be called only once at servlet initiation. Subsequent
     * reloads of the Web application will not result in subsequence calls to
     * this method.
     *
     * @param fc the Filter configuration
     * @param rc the Resource configuration
     * @param wa the Web application
     */
    protected void configure(final FilterConfig fc, ResourceConfig rc, WebApplication wa) {
        rc.getSingletons().add(new ContextInjectableProvider<FilterConfig>(
                FilterConfig.class, fc));

        String regex = (String)rc.getProperty(PROPERTY_WEB_PAGE_CONTENT_REGEX);
        if (regex != null && regex.length() > 0) {
            try {
                staticContentPattern = Pattern.compile(regex);
            } catch (PatternSyntaxException ex) {
                throw new ContainerException(
                        "The syntax is invalid for the regular expression, " + regex +
                                ", associated with the initialization parameter " + PROPERTY_WEB_PAGE_CONTENT_REGEX, ex);
            }
        }

        forwardOn404 = rc.getFeature(FEATURE_FILTER_FORWARD_ON_404);

        this.filterContextPath = filterConfig.getInitParameter(PROPERTY_FILTER_CONTEXT_PATH);
        if (filterContextPath != null) {
            if (filterContextPath.isEmpty()) {
                filterContextPath = null;
            } else {
                if (!filterContextPath.startsWith("/")) {
                    filterContextPath = '/' + filterContextPath;
                }
                if (filterContextPath.endsWith("/")) {
                    filterContextPath = filterContextPath.substring(0, filterContextPath.length() - 1);
                }
            }
        }
    }

    /**
     * Dispatches client requests to the {@link #doFilter(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) }
     * method.
     *
     * @param request the {@link HttpServletRequest} object that
     *        contains the request the client made to
     *        the servlet.
     * @param response the {@link HttpServletResponse} object that
     *        contains the response the servlet returns
     *        to the client.
     * @param chain the chain of filters from which the next filter can be invoked.
     * @throws java.io.IOException
     * @throws javax.servlet.ServletException
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain);
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
    }

    /**
     * Dispatches client requests to the {@link #service(java.net.URI, java.net.URI, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)  }
     * method.
     * <p>
     * If the servlet path matches the regular expression declared by the
     * property {@link #PROPERTY_WEB_PAGE_CONTENT_REGEX} then the request
     * is forwarded to the next filter in the filter chain so that the
     * underlying servlet engine can process the request otherwise Jersey
     * will process the request.
     *
     * @param request the {@link HttpServletRequest} object that
     *        contains the request the client made to
     *        the servlet.
     * @param response the {@link HttpServletResponse} object that
     *        contains the response the servlet returns
     *        to the client.
     * @param chain the chain of filters from which the next filter can be invoked.
     * @throws java.io.IOException
     * @throws javax.servlet.ServletException
     */
    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request.getAttribute("javax.servlet.include.request_uri") != null) {
            final String includeRequestURI = (String)request.getAttribute("javax.servlet.include.request_uri");

            if (!includeRequestURI.equals(request.getRequestURI())) {
                doFilter(request, response, chain,
                        includeRequestURI,
                        (String)request.getAttribute("javax.servlet.include.servlet_path"),
                        (String)request.getAttribute("javax.servlet.include.query_string"));
                return;
            }
        }

        /**
         * JERSEY-880 - WAS interprets HttpServletRequest#getServletPath() and HttpServletRequest#getPathInfo()
         * differently when accessing a static resource.
         */
        final String servletPath = request.getServletPath()
                + (request.getPathInfo() == null ? "" : request.getPathInfo());

        doFilter(request, response, chain,
                request.getRequestURI(),
                servletPath,
                request.getQueryString());
    }

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                          String requestURI, String servletPath, String queryString) throws IOException, ServletException {
        // if we match the static content regular expression lets delegate to
        // the filter chain to use the default container servlets & handlers
        final Pattern p = getStaticContentPattern();
        if (p != null && p.matcher(servletPath).matches()) {
            chain.doFilter(request, response);
            return;
        }

        if (filterContextPath != null) {
            if (!servletPath.startsWith(filterContextPath)) {
                throw new ContainerException("The servlet path, \"" + servletPath +
                        "\", does not start with the filter context path, \"" + filterContextPath + "\"");
            } else if (servletPath.length() == filterContextPath.length()) {
                // Path does not end in a slash, may need to redirect
                if (webComponent.getResourceConfig().getFeature(ResourceConfig.FEATURE_REDIRECT)) {
                    URI l = UriBuilder.fromUri(request.getRequestURL().toString()).
                            path("/").
                            replaceQuery(queryString).build();

                    response.setStatus(307);
                    response.setHeader("Location", l.toASCIIString());
                    return;
                } else {
                    requestURI += "/";
                }
            }
        }

        final UriBuilder absoluteUriBuilder = UriBuilder.fromUri(
                request.getRequestURL().toString());

        final URI baseUri = (filterContextPath == null)
                ? absoluteUriBuilder.replacePath(request.getContextPath()).
                path("/").
                build()
                : absoluteUriBuilder.replacePath(request.getContextPath()).
                path(filterContextPath).
                path("/").
                build();

        final URI requestUri = absoluteUriBuilder.replacePath(requestURI).
                replaceQuery(queryString).
                build();

        final int status = service(baseUri, requestUri, request, response);

        // If forwarding is configured and response is a 404 with no entity
        // body then call the next filter in the chain
        if (forwardOn404 && status == 404 && !response.isCommitted()) {
            // lets clear the response to OK before we forward to the next in the chain
            // as OK is the default set by servlet containers before filters/servlets do any wor
            // so lets hide our footsteps and pretend we were never in the chain at all and let the
            // next filter or servlet return the 404 if they can't find anything to return
            //
            // We could add an optional flag to disable this step if anyone can ever find a case where
            // this causes a problem, though I suspect any problems will really be with downstream
            // servlets not correctly setting an error status if they cannot find something to return
            response.setStatus(HttpServletResponse.SC_OK);
            chain.doFilter(request, response);
        }
    }
}
