/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2013 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.server.impl.application;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;

import javax.xml.bind.JAXBContext;

import com.sun.jersey.api.NotFoundException;
import com.sun.jersey.api.container.ContainerException;
import com.sun.jersey.api.container.MappableContainerException;
import com.sun.jersey.api.container.filter.UriConnegFilter;
import com.sun.jersey.api.core.ExtendedUriInfo;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.api.core.InjectParam;
import com.sun.jersey.api.core.ParentRef;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.api.core.ResourceConfigurator;
import com.sun.jersey.api.core.ResourceContext;
import com.sun.jersey.api.model.AbstractResource;
import com.sun.jersey.api.model.AbstractResourceMethod;
import com.sun.jersey.api.model.AbstractResourceModelContext;
import com.sun.jersey.api.model.AbstractResourceModelListener;
import com.sun.jersey.api.model.AbstractSubResourceLocator;
import com.sun.jersey.api.model.ResourceModelIssue;
import com.sun.jersey.core.header.MediaTypes;
import com.sun.jersey.core.reflection.ReflectionHelper;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.core.spi.component.ProviderFactory;
import com.sun.jersey.core.spi.component.ProviderServices;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProcessor;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProcessorFactory;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProcessorFactoryInitializer;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
import com.sun.jersey.core.spi.component.ioc.IoCProviderFactory;
import com.sun.jersey.core.spi.factory.ContextResolverFactory;
import com.sun.jersey.core.spi.factory.MessageBodyFactory;
import com.sun.jersey.core.util.FeaturesAndProperties;
import com.sun.jersey.impl.ImplMessages;
import com.sun.jersey.server.impl.BuildId;
import com.sun.jersey.server.impl.ThreadLocalHttpContext;
import com.sun.jersey.server.impl.component.IoCResourceFactory;
import com.sun.jersey.server.impl.component.ResourceFactory;
import com.sun.jersey.server.impl.container.filter.FilterFactory;
import com.sun.jersey.server.impl.inject.ServerInjectableProviderContext;
import com.sun.jersey.server.impl.inject.ServerInjectableProviderFactory;
import com.sun.jersey.server.impl.model.ResourceUriRules;
import com.sun.jersey.server.impl.model.RulesMap;
import com.sun.jersey.server.impl.model.parameter.CookieParamInjectableProvider;
import com.sun.jersey.server.impl.model.parameter.FormParamInjectableProvider;
import com.sun.jersey.server.impl.model.parameter.HeaderParamInjectableProvider;
import com.sun.jersey.server.impl.model.parameter.HttpContextInjectableProvider;
import com.sun.jersey.server.impl.model.parameter.MatrixParamInjectableProvider;
import com.sun.jersey.server.impl.model.parameter.PathParamInjectableProvider;
import com.sun.jersey.server.impl.model.parameter.QueryParamInjectableProvider;
import com.sun.jersey.server.impl.model.parameter.multivalued.MultivaluedParameterExtractorFactory;
import com.sun.jersey.server.impl.model.parameter.multivalued.MultivaluedParameterExtractorProvider;
import com.sun.jersey.server.impl.model.parameter.multivalued.StringReaderFactory;
import com.sun.jersey.server.impl.modelapi.annotation.IntrospectionModeller;
import com.sun.jersey.server.impl.modelapi.validation.BasicValidator;
import com.sun.jersey.server.impl.monitoring.MonitoringProviderFactory;
import com.sun.jersey.server.impl.resource.PerRequestFactory;
import com.sun.jersey.server.impl.template.TemplateFactory;
import com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule;
import com.sun.jersey.server.impl.wadl.WadlApplicationContextInjectionProxy;
import com.sun.jersey.server.impl.wadl.WadlFactory;
import com.sun.jersey.server.spi.component.ResourceComponentInjector;
import com.sun.jersey.server.spi.component.ResourceComponentProvider;
import com.sun.jersey.server.wadl.WadlApplicationContext;
import com.sun.jersey.spi.MessageBodyWorkers;
import com.sun.jersey.spi.StringReaderWorkers;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ContainerResponseWriter;
import com.sun.jersey.spi.container.ExceptionMapperContext;
import com.sun.jersey.spi.container.ResourceMethodCustomInvokerDispatchFactory;
import com.sun.jersey.spi.container.ResourceMethodDispatchProvider;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.WebApplicationListener;
import com.sun.jersey.spi.inject.Errors;
import com.sun.jersey.spi.inject.Inject;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import com.sun.jersey.spi.inject.InjectableProviderContext;
import com.sun.jersey.spi.inject.ServerSide;
import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider;
import com.sun.jersey.spi.monitoring.DispatchingListener;
import com.sun.jersey.spi.monitoring.RequestListener;
import com.sun.jersey.spi.monitoring.ResponseListener;
import com.sun.jersey.spi.service.ServiceFinder;
import com.sun.jersey.spi.template.TemplateContext;
import com.sun.jersey.spi.uri.rules.UriRule;
import com.sun.jersey.spi.uri.rules.UriRules;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * A Web application that contains a set of resources, each referenced by
 * an absolute URI template.
 *
 * @author Paul.Sandoz@Sun.Com
 */
public final class WebApplicationImpl implements WebApplication {

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

    private final Map<Class, AbstractResource> abstractResourceMap =
            new HashMap<Class, AbstractResource>();

    private final ConcurrentMap<Class, UriRules<UriRule>> rulesMap =
            new ConcurrentHashMap<Class, UriRules<UriRule>>();

    private final ConcurrentMap<Class, ResourceComponentProvider> providerMap =
            new ConcurrentHashMap<Class, ResourceComponentProvider>();

    private final ConcurrentMap<Class, ResourceComponentProvider> singletonMap =
            new ConcurrentHashMap<Class, ResourceComponentProvider>();

    private static class ClassAnnotationKey {
        private final Class c;

        private final Set<Annotation> as;

        public ClassAnnotationKey(Class c, Annotation[] as) {
            this.c = c;
            this.as = new HashSet<Annotation>(Arrays.asList(as));
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 67 * hash + (this.c != null ? this.c.hashCode() : 0);
            hash = 67 * hash + (this.as != null ? this.as.hashCode() : 0);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ClassAnnotationKey other = (ClassAnnotationKey) obj;
            if (this.c != other.c && (this.c == null || !this.c.equals(other.c))) {
                return false;
            }
            if (this.as != other.as && (this.as == null || !this.as.equals(other.as))) {
                return false;
            }
            return true;
        }
    }

    private final ConcurrentMap<ClassAnnotationKey, ResourceComponentProvider> providerWithAnnotationKeyMap =
            new ConcurrentHashMap<ClassAnnotationKey, ResourceComponentProvider>();

    private final ThreadLocalHttpContext context;

    private final CloseableServiceFactory closeableFactory;

    private boolean initiated;

    private ResourceConfig resourceConfig;

    private RootResourceClassesRule rootsRule;

    private ServerInjectableProviderFactory injectableFactory;

    private ProviderFactory cpFactory;

    private ResourceFactory rcpFactory;

    private IoCComponentProviderFactory provider;

    private List<IoCComponentProviderFactory> providerFactories;

    private Providers providers;

    private MessageBodyFactory bodyFactory;

    private StringReaderFactory stringReaderFactory;

    private TemplateContext templateContext;

    private ExceptionMapperFactory exceptionFactory;

    private ResourceMethodDispatchProvider dispatcherFactory;

    private ResourceContext resourceContext;

    private Set<AbstractResource> abstractRootResources;

    private Map<String, AbstractResource> explicitAbstractRootResources;

    private final AbstractResourceModelContext armContext = new AbstractResourceModelContext() {

        @Override
        public Set<AbstractResource> getAbstractRootResources() {
            return abstractRootResources;
        }
    };

    private FilterFactory filterFactory;

    private WadlFactory wadlFactory;

    private boolean isTraceEnabled;

    private RequestListener requestListener;

    private DispatchingListenerProxy dispatchingListener;

    private ResponseListener responseListener;

    public WebApplicationImpl() {
        this.context = new ThreadLocalHttpContext();

        InvocationHandler requestHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                try {
                    return method.invoke(context.getRequest(), args);
                } catch (IllegalAccessException ex) {
                    throw new IllegalStateException(ex);
                } catch (InvocationTargetException ex) {
                    throw ex.getTargetException();
                }
            }
        };
        InvocationHandler uriInfoHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                try {
                    return method.invoke(context.getUriInfo(), args);
                } catch (IllegalAccessException ex) {
                    throw new IllegalStateException(ex);
                } catch (InvocationTargetException ex) {
                    throw ex.getTargetException();
                }
            }
        };

        // Create injectable provider factory
        this.injectableFactory = new ServerInjectableProviderFactory();
        injectableFactory.add(new ContextInjectableProvider<InjectableProviderContext>(
                InjectableProviderContext.class, injectableFactory));
        injectableFactory.add(new ContextInjectableProvider<ServerInjectableProviderContext>(
                ServerInjectableProviderContext.class, injectableFactory));

        // Add proxied injectables
        final Map<Type, Object> m = new HashMap<Type, Object>();
        m.put(HttpContext.class, context);
        m.put(HttpHeaders.class, createProxy(HttpHeaders.class, requestHandler));
        m.put(UriInfo.class, createProxy(UriInfo.class, uriInfoHandler));
        m.put(ExtendedUriInfo.class, createProxy(ExtendedUriInfo.class, uriInfoHandler));
        m.put(Request.class, createProxy(Request.class, requestHandler));
        m.put(SecurityContext.class, createProxy(SecurityContext.class, requestHandler));
        injectableFactory.add(new InjectableProvider<Context, Type>() {
            @Override
            public ComponentScope getScope() {
                return ComponentScope.Singleton;
            }

            @Override
            public Injectable getInjectable(ComponentContext ic, Context a, Type c) {
                final Object o = m.get(c);
                if (o != null) {
                    return new Injectable() {
                        @Override
                        public Object getValue() {
                            return o;
                        }
                    };
                } else
                    return null;
            }
        });

        injectableFactory.add(new InjectableProvider<Context, Type>() {
            @Override
            public ComponentScope getScope() {
                return ComponentScope.Singleton;
            }

            @Override
            public Injectable<Injectable> getInjectable(ComponentContext ic, Context a, Type c) {
                if (c instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType)c;
                    if (pt.getRawType() == Injectable.class) {
                        if (pt.getActualTypeArguments().length == 1) {
                            final Injectable<?> i = injectableFactory.getInjectable(
                                    a.annotationType(),
                                    ic,
                                    a,
                                    pt.getActualTypeArguments()[0],
                                    ComponentScope.PERREQUEST_UNDEFINED_SINGLETON);
                            if (i == null)
                                return null;
                            return new Injectable<Injectable>() {
                                @Override
                                public Injectable getValue() {
                                    return i;
                                }
                            };
                        }
                    }
                }

                return null;
            }
        });

        injectableFactory.add(new InjectableProvider<Inject, Type>() {
            @Override
            public ComponentScope getScope() {
                return ComponentScope.Singleton;
            }

            @Override
            public Injectable<Injectable> getInjectable(ComponentContext ic, Inject a, Type c) {
                if (c instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType)c;
                    if (pt.getRawType() == Injectable.class) {
                        if (pt.getActualTypeArguments().length == 1) {
                            final Injectable<?> i = injectableFactory.getInjectable(
                                    a.annotationType(),
                                    ic,
                                    a,
                                    pt.getActualTypeArguments()[0],
                                    ComponentScope.PERREQUEST_UNDEFINED_SINGLETON);
                            if (i == null)
                                return null;
                            return new Injectable<Injectable>() {
                                @Override
                                public Injectable getValue() {
                                    return i;
                                }
                            };
                        }
                    }
                }

                return null;
            }
        });

        injectableFactory.add(new InjectableProvider<InjectParam, Type>() {
            @Override
            public ComponentScope getScope() {
                return ComponentScope.Singleton;
            }

            @Override
            public Injectable<Injectable> getInjectable(ComponentContext ic, InjectParam a, Type c) {
                if (c instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType)c;
                    if (pt.getRawType() == Injectable.class) {
                        if (pt.getActualTypeArguments().length == 1) {
                            final Injectable<?> i = injectableFactory.getInjectable(
                                    a.annotationType(),
                                    ic,
                                    a,
                                    pt.getActualTypeArguments()[0],
                                    ComponentScope.PERREQUEST_UNDEFINED_SINGLETON);
                            if (i == null)
                                return null;
                            return new Injectable<Injectable>() {
                                @Override
                                public Injectable getValue() {
                                    return i;
                                }
                            };
                        }
                    }
                }

                return null;
            }
        });

        closeableFactory = new CloseableServiceFactory(context);
        injectableFactory.add(closeableFactory);
    }

    private class ComponentProcessorImpl implements IoCComponentProcessor {
        private final ResourceComponentInjector rci;

        ComponentProcessorImpl(ResourceComponentInjector rci) {
            this.rci = rci;
        }

        @Override
        public void preConstruct() {
        }

        @Override
        public void postConstruct(Object o) {
            rci.inject(context.get(), o);
        }
    }

    private static final IoCComponentProcessor NULL_COMPONENT_PROCESSOR = new IoCComponentProcessor() {
        @Override
        public void preConstruct() {
        }

        @Override
        public void postConstruct(Object o) {
        }
    };

    private class ComponentProcessorFactoryImpl implements IoCComponentProcessorFactory {
        private final ConcurrentMap<Class, IoCComponentProcessor> componentProcessorMap =
                new ConcurrentHashMap<Class, IoCComponentProcessor>();

        @Override
        public ComponentScope getScope(Class c) {
            return rcpFactory.getScope(c);
        }

        @Override
        public IoCComponentProcessor get(final Class c, final ComponentScope scope) {
            IoCComponentProcessor cp = componentProcessorMap.get(c);
            if (cp != null) {
                return (cp == NULL_COMPONENT_PROCESSOR) ? null : cp;
            }

            synchronized (abstractResourceMap) {
                cp = componentProcessorMap.get(c);
                if (cp != null) {
                    return (cp == NULL_COMPONENT_PROCESSOR) ? null : cp;
                }

                final ResourceComponentInjector rci = Errors.processWithErrors(new Errors.Closure<ResourceComponentInjector>() {
                    @Override
                    public ResourceComponentInjector f() {
                        return new ResourceComponentInjector(
                                injectableFactory, scope, getAbstractResource(c));
                    }
                });

                if (rci.hasInjectableArtifacts()) {
                    cp = new ComponentProcessorImpl(rci);
                    componentProcessorMap.put(c, cp);
                } else {
                    cp = null;
                    componentProcessorMap.put(c, NULL_COMPONENT_PROCESSOR);
                }
            }
            return cp;
        }
    }

    @Override
    public FeaturesAndProperties getFeaturesAndProperties() {
        return resourceConfig;
    }

    @Override
    public WebApplication clone() {
        WebApplicationImpl wa = new WebApplicationImpl();
        wa.initiate(resourceConfig, provider);
        return wa;
    }

    /* package */ UriRules<UriRule> getUriRules(final Class c) {
        assert c != null;

        // Try the non-blocking read, the most common operation
        UriRules<UriRule> r = rulesMap.get(c);
        if (r != null) {
            return r;
        }

        // Not present use a synchronized block to ensure that only one
        // instance is created and put to the map
        synchronized (abstractResourceMap) {
            // One or more threads may have been blocking on the synchronized
            // block, re-check the map
            r = rulesMap.get(c);
            if (r != null) {
                return r;
            }

            r = Errors.processWithErrors(new Errors.Closure<ResourceUriRules>() {
                @Override
                public ResourceUriRules f() {
                    return newResourceUriRules(getAbstractResource(c));
                }
            }).getRules();
            rulesMap.put(c, r);
        }
        return r;
    }

    /* package */ ResourceComponentProvider getResourceComponentProvider(final Class c) {
        return getOrCreateResourceComponentProvider(c, false);
    }

    /* package */ ResourceComponentProvider getOrCreateResourceComponentProvider(final Class c, final boolean create) {
        assert c != null;

        // Try the non-blocking read, the most common operation
        ResourceComponentProvider rcp = providerMap.get(c);
        if (rcp != null) {
            return rcp;
        }
        if (!create && singletonMap.containsKey(c)) {
            return singletonMap.get(c);
        }

        // Not present use a synchronized block to ensure that only one
        // instance is created and put to the map
        synchronized (abstractResourceMap) {
            // One or more threads may have been blocking on the synchronized
            // block, re-check the map
            rcp = providerMap.get(c);
            if (rcp != null) {
                return rcp;
            }

            final ResourceComponentProvider _rcp = rcp = rcpFactory.getComponentProvider(null, c);
            Errors.processWithErrors(new Errors.Closure<Void>() {
                @Override
                public Void f() {
                    _rcp.init(getAbstractResource(c));
                    return null;
                }
            });

            providerMap.put(c, rcp);
        }
        return rcp;
    }

    /* package */ ResourceComponentProvider getResourceComponentProvider(final ComponentContext cc, final Class c) {
        assert c != null;

        if (cc == null || cc.getAnnotations().length == 0) {
            return getOrCreateResourceComponentProvider(c, true);
        }

        if (cc.getAnnotations().length == 1) {
            final Annotation a = cc.getAnnotations()[0];
            if (a.annotationType() == Inject.class) {
                final Inject i = Inject.class.cast(a);
                final String value = (i.value() != null)
                        ? i.value().trim()
                        : "";
                if (value.isEmpty()) {
                    return getOrCreateResourceComponentProvider(c, true);
                }
            } else if (a.annotationType() == InjectParam.class) {
                final InjectParam i = InjectParam.class.cast(a);
                final String value = (i.value() != null)
                        ? i.value().trim()
                        : "";
                if (value.isEmpty()) {
                    return getOrCreateResourceComponentProvider(c, true);
                }
            }
        }

        final ClassAnnotationKey cak = new ClassAnnotationKey(c,
                cc.getAnnotations());

        // Try the non-blocking read, the most common operation
        ResourceComponentProvider rcp = providerWithAnnotationKeyMap.get(cak);
        if (rcp != null) {
            return rcp;
        }

        // Not present use a synchronized block to ensure that only one
        // instance is created and put to the map
        synchronized (abstractResourceMap) {
            // One or more threads may have been blocking on the synchronized
            // block, re-check the map
            rcp = providerWithAnnotationKeyMap.get(cak);
            if (rcp != null) {
                return rcp;
            }

            final ResourceComponentProvider _rcp = rcp = rcpFactory.getComponentProvider(cc, c);
            Errors.processWithErrors(new Errors.Closure<Void>() {
                @Override
                public Void f() {
                    _rcp.init(getAbstractResource(c));
                    return null;
                }
            });

            providerWithAnnotationKeyMap.put(cak, rcp);
        }
        return rcp;
    }

    /* package */ void initiateResource(AbstractResource ar) {
        initiateResource(ar.getResourceClass());
    }

    /* package */ void initiateResource(Class c) {
        getUriRules(c);
        getOrCreateResourceComponentProvider(c, true);
    }

    /* package */ void initiateResource(AbstractResource ar, final Object resource) {
        final Class c = ar.getResourceClass();
        getUriRules(c);

        if (!singletonMap.containsKey(c)) {
            singletonMap.put(c, new ResourceComponentProvider() {
                @Override
                public void init(AbstractResource abstractResource) {
                }

                @Override
                public ComponentScope getScope() {
                    return ComponentScope.Singleton;
                }

                @Override
                public Object getInstance(HttpContext hc) {
                    return getInstance();
                }

                @Override
                public void destroy() {
                }

                @Override
                public Object getInstance() {
                    return resource;
                }
            });
        }
    }

    /* package */ Set<AbstractResource> getAbstractRootResources() {
        return abstractRootResources;
    }

    /* package */ Map<String, AbstractResource> getExplicitAbstractRootResources() {
        return explicitAbstractRootResources;
    }

    private ResourceUriRules newResourceUriRules(final AbstractResource ar) {
        assert null != ar;

        BasicValidator validator = new BasicValidator();
        validator.validate(ar);
        for (ResourceModelIssue issue : validator.getIssueList()) {
            Errors.error(issue.getMessage(), issue.isFatal());
        }
        return new ResourceUriRules(
                resourceConfig,
                getDispatchProvider(),
                injectableFactory,
                filterFactory,
                wadlFactory,
                dispatchingListener,
                ar);
    }

    protected ResourceMethodDispatchProvider getDispatchProvider() {
        return dispatcherFactory;
    }

    @Override
    public RequestListener getRequestListener() {
        return requestListener;
    }

    @Override
    public DispatchingListener getDispatchingListener() {
        return dispatchingListener;
    }

    @Override
    public ResponseListener getResponseListener() {
        return responseListener;
    }

    /* package */ AbstractResource getAbstractResource(Object o) {
        return getAbstractResource(o.getClass());
    }

    /* package */ AbstractResource getAbstractResource(Class c) {
        AbstractResource ar = abstractResourceMap.get(c);
        if (ar == null) {
            ar = IntrospectionModeller.createResource(c);
            abstractResourceMap.put(c, ar);
        }

        return ar;
    }

    private static class ContextInjectableProvider<T> extends
            SingletonTypeInjectableProvider<Context, T> {
        ContextInjectableProvider(Type type, T instance) {
            super(type, instance);
        }
    }

    @Override
    public boolean isInitiated () {
        return initiated;
    }

    @Override
    public void initiate(ResourceConfig resourceConfig) {
        initiate(resourceConfig, null);
    }

    @Override
    public void initiate(final ResourceConfig rc, final IoCComponentProviderFactory _provider) {
        Errors.processWithErrors(new Errors.Closure<Void>() {
            @Override
            public Void f() {
                Errors.setReportMissingDependentFieldOrMethod(false);
                _initiate(rc, _provider);
                return null;
            }
        });
    }

    private void _initiate(final ResourceConfig rc, final IoCComponentProviderFactory _provider) {
        if (rc == null) {
            throw new IllegalArgumentException("ResourceConfig instance MUST NOT be null");
        }

        if (initiated) {
            throw new ContainerException(ImplMessages.WEB_APP_ALREADY_INITIATED());
        }
        this.initiated = true;

        LOGGER.info("Initiating Jersey application, version '" + BuildId.getBuildId() + "'");

        // If there are components defined in jaxrs-components then
        // wrap resource config with appended set of classes
        Class<?>[] components = ServiceFinder.find("jersey-server-components").toClassArray();
        if (components.length > 0) {
            if (LOGGER.isLoggable(Level.INFO)) {
                StringBuilder b = new StringBuilder();
                b.append("Adding the following classes declared in META-INF/services/jersey-server-components to the resource configuration:");
                for (Class c : components)
                    b.append('\n').append("  ").append(c);
                LOGGER.log(Level.INFO, b.toString());
            }

            this.resourceConfig = rc.clone();
            this.resourceConfig.getClasses().addAll(Arrays.asList(components));
        } else {
            this.resourceConfig = rc;
        }

        this.provider = _provider;

        this.providerFactories = new ArrayList<IoCComponentProviderFactory>(2);

        for (Object o : resourceConfig.getProviderSingletons()) {
            if (o instanceof IoCComponentProviderFactory) {
                providerFactories.add((IoCComponentProviderFactory)o);
            }
        }

        if (_provider != null)
            providerFactories.add(_provider);

        // Set up the component provider factory to be
        // used with non-resource class components
        this.cpFactory = (providerFactories.isEmpty())
                ? new ProviderFactory(injectableFactory)
                : new IoCProviderFactory(injectableFactory, providerFactories);

        // Set up the resource component provider factory
        this.rcpFactory = (providerFactories.isEmpty())
                ? new ResourceFactory(this.resourceConfig, this.injectableFactory)
                : new IoCResourceFactory(this.resourceConfig, this.injectableFactory, providerFactories);

        // Initiate IoCComponentProcessorFactoryInitializer
        for (IoCComponentProviderFactory f : providerFactories) {
            IoCComponentProcessorFactory cpf;
            if (f instanceof IoCComponentProcessorFactoryInitializer) {
                cpf = new ComponentProcessorFactoryImpl();
                IoCComponentProcessorFactoryInitializer i = (IoCComponentProcessorFactoryInitializer)f;
                i.init(cpf);
            }

        }

        this.resourceContext = new ResourceContext() {
            @Override
            public ExtendedUriInfo matchUriInfo(URI u) throws ContainerException {
                try {
                    return handleMatchResourceRequest(u);
                } catch (ContainerException ex) {
                    throw ex;
                } catch (WebApplicationException ex) {
                    if (ex.getResponse().getStatus() == 404) {
                        return null;
                    } else {
                        throw new ContainerException(ex);
                    }
                } catch (RuntimeException ex) {
                    throw new ContainerException(ex);
                }
            }

            @Override
            public Object matchResource(URI u) throws ContainerException {
                ExtendedUriInfo ui = matchUriInfo(u);
                return (ui != null) ? ui.getMatchedResources().get(0) : null;
            }

            @Override
            public <T> T matchResource(URI u, Class<T> c) throws ContainerException, ClassCastException {
                return c.cast(matchResource(u));
            }

            @Override
            public <T> T getResource(Class<T> c) {
                return c.cast(getResourceComponentProvider(c).getInstance(context));
            }
        };

        final ProviderServices providerServices = new ProviderServices(
                ServerSide.class,
                this.cpFactory,
                resourceConfig.getProviderClasses(),
                resourceConfig.getProviderSingletons());

        injectableFactory.add(new ContextInjectableProvider<ProviderServices>(
                ProviderServices.class, providerServices));

        injectableFactory.add(new ContextInjectableProvider<ResourceMethodCustomInvokerDispatchFactory>(
                ResourceMethodCustomInvokerDispatchFactory.class, new ResourceMethodCustomInvokerDispatchFactory(providerServices)));

        // Add injectable provider for @ParentRef
        injectableFactory.add(
                new InjectableProvider<ParentRef, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.PerRequest;
                    }

                    @Override
                    public Injectable<Object> getInjectable(ComponentContext cc, ParentRef a, Type t) {
                        if (!(t instanceof Class))
                            return null;

                        final Class target = ReflectionHelper.getDeclaringClass(cc.getAccesibleObject());
                        final Class inject = (Class)t;
                        return new Injectable<Object>() {
                            @Override
                            public Object getValue() {
                                final UriInfo ui = context.getUriInfo();
                                final List l = ui.getMatchedResources();

                                final Object parent = getParent(l, target);
                                if (parent == null) return null;
                                try {
                                    return inject.cast(parent);
                                } catch (ClassCastException ex) {
                                    throw new ContainerException(
                                            "The parent resource is expected to be of class " + inject.getName() +
                                                    " but is of class " + parent.getClass().getName(),
                                            ex);
                                }
                            }

                            private Object getParent(List l, Class target) {
                                if (l.isEmpty()) {
                                    return null;
                                } else if (l.size() == 1) {
                                    return (l.get(0).getClass() == target) ? null : l.get(0);
                                } else {
                                    return (l.get(0).getClass() == target) ? l.get(1) : l.get(0);
                                }
                            }
                        };
                    }


                });

        // Add injectable provider for @Inject

        injectableFactory.add(
                new InjectableProvider<Inject, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.PerRequest;
                    }

                    @Override
                    public Injectable<Object> getInjectable(ComponentContext cc, Inject a, Type t) {
                        if (!(t instanceof Class))
                            return null;

                        final ResourceComponentProvider rcp = getResourceComponentProvider(cc, (Class)t);

                        return new Injectable<Object>() {
                            @Override
                            public Object getValue() {
                                return rcp.getInstance(context);
                            }
                        };
                    }

                });

        injectableFactory.add(
                new InjectableProvider<Inject, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.Undefined;
                    }

                    @Override
                    public Injectable<Object> getInjectable(ComponentContext cc, Inject a, Type t) {
                        if (!(t instanceof Class))
                            return null;

                        final ResourceComponentProvider rcp = getResourceComponentProvider(cc, (Class)t);
                        if (rcp.getScope() == ComponentScope.PerRequest)
                            return null;

                        return new Injectable<Object>() {
                            @Override
                            public Object getValue() {
                                return rcp.getInstance(context);
                            }
                        };
                    }

                });

        injectableFactory.add(
                new InjectableProvider<Inject, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.Singleton;
                    }

                    @Override
                    public Injectable<Object> getInjectable(ComponentContext cc, Inject a, Type t) {
                        if (!(t instanceof Class))
                            return null;

                        final ResourceComponentProvider rcp = getResourceComponentProvider(cc, (Class)t);
                        if (rcp.getScope() != ComponentScope.Singleton)
                            return null;

                        return new Injectable<Object>() {
                            @Override
                            public Object getValue() {
                                return rcp.getInstance(context);
                            }
                        };
                    }

                });

        // Add injectable provider for @ResourceRef

        injectableFactory.add(
                new InjectableProvider<InjectParam, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.PerRequest;
                    }

                    @Override
                    public Injectable<Object> getInjectable(ComponentContext cc, InjectParam a, Type t) {
                        if (!(t instanceof Class))
                            return null;

                        final ResourceComponentProvider rcp = getResourceComponentProvider(cc, (Class)t);

                        return new Injectable<Object>() {
                            @Override
                            public Object getValue() {
                                return rcp.getInstance(context);
                            }
                        };
                    }

                });

        injectableFactory.add(
                new InjectableProvider<InjectParam, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.Undefined;
                    }

                    @Override
                    public Injectable<Object> getInjectable(ComponentContext cc, InjectParam a, Type t) {
                        if (!(t instanceof Class))
                            return null;

                        final ResourceComponentProvider rcp = getResourceComponentProvider(cc, (Class)t);
                        if (rcp.getScope() == ComponentScope.PerRequest)
                            return null;

                        return new Injectable<Object>() {
                            @Override
                            public Object getValue() {
                                return rcp.getInstance(context);
                            }
                        };
                    }

                });

        injectableFactory.add(
                new InjectableProvider<InjectParam, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.Singleton;
                    }

                    @Override
                    public Injectable<Object> getInjectable(ComponentContext cc, InjectParam a, Type t) {
                        if (!(t instanceof Class))
                            return null;

                        final ResourceComponentProvider rcp = getResourceComponentProvider(cc, (Class)t);
                        if (rcp.getScope() != ComponentScope.Singleton)
                            return null;

                        return new Injectable<Object>() {
                            @Override
                            public Object getValue() {
                                return rcp.getInstance(context);
                            }
                        };
                    }

                });

        // Allow injection of features and properties
        injectableFactory.add(new ContextInjectableProvider<FeaturesAndProperties>(
                FeaturesAndProperties.class, resourceConfig));

        // Allow injection of resource config
        // Since the resourceConfig reference can change refer to the
        // reference directly.
        injectableFactory.add(
                new InjectableProvider<Context, Type>() {
                    @Override
                    public ComponentScope getScope() {
                        return ComponentScope.Singleton;
                    }

                    @Override
                    public Injectable<ResourceConfig> getInjectable(ComponentContext cc, Context a, Type t) {
                        if (t != ResourceConfig.class)
                            return null;
                        return new Injectable<ResourceConfig>() {
                            @Override
                            public ResourceConfig getValue() {
                                return resourceConfig;
                            }
                        };
                    }
                });

        // Allow injection of resource context
        injectableFactory.add(new ContextInjectableProvider<ResourceContext>(
                ResourceContext.class, resourceContext));

        // Configure the injectable factory with declared providers
        injectableFactory.configure(providerServices);

        boolean updateRequired = false;

        // Create application-declared Application instance as a component
        if (rc instanceof DeferredResourceConfig) {
            final DeferredResourceConfig drc = (DeferredResourceConfig)rc;
            // Check if resource config has already been cloned
            if (resourceConfig == drc)
                resourceConfig = drc.clone();

            final DeferredResourceConfig.ApplicationHolder da = drc.getApplication(cpFactory);
            resourceConfig.add(da.getApplication());
            updateRequired = true;

            injectableFactory.add(new ContextInjectableProvider<Application>(
                    Application.class, da.getOriginalApplication()));
        } else {
            injectableFactory.add(new ContextInjectableProvider<FeaturesAndProperties>(
                    Application.class, resourceConfig));
        }

        // Pipelined, decentralized configuration
        for(ResourceConfigurator configurator : providerServices.getProviders(ResourceConfigurator.class)) {
            configurator.configure(this.resourceConfig);
            updateRequired = true;
        }

        // Validate the resource config
        this.resourceConfig.validate();

        if (updateRequired) {
            // Check if application modified provider classes or singletons
            providerServices.update(resourceConfig.getProviderClasses(),
                    resourceConfig.getProviderSingletons(), injectableFactory);
        }

        // Obtain all the templates
        this.templateContext = new TemplateFactory(providerServices);
        // Allow injection of template context
        injectableFactory.add(new ContextInjectableProvider<TemplateContext>(
                TemplateContext.class, templateContext));

        // Obtain all context resolvers
        final ContextResolverFactory crf = new ContextResolverFactory();

        // Obtain all the exception mappers
        this.exceptionFactory = new ExceptionMapperFactory();

        // Obtain all message body readers/writers
        this.bodyFactory = new MessageBodyFactory(providerServices,
                getFeaturesAndProperties().getFeature(FeaturesAndProperties.FEATURE_PRE_1_4_PROVIDER_PRECEDENCE));
        injectableFactory.add(
                new ContextInjectableProvider<MessageBodyWorkers>(
                        MessageBodyWorkers.class, bodyFactory));

        // Injection of Providers
        this.providers = new Providers() {
            @Override
            public <T> MessageBodyReader<T> getMessageBodyReader(Class<T> c, Type t,
                                                                 Annotation[] as, MediaType m) {
                return bodyFactory.getMessageBodyReader(c, t, as, m);
            }

            @Override
            public <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> c, Type t,
                                                                 Annotation[] as, MediaType m) {
                return bodyFactory.getMessageBodyWriter(c, t, as, m);
            }

            @Override
            public <T extends Throwable> ExceptionMapper<T> getExceptionMapper(Class<T> c) {
                if (Throwable.class.isAssignableFrom(c))
                    return exceptionFactory.find(c);
                else
                    return null;
            }

            @Override
            public <T> ContextResolver<T> getContextResolver(Class<T> ct, MediaType m) {
                return crf.resolve(ct, m);
            }
        };
        injectableFactory.add(
                new ContextInjectableProvider<Providers>(
                        Providers.class, providers));

        // Obtain all String readers
        this.stringReaderFactory = new StringReaderFactory();
        injectableFactory.add(
                new ContextInjectableProvider<StringReaderWorkers>(
                        StringReaderWorkers.class, stringReaderFactory));

        MultivaluedParameterExtractorProvider mpep =
                new MultivaluedParameterExtractorFactory(stringReaderFactory);
        // Add the multi-valued parameter extractor provider
        injectableFactory.add(
                new ContextInjectableProvider<MultivaluedParameterExtractorProvider>(
                        MultivaluedParameterExtractorProvider.class, mpep));

        // Add per-request-based injectable providers
        injectableFactory.add(new CookieParamInjectableProvider(mpep));
        injectableFactory.add(new HeaderParamInjectableProvider(mpep));
        injectableFactory.add(new HttpContextInjectableProvider());
        injectableFactory.add(new MatrixParamInjectableProvider(mpep));
        injectableFactory.add(new PathParamInjectableProvider(mpep));
        injectableFactory.add(new QueryParamInjectableProvider(mpep));
        injectableFactory.add(new FormParamInjectableProvider(mpep));

        // Create filter factory
        filterFactory = new FilterFactory(providerServices);

        // Initiate resource method dispatchers
        dispatcherFactory = ResourceMethodDispatcherFactory.create(providerServices);

        dispatchingListener = new DispatchingListenerProxy();

        // Initiate the WADL factory
        this.wadlFactory = new WadlFactory(resourceConfig, this.providers);

        WadlApplicationContextInjectionProxy wadlApplicationContextInjectionProxy = null;

        if(!resourceConfig.getFeature(ResourceConfig.FEATURE_DISABLE_WADL)) {
            wadlApplicationContextInjectionProxy = new WadlApplicationContextInjectionProxy();
            injectableFactory.add(new SingletonTypeInjectableProvider<Context, WadlApplicationContext>
                    (WadlApplicationContext.class, wadlApplicationContextInjectionProxy) {});

            // In order for the application to properly marshall the Application
            // object we need to make sure that we provide a JAXBContext that
            // will work
            final WadlApplicationContext wac = wadlApplicationContextInjectionProxy;
            @Produces({MediaTypes.WADL_STRING,MediaTypes.WADL_JSON_STRING, MediaType.APPLICATION_XML})
            class WadlContextResolver implements ContextResolver<JAXBContext>
            {
                @Override
                public JAXBContext getContext(Class<?> type) {

                    if (com.sun.research.ws.wadl.Application.class.isAssignableFrom(type)) {
                        return wac.getJAXBContext();
                    }
                    else {
                        return null;
                    }
                }
            }

            resourceConfig.getSingletons().add(new WadlContextResolver());

            // Update the provider services, so this is used

            providerServices.update(resourceConfig.getProviderClasses(),
                    resourceConfig.getProviderSingletons(), injectableFactory);
        } else {
            injectableFactory.add(new SingletonTypeInjectableProvider<Context, WadlApplicationContext>
                    (WadlApplicationContext.class, wadlApplicationContextInjectionProxy) {});
        }

        // Initiate filter
        filterFactory.init(resourceConfig);
        if (!resourceConfig.getMediaTypeMappings().isEmpty() ||
                !resourceConfig.getLanguageMappings().isEmpty()) {
            boolean present = false;
            for (ContainerRequestFilter f : filterFactory.getRequestFilters()) {
                present |= f instanceof UriConnegFilter;
            }

            if (!present) {
                filterFactory.getRequestFilters().add(new UriConnegFilter(
                        resourceConfig.getMediaTypeMappings(),
                        resourceConfig.getLanguageMappings()));
            } else {
                LOGGER.warning("The media type and language mappings " +
                        "declared in the ResourceConfig are ignored because " +
                        "there is an instance of " + UriConnegFilter.class.getName() +
                        "present in the list of request filters.");
            }
        }

        // Initiate context resolvers
        crf.init(providerServices, injectableFactory);

        // Initiate the exception mappers
        exceptionFactory.init(providerServices);

        // Initiate message body readers/writers
        bodyFactory.init();

        // Initiate string readers
        stringReaderFactory.init(providerServices);

        // Inject on all components
        Errors.setReportMissingDependentFieldOrMethod(true);
        cpFactory.injectOnAllComponents();
        cpFactory.injectOnProviderInstances(resourceConfig.getProviderSingletons());

        // web application is ready
        for (IoCComponentProviderFactory providerFactory : providerFactories) {
            if (providerFactory instanceof WebApplicationListener) {
                WebApplicationListener listener = (WebApplicationListener) providerFactory;
                listener.onWebApplicationReady();
            }
        }

        createAbstractResourceModelStructures();

        // Obtain all root resource rules
        RulesMap<UriRule> rootRules = new RootResourceUriRules(this,
                resourceConfig, wadlFactory, injectableFactory).getRules();
        this.rootsRule = new RootResourceClassesRule(rootRules);

        if(!resourceConfig.getFeature(ResourceConfig.FEATURE_DISABLE_WADL)) {
            wadlApplicationContextInjectionProxy.init(wadlFactory);
        }

        requestListener = MonitoringProviderFactory.createRequestListener(providerServices);
        responseListener = MonitoringProviderFactory.createResponseListener(providerServices);
        dispatchingListener.init(providerServices);

        callAbstractResourceModelListenersOnLoaded(providerServices);

        this.isTraceEnabled = resourceConfig.getFeature(ResourceConfig.FEATURE_TRACE) |
                resourceConfig.getFeature(ResourceConfig.FEATURE_TRACE_PER_REQUEST);
    }

    @Override
    public Providers getProviders() {
        return providers;
    }

    @Override
    public ResourceContext getResourceContext() {
        return resourceContext;
    }

    @Override
    public MessageBodyWorkers getMessageBodyWorkers() {
        return bodyFactory;
    }

    @Override
    public ExceptionMapperContext getExceptionMapperContext() {
        return exceptionFactory;
    }

    @Override
    public ServerInjectableProviderFactory getServerInjectableProviderFactory() {
        return injectableFactory;
    }

    @Override
    public void handleRequest(ContainerRequest request, ContainerResponseWriter responseWriter)
            throws IOException {
        final ContainerResponse response = new ContainerResponse(
                this,
                request,
                responseWriter);
        handleRequest(request, response);
    }

    @Override
    public void handleRequest(ContainerRequest request, ContainerResponse response) throws IOException {
        final WebApplicationContext localContext = new
                WebApplicationContext(this, request, response);

        context.set(localContext);
        try {
            _handleRequest(localContext, request, response);
        } finally {
            PerRequestFactory.destroy(localContext);
            closeableFactory.close(localContext);
            context.set(null);
        }
    }

    private WebApplicationContext handleMatchResourceRequest(URI u) {
        final WebApplicationContext oldContext = (WebApplicationContext)context.get();

        final WebApplicationContext newContext = oldContext.createMatchResourceContext(u);

        context.set(newContext);
        try {
            _handleRequest(newContext, newContext.getContainerRequest());
            return newContext;
        } finally {
            context.set(oldContext);
        }
    }

    @Override
    public void destroy() {
        for (ResourceComponentProvider rcp : providerMap.values()) {
            rcp.destroy();
        }
        for (ResourceComponentProvider rcp : singletonMap.values()) {
            rcp.destroy();
        }

        for (ResourceComponentProvider rcp : providerWithAnnotationKeyMap.values()) {
            rcp.destroy();
        }

        cpFactory.destroy();
    }

    // Traceable

    @Override
    public boolean isTracingEnabled() {
        return isTraceEnabled;
    }

    @Override
    public void trace(String message) {
        context.get().trace(message);
    }

    private void _handleRequest(final WebApplicationContext localContext,
                                ContainerRequest request, ContainerResponse response) throws IOException {
        try {
            requestListener.onRequest(Thread.currentThread().getId(), request);
            _handleRequest(localContext, request);
        } catch (WebApplicationException e) {
            response.mapWebApplicationException(e);
        } catch (MappableContainerException e) {
            response.mapMappableContainerException(e);
        } catch (RuntimeException e) {
            if (!response.mapException(e)) {
                LOGGER.log(Level.SEVERE, "The RuntimeException could not be mapped to a response, " +
                        "re-throwing to the HTTP container", e);
                throw e;
            }
        }

        try {
            // Process response filters from resources
            for (ContainerResponseFilter f : localContext.getResponseFilters()) {
                response = f.filter(request, response);
                localContext.setContainerResponse(response);
            }

            for (ContainerResponseFilter f : filterFactory.getResponseFilters()) {
                response = f.filter(request, response);
                localContext.setContainerResponse(response);
            }
        } catch (WebApplicationException e) {
            response.mapWebApplicationException(e);
        } catch (MappableContainerException e) {
            response.mapMappableContainerException(e);
        } catch (RuntimeException e) {
            if (!response.mapException(e)) {
                LOGGER.log(Level.SEVERE, "The RuntimeException could not be mapped to a response, " +
                        "re-throwing to the HTTP container", e);
                throw e;
            }
        }

        try {
            response.write();
            responseListener.onResponse(Thread.currentThread().getId(), response);
        } catch (WebApplicationException e) {
            if (response.isCommitted()) {
                LOGGER.log(Level.SEVERE, "The response of the WebApplicationException cannot be utilized " +
                        "as the response is already committed. Re-throwing to the HTTP container", e);
                throw e;
            } else {
                response.mapWebApplicationException(e);
                response.write();
            }
        }
    }

    private void _handleRequest(final WebApplicationContext localContext,
                                ContainerRequest request) {
        for (ContainerRequestFilter f : filterFactory.getRequestFilters()) {
            request = f.filter(request);
            localContext.setContainerRequest(request);
        }

        /**
         * The matching algorithm currently works from an absolute path.
         * The path is required to be in encoded form.
         */
        StringBuilder path = new StringBuilder();
        path.append("/").append(request.getPath(false));

        if (!resourceConfig.getFeature(ResourceConfig.FEATURE_MATCH_MATRIX_PARAMS)) {
            path = stripMatrixParams(path);
        }

        if (!rootsRule.accept(path, null, localContext)) {
            throw new NotFoundException(request.getRequestUri());
        }
    }

    @Override
    public HttpContext getThreadLocalHttpContext() {
        return context;
    }

    /**
     * Strip the matrix parameters from a path
     */
    private StringBuilder stripMatrixParams(StringBuilder path) {
        int e = path.indexOf(";");
        if (e == -1) {
            return path;
        }

        int s = 0;
        StringBuilder sb = new StringBuilder();
        do {
            // Append everything up to but not including the ';'
            sb.append(path, s, e);

            // Skip everything up to but not including the '/'
            s = path.indexOf("/", e + 1);
            if (s == -1) {
                break;
            }
            e = path.indexOf(";", s);
        } while (e != -1);

        if (s != -1) {
            // Append any remaining characters
            sb.append(path, s, path.length());
        }

        return sb;
    }


    private void createAbstractResourceModelStructures() {

        final Set<AbstractResource> rootARs = new HashSet<AbstractResource>();

        // Add declared singleton instances of root resource classes
        for (final Object o : resourceConfig.getRootResourceSingletons()) {
            rootARs.add(getAbstractResource(o));
        }

        // Add declared root resource classes
        for (final Class<?> c : resourceConfig.getRootResourceClasses()) {
            rootARs.add(getAbstractResource(c));
        }

        // Add explicit declared root resource classes
        final Map<String, AbstractResource> explicitRootARs =
                new HashMap<String, AbstractResource>();
        for (final Map.Entry<String, Object> e : resourceConfig.getExplicitRootResources().entrySet()) {
            final Object o = e.getValue();
            final Class c = (o instanceof Class) ? (Class)o : o.getClass();

            final AbstractResource ar = new AbstractResource(e.getKey(),
                    getAbstractResource(c));

            rootARs.add(ar);
            explicitRootARs.put(e.getKey(), ar);
        }

        abstractRootResources = Collections.unmodifiableSet(rootARs);
        explicitAbstractRootResources = Collections.unmodifiableMap(explicitRootARs);
    }

    private void callAbstractResourceModelListenersOnLoaded(ProviderServices providerServices) {
        for (AbstractResourceModelListener aml : providerServices.getProviders(AbstractResourceModelListener.class)) {
            aml.onLoaded(armContext);
        }
    }

    private <T> T createProxy(final Class<T> c, final InvocationHandler i) {
        return AccessController.doPrivileged(new PrivilegedAction<T>() {
            @Override
            public T run() {
                return c.cast(Proxy.newProxyInstance(
                        WebApplicationImpl.this.getClass().getClassLoader(), new Class[]{c}, i));

            }
        });
    }

    private class DispatchingListenerProxy implements DispatchingListener {
        private DispatchingListener dispatchingListener;

        @Override
        public void onSubResource(long id, Class subResource) {
            dispatchingListener.onSubResource(id, subResource);
        }

        @Override
        public void onSubResourceLocator(long id, AbstractSubResourceLocator locator) {
            dispatchingListener.onSubResourceLocator(id, locator);
        }

        @Override
        public void onResourceMethod(long id, AbstractResourceMethod method) {
            dispatchingListener.onResourceMethod(id, method);
        }

        public void init(final ProviderServices providerServices) {
            dispatchingListener = MonitoringProviderFactory.createDispatchingListener(providerServices);
        }
    }
}
