Fix dispatcher handling

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1691505 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
index 9e3d671..2bffcbd 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
@@ -16,40 +16,17 @@
  */
 package org.apache.felix.http.base.internal.dispatch;
 
-import static javax.servlet.RequestDispatcher.FORWARD_CONTEXT_PATH;
-import static javax.servlet.RequestDispatcher.FORWARD_PATH_INFO;
-import static javax.servlet.RequestDispatcher.FORWARD_QUERY_STRING;
-import static javax.servlet.RequestDispatcher.FORWARD_REQUEST_URI;
-import static javax.servlet.RequestDispatcher.FORWARD_SERVLET_PATH;
-import static javax.servlet.RequestDispatcher.INCLUDE_CONTEXT_PATH;
-import static javax.servlet.RequestDispatcher.INCLUDE_PATH_INFO;
-import static javax.servlet.RequestDispatcher.INCLUDE_QUERY_STRING;
-import static javax.servlet.RequestDispatcher.INCLUDE_REQUEST_URI;
-import static javax.servlet.RequestDispatcher.INCLUDE_SERVLET_PATH;
-import static org.apache.felix.http.base.internal.util.UriUtils.concat;
-import static org.apache.felix.http.base.internal.util.UriUtils.decodePath;
-import static org.apache.felix.http.base.internal.util.UriUtils.removeDotSegments;
-
 import java.io.IOException;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.servlet.AsyncContext;
 import javax.servlet.DispatcherType;
 import javax.servlet.FilterChain;
 import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
-import javax.servlet.ServletRequestAttributeEvent;
 import javax.servlet.ServletRequestEvent;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
 import javax.servlet.http.HttpSession;
 
 import org.apache.felix.http.base.internal.context.ExtServletContext;
@@ -63,490 +40,9 @@
 import org.apache.felix.http.base.internal.registry.ServletResolution;
 import org.apache.felix.http.base.internal.util.UriUtils;
 import org.apache.felix.http.base.internal.whiteboard.WhiteboardManager;
-import org.osgi.service.http.HttpContext;
-import org.osgi.service.useradmin.Authorization;
 
-public final class Dispatcher implements RequestDispatcherProvider
+public final class Dispatcher
 {
-    /**
-     * Wrapper implementation for {@link RequestDispatcher}.
-     */
-    final class RequestDispatcherImpl implements RequestDispatcher
-    {
-        private final RequestInfo requestInfo;
-        private final ServletResolution resolution;
-
-        public RequestDispatcherImpl(final ServletResolution resolution, final RequestInfo requestInfo)
-        {
-            this.resolution = resolution;
-            this.requestInfo = requestInfo;
-        }
-
-        @Override
-        public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
-        {
-            if (response.isCommitted())
-            {
-                throw new ServletException("Response has been committed");
-            }
-            else
-            {
-                // See section 9.4 of Servlet 3.0 spec
-                response.resetBuffer();
-            }
-
-            try
-            {
-                ServletRequestWrapper req = new ServletRequestWrapper((HttpServletRequest) request,
-                        this.resolution.handler.getContext(),
-                        this.requestInfo,
-                        DispatcherType.FORWARD,
-                        this.resolution.handler.getContextServiceId(),
-                        this.resolution.handler.getServletInfo().isAsyncSupported());
-                Dispatcher.this.forward(this.resolution, req, (HttpServletResponse) response);
-            }
-            finally
-            {
-                // After a forward has taken place, the results should be committed,
-                // see section 9.4 of Servlet 3.0 spec...
-                if (!request.isAsyncStarted())
-                {
-                    response.flushBuffer();
-                    response.getWriter().close();
-                }
-            }
-        }
-
-        @Override
-        public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
-        {
-            ServletRequestWrapper req = new ServletRequestWrapper((HttpServletRequest) request,
-                    this.resolution.handler.getContext(),
-                    this.requestInfo,
-                    DispatcherType.INCLUDE,
-                    this.resolution.handler.getContextServiceId(),
-                    this.resolution.handler.getServletInfo().isAsyncSupported());
-            Dispatcher.this.include(this.resolution, req, (HttpServletResponse) response);
-        }
-    }
-
-    final class ServletResponseWrapper extends HttpServletResponseWrapper
-    {
-
-        private final HttpServletRequest request;
-
-        private final AtomicInteger invocationCount = new AtomicInteger();
-
-        private final PerContextHandlerRegistry errorRegistry;
-
-        private final String servletName;
-
-        public ServletResponseWrapper(@Nonnull final HttpServletRequest req,
-                @Nonnull final HttpServletResponse res,
-                @CheckForNull final String servletName,
-                @CheckForNull final PerContextHandlerRegistry errorRegistry)
-        {
-            super(res);
-            this.request = req;
-            this.servletName = servletName;
-            this.errorRegistry = errorRegistry;
-        }
-
-        @Override
-        public void sendError(int sc) throws IOException
-        {
-            sendError(sc, null);
-        }
-
-        @Override
-        public void sendError(final int code, final String message) throws IOException
-        {
-            resetBuffer();
-
-            setStatus(code);
-
-            boolean invokeSuper = true;
-
-            if ( invocationCount.incrementAndGet() == 1 )
-            {
-                // If we are allowed to have a body
-                if (code != SC_NO_CONTENT &&
-                    code != SC_NOT_MODIFIED &&
-                    code != SC_PARTIAL_CONTENT &&
-                    code >= SC_OK)
-                {
-                    final Throwable exception = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
-                    final ServletHandler errorResolution = (errorRegistry == null ? null :
-                            errorRegistry.getErrorHandler(code, exception));
-
-                    if ( errorResolution != null )
-                    {
-                        try
-                        {
-                            request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, new Integer(code));
-                            if ( message != null )
-                            {
-                                request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
-                            }
-                            request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
-                            if ( this.servletName != null )
-                            {
-                                request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, this.servletName);
-                            }
-
-                            final String servletPath = null;
-                            final String pathInfo = request.getRequestURI();
-                            final String queryString = null; // XXX
-
-                            final RequestInfo requestInfo = new RequestInfo(servletPath, pathInfo, queryString);
-
-                            final FilterHandler[] filterHandlers = errorRegistry.getFilterHandlers(errorResolution, DispatcherType.ERROR, request.getRequestURI());
-
-                            // TODO - is async = false correct?
-                            invokeChain(errorResolution, filterHandlers, new ServletRequestWrapper(request, errorResolution.getContext(), requestInfo, errorResolution.getContextServiceId(), false), this);
-
-                            invokeSuper = false;
-                        }
-                        catch (final ServletException e)
-                        {
-                            // ignore
-                        }
-                        finally
-                        {
-                            request.removeAttribute(RequestDispatcher.ERROR_STATUS_CODE);
-                            request.removeAttribute(RequestDispatcher.ERROR_MESSAGE);
-                            request.removeAttribute(RequestDispatcher.ERROR_REQUEST_URI);
-                            request.removeAttribute(RequestDispatcher.ERROR_SERVLET_NAME);
-                            request.removeAttribute(RequestDispatcher.ERROR_EXCEPTION);
-                            request.removeAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
-                        }
-                    }
-                }
-            }
-            if ( invokeSuper )
-            {
-                super.sendError(code, message);
-            }
-        }
-    }
-
-    final class ServletRequestWrapper extends HttpServletRequestWrapper
-    {
-        private final DispatcherType type;
-        private final RequestInfo requestInfo;
-        private final ExtServletContext servletContext;
-        private final long contextId;
-        private final boolean asyncSupported;
-
-        public ServletRequestWrapper(HttpServletRequest req, ExtServletContext servletContext, RequestInfo requestInfo, final long contextId,
-                final boolean asyncSupported)
-        {
-            this(req, servletContext, requestInfo, null /* type */, contextId, asyncSupported);
-        }
-
-        public ServletRequestWrapper(HttpServletRequest req, ExtServletContext servletContext, RequestInfo requestInfo,
-                DispatcherType type, final Long contextId, final boolean asyncSupported)
-        {
-            super(req);
-
-            this.asyncSupported = asyncSupported;
-            this.servletContext = servletContext;
-            this.requestInfo = requestInfo;
-            this.type = type;
-            this.contextId = contextId;
-        }
-
-        @Override
-        public Object getAttribute(String name)
-        {
-            HttpServletRequest request = (HttpServletRequest) getRequest();
-            if (isInclusionDispatcher())
-            {
-                // The javax.servlet.include.* attributes refer to the information of the *included* request,
-                // meaning that the request information comes from the *original* request...
-                if (INCLUDE_REQUEST_URI.equals(name))
-                {
-                    return concat(request.getContextPath(), this.requestInfo.requestURI);
-                }
-                else if (INCLUDE_CONTEXT_PATH.equals(name))
-                {
-                    return request.getContextPath();
-                }
-                else if (INCLUDE_SERVLET_PATH.equals(name))
-                {
-                    return this.requestInfo.servletPath;
-                }
-                else if (INCLUDE_PATH_INFO.equals(name))
-                {
-                    return this.requestInfo.pathInfo;
-                }
-                else if (INCLUDE_QUERY_STRING.equals(name))
-                {
-                    return this.requestInfo.queryString;
-                }
-            }
-            else if (isForwardingDispatcher())
-            {
-                // The javax.servlet.forward.* attributes refer to the information of the *original* request,
-                // meaning that the request information comes from the *forwarded* request...
-                if (FORWARD_REQUEST_URI.equals(name))
-                {
-                    return super.getRequestURI();
-                }
-                else if (FORWARD_CONTEXT_PATH.equals(name))
-                {
-                    return request.getContextPath();
-                }
-                else if (FORWARD_SERVLET_PATH.equals(name))
-                {
-                    return super.getServletPath();
-                }
-                else if (FORWARD_PATH_INFO.equals(name))
-                {
-                    return super.getPathInfo();
-                }
-                else if (FORWARD_QUERY_STRING.equals(name))
-                {
-                    return super.getQueryString();
-                }
-            }
-            return super.getAttribute(name);
-        }
-
-        @Override
-        public String getAuthType()
-        {
-            String authType = (String) getAttribute(HttpContext.AUTHENTICATION_TYPE);
-            if (authType == null)
-            {
-                authType = super.getAuthType();
-            }
-            return authType;
-        }
-
-        @Override
-        public String getContextPath()
-        {
-            return this.getServletContext().getContextPath();
-        }
-
-        @Override
-        public DispatcherType getDispatcherType()
-        {
-            return (this.type == null) ? super.getDispatcherType() : this.type;
-        }
-
-        @Override
-        public String getPathInfo()
-        {
-            if ( this.isInclusionDispatcher() )
-            {
-                return super.getPathInfo();
-            }
-            return this.requestInfo.pathInfo;
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public String getPathTranslated()
-        {
-            final String info = getPathInfo();
-            return (null == info) ? null : getRealPath(info);
-        }
-
-        @Override
-        public String getRemoteUser()
-        {
-            String remoteUser = (String) getAttribute(HttpContext.REMOTE_USER);
-            if (remoteUser != null)
-            {
-                return remoteUser;
-            }
-
-            return super.getRemoteUser();
-        }
-
-        @Override
-        public RequestDispatcher getRequestDispatcher(String path)
-        {
-            // See section 9.1 of Servlet 3.0 specification...
-            if (path == null)
-            {
-                return null;
-            }
-            // Handle relative paths, see Servlet 3.0 spec, section 9.1 last paragraph.
-            boolean relPath = !path.startsWith("/") && !"".equals(path);
-            if (relPath)
-            {
-                path = concat(getServletPath(), path);
-            }
-            return Dispatcher.this.getRequestDispatcher(this.contextId, path);
-        }
-
-        @Override
-        public String getRequestURI()
-        {
-            if ( isInclusionDispatcher() )
-            {
-                return super.getRequestURI();
-            }
-            return concat(getContextPath(), this.requestInfo.requestURI);
-        }
-
-        @Override
-        public ServletContext getServletContext()
-        {
-            return new ServletContextWrapper(this.contextId, this.servletContext, Dispatcher.this);
-        }
-
-        @Override
-        public String getServletPath()
-        {
-            if ( isInclusionDispatcher() )
-            {
-                return super.getServletPath();
-            }
-            return this.requestInfo.servletPath;
-        }
-
-        @Override
-        public HttpSession getSession() {
-            return this.getSession(true);
-        }
-
-        @Override
-        public HttpSession getSession(boolean create)
-        {
-            // FELIX-2797: wrap the original HttpSession to provide access to the correct ServletContext...
-            final HttpSession session = super.getSession(create);
-            if (session == null)
-            {
-                return null;
-            }
-            // check if internal session is available
-            if ( !create && !HttpSessionWrapper.hasSession(this.contextId, session) )
-            {
-                return null;
-            }
-            return new HttpSessionWrapper(this.contextId, session, this.servletContext, false);
-        }
-
-        @Override
-        public boolean isUserInRole(String role)
-        {
-            Authorization authorization = (Authorization) getAttribute(HttpContext.AUTHORIZATION);
-            if (authorization != null)
-            {
-                return authorization.hasRole(role);
-            }
-
-            return super.isUserInRole(role);
-        }
-
-        @Override
-        public void setAttribute(final String name, final Object value)
-        {
-            if ( value == null )
-            {
-                this.removeAttribute(name);
-            }
-            final Object oldValue = this.getAttribute(name);
-            super.setAttribute(name, value);
-            if ( this.servletContext.getServletRequestAttributeListener() != null )
-            {
-                if ( oldValue == null )
-                {
-                    this.servletContext.getServletRequestAttributeListener().attributeAdded(new ServletRequestAttributeEvent(this.servletContext, this, name, value));
-                }
-                else
-                {
-                    this.servletContext.getServletRequestAttributeListener().attributeReplaced(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
-                }
-            }
-        }
-
-        @Override
-        public void removeAttribute(final String name) {
-            final Object oldValue = this.getAttribute(name);
-            if ( oldValue != null )
-            {
-                super.removeAttribute(name);
-                if ( this.servletContext.getServletRequestAttributeListener() != null )
-                {
-                    this.servletContext.getServletRequestAttributeListener().attributeRemoved(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
-                }
-            }
-        }
-
-        @Override
-        public String toString()
-        {
-            return getClass().getSimpleName() + "->" + super.getRequest();
-        }
-
-        private boolean isForwardingDispatcher()
-        {
-            return (DispatcherType.FORWARD == this.type) && (this.requestInfo != null);
-        }
-
-        private boolean isInclusionDispatcher()
-        {
-            return (DispatcherType.INCLUDE == this.type) && (this.requestInfo != null);
-        }
-
-        @Override
-        public AsyncContext startAsync() throws IllegalStateException
-        {
-            if ( !this.asyncSupported )
-            {
-                throw new IllegalStateException();
-            }
-            return super.startAsync();
-        }
-
-        @Override
-        public AsyncContext startAsync(final ServletRequest servletRequest,
-                final ServletResponse servletResponse) throws IllegalStateException
-        {
-            if ( !this.asyncSupported )
-            {
-                throw new IllegalStateException();
-            }
-            return super.startAsync(servletRequest, servletResponse);
-        }
-
-        @Override
-        public boolean isAsyncSupported()
-        {
-            return this.asyncSupported;
-        }
-    }
-
-    private static class RequestInfo
-    {
-        final String servletPath;
-        final String pathInfo;
-        final String queryString;
-        final String requestURI;
-
-        public RequestInfo(String servletPath, String pathInfo, String queryString)
-        {
-            this.servletPath = servletPath;
-            this.pathInfo = pathInfo;
-            this.queryString = queryString;
-            this.requestURI = UriUtils.compactPath(concat(servletPath, pathInfo));
-        }
-
-        @Override
-        public String toString()
-        {
-            StringBuilder sb = new StringBuilder("RequestInfo[servletPath =");
-            sb.append(this.servletPath).append(", pathInfo = ").append(this.pathInfo);
-            sb.append(", queryString = ").append(this.queryString).append("]");
-            return sb.toString();
-        }
-    }
-
     private final HandlerRegistry handlerRegistry;
 
     private WhiteboardManager whiteboardManager;
@@ -608,7 +104,7 @@
         final ExtServletContext servletContext = pr.handler.getContext();
         final RequestInfo requestInfo = new RequestInfo(pr.servletPath, pr.pathInfo, null);
 
-        final HttpServletRequest wrappedRequest = new ServletRequestWrapper(req, servletContext, requestInfo,
+        final HttpServletRequest wrappedRequest = new ServletRequestWrapper(req, servletContext, requestInfo, null,
                 pr.handler.getContextServiceId(),
                 pr.handler.getServletInfo().isAsyncSupported());
         final FilterHandler[] filterHandlers = this.handlerRegistry.getFilters(pr, req.getDispatcherType(), pr.requestURI);
@@ -638,54 +134,14 @@
         }
     }
 
-    @Override
-    public RequestDispatcher getNamedDispatcher(final long contextId, final String name)
-    {
-        final ServletResolution resolution = this.handlerRegistry.resolveServletByName(contextId, name);
-        return resolution != null ? new RequestDispatcherImpl(resolution, null) : null;
-    }
-
-    @Override
-    public RequestDispatcher getRequestDispatcher(final long contextId, String path)
-    {
-        // See section 9.1 of Servlet 3.x specification...
-        if (path == null || (!path.startsWith("/") && !"".equals(path)))
-        {
-            return null;
-        }
-
-        String query = null;
-        int q = 0;
-        if ((q = path.indexOf('?')) > 0)
-        {
-            query = path.substring(q + 1);
-            path = path.substring(0, q);
-        }
-        // TODO remove path parameters...
-        String requestURI = decodePath(removeDotSegments(path));
-        if ( requestURI == null )
-        {
-            requestURI = "";
-        }
-
-        final PathResolution pr = this.handlerRegistry.resolveServlet(requestURI);
-        if (pr == null)
-        {
-            return null;
-        }
-
-        final RequestInfo requestInfo = new RequestInfo(pr.servletPath, pr.pathInfo, query);
-        return new RequestDispatcherImpl(pr, requestInfo);
-    }
-
     /**
      * @param servletHandler the servlet that should handle the forward request;
      * @param request the {@link HttpServletRequest};
      * @param response the {@link HttpServletResponse};
      */
-    void forward(final ServletResolution resolution, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    public void forward(final ServletResolution resolution, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
     {
-        final String requestURI = getRequestURI(request);
+        final String requestURI = UriUtils.relativePath(request.getContextPath(), request.getRequestURI());
         final FilterHandler[] filterHandlers = this.handlerRegistry.getFilters(resolution, DispatcherType.FORWARD, requestURI);
 
         invokeChain(resolution.handler, filterHandlers, request, response);
@@ -696,19 +152,14 @@
      * @param request the {@link HttpServletRequest};
      * @param response the {@link HttpServletResponse};
      */
-    void include(final ServletResolution resolution, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    public void include(final ServletResolution resolution, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
     {
-        final String requestURI = getRequestURI(request);
+        final String requestURI = UriUtils.relativePath(request.getContextPath(), request.getRequestURI());
         final FilterHandler[] filterHandlers = this.handlerRegistry.getFilters(resolution, DispatcherType.INCLUDE, requestURI);
 
         invokeChain(resolution.handler, filterHandlers, request, response);
     }
 
-    private String getRequestURI(final HttpServletRequest req)
-    {
-        return UriUtils.relativePath(req.getContextPath(), req.getRequestURI());
-    }
-
     private void invokeChain(final ServletHandler servletHandler,
             final FilterHandler[] filterHandlers,
             final HttpServletRequest request,
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
new file mode 100644
index 0000000..9a97cef
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherImpl.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.dispatch;
+
+import java.io.IOException;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.base.internal.handler.FilterHandler;
+import org.apache.felix.http.base.internal.registry.ServletResolution;
+import org.apache.felix.http.base.internal.util.UriUtils;
+
+/**
+ * Wrapper implementation for {@link RequestDispatcher}.
+ */
+public final class RequestDispatcherImpl implements RequestDispatcher
+{
+    private final RequestInfo requestInfo;
+    private final ServletResolution resolution;
+
+    public RequestDispatcherImpl(final ServletResolution resolution, 
+    		final RequestInfo requestInfo)
+    {
+    	System.out.println("New dispatcher with " + requestInfo);
+        this.resolution = resolution;
+        this.requestInfo = requestInfo;
+    }
+
+    @Override
+    public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        if (response.isCommitted())
+        {
+            throw new ServletException("Response has been committed");
+        }
+        else
+        {
+            // See section 9.4 of Servlet 3.0 spec
+            response.resetBuffer();
+        }
+
+        try
+        {
+            final ServletRequestWrapper req = new ServletRequestWrapper((HttpServletRequest) request,
+                    this.resolution.handler.getContext(),
+                    this.requestInfo,
+                    DispatcherType.FORWARD,
+                    this.resolution.handler.getContextServiceId(),
+                    this.resolution.handler.getServletInfo().isAsyncSupported());
+            final String requestURI = UriUtils.relativePath(req.getContextPath(), req.getRequestURI());
+            final FilterHandler[] filterHandlers = this.resolution.handlerRegistry.getFilterHandlers(this.resolution.handler, DispatcherType.FORWARD, requestURI);
+
+            final FilterChain filterChain = new InvocationChain(resolution.handler, filterHandlers);
+            filterChain.doFilter( req, (HttpServletResponse) response);
+        }
+        finally
+        {
+            // After a forward has taken place, the results should be committed,
+            // see section 9.4 of Servlet 3.0 spec...
+            if (!request.isAsyncStarted())
+            {
+                response.flushBuffer();
+                response.getWriter().close();
+            }
+        }
+    }
+
+    @Override
+    public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        final ServletRequestWrapper req = new ServletRequestWrapper((HttpServletRequest) request,
+                this.resolution.handler.getContext(),
+                this.requestInfo,
+                DispatcherType.INCLUDE,
+                this.resolution.handler.getContextServiceId(),
+                this.resolution.handler.getServletInfo().isAsyncSupported());
+        final String requestURI = UriUtils.relativePath(req.getContextPath(), req.getRequestURI());
+        final FilterHandler[] filterHandlers = this.resolution.handlerRegistry.getFilterHandlers(this.resolution.handler, DispatcherType.INCLUDE, requestURI);
+
+        final FilterChain filterChain = new InvocationChain(resolution.handler, filterHandlers);
+        filterChain.doFilter( req, (HttpServletResponse) response);
+    }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherProvider.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherProvider.java
deleted file mode 100644
index 45fab9f..0000000
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherProvider.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.felix.http.base.internal.dispatch;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletContext;
-
-public interface RequestDispatcherProvider
-{
-    /**
-     * @see ServletContext#getNamedDispatcher(String)
-     */
-    RequestDispatcher getNamedDispatcher(long contextId, String name);
-
-    /**
-     * @see ServletContext#getRequestDispatcher(String)
-     */
-    RequestDispatcher getRequestDispatcher(long contextId, String path);
-}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestInfo.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestInfo.java
new file mode 100644
index 0000000..c7a082f
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestInfo.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.dispatch;
+
+import static org.apache.felix.http.base.internal.util.UriUtils.concat;
+
+import org.apache.felix.http.base.internal.util.UriUtils;
+
+public final class RequestInfo
+{
+    final String servletPath;
+    final String pathInfo;
+    final String queryString;
+    final String requestURI;
+
+    public RequestInfo(String servletPath, String pathInfo, String queryString)
+    {
+        this.servletPath = servletPath;
+        this.pathInfo = pathInfo;
+        this.queryString = queryString;
+        this.requestURI = UriUtils.compactPath(concat(servletPath, pathInfo));
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder("RequestInfo[servletPath =");
+        sb.append(this.servletPath).append(", pathInfo = ").append(this.pathInfo);
+        sb.append(", queryString = ").append(this.queryString).append("]");
+        return sb.toString();
+    }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletContextWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletContextWrapper.java
deleted file mode 100644
index 882e102..0000000
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletContextWrapper.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.felix.http.base.internal.dispatch;
-
-import javax.servlet.RequestDispatcher;
-
-import org.apache.felix.http.base.internal.context.ExtServletContext;
-import org.apache.felix.http.base.internal.context.ExtServletContextWrapper;
-
-class ServletContextWrapper extends ExtServletContextWrapper
-{
-    private final RequestDispatcherProvider provider;
-
-    private final long contextId;
-
-    /**
-     * Creates a new {@link ServletContextWrapper} instance.
-     */
-    public ServletContextWrapper(final long contextId, final ExtServletContext delegate, final RequestDispatcherProvider provider)
-    {
-        super(delegate);
-
-        this.provider = provider;
-        this.contextId = contextId;
-    }
-
-    @Override
-    public RequestDispatcher getNamedDispatcher(String name)
-    {
-        if (name == null)
-        {
-            return null;
-        }
-
-        RequestDispatcher dispatcher = this.provider.getNamedDispatcher(contextId, name);
-        return dispatcher != null ? dispatcher : super.getNamedDispatcher(name);
-    }
-
-    @Override
-    public RequestDispatcher getRequestDispatcher(String path)
-    {
-        // See section 9.1 of Servlet 3.x specification...
-        if (path == null || (!path.startsWith("/") && !"".equals(path)))
-        {
-            return null;
-        }
-
-        RequestDispatcher dispatcher = this.provider.getRequestDispatcher(contextId, path);
-        return dispatcher != null ? dispatcher : super.getRequestDispatcher(path);
-    }
-}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletRequestWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletRequestWrapper.java
new file mode 100644
index 0000000..d2d2519
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletRequestWrapper.java
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.dispatch;
+
+import static javax.servlet.RequestDispatcher.FORWARD_CONTEXT_PATH;
+import static javax.servlet.RequestDispatcher.FORWARD_PATH_INFO;
+import static javax.servlet.RequestDispatcher.FORWARD_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.FORWARD_REQUEST_URI;
+import static javax.servlet.RequestDispatcher.FORWARD_SERVLET_PATH;
+import static javax.servlet.RequestDispatcher.INCLUDE_CONTEXT_PATH;
+import static javax.servlet.RequestDispatcher.INCLUDE_PATH_INFO;
+import static javax.servlet.RequestDispatcher.INCLUDE_QUERY_STRING;
+import static javax.servlet.RequestDispatcher.INCLUDE_REQUEST_URI;
+import static javax.servlet.RequestDispatcher.INCLUDE_SERVLET_PATH;
+import static org.apache.felix.http.base.internal.util.UriUtils.concat;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.handler.HttpSessionWrapper;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.useradmin.Authorization;
+
+final class ServletRequestWrapper extends HttpServletRequestWrapper
+{
+    private final DispatcherType type;
+    private final RequestInfo requestInfo;
+    private final ExtServletContext servletContext;
+    private final long contextId;
+    private final boolean asyncSupported;
+    
+    public ServletRequestWrapper(HttpServletRequest req, 
+    		ExtServletContext servletContext, 
+    		RequestInfo requestInfo,
+            DispatcherType type, 
+            final Long contextId, 
+            final boolean asyncSupported)
+    {
+        super(req);
+
+        this.asyncSupported = asyncSupported;
+        this.servletContext = servletContext;
+        this.requestInfo = requestInfo;
+        this.type = type;
+        this.contextId = contextId;
+    }
+
+    @Override
+    public Object getAttribute(String name)
+    {
+        HttpServletRequest request = (HttpServletRequest) getRequest();
+        if (isInclusionDispatcher())
+        {
+            // The javax.servlet.include.* attributes refer to the information of the *included* request,
+            // meaning that the request information comes from the *original* request...
+            if (INCLUDE_REQUEST_URI.equals(name))
+            {
+                return concat(request.getContextPath(), this.requestInfo.requestURI);
+            }
+            else if (INCLUDE_CONTEXT_PATH.equals(name))
+            {
+                return request.getContextPath();
+            }
+            else if (INCLUDE_SERVLET_PATH.equals(name))
+            {
+                return this.requestInfo.servletPath;
+            }
+            else if (INCLUDE_PATH_INFO.equals(name))
+            {
+                return this.requestInfo.pathInfo;
+            }
+            else if (INCLUDE_QUERY_STRING.equals(name))
+            {
+                return this.requestInfo.queryString;
+            }
+        }
+        else if (isForwardingDispatcher())
+        {
+            // The javax.servlet.forward.* attributes refer to the information of the *original* request,
+            // meaning that the request information comes from the *forwarded* request...
+            if (FORWARD_REQUEST_URI.equals(name))
+            {
+                return super.getRequestURI();
+            }
+            else if (FORWARD_CONTEXT_PATH.equals(name))
+            {
+                return request.getContextPath();
+            }
+            else if (FORWARD_SERVLET_PATH.equals(name))
+            {
+                return super.getServletPath();
+            }
+            else if (FORWARD_PATH_INFO.equals(name))
+            {
+                return super.getPathInfo();
+            }
+            else if (FORWARD_QUERY_STRING.equals(name))
+            {
+                return super.getQueryString();
+            }
+        }
+        return super.getAttribute(name);
+    }
+
+    @Override
+    public String getAuthType()
+    {
+        String authType = (String) getAttribute(HttpContext.AUTHENTICATION_TYPE);
+        if (authType == null)
+        {
+            authType = super.getAuthType();
+        }
+        return authType;
+    }
+
+    @Override
+    public String getContextPath()
+    {
+        return this.getServletContext().getContextPath();
+    }
+
+    @Override
+    public DispatcherType getDispatcherType()
+    {
+        return (this.type == null) ? super.getDispatcherType() : this.type;
+    }
+
+    @Override
+    public String getPathInfo()
+    {
+        if ( this.isInclusionDispatcher() )
+        {
+            return super.getPathInfo();
+        }
+        return this.requestInfo.pathInfo;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String getPathTranslated()
+    {
+        final String info = getPathInfo();
+        return (null == info) ? null : getRealPath(info);
+    }
+
+    @Override
+    public String getRemoteUser()
+    {
+        String remoteUser = (String) getAttribute(HttpContext.REMOTE_USER);
+        if (remoteUser != null)
+        {
+            return remoteUser;
+        }
+
+        return super.getRemoteUser();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        // See section 9.1 of Servlet 3.0 specification...
+        if (path == null)
+        {
+            return null;
+        }
+        // Handle relative paths, see Servlet 3.0 spec, section 9.1 last paragraph.
+        boolean relPath = !path.startsWith("/") && !"".equals(path);
+        if (relPath)
+        {
+            path = concat(getServletPath(), path);
+        }
+        return this.servletContext.getRequestDispatcher(path);
+    }
+
+    @Override
+    public String getRequestURI()
+    {
+        if ( isInclusionDispatcher() )
+        {
+            return super.getRequestURI();
+        }
+        return concat(getContextPath(), this.requestInfo.requestURI);
+    }
+
+    @Override
+    public ServletContext getServletContext()
+    {
+        return this.servletContext;
+    }
+
+    @Override
+    public String getServletPath()
+    {
+        if ( isInclusionDispatcher() )
+        {
+            return super.getServletPath();
+        }
+        return this.requestInfo.servletPath;
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return this.getSession(true);
+    }
+
+    @Override
+    public HttpSession getSession(boolean create)
+    {
+        // FELIX-2797: wrap the original HttpSession to provide access to the correct ServletContext...
+        final HttpSession session = super.getSession(create);
+        if (session == null)
+        {
+            return null;
+        }
+        // check if internal session is available
+        if ( !create && !HttpSessionWrapper.hasSession(this.contextId, session) )
+        {
+            return null;
+        }
+        return new HttpSessionWrapper(this.contextId, session, this.servletContext, false);
+    }
+
+    @Override
+    public boolean isUserInRole(String role)
+    {
+        Authorization authorization = (Authorization) getAttribute(HttpContext.AUTHORIZATION);
+        if (authorization != null)
+        {
+            return authorization.hasRole(role);
+        }
+
+        return super.isUserInRole(role);
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object value)
+    {
+        if ( value == null )
+        {
+            this.removeAttribute(name);
+        }
+        final Object oldValue = this.getAttribute(name);
+        super.setAttribute(name, value);
+        if ( this.servletContext.getServletRequestAttributeListener() != null )
+        {
+            if ( oldValue == null )
+            {
+                this.servletContext.getServletRequestAttributeListener().attributeAdded(new ServletRequestAttributeEvent(this.servletContext, this, name, value));
+            }
+            else
+            {
+                this.servletContext.getServletRequestAttributeListener().attributeReplaced(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
+            }
+        }
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        final Object oldValue = this.getAttribute(name);
+        if ( oldValue != null )
+        {
+            super.removeAttribute(name);
+            if ( this.servletContext.getServletRequestAttributeListener() != null )
+            {
+                this.servletContext.getServletRequestAttributeListener().attributeRemoved(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
+            }
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return getClass().getSimpleName() + "->" + super.getRequest();
+    }
+
+    private boolean isForwardingDispatcher()
+    {
+        return (DispatcherType.FORWARD == this.type) && (this.requestInfo != null);
+    }
+
+    private boolean isInclusionDispatcher()
+    {
+        return (DispatcherType.INCLUDE == this.type) && (this.requestInfo != null);
+    }
+
+    @Override
+    public AsyncContext startAsync() throws IllegalStateException
+    {
+        if ( !this.asyncSupported )
+        {
+            throw new IllegalStateException();
+        }
+        return super.startAsync();
+    }
+
+    @Override
+    public AsyncContext startAsync(final ServletRequest servletRequest,
+            final ServletResponse servletResponse) throws IllegalStateException
+    {
+        if ( !this.asyncSupported )
+        {
+            throw new IllegalStateException();
+        }
+        return super.startAsync(servletRequest, servletResponse);
+    }
+
+    @Override
+    public boolean isAsyncSupported()
+    {
+        return this.asyncSupported;
+    }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletResponseWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletResponseWrapper.java
new file mode 100644
index 0000000..e12764e
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletResponseWrapper.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.dispatch;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.felix.http.base.internal.handler.FilterHandler;
+import org.apache.felix.http.base.internal.handler.ServletHandler;
+import org.apache.felix.http.base.internal.registry.PerContextHandlerRegistry;
+
+final class ServletResponseWrapper extends HttpServletResponseWrapper
+{
+
+    private final HttpServletRequest request;
+
+    private final AtomicInteger invocationCount = new AtomicInteger();
+
+    private final PerContextHandlerRegistry errorRegistry;
+
+    private final String servletName;
+
+    public ServletResponseWrapper(@Nonnull final HttpServletRequest req,
+            @Nonnull final HttpServletResponse res,
+            @CheckForNull final String servletName,
+            @CheckForNull final PerContextHandlerRegistry errorRegistry)
+    {
+        super(res);
+        this.request = req;
+        this.servletName = servletName;
+        this.errorRegistry = errorRegistry;
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException
+    {
+        sendError(sc, null);
+    }
+
+    @Override
+    public void sendError(final int code, final String message) throws IOException
+    {
+        resetBuffer();
+
+        setStatus(code);
+
+        boolean invokeSuper = true;
+
+        if ( invocationCount.incrementAndGet() == 1 )
+        {
+            // If we are allowed to have a body
+            if (code != SC_NO_CONTENT &&
+                code != SC_NOT_MODIFIED &&
+                code != SC_PARTIAL_CONTENT &&
+                code >= SC_OK)
+            {
+                final Throwable exception = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+                final ServletHandler errorResolution = (errorRegistry == null ? null :
+                        errorRegistry.getErrorHandler(code, exception));
+
+                if ( errorResolution != null )
+                {
+                    try
+                    {
+                        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, new Integer(code));
+                        if ( message != null )
+                        {
+                            request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+                        }
+                        request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
+                        if ( this.servletName != null )
+                        {
+                            request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, this.servletName);
+                        }
+
+                        final String servletPath = null;
+                        final String pathInfo = request.getRequestURI();
+                        final String queryString = null; // XXX
+
+                        final RequestInfo requestInfo = new RequestInfo(servletPath, pathInfo, queryString);
+
+                        final FilterHandler[] filterHandlers = errorRegistry.getFilterHandlers(errorResolution, DispatcherType.ERROR, request.getRequestURI());
+
+                        // TODO - is async = false correct?
+                        final ServletRequestWrapper reqWrapper = new ServletRequestWrapper(request, errorResolution.getContext(), requestInfo, null, errorResolution.getContextServiceId(), false);
+                        final FilterChain filterChain = new InvocationChain(errorResolution, filterHandlers);
+                        filterChain.doFilter(reqWrapper, this);
+
+                        invokeSuper = false;
+                    }
+                    catch (final ServletException e)
+                    {
+                        // ignore
+                    }
+                    finally
+                    {
+                        request.removeAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+                        request.removeAttribute(RequestDispatcher.ERROR_MESSAGE);
+                        request.removeAttribute(RequestDispatcher.ERROR_REQUEST_URI);
+                        request.removeAttribute(RequestDispatcher.ERROR_SERVLET_NAME);
+                        request.removeAttribute(RequestDispatcher.ERROR_EXCEPTION);
+                        request.removeAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
+                    }
+                }
+            }
+        }
+        if ( invokeSuper )
+        {
+            super.sendError(code, message);
+        }
+    }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/PerBundleHttpServiceImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/PerBundleHttpServiceImpl.java
index b2734d5..86ebf7a 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/PerBundleHttpServiceImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/PerBundleHttpServiceImpl.java
@@ -75,7 +75,13 @@
         }
 
         this.bundle = bundle;
-        this.contextManager = new ServletContextManager(this.bundle, context, servletAttributeListener, sharedContextAttributes, reqListener, reqAttrListener);
+        this.contextManager = new ServletContextManager(this.bundle, 
+        		context, 
+        		servletAttributeListener, 
+        		sharedContextAttributes, 
+        		reqListener, 
+        		reqAttrListener,
+        		sharedHttpService.getHandlerRegistry().getRegistry(HttpServiceFactory.HTTP_SERVICE_CONTEXT_SERVICE_ID));
         this.sharedHttpService = sharedHttpService;
     }
 
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextImpl.java
index 1871bcf..464f3cc 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextImpl.java
@@ -16,6 +16,9 @@
  */
 package org.apache.felix.http.base.internal.service;
 
+import static org.apache.felix.http.base.internal.util.UriUtils.decodePath;
+import static org.apache.felix.http.base.internal.util.UriUtils.removeDotSegments;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
@@ -47,7 +50,14 @@
 import javax.servlet.http.HttpSessionListener;
 
 import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.dispatch.RequestDispatcherImpl;
+import org.apache.felix.http.base.internal.dispatch.RequestInfo;
+import org.apache.felix.http.base.internal.handler.ServletHandler;
 import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.apache.felix.http.base.internal.registry.HandlerRegistry;
+import org.apache.felix.http.base.internal.registry.PathResolution;
+import org.apache.felix.http.base.internal.registry.PerContextHandlerRegistry;
+import org.apache.felix.http.base.internal.registry.ServletResolution;
 import org.apache.felix.http.base.internal.util.MimeTypes;
 import org.osgi.framework.Bundle;
 import org.osgi.service.http.HttpContext;
@@ -64,7 +74,8 @@
     private final HttpSessionListener httpSessionListener;
     private final ServletRequestListener servletRequestListener;
     private final ServletRequestAttributeListener servletRequestAttributeListener;
-
+    private final PerContextHandlerRegistry handlerRegistry;
+    
     public ServletContextImpl(final Bundle bundle,
             final ServletContext context,
             final HttpContext httpContext,
@@ -73,7 +84,8 @@
             final HttpSessionAttributeListener httpSessionAttributeListener,
             final HttpSessionListener httpSessionListener,
             final ServletRequestListener servletRequestListener,
-            final ServletRequestAttributeListener servletRequestAttributeListener)
+            final ServletRequestAttributeListener servletRequestAttributeListener,
+            final PerContextHandlerRegistry registry)
     {
         this.bundle = bundle;
         this.context = context;
@@ -84,6 +96,7 @@
         this.httpSessionListener = httpSessionListener;
         this.servletRequestAttributeListener = servletRequestAttributeListener;
         this.servletRequestListener = servletRequestListener;
+        this.handlerRegistry = registry;
     }
 
     @Override
@@ -278,12 +291,6 @@
     }
 
     @Override
-    public RequestDispatcher getNamedDispatcher(String name)
-    {
-        return this.context.getNamedDispatcher(name);
-    }
-
-    @Override
     public String getRealPath(String name)
     {
         URL url = getResource(name);
@@ -295,12 +302,6 @@
     }
 
     @Override
-    public RequestDispatcher getRequestDispatcher(String uri)
-    {
-        return this.context.getRequestDispatcher(uri);
-    }
-
-    @Override
     public URL getResource(String path)
     {
         return this.httpContext.getResource(normalizeResourcePath(path));
@@ -501,6 +502,72 @@
         this.context.setSessionTrackingModes(modes);
     }
 
+    @Override
+    public RequestDispatcher getNamedDispatcher(final String name)
+    {
+        if (name == null)
+        {
+            return null;
+        }
+
+        final RequestDispatcher dispatcher;
+        final ServletHandler servletHandler = this.handlerRegistry.resolveServletByName(name);
+        if ( servletHandler != null ) 
+        {
+        	final ServletResolution resolution = new ServletResolution();
+        	resolution.handler = servletHandler;
+            resolution.handlerRegistry = this.handlerRegistry;
+            // TODO - what is the path of a named servlet?
+            final RequestInfo requestInfo = new RequestInfo("", null, null);
+            dispatcher = new RequestDispatcherImpl(resolution, requestInfo);
+        }
+        else 
+        {
+        	dispatcher = null;
+        }
+        return dispatcher;
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        // See section 9.1 of Servlet 3.x specification...
+        if (path == null || (!path.startsWith("/") && !"".equals(path)))
+        {
+            return null;
+        }
+
+        String query = null;
+        int q = 0;
+        if ((q = path.indexOf('?')) > 0)
+        {
+            query = path.substring(q + 1);
+            path = path.substring(0, q);
+        }
+        // TODO remove path parameters...
+        String requestURI = decodePath(removeDotSegments(path));
+        if ( requestURI == null )
+        {
+            requestURI = "";
+        }
+
+        final RequestDispatcher dispatcher;
+        final PathResolution pathResolution = this.handlerRegistry.resolve(requestURI);
+        if ( pathResolution != null ) 
+        {
+        	final ServletResolution resolution = new ServletResolution();
+        	resolution.handler = pathResolution.handler;
+            resolution.handlerRegistry = this.handlerRegistry;
+            final RequestInfo requestInfo = new RequestInfo(pathResolution.servletPath, pathResolution.pathInfo, query);
+            dispatcher = new RequestDispatcherImpl(resolution, requestInfo);
+        }
+        else 
+        {
+        	dispatcher = null;
+        }
+        return dispatcher;
+    }
+    
     private String normalizePath(String path)
     {
         if (path == null)
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextManager.java
index bfa50f0..ecb11b9 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextManager.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/ServletContextManager.java
@@ -25,6 +25,8 @@
 import javax.servlet.ServletRequestListener;
 
 import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.registry.HandlerRegistry;
+import org.apache.felix.http.base.internal.registry.PerContextHandlerRegistry;
 import org.osgi.framework.Bundle;
 import org.osgi.service.http.HttpContext;
 
@@ -37,6 +39,7 @@
     private final boolean sharedAttributes;
     private final ServletRequestListener servletRequestListener;
     private final ServletRequestAttributeListener servletRequestAttributeListener;
+    private final PerContextHandlerRegistry handlerRegistry;
 
     public ServletContextManager(
             final Bundle bundle,
@@ -44,7 +47,8 @@
             final ServletContextAttributeListener attributeListener,
             final boolean sharedAttributes,
             final ServletRequestListener servletRequestListener,
-            final ServletRequestAttributeListener servletRequestAttributeListener)
+            final ServletRequestAttributeListener servletRequestAttributeListener,
+            final PerContextHandlerRegistry registry)
     {
         this.bundle = bundle;
         this.context = context;
@@ -56,6 +60,7 @@
         // drops to zero.
         this.contextMap = new WeakHashMap<HttpContext, ExtServletContext>();
         this.sharedAttributes = sharedAttributes;
+        this.handlerRegistry = registry;
     }
 
     public ExtServletContext getServletContext(HttpContext httpContext)
@@ -82,7 +87,8 @@
                 null,
                 null,
                 servletRequestListener,
-                servletRequestAttributeListener);
+                servletRequestAttributeListener,
+                handlerRegistry);
         this.contextMap.put(httpContext, context);
         return context;
     }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java
index ea25eb4..05e952c 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/SharedHttpServiceImpl.java
@@ -128,4 +128,9 @@
             this.handlerRegistry.getRegistry(handler.getContextServiceId()).unregisterFilter(handler.getFilterInfo(), destroy);
         }
     }
+
+	public HandlerRegistry getHandlerRegistry() 
+	{
+		return this.handlerRegistry;
+	}
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/SharedServletContextImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/SharedServletContextImpl.java
index 75aa90c..d0340d2 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/SharedServletContextImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/SharedServletContextImpl.java
@@ -16,6 +16,9 @@
  */
 package org.apache.felix.http.base.internal.whiteboard;
 
+import static org.apache.felix.http.base.internal.util.UriUtils.decodePath;
+import static org.apache.felix.http.base.internal.util.UriUtils.removeDotSegments;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
@@ -40,7 +43,13 @@
 import javax.servlet.SessionTrackingMode;
 import javax.servlet.descriptor.JspConfigDescriptor;
 
+import org.apache.felix.http.base.internal.dispatch.RequestDispatcherImpl;
+import org.apache.felix.http.base.internal.dispatch.RequestInfo;
+import org.apache.felix.http.base.internal.handler.ServletHandler;
 import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.apache.felix.http.base.internal.registry.PathResolution;
+import org.apache.felix.http.base.internal.registry.PerContextHandlerRegistry;
+import org.apache.felix.http.base.internal.registry.ServletResolution;
 
 /**
  * This servlet context implementation represents the shared
@@ -55,6 +64,7 @@
     private final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
     private final String contextPath;
     private final String name;
+    private final PerContextHandlerRegistry registry;
     private final ServletContextAttributeListener attributeListener;
     private final Map<String, String> initParameters = new HashMap<String, String>();
 
@@ -62,7 +72,7 @@
             final String name,
             final String path,
             final Map<String, String> initParameters,
-            final ServletContextAttributeListener servletContextAttributeListener)
+            final PerContextHandlerRegistry registry)
     {
         this.context = webContext;
         if ( path.equals("/") )
@@ -78,7 +88,8 @@
         {
             this.initParameters.putAll(initParameters);
         }
-        this.attributeListener = servletContextAttributeListener;
+        this.attributeListener = registry.getEventListenerRegistry();
+        this.registry = registry;
     }
 
     @Override
@@ -295,21 +306,73 @@
     @Override
     public RequestDispatcher getNamedDispatcher(final String name)
     {
-        // This is implemented by the ServletContext wrapper created in the Dispatcher
-        return null;
+        if (name == null)
+        {
+            return null;
+        }
+
+        final RequestDispatcher dispatcher;
+        final ServletHandler servletHandler = this.registry.resolveServletByName(name);
+        if ( servletHandler != null ) 
+        {
+        	final ServletResolution resolution = new ServletResolution();
+        	resolution.handler = servletHandler;
+            resolution.handlerRegistry = this.registry;
+            // TODO - what is the path of a named servlet?
+            final RequestInfo requestInfo = new RequestInfo("", null, null);
+            dispatcher = new RequestDispatcherImpl(resolution, requestInfo);
+        }
+        else 
+        {
+        	dispatcher = null;
+        }
+        return dispatcher;
     }
 
     @Override
-    public RequestDispatcher getRequestDispatcher(final String uri)
+    public RequestDispatcher getRequestDispatcher(String path)
     {
-        // This is implemented by the ServletContext wrapper created in the Dispatcher
-        return null;
+        // See section 9.1 of Servlet 3.x specification...
+        if (path == null || (!path.startsWith("/") && !"".equals(path)))
+        {
+            return null;
+        }
+
+        String query = null;
+        int q = 0;
+        if ((q = path.indexOf('?')) > 0)
+        {
+            query = path.substring(q + 1);
+            path = path.substring(0, q);
+        }
+        // TODO remove path parameters...
+        String requestURI = decodePath(removeDotSegments(path));
+        if ( requestURI == null )
+        {
+            requestURI = "";
+        }
+
+        final RequestDispatcher dispatcher;
+        final PathResolution pathResolution = this.registry.resolve(requestURI);
+        if ( pathResolution != null ) 
+        {
+        	final ServletResolution resolution = new ServletResolution();
+        	resolution.handler = pathResolution.handler;
+            resolution.handlerRegistry = this.registry;
+            final RequestInfo requestInfo = new RequestInfo(pathResolution.servletPath, pathResolution.pathInfo, query);
+            dispatcher = new RequestDispatcherImpl(resolution, requestInfo);
+        }
+        else 
+        {
+        	dispatcher = null;
+        }
+        return dispatcher;
     }
 
     @Override
     public InputStream getResourceAsStream(final String path)
     {
-        // This is implemented by the ServletContext wrapper created in the Dispatcher
+        // is implemented by {@link PerBundleServletContextImpl}.
         return null;
     }
 
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardContextHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardContextHandler.java
index 6bdca22..e733f05 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardContextHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardContextHandler.java
@@ -92,7 +92,7 @@
                 info.getName(),
                 info.getPath(),
                 info.getInitParameters(),
-                this.registry.getEventListenerRegistry());
+                this.registry);
         final boolean activate = getServletContext(httpBundle) != null;
         if ( !activate )
         {
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextImplTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextImplTest.java
index 0a3cf91..8920056 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextImplTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextImplTest.java
@@ -452,7 +452,7 @@
         this.httpContext = Mockito.mock(HttpContext.class);
         this.listener = new AttributeListener();
         this.context = new ServletContextImpl(this.bundle, globalContext, this.httpContext, this.listener, false,
-                null, null, null, null);
+                null, null, null, null, null);
     }
 
     @Test
@@ -587,9 +587,9 @@
     {
         ServletContext globalContext = new MockServletContext();
         ServletContext ctx1 = new ServletContextImpl(bundle, globalContext, httpContext, listener, true,
-                null, null, null, null);
+                null, null, null, null, null);
         ServletContext ctx2 = new ServletContextImpl(bundle, globalContext, httpContext, listener, true,
-                null, null, null, null);
+                null, null, null, null, null);
 
         Assert.assertNull(ctx1.getAttribute("key1"));
         Assert.assertNull(ctx2.getAttribute("key1"));
@@ -698,9 +698,9 @@
     {
         ServletContext globalContext = new MockServletContext();
         ServletContext ctx1 = new ServletContextImpl(bundle, globalContext, httpContext, listener, true,
-                null, null, null, null);
+                null, null, null, null, null);
         ServletContext ctx2 = new ServletContextImpl(bundle, globalContext, httpContext, listener, true,
-                null, null, null, null);
+                null, null, null, null, null);
 
         Enumeration e = ctx1.getAttributeNames();
         Assert.assertNotNull(e);
@@ -736,9 +736,9 @@
     {
         ServletContext globalContext = new MockServletContext();
         ServletContext ctx1 = new ServletContextImpl(bundle, globalContext, httpContext, listener, false,
-                null, null, null, null);
+                null, null, null, null, null);
         ServletContext ctx2 = new ServletContextImpl(bundle, globalContext, httpContext, listener, false,
-                null, null, null, null);
+                null, null, null, null, null);
 
         Assert.assertNull(ctx1.getAttribute("key1"));
         Assert.assertNull(ctx2.getAttribute("key1"));
@@ -784,9 +784,9 @@
     {
         ServletContext globalContext = new MockServletContext();
         ServletContext ctx1 = new ServletContextImpl(bundle, globalContext, httpContext, listener, false,
-                null, null, null, null);
+                null, null, null, null, null);
         ServletContext ctx2 = new ServletContextImpl(bundle, globalContext, httpContext, listener, false,
-                null, null, null, null);
+                null, null, null, null, null);
 
         Enumeration e = ctx1.getAttributeNames();
         Assert.assertNotNull(e);
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextManagerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextManagerTest.java
index 2ea84f5..39f79b7 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextManagerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/context/ServletContextManagerTest.java
@@ -36,7 +36,7 @@
         Bundle bundle = Mockito.mock(Bundle.class);
         ServletContext globalContext = Mockito.mock(ServletContext.class);
         this.manager = new ServletContextManager(bundle, globalContext, null, false,
-                null, null);
+                null, null, null);
     }
 
     @Test