FELIX-2774, FELIX-2797 & FELIX-4424:

- implement support for including and forwarding requests using the Servlet
  RequestDispatcher, including several itests to verify behaviour [FELIX-2774];
- wrap HttpSessions in order to provide access to the correct ServletContext
  [FELIX-2797];
- rudimentary fix for possible classloader leakage [FELIX-4424];
- prepared the Servlet- & FilterHandlers for named servlet/filters;
- several fixes to make the HTTP implementation more conform the Servlet 3.0
  specification.



git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1567561 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java b/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java
index 36d21b3..05b93a7 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/DispatcherServlet.java
@@ -16,22 +16,22 @@
  */
 package org.apache.felix.http.base.internal;
 
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestEvent;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletRequestAttributeEvent;
-import javax.servlet.ServletRequestEvent;
 
 import org.apache.felix.http.base.internal.listener.ServletRequestAttributeListenerManager;
+import org.apache.felix.http.base.internal.listener.ServletRequestListenerManager;
 
-import java.io.IOException;
-
-public final class DispatcherServlet
-    extends HttpServlet
+public final class DispatcherServlet extends HttpServlet
 {
     private final HttpServiceController controller;
 
@@ -41,8 +41,7 @@
     }
 
     @Override
-    public void init(ServletConfig config)
-        throws ServletException
+    public void init(ServletConfig config) throws ServletException
     {
         super.init(config);
         this.controller.register(getServletContext());
@@ -56,11 +55,12 @@
     }
 
     @Override
-    protected void service(HttpServletRequest req, HttpServletResponse res)
-        throws ServletException, IOException
+    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
     {
+        ServletRequestListenerManager requestListener = this.controller.getRequestListener();
+
         final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), req);
-        this.controller.getRequestListener().requestInitialized(sre);
+        requestListener.requestInitialized(sre);
         try
         {
             req = new AttributeEventRequest(getServletContext(), this.controller.getRequestAttributeListener(), req);
@@ -68,22 +68,20 @@
         }
         finally
         {
-            this.controller.getRequestListener().requestDestroyed(sre);
+            requestListener.requestDestroyed(sre);
         }
     }
 
     private static class AttributeEventRequest extends HttpServletRequestWrapper
     {
-
         private final ServletContext servletContext;
-        private final ServletRequestAttributeListenerManager requestAttributeListener;
+        private final ServletRequestAttributeListenerManager listener;
 
-        public AttributeEventRequest(ServletContext servletContext,
-            ServletRequestAttributeListenerManager requestAttributeListener, HttpServletRequest request)
+        public AttributeEventRequest(ServletContext servletContext, ServletRequestAttributeListenerManager requestAttributeListener, HttpServletRequest request)
         {
             super(request);
             this.servletContext = servletContext;
-            this.requestAttributeListener = requestAttributeListener;
+            this.listener = requestAttributeListener;
         }
 
         public void setAttribute(String name, Object value)
@@ -99,13 +97,11 @@
 
                 if (oldValue == null)
                 {
-                    requestAttributeListener.attributeAdded(new ServletRequestAttributeEvent(servletContext, this,
-                        name, value));
+                    this.listener.attributeAdded(new ServletRequestAttributeEvent(this.servletContext, this, name, value));
                 }
                 else
                 {
-                    requestAttributeListener.attributeReplaced(new ServletRequestAttributeEvent(servletContext, this,
-                        name, oldValue));
+                    this.listener.attributeReplaced(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
                 }
             }
         }
@@ -117,9 +113,14 @@
 
             if (oldValue != null)
             {
-                requestAttributeListener.attributeRemoved(new ServletRequestAttributeEvent(servletContext, this, name,
-                    oldValue));
+                this.listener.attributeRemoved(new ServletRequestAttributeEvent(this.servletContext, this, name, oldValue));
             }
         }
+
+        @Override
+        public String toString()
+        {
+            return getClass().getSimpleName() + "->" + super.getRequest();
+        }
     }
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java b/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
index dfab10a..34d5903 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/HttpServiceController.java
@@ -81,7 +81,7 @@
         this.sessionListener = new HttpSessionListenerManager(bundleContext);
         this.sessionAttributeListener = new HttpSessionAttributeListenerManager(bundleContext);
         this.sharedContextAttributes = getBoolean(FELIX_HTTP_SHARED_SERVLET_CONTEXT_ATTRIBUTES);
-        this.plugin = new HttpServicePlugin(bundleContext,registry);
+        this.plugin = new HttpServicePlugin(bundleContext, registry);
     }
 
     public Dispatcher getDispatcher()
@@ -119,7 +119,8 @@
         this.serviceProps.clear();
         this.serviceProps.putAll(props);
 
-        if (this.serviceReg != null) {
+        if (this.serviceReg != null)
+        {
             this.serviceReg.setProperties(this.serviceProps);
         }
     }
@@ -133,15 +134,16 @@
         this.sessionAttributeListener.open();
         this.plugin.register();
 
-        HttpServiceFactory factory = new HttpServiceFactory(servletContext, this.registry,
-            this.contextAttributeListener, this.sharedContextAttributes);
         String[] ifaces = new String[] { HttpService.class.getName(), ExtHttpService.class.getName() };
+        HttpServiceFactory factory = new HttpServiceFactory(servletContext, this.registry, this.contextAttributeListener, this.sharedContextAttributes);
+
         this.serviceReg = this.bundleContext.registerService(ifaces, factory, this.serviceProps);
     }
 
     public void unregister()
     {
-        if (this.serviceReg == null) {
+        if (this.serviceReg == null)
+        {
             return;
         }
 
@@ -152,10 +154,13 @@
         this.requestAttributeListener.close();
         this.plugin.unregister();
 
-        try {
+        try
+        {
             this.serviceReg.unregister();
             this.registry.removeAll();
-        } finally {
+        }
+        finally
+        {
             this.serviceReg = null;
         }
     }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java
index 56f67d7..fc7ec8b 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ExtServletContext.java
@@ -22,9 +22,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-public interface ExtServletContext
-    extends ServletContext
+public interface ExtServletContext extends ServletContext
 {
-    public boolean handleSecurity(HttpServletRequest req, HttpServletResponse res)
-        throws IOException;
+    boolean handleSecurity(HttpServletRequest req, HttpServletResponse res) throws IOException;
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java
index ca6d952..e0fa187 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextImpl.java
@@ -48,7 +48,7 @@
 import org.osgi.service.http.HttpContext;
 
 @SuppressWarnings("deprecation")
-public final class ServletContextImpl implements ExtServletContext
+public class ServletContextImpl implements ExtServletContext
 {
     private final Bundle bundle;
     private final ServletContext context;
@@ -65,6 +65,16 @@
         this.attributes = sharedAttributes ? null : new ConcurrentHashMap<String, Object>();
     }
 
+    protected ServletContextImpl(ExtServletContext delegate)
+    {
+        ServletContextImpl impl = (ServletContextImpl) delegate;
+        this.bundle = impl.bundle;
+        this.context = impl.context;
+        this.httpContext = impl.httpContext;
+        this.attributeListener = impl.attributeListener;
+        this.attributes = impl.attributes;
+    }
+
     public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> type)
     {
         throw new UnsupportedOperationException();
@@ -229,7 +239,8 @@
     public String getRealPath(String name)
     {
         URL url = getResource(normalizePath(name));
-        if (url == null) {
+        if (url == null)
+        {
             return null;
         }
         return url.toExternalForm();
@@ -354,7 +365,7 @@
 
         if (oldValue != null)
         {
-            attributeListener.attributeRemoved(new ServletContextAttributeEvent(this, name, oldValue));
+            this.attributeListener.attributeRemoved(new ServletContextAttributeEvent(this, name, oldValue));
         }
     }
 
@@ -379,11 +390,11 @@
 
             if (oldValue == null)
             {
-                attributeListener.attributeAdded(new ServletContextAttributeEvent(this, name, value));
+                this.attributeListener.attributeAdded(new ServletContextAttributeEvent(this, name, value));
             }
             else
             {
-                attributeListener.attributeReplaced(new ServletContextAttributeEvent(this, name, oldValue));
+                this.attributeListener.attributeReplaced(new ServletContextAttributeEvent(this, name, oldValue));
             }
         }
     }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java
index 2ab7919..9f6aec6 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/context/ServletContextManager.java
@@ -16,8 +16,8 @@
  */
 package org.apache.felix.http.base.internal.context;
 
-import java.util.HashMap;
 import java.util.Map;
+import java.util.WeakHashMap;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContextAttributeListener;
@@ -33,32 +33,35 @@
     private final Map<HttpContext, ExtServletContext> contextMap;
     private final boolean sharedAttributes;
 
-    public ServletContextManager(Bundle bundle, ServletContext context,
-        ServletContextAttributeListener attributeListener, boolean sharedAttributes)
+    public ServletContextManager(Bundle bundle, ServletContext context, ServletContextAttributeListener attributeListener, boolean sharedAttributes)
     {
         this.bundle = bundle;
         this.context = context;
         this.attributeListener = attributeListener;
-        this.contextMap = new HashMap<HttpContext, ExtServletContext>();
+        // FELIX-4424 : avoid classloader leakage through HttpContext, for now this is sufficient, 
+        // the real fix should be to remove ExtServletContext's when the usage count of HttpContext 
+        // drops to zero. 
+        this.contextMap = new WeakHashMap<HttpContext, ExtServletContext>();
         this.sharedAttributes = sharedAttributes;
     }
 
     public ExtServletContext getServletContext(HttpContext httpContext)
     {
-        synchronized (this.contextMap) {
-            ExtServletContext context = this.contextMap.get(httpContext);
-            if (context == null) {
+        ExtServletContext context;
+        synchronized (this.contextMap)
+        {
+            context = this.contextMap.get(httpContext);
+            if (context == null)
+            {
                 context = addServletContext(httpContext);
             }
-
-            return context;
         }
+        return context;
     }
 
     private ExtServletContext addServletContext(HttpContext httpContext)
     {
-        ExtServletContext context = new ServletContextImpl(this.bundle, this.context, httpContext, attributeListener,
-            sharedAttributes);
+        ExtServletContext context = new ServletContextImpl(this.bundle, this.context, httpContext, this.attributeListener, this.sharedAttributes);
         this.contextMap.put(httpContext, context);
         return context;
     }
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 a7e6ba0..f48edf8 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,14 +16,21 @@
  */
 package org.apache.felix.http.base.internal.dispatch;
 
-import org.apache.felix.http.base.internal.handler.HandlerRegistry;
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.ServletException;
-import java.io.IOException;
+
+import org.apache.felix.http.base.internal.handler.HandlerRegistry;
 
 public final class Dispatcher
 {
+    public static final String REQUEST_DISPATCHER_PROVIDER = "org.apache.felix.http.requestDispatcherProvider";
+
+    private static final FilterChain DEFAULT_CHAIN = new NotFoundFilterChain();
+
     private final HandlerRegistry handlerRegistry;
 
     public Dispatcher(HandlerRegistry handlerRegistry)
@@ -31,11 +38,19 @@
         this.handlerRegistry = handlerRegistry;
     }
 
-    public void dispatch(HttpServletRequest req, HttpServletResponse res)
-        throws ServletException, IOException
+    public void dispatch(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
     {
         ServletPipeline servletPipeline = new ServletPipeline(this.handlerRegistry.getServlets());
-        FilterPipeline filterPipeline = new FilterPipeline(this.handlerRegistry.getFilters(), servletPipeline);
-        filterPipeline.dispatch(req, res, new NotFoundFilterChain());
+        // Provides access to the correct request dispatcher...
+        req.setAttribute(REQUEST_DISPATCHER_PROVIDER, servletPipeline);
+
+        try
+        {
+            new FilterPipeline(this.handlerRegistry.getFilters(), servletPipeline).dispatch(req, res, DEFAULT_CHAIN);
+        }
+        finally
+        {
+            req.removeAttribute(REQUEST_DISPATCHER_PROVIDER);
+        }
     }
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java
index 880b78c..c6a696a 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/FilterPipeline.java
@@ -16,17 +16,45 @@
  */
 package org.apache.felix.http.base.internal.dispatch;
 
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.ServletException;
+import java.io.IOException;
+
 import javax.servlet.FilterChain;
 import javax.servlet.RequestDispatcher;
-import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.felix.http.base.internal.handler.FilterHandler;
 
 public final class FilterPipeline
 {
+    private static class FilterRequestWrapper extends HttpServletRequestWrapper
+    {
+        public FilterRequestWrapper(HttpServletRequest req)
+        {
+            super(req);
+        }
+
+        @Override
+        public RequestDispatcher getRequestDispatcher(String path)
+        {
+            RequestDispatcherProvider provider = (RequestDispatcherProvider) getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+            RequestDispatcher dispatcher = null;
+            if (provider != null)
+            {
+                dispatcher = provider.getRequestDispatcher(path);
+            }
+            return (dispatcher != null) ? dispatcher : super.getRequestDispatcher(path);
+        }
+
+        @Override
+        public String toString()
+        {
+            return getClass().getSimpleName() + "->" + super.getRequest();
+        }
+    }
+
     private final FilterHandler[] handlers;
     private final ServletPipeline servletPipeline;
 
@@ -36,31 +64,15 @@
         this.servletPipeline = servletPipeline;
     }
 
-    public void dispatch(HttpServletRequest req, HttpServletResponse res, FilterChain proceedingChain)
-        throws ServletException, IOException
+    public void dispatch(HttpServletRequest req, HttpServletResponse res, FilterChain proceedingChain) throws ServletException, IOException
     {
         FilterChain chain = new InvocationFilterChain(this.handlers, this.servletPipeline, proceedingChain);
 
-        if (this.servletPipeline.hasServletsMapped()) {
-            req = new RequestWrapper(req);
+        if (this.servletPipeline.hasServletsMapped())
+        {
+            req = new FilterRequestWrapper(req);
         }
 
         chain.doFilter(req, res);
     }
-
-    private final class RequestWrapper
-        extends HttpServletRequestWrapper
-    {
-        public RequestWrapper(HttpServletRequest req)
-        {
-            super(req);
-        }
-
-        @Override
-        public RequestDispatcher getRequestDispatcher(String path)
-        {
-            final RequestDispatcher dispatcher = servletPipeline.getRequestDispatcher(path);
-            return (null != dispatcher) ? dispatcher : super.getRequestDispatcher(path);
-        }        
-    }
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java
index 57fb49b..e8cc480 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java
@@ -24,15 +24,12 @@
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
-public abstract class HttpFilterChain 
-    implements FilterChain
+public abstract class HttpFilterChain implements FilterChain
 {
-    public final void doFilter(ServletRequest req, ServletResponse res)
-        throws IOException, ServletException
+    public final void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException
     {
-        doFilter((HttpServletRequest)req, (HttpServletResponse)res);
+        doFilter((HttpServletRequest) req, (HttpServletResponse) res);
     }
 
-    protected abstract void doFilter(HttpServletRequest req, HttpServletResponse res)
-        throws IOException, ServletException;
+    protected abstract void doFilter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException;
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java
index 0072a91..6bbf4f2 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java
@@ -23,12 +23,11 @@
 import java.io.IOException;
 import org.apache.felix.http.base.internal.handler.FilterHandler;
 
-public final class InvocationFilterChain
-    extends HttpFilterChain
+public final class InvocationFilterChain extends HttpFilterChain
 {
     private final FilterHandler[] handlers;
     private final ServletPipeline servletPipeline;
-    private final FilterChain proceedingChain;    
+    private final FilterChain proceedingChain;
     private int index = -1;
 
     public InvocationFilterChain(FilterHandler[] handlers, ServletPipeline servletPipeline, FilterChain proceedingChain)
@@ -38,15 +37,18 @@
         this.proceedingChain = proceedingChain;
     }
 
-    protected void doFilter(HttpServletRequest req, HttpServletResponse res)
-        throws IOException, ServletException
+    protected void doFilter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException
     {
         this.index++;
 
-        if (this.index < this.handlers.length) {
+        if (this.index < this.handlers.length)
+        {
             this.handlers[this.index].handle(req, res, this);
-        } else {
-            if (!this.servletPipeline.handle(req, res)) {
+        }
+        else
+        {
+            if (!this.servletPipeline.handle(req, res))
+            {
                 this.proceedingChain.doFilter(req, res);
             }
         }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java
index 3ee23b2..4f0d012 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java
@@ -21,11 +21,9 @@
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
-public final class NotFoundFilterChain
-    extends HttpFilterChain
+public final class NotFoundFilterChain extends HttpFilterChain
 {
-    protected void doFilter(HttpServletRequest req, HttpServletResponse res)
-        throws IOException, ServletException
+    protected void doFilter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException
     {
         res.sendError(HttpServletResponse.SC_NOT_FOUND);
     }
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
new file mode 100644
index 0000000..a863bd2
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/RequestDispatcherProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface RequestDispatcherProvider
+{
+    /**
+     * @see ServletContext#getNamedDispatcher(String)
+     */
+    RequestDispatcher getNamedDispatcher(String name);
+
+    /**
+     * @see ServletContext#getRequestDispatcher(String)
+     */
+    RequestDispatcher getRequestDispatcher(String path);
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java
index 053a09d..b2618f2 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/ServletPipeline.java
@@ -16,17 +16,19 @@
  */
 package org.apache.felix.http.base.internal.dispatch;
 
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.ServletException;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+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 javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.felix.http.base.internal.handler.ServletHandler;
 
-public final class ServletPipeline
+public final class ServletPipeline implements RequestDispatcherProvider
 {
     private final ServletHandler[] handlers;
 
@@ -35,11 +37,61 @@
         this.handlers = handlers;
     }
 
-    public boolean handle(HttpServletRequest req, HttpServletResponse res)
-        throws ServletException, IOException
+    public RequestDispatcher getNamedDispatcher(String name)
     {
-        for (ServletHandler handler : this.handlers) {
-            if (handler.handle(req, res)) {
+        // See section 9.1 of Servlet 3.x specification...
+        if (name == null)
+        {
+            return null;
+        }
+
+        for (ServletHandler handler : this.handlers)
+        {
+            if (name.equals(handler.getName()))
+            {
+                return handler.createNamedRequestDispatcher();
+            }
+        }
+
+        return null;
+    }
+
+    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 pathInContext = decodePath(removeDotSegments(path));
+
+        for (ServletHandler handler : this.handlers)
+        {
+            if (handler.matches(pathInContext))
+            {
+                return handler.createRequestDispatcher(path, pathInContext, query);
+            }
+        }
+
+        return null;
+    }
+
+    public boolean handle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
+    {
+        // NOTE: this code assumes that HttpServletRequest#getRequestDispatcher() is properly mapped, see FilterPipeline.FilterRequestWrapper!
+        for (ServletHandler handler : this.handlers)
+        {
+            if (handler.handle(req, res))
+            {
                 return true;
             }
         }
@@ -51,61 +103,4 @@
     {
         return this.handlers.length > 0;
     }
-
-    public RequestDispatcher getRequestDispatcher(String path)
-    {
-        for (ServletHandler handler : this.handlers) {
-            if (handler.matches(path)) {
-                return new Dispatcher(path, handler);
-            }
-        }
-        
-        return null;
-    }
-
-    private final class Dispatcher
-        implements RequestDispatcher
-    {
-        private final String path;
-        private final ServletHandler handler;
-
-        public Dispatcher(String path, ServletHandler handler)
-        {
-            this.path = path;
-            this.handler = handler;
-        }
-
-        public void forward(ServletRequest req, ServletResponse res)
-            throws ServletException, IOException
-        {
-            if (res.isCommitted()) {
-                throw new ServletException("Response has been committed");
-            }
-
-            this.handler.handle(new RequestWrapper((HttpServletRequest)req, this.path), (HttpServletResponse)res);
-        }
-
-        public void include(ServletRequest req, ServletResponse res)
-            throws ServletException, IOException
-        {
-            this.handler.handle((HttpServletRequest)req, (HttpServletResponse)res);
-        }
-    }
-
-    private final class RequestWrapper
-        extends HttpServletRequestWrapper
-    {
-        private final String requestUri;
-        
-        public RequestWrapper(HttpServletRequest req, String requestUri)
-        {
-            super(req);
-            this.requestUri = requestUri;
-        }
-
-        public String getRequestURI()
-        {
-            return this.requestUri;
-        }
-    }
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java
index f5c7041..008732e 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/AbstractHandler.java
@@ -16,30 +16,70 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
 import javax.servlet.ServletException;
+
 import org.apache.felix.http.base.internal.context.ExtServletContext;
+
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 
 public abstract class AbstractHandler
 {
-    private final static AtomicInteger ID =
-        new AtomicInteger();
+    private final static AtomicInteger ID = new AtomicInteger();
 
-    private final String id;
+    private final int id;
+    private final String baseName;
     private final ExtServletContext context;
     private final Map<String, String> initParams;
 
-    public AbstractHandler(ExtServletContext context)
+    public AbstractHandler(ExtServletContext context, String baseName)
     {
-        this.id = "" + ID.incrementAndGet();
         this.context = context;
+        this.baseName = baseName;
+        this.id = ID.incrementAndGet();
         this.initParams = new HashMap<String, String>();
     }
 
-    public final String getId()
+    public abstract void destroy();
+
+    public final Map<String, String> getInitParams()
     {
-        return this.id;
+        return this.initParams;
+    }
+
+    public final String getName()
+    {
+        String name = this.baseName;
+        if (name == null)
+        {
+            name = String.format("%s_%d", getSubject().getClass(), this.id);
+        }
+        return name;
+    }
+
+    public abstract void init() throws ServletException;
+
+    public final void setInitParams(Dictionary map)
+    {
+        this.initParams.clear();
+        if (map == null)
+        {
+            return;
+        }
+
+        Enumeration e = map.keys();
+        while (e.hasMoreElements())
+        {
+            Object key = e.nextElement();
+            Object value = map.get(key);
+
+            if ((key instanceof String) && (value instanceof String))
+            {
+                this.initParams.put((String) key, (String) value);
+            }
+        }
     }
 
     protected final ExtServletContext getContext()
@@ -47,31 +87,16 @@
         return this.context;
     }
 
-    public final Map<String, String> getInitParams()
+    /**
+     * @return a unique ID for this handler, &gt; 0.
+     */
+    protected final int getId()
     {
-        return this.initParams;
+        return id;
     }
 
-    public final void setInitParams(Dictionary map)
-    {
-        this.initParams.clear();
-        if (map == null) {
-            return;
-        }
-
-        Enumeration e = map.keys();
-        while (e.hasMoreElements()) {
-            Object key = e.nextElement();
-            Object value = map.get(key);
-
-            if ((key instanceof String) && (value instanceof String)) {
-                this.initParams.put((String)key, (String)value);
-            }
-        }
-    }
-
-    public abstract void init()
-        throws ServletException;
-
-    public abstract void destroy();
+    /**
+     * @return the {@link Servlet} or {@link Filter} this handler handles.
+     */
+    protected abstract Object getSubject();
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java
index b807fe1..f0bcdab 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterConfigImpl.java
@@ -22,8 +22,7 @@
 import java.util.Collections;
 import java.util.Map;
 
-public final class FilterConfigImpl
-    implements FilterConfig
+public final class FilterConfigImpl implements FilterConfig
 {
     private final String name;
     private final ServletContext context;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java
index f28bc31..52770fe 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java
@@ -19,74 +19,26 @@
 import java.io.IOException;
 import java.util.regex.Pattern;
 
-import javax.servlet.*;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.http.base.internal.context.ExtServletContext;
 
-public final class FilterHandler
-    extends AbstractHandler implements Comparable<FilterHandler>
+public final class FilterHandler extends AbstractHandler implements Comparable<FilterHandler>
 {
     private final Filter filter;
     private final Pattern regex;
     private final int ranking;
 
-    public FilterHandler(ExtServletContext context, Filter filter, String pattern, int ranking)
+    public FilterHandler(ExtServletContext context, Filter filter, String pattern, int ranking, String name)
     {
-        super(context);
+        super(context, name);
         this.filter = filter;
         this.ranking = ranking;
-	    this.regex = Pattern.compile(pattern);
-    }
-
-    public Filter getFilter()
-    {
-        return this.filter;
-    }
-
-    public void init()
-        throws ServletException
-    {
-        String name = "filter_" + getId();
-        FilterConfig config = new FilterConfigImpl(name, getContext(), getInitParams());
-        this.filter.init(config);
-    }
-
-    public void destroy()
-    {
-        this.filter.destroy();
-    }
-
-    public boolean matches(String uri)
-    {
-        // assume root if uri is null
-        if (uri == null) {
-            uri = "/";
-        }
-
-        return this.regex.matcher(uri).matches();
-    }
-
-    public void handle(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
-        throws ServletException, IOException
-    {
-        final boolean matches = matches(req.getPathInfo());
-        if (matches) {
-            doHandle(req, res, chain);
-        } else {
-            chain.doFilter(req, res);
-        }
-    }
-
-    private void doHandle(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
-        throws ServletException, IOException
-    {
-        if (!getContext().handleSecurity(req, res)) {
-            res.sendError(HttpServletResponse.SC_FORBIDDEN);
-        } else {
-            this.filter.doFilter(req, res, chain);
-        }
+        this.regex = Pattern.compile(pattern);
     }
 
     public int compareTo(FilterHandler other)
@@ -99,13 +51,70 @@
         return (other.ranking > this.ranking) ? 1 : -1;
     }
 
-    public int getRanking()
+    public void destroy()
     {
-        return ranking;
+        this.filter.destroy();
+    }
+
+    public Filter getFilter()
+    {
+        return this.filter;
     }
 
     public String getPattern()
     {
         return regex.toString();
     }
+
+    public int getRanking()
+    {
+        return ranking;
+    }
+
+    public void handle(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException
+    {
+        final boolean matches = matches(req.getPathInfo());
+        if (matches)
+        {
+            doHandle(req, res, chain);
+        }
+        else
+        {
+            chain.doFilter(req, res);
+        }
+    }
+
+    public void init() throws ServletException
+    {
+        this.filter.init(new FilterConfigImpl(getName(), getContext(), getInitParams()));
+    }
+
+    public boolean matches(String uri)
+    {
+        // assume root if uri is null
+        if (uri == null)
+        {
+            uri = "/";
+        }
+
+        return this.regex.matcher(uri).matches();
+    }
+
+    final void doHandle(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException
+    {
+        if (!getContext().handleSecurity(req, res))
+        {
+            res.sendError(HttpServletResponse.SC_FORBIDDEN);
+        }
+        else
+        {
+            this.filter.doFilter(req, res, chain);
+        }
+    }
+    
+    @Override
+    protected Object getSubject()
+    {
+        return this.filter;
+    }
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
index 6e6c503..0b9ee16 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
@@ -51,14 +51,15 @@
         return this.filters;
     }
 
-    public synchronized void addServlet(ServletHandler handler)
-        throws ServletException, NamespaceException
+    public synchronized void addServlet(ServletHandler handler) throws ServletException, NamespaceException
     {
-        if (this.servletMap.containsKey(handler.getServlet())) {
+        if (this.servletMap.containsKey(handler.getServlet()))
+        {
             throw new ServletException("Servlet instance already registered");
         }
 
-        if (this.aliasMap.containsKey(handler.getAlias())) {
+        if (this.aliasMap.containsKey(handler.getAlias()))
+        {
             throw new NamespaceException("Servlet with alias already registered");
         }
 
@@ -68,10 +69,10 @@
         updateServletArray();
     }
 
-    public synchronized void addFilter(FilterHandler handler)
-        throws ServletException
+    public synchronized void addFilter(FilterHandler handler) throws ServletException
     {
-        if (this.filterMap.containsKey(handler.getFilter())) {
+        if (this.filterMap.containsKey(handler.getFilter()))
+        {
             throw new ServletException("Filter instance already registered");
         }
 
@@ -83,7 +84,8 @@
     public synchronized void removeServlet(Servlet servlet, final boolean destroy)
     {
         ServletHandler handler = this.servletMap.remove(servlet);
-        if (handler != null) {
+        if (handler != null)
+        {
             updateServletArray();
             this.aliasMap.remove(handler.getAlias());
             if (destroy)
@@ -96,7 +98,8 @@
     public synchronized void removeFilter(Filter filter, final boolean destroy)
     {
         FilterHandler handler = this.filterMap.remove(filter);
-        if (handler != null) {
+        if (handler != null)
+        {
             updateFilterArray();
             if (destroy)
             {
@@ -112,11 +115,13 @@
 
     public synchronized void removeAll()
     {
-        for (ServletHandler handler : this.servletMap.values()) {
+        for (ServletHandler handler : this.servletMap.values())
+        {
             handler.destroy();
         }
 
-        for (FilterHandler handler : this.filterMap.values()) {
+        for (FilterHandler handler : this.filterMap.values())
+        {
             handler.destroy();
         }
 
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpSessionWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpSessionWrapper.java
new file mode 100644
index 0000000..602a106
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HttpSessionWrapper.java
@@ -0,0 +1,130 @@
+/*
+ * 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.handler;
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("deprecation")
+public class HttpSessionWrapper implements HttpSession
+{
+    private final HttpSession delegate;
+    private final ServletContext context;
+
+    /**
+     * Creates a new {@link HttpSessionWrapper} instance.
+     */
+    public HttpSessionWrapper(HttpSession session, ServletContext context)
+    {
+        this.delegate = session;
+        this.context = context;
+    }
+
+    public Object getAttribute(String name)
+    {
+        return this.delegate.getAttribute(name);
+    }
+
+    public Enumeration<String> getAttributeNames()
+    {
+        return this.delegate.getAttributeNames();
+    }
+
+    public long getCreationTime()
+    {
+        return this.delegate.getCreationTime();
+    }
+
+    public String getId()
+    {
+        return this.delegate.getId();
+    }
+
+    public long getLastAccessedTime()
+    {
+        return this.delegate.getLastAccessedTime();
+    }
+
+    public int getMaxInactiveInterval()
+    {
+        return this.delegate.getMaxInactiveInterval();
+    }
+
+    public ServletContext getServletContext()
+    {
+        return this.context;
+    }
+
+    public HttpSessionContext getSessionContext()
+    {
+        return this.delegate.getSessionContext();
+    }
+
+    public Object getValue(String name)
+    {
+        return this.delegate.getValue(name);
+    }
+
+    public String[] getValueNames()
+    {
+        return this.delegate.getValueNames();
+    }
+
+    public void invalidate()
+    {
+        this.delegate.invalidate();
+    }
+
+    public boolean isNew()
+    {
+        return this.delegate.isNew();
+    }
+
+    public void putValue(String name, Object value)
+    {
+        this.delegate.putValue(name, value);
+    }
+
+    public void removeAttribute(String name)
+    {
+        this.delegate.removeAttribute(name);
+    }
+
+    public void removeValue(String name)
+    {
+        this.delegate.removeValue(name);
+    }
+
+    public void setAttribute(String name, Object value)
+    {
+        this.delegate.setAttribute(name, value);
+    }
+
+    public void setMaxInactiveInterval(int interval)
+    {
+        this.delegate.setMaxInactiveInterval(interval);
+    }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java
index 43dc2b1..c3a0c7f 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletConfigImpl.java
@@ -22,8 +22,7 @@
 import java.util.Collections;
 import java.util.Map;
 
-public final class ServletConfigImpl
-    implements ServletConfig
+public final class ServletConfigImpl implements ServletConfig
 {
     private final String name;
     private final ServletContext context;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletContextWrapper.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletContextWrapper.java
new file mode 100644
index 0000000..33822fc
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletContextWrapper.java
@@ -0,0 +1,69 @@
+/*
+ * 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.handler;
+
+import javax.servlet.RequestDispatcher;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.context.ServletContextImpl;
+import org.apache.felix.http.base.internal.dispatch.RequestDispatcherProvider;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+class ServletContextWrapper extends ServletContextImpl
+{
+    private final RequestDispatcherProvider provider;
+
+    /**
+     * Creates a new {@link ServletContextWrapper} instance.
+     */
+    public ServletContextWrapper(ExtServletContext delegate, RequestDispatcherProvider provider)
+    {
+        super(delegate);
+
+        this.provider = provider;
+    }
+
+    @Override
+    public RequestDispatcher getNamedDispatcher(String name)
+    {
+        if (name == null)
+        {
+            return null;
+        }
+
+        RequestDispatcher dispatcher = this.provider.getNamedDispatcher(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(path);
+        return dispatcher != null ? dispatcher : super.getRequestDispatcher(path);
+    }
+}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
index a47db6c..cf392b9 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandler.java
@@ -16,27 +16,252 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-import javax.servlet.ServletConfig;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.apache.felix.http.base.internal.context.ExtServletContext;
+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 java.io.IOException;
 
-public final class ServletHandler
-    extends AbstractHandler implements Comparable<ServletHandler>
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class ServletHandler extends AbstractHandler implements Comparable<ServletHandler>
 {
+    private class RequestDispatcherImpl implements RequestDispatcher
+    {
+        final String servletPath;
+        final String requestURI;
+        final String pathInfo;
+        final String query;
+        final boolean named;
+
+        public RequestDispatcherImpl()
+        {
+            this.requestURI = null;
+            // PathMap.pathMatch(servlet_path_spec,target);
+            this.servletPath = getAlias(); // XXX handle wildcard aliases!
+            this.pathInfo = null;
+            this.query = null;
+            this.named = true;
+        }
+
+        public RequestDispatcherImpl(String uri, String pathInContext, String query)
+        {
+            this.requestURI = uri;
+            this.servletPath = getAlias(); // XXX handle wildcard aliases!
+            this.pathInfo = this.servletPath.equals(pathInContext) ? null : pathInContext;
+            this.query = query;
+            this.named = false;
+        }
+
+        public void forward(ServletRequest req, ServletResponse res) throws ServletException, IOException
+        {
+            if (res.isCommitted())
+            {
+                throw new ServletException("Response has been committed");
+            }
+            else
+            {
+                // See section 9.4 of Servlet 3.0 spec 
+                res.resetBuffer();
+            }
+
+            // Since we're already created this RequestDispatcher for *this* servlet handler, we do not need to 
+            // recheck whether its patch matches, but instead can directly handle the forward-request...
+            doHandle(new ServletRequestWrapper((HttpServletRequest) req, this, DispatcherType.FORWARD), (HttpServletResponse) res);
+
+            // After a forward has taken place, the results should be committed, 
+            // see section 9.4 of Servlet 3.0 spec...
+            if (!req.isAsyncStarted())
+            {
+                res.flushBuffer();
+                res.getWriter().close();
+            }
+        }
+
+        public void include(ServletRequest req, ServletResponse res) throws ServletException, IOException
+        {
+            // Since we're already created this RequestDispatcher for *this* servlet handler, we do not need to 
+            // recheck whether its patch matches, but instead can directly handle the include-request...
+            doHandle(new ServletRequestWrapper((HttpServletRequest) req, this, DispatcherType.INCLUDE), (HttpServletResponse) res);
+        }
+
+        boolean isNamedDispatcher()
+        {
+            return this.named;
+        }
+    }
+
+    private static class ServletRequestWrapper extends HttpServletRequestWrapper
+    {
+        private final RequestDispatcherImpl dispatcher;
+        private final DispatcherType type;
+
+        public ServletRequestWrapper(HttpServletRequest req, RequestDispatcherImpl dispatcher, DispatcherType type)
+        {
+            super(req);
+            this.dispatcher = dispatcher;
+            this.type = type;
+        }
+
+        @Override
+        public Object getAttribute(String name)
+        {
+            HttpServletRequest request = (HttpServletRequest) getRequest();
+            if (isInclusionDispatcher())
+            {
+                if (INCLUDE_REQUEST_URI.equals(name))
+                {
+                    return concat(request.getContextPath(), this.dispatcher.requestURI);
+                }
+                else if (INCLUDE_CONTEXT_PATH.equals(name))
+                {
+                    return request.getContextPath();
+                }
+                else if (INCLUDE_SERVLET_PATH.equals(name))
+                {
+                    return this.dispatcher.servletPath;
+                }
+                else if (INCLUDE_PATH_INFO.equals(name))
+                {
+                    return this.dispatcher.pathInfo;
+                }
+                else if (INCLUDE_QUERY_STRING.equals(name))
+                {
+                    return this.dispatcher.query;
+                }
+            }
+            else if (isForwardingDispatcher())
+            {
+                // NOTE: the forward.* attributes *always* yield the *original* values...
+                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 DispatcherType getDispatcherType()
+        {
+            return this.type;
+        }
+
+        @Override
+        public String getPathInfo()
+        {
+            if (isForwardingDispatcher())
+            {
+                return this.dispatcher.pathInfo;
+            }
+            return super.getPathInfo();
+        }
+
+        @Override
+        public String getRequestURI()
+        {
+            if (isForwardingDispatcher())
+            {
+                return concat(getContextPath(), this.dispatcher.requestURI);
+            }
+            return super.getRequestURI();
+        }
+
+        @Override
+        public String getServletPath()
+        {
+            if (isForwardingDispatcher())
+            {
+                return this.dispatcher.servletPath;
+            }
+            return super.getServletPath();
+        }
+
+        @Override
+        public String toString()
+        {
+            return getClass().getSimpleName() + "->" + super.getRequest();
+        }
+
+        private boolean isForwardingDispatcher()
+        {
+            return DispatcherType.FORWARD == this.type && !this.dispatcher.isNamedDispatcher();
+        }
+
+        private boolean isInclusionDispatcher()
+        {
+            return DispatcherType.INCLUDE == this.type && !this.dispatcher.isNamedDispatcher();
+        }
+    }
+
     private final String alias;
     private final Servlet servlet;
 
-    public ServletHandler(ExtServletContext context, Servlet servlet, String alias)
+    public ServletHandler(ExtServletContext context, Servlet servlet, String alias, String name)
     {
-        super(context);
+        super(context, name);
         this.alias = alias;
         this.servlet = servlet;
     }
 
+    public int compareTo(ServletHandler other)
+    {
+        return other.alias.length() - this.alias.length();
+    }
+
+    public RequestDispatcher createNamedRequestDispatcher()
+    {
+        return new RequestDispatcherImpl();
+    }
+
+    public RequestDispatcher createRequestDispatcher(String path, String pathInContext, String query)
+    {
+        return new RequestDispatcherImpl(path, pathInContext, query);
+    }
+
+    public void destroy()
+    {
+        this.servlet.destroy();
+    }
+
     public String getAlias()
     {
         return this.alias;
@@ -47,43 +272,58 @@
         return this.servlet;
     }
 
-    public void init()
-        throws ServletException
+    public boolean handle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
     {
-        String name = "servlet_" + getId();
-        ServletConfig config = new ServletConfigImpl(name, getContext(), getInitParams());
-        this.servlet.init(config);
-    }
-
-    public void destroy()
-    {
-        this.servlet.destroy();
-    }
-
-    public boolean matches(String uri)
-    {
-        if (uri == null) {
-            return this.alias.equals("/");
-        } else if (this.alias.equals("/")) {
-            return uri.startsWith(this.alias);
-        } else {
-            return uri.equals(this.alias) || uri.startsWith(this.alias + "/");
+        String path;
+        if (DispatcherType.INCLUDE == req.getDispatcherType())
+        {
+            path = (String) req.getAttribute(INCLUDE_SERVLET_PATH);
         }
-    }
+        else if (DispatcherType.FORWARD == req.getDispatcherType())
+        {
+            path = (String) req.getAttribute(FORWARD_SERVLET_PATH);
+        }
+        else if (DispatcherType.ASYNC == req.getDispatcherType())
+        {
+            path = (String) req.getAttribute("javax.servlet.async.path_info");
+        }
+        else
+        {
+            path = req.getPathInfo();
+        }
 
-    public boolean handle(HttpServletRequest req, HttpServletResponse res)
-        throws ServletException, IOException
-    {
-        final boolean matches = matches(req.getPathInfo());
-        if (matches) {
+        final boolean matches = matches(path);
+        if (matches)
+        {
             doHandle(req, res);
         }
 
         return matches;
     }
 
-    private void doHandle(HttpServletRequest req, HttpServletResponse res)
-        throws ServletException, IOException
+    public void init() throws ServletException
+    {
+        this.servlet.init(new ServletConfigImpl(getName(), getContext(), getInitParams()));
+    }
+
+    public boolean matches(String uri)
+    {
+        // TODO handle wildcard aliases and extension specs...
+        if (uri == null)
+        {
+            return this.alias.equals("/");
+        }
+        else if (this.alias.equals("/"))
+        {
+            return uri.startsWith(this.alias);
+        }
+        else
+        {
+            return uri.equals(this.alias) || uri.startsWith(this.alias + "/");
+        }
+    }
+
+    final void doHandle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
     {
         // set a sensible status code in case handleSecurity returns false
         // but fails to send a response
@@ -93,12 +333,22 @@
             // reset status to OK for further processing
             res.setStatus(HttpServletResponse.SC_OK);
 
-            this.servlet.service(new ServletHandlerRequest(req, this.alias), res);
+            // Only wrap the original ServletRequest in case we're handling plain requests, 
+            // not inclusions or forwards from servlets. Should solve FELIX-2774 (partly)... 
+            if (DispatcherType.REQUEST == req.getDispatcherType())
+            {
+                this.servlet.service(new ServletHandlerRequest(req, getContext(), this.alias), res);
+            }
+            else
+            {
+                this.servlet.service(req, res);
+            }
         }
     }
 
-    public int compareTo(ServletHandler other)
+    @Override
+    protected Object getSubject()
     {
-        return other.alias.length() - this.alias.length();
+        return this.servlet;
     }
 }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java
index 1e3c10e..9e6a9cf 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequest.java
@@ -16,22 +16,35 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
-import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
 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.dispatch.Dispatcher;
+import org.apache.felix.http.base.internal.dispatch.RequestDispatcherProvider;
+import org.apache.felix.http.base.internal.util.UriUtils;
 import org.osgi.service.http.HttpContext;
+import org.osgi.service.useradmin.Authorization;
 
-final class ServletHandlerRequest
-    extends HttpServletRequestWrapper
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("deprecation")
+class ServletHandlerRequest extends HttpServletRequestWrapper
 {
     private final String alias;
+    private final ServletContextWrapper context;
     private String contextPath;
     private String pathInfo;
     private boolean pathInfoCalculated = false;
 
-    public ServletHandlerRequest(HttpServletRequest req, String alias)
+    public ServletHandlerRequest(HttpServletRequest req, ExtServletContext context, String alias)
     {
         super(req);
+        this.context = new ServletContextWrapper(context, (RequestDispatcherProvider) req.getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER));
         this.alias = alias;
     }
 
@@ -39,7 +52,8 @@
     public String getAuthType()
     {
         String authType = (String) getAttribute(HttpContext.AUTHENTICATION_TYPE);
-        if (authType != null) {
+        if (authType != null)
+        {
             return authType;
         }
 
@@ -53,14 +67,20 @@
          * FELIX-2030 Calculate the context path for the Http Service
          * registered servlets from the container context and servlet paths
          */
-        if (contextPath == null) {
+        if (contextPath == null)
+        {
             final String context = super.getContextPath();
             final String servlet = super.getServletPath();
-            if (context.length() == 0) {
+            if (context == null || context.length() == 0)
+            {
                 contextPath = servlet;
-            } else if (servlet.length() == 0) {
+            }
+            else if (servlet == null || servlet.length() == 0)
+            {
                 contextPath = context;
-            } else {
+            }
+            else
+            {
                 contextPath = context + servlet;
             }
         }
@@ -71,7 +91,8 @@
     @Override
     public String getPathInfo()
     {
-        if (!this.pathInfoCalculated) {
+        if (!this.pathInfoCalculated)
+        {
             this.pathInfo = calculatePathInfo();
             this.pathInfoCalculated = true;
         }
@@ -90,7 +111,8 @@
     public String getRemoteUser()
     {
         String remoteUser = (String) getAttribute(HttpContext.REMOTE_USER);
-        if (remoteUser != null) {
+        if (remoteUser != null)
+        {
             return remoteUser;
         }
 
@@ -98,14 +120,68 @@
     }
 
     @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 = UriUtils.concat(this.alias, path);
+        }
+        return super.getRequestDispatcher(path);
+    }
+
+    @Override
+    public ServletContext getServletContext()
+    {
+        return this.context;
+    }
+
+    @Override
     public String getServletPath()
     {
-        if ("/".equals(this.alias)) {
+        if ("/".equals(this.alias))
+        {
             return "";
         }
         return this.alias;
     }
 
+    @Override
+    public HttpSession getSession(boolean create)
+    {
+        // FELIX-2797: wrap the original HttpSession to provide access to the correct ServletContext...
+        HttpSession session = super.getSession(create);
+        if (session == null)
+        {
+            return null;
+        }
+        return new HttpSessionWrapper(session, this.context);
+    }
+
+    @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 String toString()
+    {
+        return getClass().getSimpleName() + "->" + super.getRequest();
+    }
+
     private String calculatePathInfo()
     {
         /*
@@ -118,20 +194,20 @@
          * Note, the servlet container pathInfo may also be null if the
          * servlet is registered as the root servlet
          */
-
         String pathInfo = super.getPathInfo();
-        if (pathInfo != null) {
-
+        if (pathInfo != null)
+        {
             // cut off alias of this servlet (if not the root servlet)
-            if (!"/".equals(alias)) {
+            if (!"/".equals(this.alias) && pathInfo.startsWith(this.alias))
+            {
                 pathInfo = pathInfo.substring(alias.length());
             }
 
             // ensure empty string is coerced to null
-            if (pathInfo.length() == 0) {
+            if (pathInfo.length() == 0)
+            {
                 pathInfo = null;
             }
-
         }
 
         return pathInfo;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java b/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java
index b0e586b..9d59382 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/service/HttpServiceImpl.java
@@ -30,8 +30,7 @@
 import org.osgi.service.http.HttpContext;
 import org.osgi.service.http.NamespaceException;
 
-public final class HttpServiceImpl
-    implements ExtHttpService
+public final class HttpServiceImpl implements ExtHttpService
 {
     private final Bundle bundle;
     private final HandlerRegistry handlerRegistry;
@@ -39,33 +38,33 @@
     private final HashSet<Filter> localFilters;
     private final ServletContextManager contextManager;
 
-    public HttpServiceImpl(Bundle bundle, ServletContext context, HandlerRegistry handlerRegistry,
-        ServletContextAttributeListener servletAttributeListener, boolean sharedContextAttributes)
+    public HttpServiceImpl(Bundle bundle, ServletContext context, HandlerRegistry handlerRegistry, ServletContextAttributeListener servletAttributeListener, boolean sharedContextAttributes)
     {
         this.bundle = bundle;
         this.handlerRegistry = handlerRegistry;
         this.localServlets = new HashSet<Servlet>();
         this.localFilters = new HashSet<Filter>();
-        this.contextManager = new ServletContextManager(this.bundle, context, servletAttributeListener,
-            sharedContextAttributes);
+        this.contextManager = new ServletContextManager(this.bundle, context, servletAttributeListener, sharedContextAttributes);
     }
 
     private ExtServletContext getServletContext(HttpContext context)
     {
-        if (context == null) {
+        if (context == null)
+        {
             context = createDefaultHttpContext();
         }
 
         return this.contextManager.getServletContext(context);
     }
 
-    public void registerFilter(Filter filter, String pattern, Dictionary initParams, int ranking, HttpContext context)
-        throws ServletException
+    public void registerFilter(Filter filter, String pattern, Dictionary initParams, int ranking, HttpContext context) throws ServletException
     {
-        if (filter == null ) {
+        if (filter == null)
+        {
             throw new IllegalArgumentException("Filter must not be null");
         }
-        FilterHandler handler = new FilterHandler(getServletContext(context), filter, pattern, ranking);
+        String filterName = null; // XXX
+        FilterHandler handler = new FilterHandler(getServletContext(context), filter, pattern, ranking, filterName);
         handler.setInitParams(initParams);
         this.handlerRegistry.addFilter(handler);
         this.localFilters.add(filter);
@@ -81,32 +80,37 @@
         unregisterServlet(servlet, true);
     }
 
-    public void registerServlet(String alias, Servlet servlet, Dictionary initParams, HttpContext context)
-        throws ServletException, NamespaceException
+    public void registerServlet(String alias, Servlet servlet, Dictionary initParams, HttpContext context) throws ServletException, NamespaceException
     {
-        if (servlet == null ) {
+        if (servlet == null)
+        {
             throw new IllegalArgumentException("Servlet must not be null");
         }
-        if (!isAliasValid(alias)) {
-            throw new IllegalArgumentException( "Malformed servlet alias [" + alias + "]");
+        if (!isAliasValid(alias))
+        {
+            throw new IllegalArgumentException("Malformed servlet alias [" + alias + "]");
         }
-        ServletHandler handler = new ServletHandler(getServletContext(context), servlet, alias);
+        String servletName = null; // XXX
+        ServletHandler handler = new ServletHandler(getServletContext(context), servlet, alias, servletName);
         handler.setInitParams(initParams);
         this.handlerRegistry.addServlet(handler);
         this.localServlets.add(servlet);
     }
 
-    public void registerResources(String alias, String name, HttpContext context)
-        throws NamespaceException
+    public void registerResources(String alias, String name, HttpContext context) throws NamespaceException
     {
-        if (!isNameValid(name)) {
-            throw new IllegalArgumentException( "Malformed resource name [" + name + "]");
+        if (!isNameValid(name))
+        {
+            throw new IllegalArgumentException("Malformed resource name [" + name + "]");
         }
 
-        try {
+        try
+        {
             Servlet servlet = new ResourceServlet(name);
             registerServlet(alias, servlet, null, context);
-        } catch (ServletException e) {
+        }
+        catch (ServletException e)
+        {
             SystemLogger.error("Failed to register resources", e);
         }
     }
@@ -124,19 +128,22 @@
     public void unregisterAll()
     {
         HashSet<Servlet> servlets = new HashSet<Servlet>(this.localServlets);
-        for (Servlet servlet : servlets) {
+        for (Servlet servlet : servlets)
+        {
             unregisterServlet(servlet, false);
         }
 
         HashSet<Filter> filters = new HashSet<Filter>(this.localFilters);
-        for (Filter fiter : filters) {
+        for (Filter fiter : filters)
+        {
             unregisterFilter(fiter, false);
         }
     }
 
     private void unregisterFilter(Filter filter, final boolean destroy)
     {
-        if (filter != null) {
+        if (filter != null)
+        {
             this.handlerRegistry.removeFilter(filter, destroy);
             this.localFilters.remove(filter);
         }
@@ -144,7 +151,8 @@
 
     private void unregisterServlet(Servlet servlet, final boolean destroy)
     {
-        if (servlet != null) {
+        if (servlet != null)
+        {
             this.handlerRegistry.removeServlet(servlet, destroy);
             this.localServlets.remove(servlet);
         }
@@ -152,11 +160,13 @@
 
     private boolean isNameValid(String name)
     {
-        if (name == null) {
+        if (name == null)
+        {
             return false;
         }
 
-        if (!name.equals("/") && name.endsWith( "/" )) {
+        if (!name.equals("/") && name.endsWith("/"))
+        {
             return false;
         }
 
@@ -165,11 +175,13 @@
 
     private boolean isAliasValid(String alias)
     {
-        if (alias == null) {
+        if (alias == null)
+        {
             return false;
         }
 
-        if (!alias.equals("/") && ( !alias.startsWith("/") || alias.endsWith("/"))) {
+        if (!alias.equals("/") && (!alias.startsWith("/") || alias.endsWith("/")))
+        {
             return false;
         }
 
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/util/UriUtils.java b/http/base/src/main/java/org/apache/felix/http/base/internal/util/UriUtils.java
new file mode 100644
index 0000000..4646089
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/util/UriUtils.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.util;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+/**
+ * Some convenience methods for handling URI(-parts).
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class UriUtils
+{
+    private static final String SLASH = "/";
+
+    /**
+     * Concatenates two paths keeping their respective path-parts into consideration.
+     * 
+     * @param path1 the first part of the path, can be <code>null</code>;
+     * @param path2 the second part of the path, can be <code>null</code>.
+     * @return the concatenated path, can be <code>null</code> in case both given arguments were <code>null</code>.
+     */
+    public static String concat(String path1, String path2)
+    {
+        // Handle special cases...
+        if (path1 == null && path2 == null)
+        {
+            return null;
+        }
+        if (path1 == null)
+        {
+            path1 = "";
+        }
+        if (path2 == null)
+        {
+            path2 = "";
+        }
+        if (isEmpty(path1) && isEmpty(path2))
+        {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        int idx = path1.indexOf('?');
+        if (idx == 0)
+        {
+            // path1 only consists of a query, append it to the second path...
+            return path2.concat(path1);
+        }
+        else if (idx > 0)
+        {
+            // path1 contains of a path + query, append the path first...
+            sb.append(path1.substring(0, idx));
+        }
+        else
+        {
+            // Plain paths...
+            sb.append(path1);
+            // need a slash?
+        }
+
+        if (endsWith(sb, SLASH))
+        {
+            if (path2.startsWith(SLASH))
+            {
+                sb.append(path2.substring(1));
+            }
+            else
+            {
+                sb.append(path2);
+            }
+        }
+        else
+        {
+            if (path2.startsWith(SLASH))
+            {
+                sb.append(path2);
+            }
+            else if (sb.length() > 0 && !isEmpty(path2))
+            {
+                sb.append(SLASH).append(path2);
+            }
+            else
+            {
+                sb.append(path2);
+            }
+        }
+
+        if (idx > 0)
+        {
+            // Add the query of path1...
+            sb.append(path1.substring(idx, path1.length()));
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Decodes a given URL-encoded path assuming it is UTF-8 encoded.
+     *  
+     * @param path the URL-encoded path, can be <code>null</code>.
+     * @return the decoded path, can be <code>null</code> only if the given path was <code>null</code>.
+     */
+    public static String decodePath(String path)
+    {
+        return decodePath(path, "UTF-8");
+    }
+
+    /**
+     * Decodes a given URL-encoded path using a given character encoding.
+     *  
+     * @param path the URL-encoded path, can be <code>null</code>;
+     * @param encoding the character encoding to use, cannot be <code>null</code>.
+     * @return the decoded path, can be <code>null</code> only if the given path was <code>null</code>.
+     */
+    public static String decodePath(String path, String encoding)
+    {
+        // Special cases...
+        if (path == null)
+        {
+            return null;
+        }
+
+        CharsetDecoder decoder = Charset.forName(encoding).newDecoder();
+        decoder.onMalformedInput(CodingErrorAction.REPORT);
+        decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+
+        int len = path.length();
+        ByteBuffer buf = ByteBuffer.allocate(len);
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < len; i++)
+        {
+            char ch = path.charAt(i);
+            if (ch == '%' && (i + 2 < len))
+            {
+                // URL-encoded char...
+                buf.put((byte) ((16 * hexVal(path, ++i)) + hexVal(path, ++i)));
+            }
+            else
+            {
+                if (buf.position() > 0)
+                {
+                    // flush encoded chars first...
+                    sb.append(decode(buf, decoder));
+                    buf.clear();
+                }
+
+                sb.append(ch);
+            }
+        }
+
+        // flush trailing encoded characters...
+        if (buf.position() > 0)
+        {
+            sb.append(decode(buf, decoder));
+            buf.clear();
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Removes all superfluous dot-segments using the algorithm described in RFC-3986 section 5.2.4.
+     *  
+     * @param path the path to remove all dot-segments from, can be <code>null</code>.
+     * @return the cleaned path, can be <code>null</code> only if the given path was <code>null</code>.
+     */
+    public static String removeDotSegments(String path)
+    {
+        // Handle special cases...
+        if (path == null)
+        {
+            return null;
+        }
+        if (isEmpty(path))
+        {
+            return "";
+        }
+
+        StringBuilder scratch = new StringBuilder(path);
+        StringBuilder sb = new StringBuilder();
+        char ch, la = 0, laa = 0;
+
+        while (scratch.length() > 0)
+        {
+            int len = scratch.length();
+            ch = scratch.charAt(0);
+            if (ch == '.')
+            {
+                if (len > 1)
+                {
+                    la = scratch.charAt(1);
+                }
+                if (la == '.' && (len > 2))
+                {
+                    laa = scratch.charAt(2);
+                }
+
+                if (la == '/' || laa == '/')
+                {
+                    // Step A: remove '../' or './' from input...
+                    scratch.delete(0, (laa == '/') ? 3 : 2);
+                    la = laa = 0;
+                    continue;
+                }
+                else
+                {
+                    // Step D: remove '..' or '.' from input...
+                    scratch.delete(0, (laa == '/') ? 2 : 1);
+                    la = laa = 0;
+                    continue;
+                }
+            }
+            else if (ch == '/')
+            {
+                if (len > 1)
+                {
+                    la = scratch.charAt(1);
+                }
+                if (la == '.' && (len > 2))
+                {
+                    laa = scratch.charAt(2);
+                }
+
+                if (la == '.' && laa == '.')
+                {
+                    // Step C: remove '/../' or '/..' from input...
+                    char laaa = (len > 3) ? scratch.charAt(3) : 0;
+                    int lastSegment = sb.lastIndexOf(SLASH);
+                    scratch.replace(0, laaa == '/' ? 4 : 3, "/");
+                    sb.setLength(Math.max(0, lastSegment));
+                    la = laa = 0;
+                    continue;
+                }
+                if ((la == '.' && laa == '/') || (la == '.' && laa != '/'))
+                {
+                    // Step B: remove '/./' or '/.' from input...
+                    scratch.replace(0, laa == '/' ? 3 : 2, "/");
+                    la = laa = 0;
+                    continue;
+                }
+            }
+
+            sb.append(ch);
+            scratch.delete(0, 1);
+        }
+
+        return sb.toString();
+    }
+
+    private static String decode(ByteBuffer bb, CharsetDecoder decoder)
+    {
+        CharBuffer cb = CharBuffer.allocate(128);
+
+        CoderResult result = decoder.decode((ByteBuffer) bb.flip(), cb, true /* endOfInput */);
+        if (result.isError())
+        {
+            throw new IllegalArgumentException("Malformed UTF-8!");
+        }
+
+        return ((CharBuffer) cb.flip()).toString();
+    }
+
+    private static boolean endsWith(CharSequence seq, String part)
+    {
+        int len = part.length();
+        if (seq.length() < len)
+        {
+            return false;
+        }
+        for (int i = 0; i < len; i++)
+        {
+            if (seq.charAt(seq.length() - (i + 1)) != part.charAt(i))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static int hexVal(CharSequence seq, int idx)
+    {
+        char ch = seq.charAt(idx);
+        if (ch >= '0' && ch <= '9')
+        {
+            return ch - '0';
+        }
+        else if (ch >= 'a' && ch <= 'f')
+        {
+            return 10 + (ch - 'a');
+        }
+        else if (ch >= 'A' && ch <= 'F')
+        {
+            return 10 + (ch - 'A');
+        }
+        throw new IllegalArgumentException("Invalid hex digit: " + ch);
+    }
+
+    private static boolean isEmpty(String value)
+    {
+        return value == null || "".equals(value.trim());
+    }
+
+    /**
+     * Creates a new {@link UriUtils} instance.
+     */
+    private UriUtils()
+    {
+        // Nop
+    }
+}
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java
index 9651998..5e97af7 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/AbstractHandlerTest.java
@@ -16,12 +16,13 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
-import org.junit.Test;
-import org.junit.Assert;
-import org.mockito.Mockito;
-import org.apache.felix.http.base.internal.context.ExtServletContext;
 import java.util.Hashtable;
 
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
 public abstract class AbstractHandlerTest
 {
     protected ExtServletContext context;
@@ -32,16 +33,16 @@
     {
         this.context = Mockito.mock(ExtServletContext.class);
     }
-    
+
     @Test
     public void testId()
     {
         AbstractHandler h1 = createHandler();
         AbstractHandler h2 = createHandler();
 
-        Assert.assertNotNull(h1.getId());
-        Assert.assertNotNull(h2.getId());
-        Assert.assertFalse(h1.getId().equals(h2.getId()));
+        Assert.assertTrue(h1.getId() > 0);
+        Assert.assertTrue(h2.getId() > 0);
+        Assert.assertFalse(h1.getId() == h2.getId());
     }
 
     @Test
@@ -49,7 +50,7 @@
     {
         AbstractHandler handler = createHandler();
         Assert.assertEquals(0, handler.getInitParams().size());
-        
+
         Hashtable<String, String> map = new Hashtable<String, String>();
         map.put("key1", "value1");
 
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java
index b1ab850..53da35d 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/FilterHandlerTest.java
@@ -26,8 +26,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-public class FilterHandlerTest
-    extends AbstractHandlerTest
+public class FilterHandlerTest extends AbstractHandlerTest
 {
     private Filter filter;
 
@@ -45,7 +44,7 @@
 
     private FilterHandler createHandler(String pattern, int ranking)
     {
-        return new FilterHandler(this.context, this.filter, pattern, ranking);
+        return new FilterHandler(this.context, this.filter, pattern, ranking, null /* name */);
     }
 
     @Test
@@ -83,8 +82,7 @@
     }
 
     @Test
-    public void testInit()
-        throws Exception
+    public void testInit() throws Exception
     {
         FilterHandler h1 = createHandler("/a", 0);
         h1.init();
@@ -100,8 +98,7 @@
     }
 
     @Test
-    public void testHandleNotFound()
-        throws Exception
+    public void testHandleNotFound() throws Exception
     {
         FilterHandler h1 = createHandler("/a", 0);
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -116,8 +113,7 @@
     }
 
     @Test
-    public void testHandleFound()
-        throws Exception
+    public void testHandleFound() throws Exception
     {
         FilterHandler h1 = createHandler("/a", 0);
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -133,8 +129,7 @@
     }
 
     @Test
-    public void testHandleFoundForbidden()
-        throws Exception
+    public void testHandleFoundForbidden() throws Exception
     {
         FilterHandler h1 = createHandler("/a", 0);
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -151,8 +146,7 @@
     }
 
     @Test
-    public void testHandleNotFoundContextRoot()
-        throws Exception
+    public void testHandleNotFoundContextRoot() throws Exception
     {
         FilterHandler h1 = createHandler("/a", 0);
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -167,8 +161,7 @@
     }
 
     @Test
-    public void testHandleFoundContextRoot()
-        throws Exception
+    public void testHandleFoundContextRoot() throws Exception
     {
         FilterHandler h1 = createHandler("/", 0);
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java
index 105aaf5..877114d 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerRequestTest.java
@@ -16,13 +16,20 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
-import org.mockito.Mockito;
-import org.osgi.service.http.HttpContext;
-import org.junit.Test;
-import org.junit.Assert;
-import org.junit.Before;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.felix.http.base.internal.context.ServletContextImpl;
+import org.apache.felix.http.base.internal.dispatch.Dispatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.service.http.HttpContext;
+
 public class ServletHandlerRequestTest
 {
     private HttpServletRequest superReq1;
@@ -38,106 +45,112 @@
     @Before
     public void setUp()
     {
-        this.superReq1 = Mockito.mock(HttpServletRequest.class);
-        Mockito.when(this.superReq1.getContextPath()).thenReturn("/mycontext");
-        Mockito.when(this.superReq1.getServletPath()).thenReturn("");
-        Mockito.when(this.superReq1.getRequestURI()).thenReturn("/mycontext/request/to/resource");
-        Mockito.when(this.superReq1.getPathInfo()).thenReturn("/request/to/resource");
-        Mockito.when(this.superReq1.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
-        Mockito.when(this.superReq1.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
-        this.req1 = new ServletHandlerRequest(this.superReq1, "/");
+        ServletContextImpl context = mock(ServletContextImpl.class);
 
-        this.superReq2 = Mockito.mock(HttpServletRequest.class);
-        Mockito.when(this.superReq2.getContextPath()).thenReturn("/mycontext");
-        Mockito.when(this.superReq2.getServletPath()).thenReturn("");
-        Mockito.when(this.superReq2.getRequestURI()).thenReturn("/mycontext/myservlet/request/to/resource;jsession=123");
-        Mockito.when(this.superReq2.getPathInfo()).thenReturn("/myservlet/request/to/resource");
-        Mockito.when(this.superReq2.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
-        Mockito.when(this.superReq2.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
-        Mockito.when(this.superReq2.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
-        Mockito.when(this.superReq2.getRemoteUser()).thenReturn("sling");
-        this.req2 = new ServletHandlerRequest(this.superReq2, "/myservlet");
+        this.superReq1 = mock(HttpServletRequest.class);
+        when(this.superReq1.getContextPath()).thenReturn("/mycontext");
+        when(this.superReq1.getServletPath()).thenReturn("");
+        when(this.superReq1.getRequestURI()).thenReturn("/mycontext/request/to/resource");
+        when(this.superReq1.getPathInfo()).thenReturn("/request/to/resource");
+        when(this.superReq1.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
+        when(this.superReq1.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
+        this.req1 = new ServletHandlerRequest(this.superReq1, context, "/");
 
-        this.superReq3 = Mockito.mock(HttpServletRequest.class);
-        Mockito.when(this.superReq3.getContextPath()).thenReturn("/mycontext");
-        Mockito.when(this.superReq3.getServletPath()).thenReturn("/proxyservlet");
-        Mockito.when(this.superReq3.getRequestURI()).thenReturn("/mycontext/proxyservlet/request/to/resource");
-        Mockito.when(this.superReq3.getPathInfo()).thenReturn("/request/to/resource");
-        Mockito.when(this.superReq3.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
-        Mockito.when(this.superReq3.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
-        this.req3 = new ServletHandlerRequest(this.superReq3, "/");
+        this.superReq2 = mock(HttpServletRequest.class);
+        when(this.superReq2.getContextPath()).thenReturn("/mycontext");
+        when(this.superReq2.getServletPath()).thenReturn("");
+        when(this.superReq2.getRequestURI()).thenReturn("/mycontext/myservlet/request/to/resource;jsession=123");
+        when(this.superReq2.getPathInfo()).thenReturn("/myservlet/request/to/resource");
+        when(this.superReq2.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
+        when(this.superReq2.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
+        when(this.superReq2.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
+        when(this.superReq2.getRemoteUser()).thenReturn("sling");
+        this.req2 = new ServletHandlerRequest(this.superReq2, context, "/myservlet");
 
-        this.superReq4 = Mockito.mock(HttpServletRequest.class);
-        Mockito.when(this.superReq4.getContextPath()).thenReturn("/mycontext");
-        Mockito.when(this.superReq4.getServletPath()).thenReturn("/proxyservlet");
-        Mockito.when(this.superReq4.getRequestURI()).thenReturn("/mycontext/proxyservlet/myservlet/request/to/resource;jsession=123");
-        Mockito.when(this.superReq4.getPathInfo()).thenReturn("/myservlet/request/to/resource");
-        Mockito.when(this.superReq4.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
-        Mockito.when(this.superReq4.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
-        Mockito.when(this.superReq4.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
-        Mockito.when(this.superReq4.getRemoteUser()).thenReturn("sling");
-        this.req4 = new ServletHandlerRequest(this.superReq4, "/myservlet");
+        this.superReq3 = mock(HttpServletRequest.class);
+        when(this.superReq3.getContextPath()).thenReturn("/mycontext");
+        when(this.superReq3.getServletPath()).thenReturn("/proxyservlet");
+        when(this.superReq3.getRequestURI()).thenReturn("/mycontext/proxyservlet/request/to/resource");
+        when(this.superReq3.getPathInfo()).thenReturn("/request/to/resource");
+        when(this.superReq3.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(HttpServletRequest.BASIC_AUTH);
+        when(this.superReq3.getAttribute(HttpContext.REMOTE_USER)).thenReturn("felix");
+        this.req3 = new ServletHandlerRequest(this.superReq3, context, "/");
+
+        this.superReq4 = mock(HttpServletRequest.class);
+        when(this.superReq4.getContextPath()).thenReturn("/mycontext");
+        when(this.superReq4.getServletPath()).thenReturn("/proxyservlet");
+        when(this.superReq4.getRequestURI()).thenReturn("/mycontext/proxyservlet/myservlet/request/to/resource;jsession=123");
+        when(this.superReq4.getPathInfo()).thenReturn("/myservlet/request/to/resource");
+        when(this.superReq4.getAttribute(HttpContext.AUTHENTICATION_TYPE)).thenReturn(null);
+        when(this.superReq4.getAuthType()).thenReturn(HttpServletRequest.DIGEST_AUTH);
+        when(this.superReq4.getAttribute(HttpContext.REMOTE_USER)).thenReturn(null);
+        when(this.superReq4.getRemoteUser()).thenReturn("sling");
+        this.req4 = new ServletHandlerRequest(this.superReq4, context, "/myservlet");
     }
 
     @Test
     public void testPathInfo()
     {
-        Assert.assertEquals("/request/to/resource", this.req1.getPathInfo());
-        Assert.assertEquals("/request/to/resource", this.req2.getPathInfo());
-        Assert.assertEquals("/request/to/resource", this.req3.getPathInfo());
-        Assert.assertEquals("/request/to/resource", this.req4.getPathInfo());
+        assertEquals("/request/to/resource", this.req1.getPathInfo());
+        assertEquals("/request/to/resource", this.req2.getPathInfo());
+        assertEquals("/request/to/resource", this.req3.getPathInfo());
+        assertEquals("/request/to/resource", this.req4.getPathInfo());
     }
 
     @Test
     public void testSuperGetServletPath()
     {
-        Assert.assertEquals("", this.superReq1.getServletPath());
-        Assert.assertEquals("", this.superReq2.getServletPath());
-        Assert.assertEquals("/proxyservlet", this.superReq3.getServletPath());
-        Assert.assertEquals("/proxyservlet", this.superReq4.getServletPath());
+        assertEquals("", this.superReq1.getServletPath());
+        assertEquals("", this.superReq2.getServletPath());
+        assertEquals("/proxyservlet", this.superReq3.getServletPath());
+        assertEquals("/proxyservlet", this.superReq4.getServletPath());
     }
 
     @Test
     public void testServletPath()
     {
-        Assert.assertEquals("", this.req1.getServletPath());
-        Assert.assertEquals("/myservlet", this.req2.getServletPath());
-        Assert.assertEquals("", this.req3.getServletPath());
-        Assert.assertEquals("/myservlet", this.req4.getServletPath());
+        assertEquals("", this.req1.getServletPath());
+        assertEquals("/myservlet", this.req2.getServletPath());
+        assertEquals("", this.req3.getServletPath());
+        assertEquals("/myservlet", this.req4.getServletPath());
     }
 
     @Test
     public void testContextPath()
     {
-        Assert.assertEquals("/mycontext", this.req1.getContextPath());
-        Assert.assertEquals("/mycontext", this.req2.getContextPath());
-        Assert.assertEquals("/mycontext/proxyservlet", this.req3.getContextPath());
-        Assert.assertEquals("/mycontext/proxyservlet", this.req4.getContextPath());
+        assertEquals("/mycontext", this.req1.getContextPath());
+        assertEquals("/mycontext", this.req2.getContextPath());
+        assertEquals("/mycontext/proxyservlet", this.req3.getContextPath());
+        assertEquals("/mycontext/proxyservlet", this.req4.getContextPath());
     }
 
     @Test
     public void testGetAuthType()
     {
-        Assert.assertEquals(HttpServletRequest.BASIC_AUTH, this.req1.getAuthType());
-        Mockito.verify(this.superReq1).getAttribute(HttpContext.AUTHENTICATION_TYPE);
-        Mockito.verifyNoMoreInteractions(this.superReq1);
+        assertEquals(HttpServletRequest.BASIC_AUTH, this.req1.getAuthType());
+        verify(this.superReq1).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+        verify(this.superReq1).getAttribute(HttpContext.AUTHENTICATION_TYPE);
+        verifyNoMoreInteractions(this.superReq1);
 
-        Assert.assertEquals(HttpServletRequest.DIGEST_AUTH, this.req2.getAuthType());
-        Mockito.verify(this.superReq2).getAttribute(HttpContext.AUTHENTICATION_TYPE);
-        Mockito.verify(this.superReq2).getAuthType();
-        Mockito.verifyNoMoreInteractions(this.superReq2);
+        assertEquals(HttpServletRequest.DIGEST_AUTH, this.req2.getAuthType());
+        verify(this.superReq2).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+        verify(this.superReq2).getAttribute(HttpContext.AUTHENTICATION_TYPE);
+        verify(this.superReq2).getAuthType();
+        verifyNoMoreInteractions(this.superReq2);
     }
 
     @Test
     public void testGetRemoteUser()
     {
-        Assert.assertEquals("felix", this.req1.getRemoteUser());
-        Mockito.verify(this.superReq1).getAttribute(HttpContext.REMOTE_USER);
-        Mockito.verifyNoMoreInteractions(this.superReq1);
+        assertEquals("felix", this.req1.getRemoteUser());
+        verify(this.superReq1).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+        verify(this.superReq1).getAttribute(HttpContext.REMOTE_USER);
+        verifyNoMoreInteractions(this.superReq1);
 
-        Assert.assertEquals("sling", this.req2.getRemoteUser());
-        Mockito.verify(this.superReq2).getAttribute(HttpContext.REMOTE_USER);
-        Mockito.verify(this.superReq2).getRemoteUser();
-        Mockito.verifyNoMoreInteractions(this.superReq2);
+        assertEquals("sling", this.req2.getRemoteUser());
+        verify(this.superReq2).getAttribute(Dispatcher.REQUEST_DISPATCHER_PROVIDER);
+        verify(this.superReq2).getAttribute(HttpContext.REMOTE_USER);
+        verify(this.superReq2).getRemoteUser();
+        verifyNoMoreInteractions(this.superReq2);
     }
 }
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java
index abb49b4..f1f9626 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/ServletHandlerTest.java
@@ -16,17 +16,17 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.Assert;
-import org.mockito.Mockito;
 import javax.servlet.Servlet;
 import javax.servlet.ServletConfig;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-public class ServletHandlerTest
-    extends AbstractHandlerTest
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class ServletHandlerTest extends AbstractHandlerTest
 {
     private Servlet servlet;
 
@@ -44,7 +44,7 @@
 
     private ServletHandler createHandler(String alias)
     {
-        return new ServletHandler(this.context, this.servlet, alias);
+        return new ServletHandler(this.context, this.servlet, alias, null /* name */);
     }
 
     @Test
@@ -71,8 +71,7 @@
     }
 
     @Test
-    public void testInit()
-        throws Exception
+    public void testInit() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
         h1.init();
@@ -88,8 +87,7 @@
     }
 
     @Test
-    public void testHandleNotFound()
-        throws Exception
+    public void testHandleNotFound() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -103,8 +101,7 @@
     }
 
     @Test
-    public void testHandleFound()
-    throws Exception
+    public void testHandleFound() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -115,13 +112,11 @@
         boolean result = h1.handle(req, res);
 
         Assert.assertTrue(result);
-        Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class),
-            Mockito.any(HttpServletResponse.class));
+        Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class), Mockito.any(HttpServletResponse.class));
     }
 
     @Test
-    public void testHandleFoundForbidden()
-        throws Exception
+    public void testHandleFoundForbidden() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -137,8 +132,7 @@
     }
 
     @Test
-    public void testHandleNotFoundContextRoot()
-        throws Exception
+    public void testHandleNotFoundContextRoot() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -153,8 +147,7 @@
     }
 
     @Test
-    public void testHandleFoundContextRoot()
-        throws Exception
+    public void testHandleFoundContextRoot() throws Exception
     {
         ServletHandler h1 = createHandler("/");
         HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
@@ -165,7 +158,6 @@
         boolean result = h1.handle(req, res);
 
         Assert.assertTrue(result);
-        Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class),
-                Mockito.any(HttpServletResponse.class));
+        Mockito.verify(this.servlet).service(Mockito.any(HttpServletRequest.class), Mockito.any(HttpServletResponse.class));
     }
 }
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/util/UriUtilsTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/util/UriUtilsTest.java
new file mode 100644
index 0000000..9d6ad2e
--- /dev/null
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/util/UriUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.util;
+
+import static org.apache.felix.http.base.internal.util.UriUtils.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Test cases for {@link UriUtils}.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class UriUtilsTest
+{
+    @Test
+    public void testConcatOk()
+    {
+        assertEquals(null, concat(null, null));
+        assertEquals("", concat(null, ""));
+        assertEquals("/", concat(null, "/"));
+        assertEquals("foo", concat(null, "foo"));
+        assertEquals("/foo", concat(null, "/foo"));
+
+        assertEquals("", concat("", null));
+        assertEquals("/", concat("/", null));
+        assertEquals("foo", concat("foo", null));
+        assertEquals("/foo", concat("/foo", null));
+
+        assertEquals("", concat("", ""));
+        assertEquals("foo", concat("", "foo"));
+        assertEquals("/", concat("", "/"));
+        assertEquals("/foo", concat("", "/foo"));
+
+        assertEquals("foo", concat("foo", ""));
+        assertEquals("/", concat("/", ""));
+        assertEquals("/foo", concat("/foo", ""));
+
+        assertEquals("foo", concat("foo", ""));
+        assertEquals("foo/bar", concat("foo", "bar"));
+        assertEquals("foo/", concat("foo", "/"));
+        assertEquals("foo/bar", concat("foo", "/bar"));
+
+        assertEquals("/foo", concat("/", "foo"));
+        assertEquals("/", concat("/", "/"));
+        assertEquals("/bar", concat("/", "/bar"));
+
+        assertEquals("foo/", concat("foo/", null));
+        assertEquals("foo/", concat("foo/", ""));
+        assertEquals("foo/bar", concat("foo/", "bar"));
+        assertEquals("foo/", concat("foo/", "/"));
+        assertEquals("foo/bar", concat("foo/", "/bar"));
+
+        assertEquals("?quu=1", concat("?quu=1", null));
+        assertEquals("?quu=1", concat("?quu=1", ""));
+        assertEquals("foo?quu=1", concat("?quu=1", "foo"));
+        assertEquals("/?quu=1", concat("?quu=1", "/"));
+        assertEquals("/foo?quu=1", concat("?quu=1", "/foo"));
+
+        assertEquals("foo?quu=1", concat("foo?quu=1", null));
+        assertEquals("foo?quu=1", concat("foo?quu=1", ""));
+        assertEquals("foo/bar?quu=1", concat("foo?quu=1", "bar"));
+        assertEquals("foo/?quu=1", concat("foo?quu=1", "/"));
+        assertEquals("foo/bar?quu=1", concat("foo?quu=1", "/bar"));
+
+        assertEquals("foo/?quu=1", concat("foo/?quu=1", null));
+        assertEquals("foo/?quu=1", concat("foo/?quu=1", ""));
+        assertEquals("foo/bar?quu=1", concat("foo/?quu=1", "bar"));
+        assertEquals("foo/?quu=1", concat("foo/?quu=1", "/"));
+        assertEquals("foo/bar?quu=1", concat("foo/?quu=1", "/bar"));
+    }
+
+    @Test
+    public void testDecodePathOk()
+    {
+        assertEquals(null, decodePath(null));
+        assertEquals("foo bar", decodePath("foo%20bar"));
+        assertEquals("foo%23;,:=b a r", decodePath("foo%2523%3b%2c:%3db%20a%20r"));
+        assertEquals("f\u00e4\u00e4%23;,:=b a r=", decodePath("f\u00e4\u00e4%2523%3b%2c:%3db%20a%20r%3D"));
+        assertEquals("f\u0629\u0629%23;,:=b a r", decodePath("f%d8%a9%d8%a9%2523%3b%2c:%3db%20a%20r"));
+    }
+
+    @Test
+    public void testRemoveDotSegmentsOk()
+    {
+        assertEquals(null, removeDotSegments(null));
+        assertEquals("", removeDotSegments(""));
+        assertEquals("/", removeDotSegments("/"));
+        assertEquals("foo", removeDotSegments("./foo"));
+        assertEquals("/bar/", removeDotSegments("./foo/../bar/"));
+        assertEquals("foo", removeDotSegments("../foo"));
+        assertEquals("/", removeDotSegments("/foo/.."));
+        assertEquals("/foo/", removeDotSegments("/foo/."));
+        assertEquals("/foo/bar", removeDotSegments("/foo/./bar"));
+        assertEquals("/bar", removeDotSegments("/foo/../bar"));
+        assertEquals("/bar", removeDotSegments("/foo/./../bar"));
+        assertEquals("/bar//", removeDotSegments("/foo/./../bar//"));
+        assertEquals("/", removeDotSegments("/foo/../bar/.."));
+        assertEquals("/foo/quu", removeDotSegments("/foo/bar/qux/./../../quu"));
+        assertEquals("mid/6", removeDotSegments("mid/content=5/../6"));
+    }
+}
diff --git a/http/itest/pom.xml b/http/itest/pom.xml
index 708876c..73b7c2d 100644
--- a/http/itest/pom.xml
+++ b/http/itest/pom.xml
@@ -72,6 +72,11 @@
             <artifactId>org.apache.felix.http.jetty</artifactId>
             <version>${felix.http.api.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.configadmin</artifactId>
+            <version>1.8.0</version>
+        </dependency>
 
         <dependency>
             <groupId>junit</groupId>
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java
new file mode 100644
index 0000000..8eb6ec7
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.itest;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class AsyncTest extends BaseIntegrationTest
+{
+
+    /**
+     * Tests that we can use an asynchronous servlet (introduced in Servlet 3.0 spec).
+     */
+//    @Test
+    public void testAsyncServletOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                final AsyncContext asyncContext = req.startAsync(req, resp);
+                asyncContext.setTimeout(2000);
+                asyncContext.start(new Runnable()
+                {
+                    public void run()
+                    {
+                        try
+                        {
+                            // Simulate a long running process...
+                            Thread.sleep(1000);
+
+                            HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
+                            response.setStatus(SC_OK);
+                            response.getWriter().printf("Hello Async world!");
+
+                            asyncContext.complete();
+                        }
+                        catch (InterruptedException e)
+                        {
+                            Thread.currentThread().interrupt();
+                        }
+                        catch (Exception e)
+                        {
+                            e.printStackTrace();
+                        }
+                    }
+                });
+            }
+        };
+
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("Hello Async world!", createURL("/test"));
+
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+        assertResponseCode(SC_NOT_FOUND, createURL("/test"));
+    }
+
+    /**
+     * Tests that we can use an asynchronous servlet (introduced in Servlet 3.0 spec) using the dispatching functionality.
+     */
+    @Test
+    public void testAsyncServletWithDispatchOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                DispatcherType dispatcherType = req.getDispatcherType();
+                if (DispatcherType.REQUEST == dispatcherType)
+                {
+                    final AsyncContext asyncContext = req.startAsync(req, resp);
+                    asyncContext.start(new Runnable()
+                    {
+                        public void run()
+                        {
+                            try
+                            {
+                                // Simulate a long running process...
+                                Thread.sleep(1000);
+
+                                asyncContext.getRequest().setAttribute("msg", "Hello Async world!");
+                                asyncContext.dispatch();
+                            }
+                            catch (InterruptedException e)
+                            {
+                                Thread.currentThread().interrupt();
+                            }
+                        }
+                    });
+                }
+                else if (DispatcherType.ASYNC == dispatcherType)
+                {
+                    String response = (String) req.getAttribute("msg");
+                    resp.setStatus(SC_OK);
+                    resp.getWriter().printf(response);
+                    resp.flushBuffer();
+                }
+            }
+        };
+
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("Hello Async world!", createURL("/test"));
+
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+        assertResponseCode(SC_NOT_FOUND, createURL("/test"));
+    }
+}
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
index 8fa35ac..37b2cce 100644
--- a/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
 import static org.ops4j.pax.exam.Constants.START_LEVEL_TEST_BUNDLE;
 import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackage;
@@ -37,8 +38,11 @@
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
 import java.util.Scanner;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 import javax.servlet.Filter;
@@ -60,6 +64,10 @@
 import org.ops4j.pax.exam.junit.Configuration;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
 import org.osgi.service.http.HttpContext;
 import org.osgi.service.http.HttpService;
 import org.osgi.service.http.NamespaceException;
@@ -144,7 +152,7 @@
         }
 
         @Override
-        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
         {
             resp.setStatus(HttpServletResponse.SC_OK);
         }
@@ -224,6 +232,18 @@
         }
     }
 
+    protected static Dictionary<String, ?> createDictionary(Object... entries)
+    {
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        for (int i = 0; i < entries.length; i += 2)
+        {
+            String key = (String) entries[i];
+            Object value = entries[i + 1];
+            props.put(key, value);
+        }
+        return props;
+    }
+
     protected static URL createURL(String path)
     {
         if (path == null)
@@ -295,6 +315,7 @@
             url("link:classpath:META-INF/links/org.apache.geronimo.specs.atinject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
 
             mavenBundle("org.apache.felix", ORG_APACHE_FELIX_HTTP_JETTY).versionAsInProject().startLevel(START_LEVEL_SYSTEM_BUNDLES),
+            mavenBundle("org.apache.felix", "org.apache.felix.configadmin").versionAsInProject().startLevel(START_LEVEL_SYSTEM_BUNDLES),
 
             junitBundles(), frameworkStartLevel(START_LEVEL_TEST_BUNDLE), felix());
     }
@@ -339,6 +360,64 @@
         return result;
     }
 
+    protected org.osgi.service.cm.Configuration configureHttpService(Dictionary<?, ?> props) throws Exception
+    {
+        final String pid = "org.apache.felix.http";
+        ServiceTracker tracker = new ServiceTracker(m_context, ConfigurationAdmin.class.getName(), null);
+        tracker.open();
+
+        ServiceRegistration reg = null;
+        org.osgi.service.cm.Configuration config = null;
+
+        try
+        {
+            ConfigurationAdmin configAdmin = (ConfigurationAdmin) tracker.waitForService(TimeUnit.SECONDS.toMillis(5));
+            assertNotNull("No configuration admin service found?!", configAdmin);
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final int configEvent = (props != null) ? ConfigurationEvent.CM_UPDATED : ConfigurationEvent.CM_DELETED;
+
+            config = configAdmin.getConfiguration(pid, null);
+
+            reg = m_context.registerService(ConfigurationListener.class.getName(), new ConfigurationListener()
+            {
+                @Override
+                public void configurationEvent(ConfigurationEvent event)
+                {
+                    if (pid.equals(event.getPid()) && event.getType() == configEvent)
+                    {
+                        latch.countDown();
+                    }
+                }
+            }, null);
+
+            if (props != null)
+            {
+                config.update(props);
+            }
+            else
+            {
+                config.delete();
+            }
+
+            assertTrue("Configuration not provisioned in time!", latch.await(5, TimeUnit.SECONDS));
+
+            // Needed to get Jetty restarted...
+            TimeUnit.MILLISECONDS.sleep(150);
+
+            return config;
+        }
+        finally
+        {
+            if (reg != null)
+            {
+                reg.unregister();
+            }
+
+            tracker.close();
+        }
+    }
+
     /**
      * @param bsn
      * @return
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/RequestDispatchTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/RequestDispatchTest.java
new file mode 100644
index 0000000..6630525
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/RequestDispatchTest.java
@@ -0,0 +1,514 @@
+/*
+ * 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.itest;
+
+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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.http.NamespaceException;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class RequestDispatchTest extends BaseIntegrationTest
+{
+    /**
+     * Tests that we can forward content from other servlets using the {@link RequestDispatcher} service.
+     */
+    @Test
+    public void testDispatchForwardToAbsoluteURIOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(2);
+        CountDownLatch destroyLatch = new CountDownLatch(2);
+
+        TestServlet forward = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Object includeContextPath = req.getAttribute(FORWARD_CONTEXT_PATH);
+                if (includeContextPath != null)
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/forward", req.getServletPath());
+                    assertEquals(null, req.getPathInfo());
+                    assertEquals("/forward", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    assertEquals("", includeContextPath);
+                    assertEquals("/test", req.getAttribute(FORWARD_SERVLET_PATH));
+                    assertEquals("/foo", req.getAttribute(FORWARD_PATH_INFO));
+                    assertEquals("/test/foo", req.getAttribute(FORWARD_REQUEST_URI));
+                    assertEquals("bar=qux&quu", req.getAttribute(FORWARD_QUERY_STRING));
+                }
+                else
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/forward", req.getServletPath());
+                    assertEquals("/bar", req.getPathInfo());
+                    assertEquals("/forward/bar", req.getRequestURI());
+                    assertEquals("quu=qux", req.getQueryString());
+                }
+
+                resp.getWriter().println("FORWARD");
+            }
+        };
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                assertEquals("", req.getContextPath());
+                assertEquals("/test", req.getServletPath());
+                assertEquals("/foo", req.getPathInfo());
+                assertEquals("/test/foo", req.getRequestURI());
+                assertEquals("bar=qux&quu", req.getQueryString());
+
+                resp.getWriter().println("NOT_SEND");
+                req.getRequestDispatcher("/forward").forward(req, resp);
+                resp.getWriter().println("NOT_SEND");
+            }
+        };
+
+        register("/forward", forward);
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("FORWARD\n", createURL("/test/foo?bar=qux&quu"));
+        assertContent("FORWARD\n", createURL("/forward/bar?quu=qux"));
+
+        unregister(forward);
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Tests that we can forward content from other servlets using the {@link RequestDispatcher} service.
+     */
+    @Test
+    public void testDispatchForwardToRelativeURIOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Object contextPathAttr = req.getAttribute(FORWARD_CONTEXT_PATH);
+                if (contextPathAttr != null)
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/test", req.getServletPath());
+                    assertEquals("/test/forward", req.getPathInfo()); // XXX ?
+                    assertEquals("/test/forward", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    assertEquals("", contextPathAttr);
+                    assertEquals("/test", req.getAttribute(FORWARD_SERVLET_PATH));
+                    assertEquals("/foo", req.getAttribute(FORWARD_PATH_INFO));
+                    assertEquals("/test/foo", req.getAttribute(FORWARD_REQUEST_URI));
+                    assertEquals("bar=qux&quu", req.getAttribute(FORWARD_QUERY_STRING));
+
+                    resp.getWriter().println("FORWARD");
+                }
+                else
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/test", req.getServletPath());
+                    assertEquals("/foo", req.getPathInfo());
+                    assertEquals("/test/foo", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    resp.getWriter().println("NOT_SEND");
+
+                    // ServletContext#getRequestDispatcher only takes absolute paths...
+                    RequestDispatcher disp = req.getServletContext().getRequestDispatcher("forward");
+                    assertNull("ServletContext returned RequestDispatcher for relative path?!", disp);
+                    // Causes a request to ourselves being made (/test/forward)...
+                    disp = req.getRequestDispatcher("forward");
+                    assertNotNull("ServletRequest returned NO RequestDispatcher for relative path?!", disp);
+
+                    disp.forward(req, resp);
+                    resp.getWriter().println("NOT_SEND");
+                }
+            }
+        };
+
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("FORWARD\n", createURL("/test/foo?bar=qux&quu"));
+
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Tests that we can include content from other servlets using the {@link RequestDispatcher} service.
+     */
+    @Test
+    public void testDispatchIncludeAbsoluteURIOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(2);
+        CountDownLatch destroyLatch = new CountDownLatch(2);
+
+        TestServlet include = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Object includeContextPath = req.getAttribute(INCLUDE_CONTEXT_PATH);
+                if (includeContextPath != null)
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/test", req.getServletPath());
+                    assertEquals("/foo", req.getPathInfo());
+                    assertEquals("/test/foo", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    assertEquals("", includeContextPath);
+                    assertEquals("/include", req.getAttribute(INCLUDE_SERVLET_PATH));
+                    assertEquals(null, req.getAttribute(INCLUDE_PATH_INFO));
+                    assertEquals("/include", req.getAttribute(INCLUDE_REQUEST_URI));
+                    assertEquals(null, req.getAttribute(INCLUDE_QUERY_STRING));
+                }
+                else
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/include", req.getServletPath());
+                    assertEquals("/bar", req.getPathInfo());
+                    assertEquals("/include/bar", req.getRequestURI());
+                    assertEquals("quu=qux", req.getQueryString());
+                }
+
+                resp.getWriter().println("INCLUDE");
+            }
+        };
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                assertEquals("", req.getContextPath());
+                assertEquals("/test", req.getServletPath());
+                assertEquals("/foo", req.getPathInfo());
+                assertEquals("/test/foo", req.getRequestURI());
+                assertEquals("bar=qux&quu", req.getQueryString());
+
+                resp.getWriter().println("BEFORE");
+                req.getRequestDispatcher("/include").include(req, resp);
+                resp.getWriter().println("AFTER");
+            }
+        };
+
+        register("/include", include);
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("BEFORE\nINCLUDE\nAFTER\n", createURL("/test/foo?bar=qux&quu"));
+        assertContent("INCLUDE\n", createURL("/include/bar?quu=qux"));
+
+        unregister(include);
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Tests that we can include content from other servlets using the {@link RequestDispatcher} service.
+     */
+    @Test
+    public void testDispatchIncludeRelativeURIOk() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(1);
+        CountDownLatch destroyLatch = new CountDownLatch(1);
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Object contextPathAttr = req.getAttribute(INCLUDE_CONTEXT_PATH);
+                if (contextPathAttr != null)
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/foo", req.getPathInfo());
+                    assertEquals("/test", req.getServletPath());
+                    assertEquals("/test/foo", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    assertEquals("", contextPathAttr);
+                    assertEquals("/test", req.getAttribute(INCLUDE_SERVLET_PATH));
+                    //                    assertEquals("/include", req.getAttribute(INCLUDE_PATH_INFO));
+                    //                    assertEquals("/test/include", req.getAttribute(INCLUDE_REQUEST_URI));
+                    assertEquals(null, req.getAttribute(INCLUDE_QUERY_STRING));
+
+                    resp.getWriter().println("INCLUDE");
+                }
+                else
+                {
+                    assertEquals("", req.getContextPath());
+                    assertEquals("/test", req.getServletPath());
+                    //                    assertEquals("/foo", req.getPathInfo());
+                    //                    assertEquals("/test/foo", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    resp.getWriter().println("BEFORE");
+
+                    // ServletContext#getRequestDispatcher only takes absolute paths...
+                    RequestDispatcher disp = req.getServletContext().getRequestDispatcher("include");
+                    assertNull("ServletContext returned RequestDispatcher for relative path?!", disp);
+                    // Causes a request to ourselves being made (/test/forward)...
+                    disp = req.getRequestDispatcher("include");
+                    assertNotNull("ServletRequest returned NO RequestDispatcher for relative path?!", disp);
+
+                    disp.include(req, resp);
+                    resp.getWriter().println("AFTER");
+                }
+            }
+        };
+
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("BEFORE\nINCLUDE\nAFTER\n", createURL("/test/foo?bar=qux&quu"));
+
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Tests that we can forward content from other servlets using the {@link RequestDispatcher} service.
+     */
+    @Test
+    public void testDispatchOnNonRootContextPathOk() throws Exception
+    {
+        // Configure HTTP on a different context path...
+        Configuration config = configureHttpService(createDictionary("org.apache.felix.http.context_path", "/context", "org.osgi.service.http.port", "8080"));
+
+        try
+        {
+            // Include two tests in one as to keep tests a little easier to read...
+            doTestForwardAbsoluteURI();
+            doTestIncludeAbsoluteURI();
+        }
+        finally
+        {
+            config.delete();
+        }
+    }
+
+    private void doTestForwardAbsoluteURI() throws ServletException, NamespaceException, InterruptedException, IOException
+    {
+        CountDownLatch initLatch = new CountDownLatch(2);
+        CountDownLatch destroyLatch = new CountDownLatch(2);
+
+        TestServlet forward = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Object includeContextPath = req.getAttribute(FORWARD_CONTEXT_PATH);
+                if (includeContextPath != null)
+                {
+                    assertEquals("/context", req.getContextPath());
+                    assertEquals("/forward", req.getServletPath());
+                    assertEquals(null, req.getPathInfo());
+                    assertEquals("/context/forward", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    assertEquals("/context", includeContextPath);
+                    assertEquals("/test", req.getAttribute(FORWARD_SERVLET_PATH));
+                    assertEquals("/foo", req.getAttribute(FORWARD_PATH_INFO));
+                    assertEquals("/context/test/foo", req.getAttribute(FORWARD_REQUEST_URI));
+                    assertEquals("bar=qux&quu", req.getAttribute(FORWARD_QUERY_STRING));
+                }
+                else
+                {
+                    assertEquals("/context", req.getContextPath());
+                    assertEquals("/forward", req.getServletPath());
+                    assertEquals("/bar", req.getPathInfo());
+                    assertEquals("/context/forward/bar", req.getRequestURI());
+                    assertEquals("quu=qux", req.getQueryString());
+                }
+
+                resp.getWriter().println("FORWARD");
+            }
+        };
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                assertEquals("/context", req.getContextPath());
+                assertEquals("/test", req.getServletPath());
+                assertEquals("/foo", req.getPathInfo());
+                assertEquals("/context/test/foo", req.getRequestURI());
+                assertEquals("bar=qux&quu", req.getQueryString());
+
+                resp.getWriter().println("NOT_SEND");
+                req.getRequestDispatcher("/forward").forward(req, resp);
+                resp.getWriter().println("NOT_SEND");
+            }
+        };
+
+        register("/forward", forward);
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("FORWARD\n", createURL("/context/test/foo?bar=qux&quu"));
+        assertContent("FORWARD\n", createURL("/context/forward/bar?quu=qux"));
+
+        unregister(forward);
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Tests that we can include content from other servlets using the {@link RequestDispatcher} service.
+     */
+    private void doTestIncludeAbsoluteURI() throws Exception
+    {
+        CountDownLatch initLatch = new CountDownLatch(2);
+        CountDownLatch destroyLatch = new CountDownLatch(2);
+
+        TestServlet include = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                Object includeContextPath = req.getAttribute(INCLUDE_CONTEXT_PATH);
+                if (includeContextPath != null)
+                {
+                    assertEquals("/context", req.getContextPath());
+                    assertEquals("/test", req.getServletPath());
+                    assertEquals("/foo", req.getPathInfo());
+                    assertEquals("/context/test/foo", req.getRequestURI());
+                    assertEquals("bar=qux&quu", req.getQueryString());
+
+                    assertEquals("/context", includeContextPath);
+                    assertEquals("/include", req.getAttribute(INCLUDE_SERVLET_PATH));
+                    assertEquals(null, req.getAttribute(INCLUDE_PATH_INFO));
+                    assertEquals("/context/include", req.getAttribute(INCLUDE_REQUEST_URI));
+                    assertEquals(null, req.getAttribute(INCLUDE_QUERY_STRING));
+                }
+                else
+                {
+                    assertEquals("/context", req.getContextPath());
+                    assertEquals("/include", req.getServletPath());
+                    assertEquals("/bar", req.getPathInfo());
+                    assertEquals("/context/include/bar", req.getRequestURI());
+                    assertEquals("quu=qux", req.getQueryString());
+                }
+
+                resp.getWriter().println("INCLUDE");
+            }
+        };
+
+        TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+            {
+                assertEquals("/context", req.getContextPath());
+                assertEquals("/test", req.getServletPath());
+                assertEquals("/foo", req.getPathInfo());
+                assertEquals("/context/test/foo", req.getRequestURI());
+                assertEquals("bar=qux&quu", req.getQueryString());
+
+                resp.getWriter().println("BEFORE");
+                req.getRequestDispatcher("/include").include(req, resp);
+                resp.getWriter().println("AFTER");
+            }
+        };
+
+        register("/include", include);
+        register("/test", servlet);
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent("BEFORE\nINCLUDE\nAFTER\n", createURL("/context/test/foo?bar=qux&quu"));
+        assertContent("INCLUDE\n", createURL("/context/include/bar?quu=qux"));
+
+        unregister(include);
+        unregister(servlet);
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+    }
+}