FELIX-4888 : ServletHandler's are not sorted by longest matching path

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1679603 13f79535-47bb-0310-9956-ffa450edef68
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 8976da9..aaf9ecd 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
@@ -24,7 +24,6 @@
 import javax.servlet.http.HttpSessionIdListener;
 import javax.servlet.http.HttpSessionListener;
 
-import org.apache.felix.http.base.internal.console.HttpServicePlugin;
 import org.apache.felix.http.base.internal.dispatch.Dispatcher;
 import org.apache.felix.http.base.internal.handler.HandlerRegistry;
 import org.apache.felix.http.base.internal.handler.HttpSessionWrapper;
@@ -38,7 +37,6 @@
     private final BundleContext bundleContext;
     private final HandlerRegistry registry;
     private final Dispatcher dispatcher;
-    private final HttpServicePlugin plugin;
     private final HttpServiceFactory httpServiceFactory;
     private final WhiteboardManager whiteboardManager;
 
@@ -49,7 +47,6 @@
         this.bundleContext = bundleContext;
         this.registry = new HandlerRegistry();
         this.dispatcher = new Dispatcher(this.registry);
-        this.plugin = new HttpServicePlugin(bundleContext, registry);
         this.httpServiceFactory = new HttpServiceFactory(this.bundleContext, this.registry);
         this.whiteboardManager = new WhiteboardManager(bundleContext, this.httpServiceFactory, this.registry);
     }
@@ -113,8 +110,6 @@
     {
         this.registry.init();
 
-        this.plugin.register();
-
         this.httpServiceFactory.start(servletContext);
         this.whiteboardManager.start(servletContext);
 
@@ -123,8 +118,6 @@
 
     public void unregister()
     {
-        this.plugin.unregister();
-
         this.dispatcher.setWhiteboardManager(null);
 
         if ( this.whiteboardManager != null )
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/console/HttpServicePlugin.java b/http/base/src/main/java/org/apache/felix/http/base/internal/console/HttpServicePlugin.java
index 8665277..2a04d90 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/console/HttpServicePlugin.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/console/HttpServicePlugin.java
@@ -24,6 +24,7 @@
 import java.util.Arrays;
 import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.Map;
 
 import javax.servlet.Servlet;
 import javax.servlet.http.HttpServlet;
@@ -31,26 +32,33 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.felix.http.base.internal.handler.FilterHandler;
-import org.apache.felix.http.base.internal.handler.HandlerRegistry;
 import org.apache.felix.http.base.internal.handler.ServletHandler;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.dto.ServiceReferenceDTO;
+import org.osgi.service.http.runtime.HttpServiceRuntime;
+import org.osgi.service.http.runtime.dto.RuntimeDTO;
+import org.osgi.service.http.runtime.dto.ServletContextDTO;
+import org.osgi.service.http.runtime.dto.ServletDTO;
 
+/**
+ * This is a web console plugin.
+ */
 @SuppressWarnings("serial")
 public class HttpServicePlugin extends HttpServlet
 {
 
-    private final HandlerRegistry registry;
+    private final HttpServiceRuntime runtime;
     private final BundleContext context;
 
-    private ServiceRegistration serviceReg;
+    private volatile ServiceRegistration<Servlet> serviceReg;
 
-    public HttpServicePlugin(BundleContext context, HandlerRegistry registry)
+    public HttpServicePlugin(final BundleContext context, final HttpServiceRuntime runtime)
     {
-        this.registry = registry;
+        this.runtime = runtime;
         this.context = context;
     }
 
@@ -62,7 +70,7 @@
         props.put("felix.webconsole.label", "httpservice");
         props.put("felix.webconsole.title", "HTTP Service");
         props.put("felix.webconsole.configprinter.modes", "always");
-        this.serviceReg = context.registerService(Servlet.class.getName(), this, props);
+        this.serviceReg = context.registerService(Servlet.class, this, props);
     }
 
     @Override
@@ -71,18 +79,118 @@
         getHtml(resp);
     }
 
-    private void getHtml(HttpServletResponse resp) throws IOException
+    private void getHtml(final HttpServletResponse resp) throws IOException
     {
+        final RuntimeDTO dto = this.runtime.getRuntimeDTO();
+
         final PrintWriter pw = resp.getWriter();
 
-        printServletDetails(pw);
-        printFilterDetails(pw);
-
+        printRuntimeDetails(pw, dto.serviceDTO);
+        for(final ServletContextDTO ctxDto : dto.servletContextDTOs )
+        {
+            printContextDetails(pw, ctxDto);
+        }
     }
 
-    private void printFilterDetails(PrintWriter pw)
+    private String getValueAsString(final Object value)
     {
-        pw.println("<p class=\"statline ui-state-highlight\">${Registered Filter Services}</p>");
+        if ( value.getClass().isArray() )
+        {
+            if (value instanceof long[])
+            {
+                return Arrays.toString((long[])value);
+            }
+            else if (value instanceof int[])
+            {
+                return Arrays.toString((int[])value);
+            }
+            else if (value instanceof double[])
+            {
+                return Arrays.toString((double[])value);
+            }
+            else if (value instanceof byte[])
+            {
+                return Arrays.toString((byte[])value);
+            }
+            else if (value instanceof float[])
+            {
+                return Arrays.toString((float[])value);
+            }
+            else if (value instanceof short[])
+            {
+                return Arrays.toString((short[])value);
+            }
+            else if (value instanceof boolean[])
+            {
+                return Arrays.toString((boolean[])value);
+            }
+            else if (value instanceof char[])
+            {
+                return Arrays.toString((char[])value);
+            }
+            else
+            {
+                return Arrays.toString((Object[])value);
+            }
+        }
+        return value.toString();
+    }
+
+    private void printRuntimeDetails(final PrintWriter pw, final ServiceReferenceDTO dto)
+    {
+        pw.println("<p class=\"statline ui-state-highlight\">${Runtime Properties}</p>");
+        pw.println("<table class=\"nicetable\">");
+        pw.println("<thead><tr>");
+        pw.println("<th class=\"header\">${Name}</th>");
+        pw.println("<th class=\"header\">${Value}</th>");
+        pw.println("</tr></thead>");
+        boolean odd = true;
+        for(final Map.Entry<String, Object> prop : dto.properties.entrySet())
+        {
+            odd = printRow(pw, odd, prop.getKey(), getValueAsString(prop.getValue()));
+        }
+        pw.println("</table>");
+    }
+
+    private boolean printRow(final PrintWriter pw, final boolean odd, final String...columns)
+    {
+        pw.print("<tr class=\"");
+        if ( odd ) pw.print("odd"); else pw.print("even");
+        pw.println(" ui-state-default\">");
+
+        for(final String val : columns)
+        {
+            pw.print("<td>");
+            pw.print(val);
+            pw.println("</td>");
+        }
+
+        pw.println("</tr>");
+        return !odd;
+    }
+
+    private void printContextDetails(final PrintWriter pw, final ServletContextDTO dto)
+    {
+        pw.print("<p class=\"statline ui-state-highlight\">${Servlet Context} '");
+        pw.print(dto.name);
+        pw.println("'</p>");
+
+        pw.println("<table class=\"nicetable\">");
+
+        boolean odd = true;
+        odd = printRow(pw, odd, "${Path}", dto.contextPath);
+        odd = printRow(pw, odd, "${service.id}", String.valueOf(dto.serviceId));
+        pw.println("</table>");
+
+        printServletDetails(pw, dto);
+        printFilterDetails(pw, dto);
+    }
+
+    private void printFilterDetails(final PrintWriter pw, final ServletContextDTO dto)
+    {
+        pw.print("<p class=\"statline ui-state-highlight\">${Servlet Context} '");
+        pw.print(dto.name);
+        pw.println("' ${Registered Filter Services}</p>");
 
         pw.println("<table class=\"nicetable\">");
         pw.println("<thead><tr>");
@@ -114,37 +222,26 @@
         pw.println("</table>");
     }
 
-    private void printServletDetails(PrintWriter pw)
+    private void printServletDetails(final PrintWriter pw, final ServletContextDTO dto)
     {
-        pw.println("<p class=\"statline ui-state-highlight\">${Registered Servlet Services}</p>");
+        pw.print("<p class=\"statline ui-state-highlight\">${Servlet Context} '");
+        pw.print(dto.name);
+        pw.println("' ${Registered Servlet Services}</p>");
 
         pw.println("<table class=\"nicetable\">");
         pw.println("<thead><tr>");
-        pw.println("<th class=\"header\">${Alias}</th>");
-        pw.println("<th class=\"header\">${Servlet}</th>");
-        pw.println("<th class=\"header\">${Bundle}</th>");
+        pw.println("<th class=\"header\">${Path}</th>");
+        pw.println("<th class=\"header\">${Name}</th>");
+        pw.println("<th class=\"header\">${Info}</th>");
         pw.println("</tr></thead>");
 
-        ServletHandler[] servlets = new ServletHandler[0]; // XXX was: registry.getServlets();
-        String rowClass = "odd";
-        for (ServletHandler servlet : servlets)
+        boolean odd = true;
+        for (ServletDTO servlet : dto.servletDTOs)
         {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("${service.id} : ").append(String.valueOf(servlet.serviceId)).append("\n");
 
-            pw.println("<tr class=\"" + rowClass + " ui-state-default\">");
-//            pw.println("<td>" + Arrays.toString(servlet.getPatternStrings()) + "</td>"); // XXX
-//            pw.println("<td>" + servlet.getServlet().getClass().getName() + "</td>");
-
-            printBundleDetails(pw, servlet.getServlet().getClass());
-
-            pw.println("</tr>");
-            if (rowClass.equals("odd"))
-            {
-                rowClass = "even";
-            }
-            else
-            {
-                rowClass = "odd";
-            }
+            odd = printRow(pw, odd, getValueAsString(servlet.patterns), servlet.name, sb.toString());
         }
         pw.println("</table>");
     }
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 d3ea3b1..efbf91f 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
@@ -541,11 +541,6 @@
         }
     }
 
-    /**
-     * Catch-all filter chain that simple finishes all requests with a "404 Not Found" error.
-     */
-    private static final FilterChain DEFAULT_CHAIN = new NotFoundFilterChain();
-
     private final HandlerRegistry handlerRegistry;
 
     private WhiteboardManager whiteboardManager;
@@ -570,7 +565,7 @@
      */
     public void dispatch(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException
     {
-        // invalid sessions first
+        // check for invalidating session(s) first
         final HttpSession session = req.getSession(false);
         if ( session != null )
         {
@@ -600,7 +595,7 @@
         String pathInfo = UriUtils.compactPath(UriUtils.relativePath(servletPath, requestURI));
         String queryString = null; // XXX
 
-        ExtServletContext servletContext = (servletHandler != null) ? servletHandler.getContext() : null;
+        final ExtServletContext servletContext = servletHandler.getContext();
         final RequestInfo requestInfo = new RequestInfo(servletPath, pathInfo, queryString);
 
         final HttpServletRequest wrappedRequest = new ServletRequestWrapper(req, servletContext, requestInfo, servletHandler.getContextServiceId(),
@@ -707,7 +702,7 @@
 
     private void invokeChain(FilterHandler[] filterHandlers, ServletHandler servletHandler, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
     {
-        FilterChain filterChain = new InvocationFilterChain(servletHandler, filterHandlers, DEFAULT_CHAIN);
+        final FilterChain filterChain = new InvocationChain(servletHandler, filterHandlers);
         filterChain.doFilter(request, response);
     }
 }
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
deleted file mode 100644
index 574f572..0000000
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/HttpFilterChain.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.felix.http.base.internal.dispatch;
-
-import java.io.IOException;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * Convenience adapter for {@link FilterChain}s that makes the obtrusive casting unnecessary.
- */
-public abstract class HttpFilterChain implements FilterChain
-{
-    @Override
-    public final void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException
-    {
-        doFilter((HttpServletRequest) req, (HttpServletResponse) res);
-    }
-
-    /**
-     * @see FilterChain#doFilter(ServletRequest, ServletResponse)
-     */
-    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/InvocationChain.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationChain.java
new file mode 100644
index 0000000..eda9509
--- /dev/null
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationChain.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.base.internal.dispatch;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.base.internal.handler.FilterHandler;
+import org.apache.felix.http.base.internal.handler.ServletHandler;
+
+public class InvocationChain implements FilterChain
+{
+    private final ServletHandler servletHandler;
+    private final FilterHandler[] filterHandlers;
+
+    private int index = -1;
+
+    public InvocationChain(@Nonnull final ServletHandler servletHandler, @Nonnull final FilterHandler[] filterHandlers)
+    {
+        this.filterHandlers = filterHandlers;
+        this.servletHandler = servletHandler;
+    }
+
+    @Override
+    public final void doFilter(ServletRequest req, ServletResponse res) throws IOException, ServletException
+    {
+        if ( this.index == -1 )
+        {
+            final HttpServletRequest hReq = (HttpServletRequest) req;
+            final HttpServletResponse hRes = (HttpServletResponse) res;
+
+            // invoke security
+            if ( !servletHandler.getContext().handleSecurity(hReq, hRes))
+            {
+                // FELIX-3988: If the response is not yet committed and still has the default
+                // status, we're going to override this and send an error instead.
+                if (!res.isCommitted() && (hRes.getStatus() == SC_OK || hRes.getStatus() == 0))
+                {
+                    hRes.sendError(SC_FORBIDDEN);
+                }
+
+                // we're done
+                return;
+            }
+        }
+        this.index++;
+
+        if (this.index < this.filterHandlers.length)
+        {
+            this.filterHandlers[this.index].handle(req, res, this);
+        }
+        else
+        {
+            // Last entry in the chain...
+            this.servletHandler.handle(req, res);
+        }
+    }
+}
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
deleted file mode 100644
index b806c39..0000000
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/InvocationFilterChain.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.felix.http.base.internal.dispatch;
-
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-
-import java.io.IOException;
-
-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.handler.FilterHandler;
-import org.apache.felix.http.base.internal.handler.ServletHandler;
-
-class InvocationFilterChain extends HttpFilterChain
-{
-    private final ServletHandler servletHandler;
-    private final FilterHandler[] filterHandlers;
-    private final FilterChain defaultChain;
-
-    private int index = -1;
-
-    public InvocationFilterChain(ServletHandler servletHandler, FilterHandler[] filterHandlers, FilterChain defaultChain)
-    {
-        this.filterHandlers = filterHandlers;
-        this.servletHandler = servletHandler;
-        this.defaultChain = defaultChain;
-    }
-
-    @Override
-    protected void doFilter(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException
-    {
-        this.index++;
-
-        if (this.index < this.filterHandlers.length)
-        {
-            if (this.filterHandlers[this.index].handle(req, res, this))
-            {
-                // We're done...
-                return;
-            }
-        }
-
-        // Last entry in the chain...
-        if (this.servletHandler != null && this.servletHandler.handle(req, res))
-        {
-            // We're done...
-            return;
-        }
-
-        // FELIX-3988: If the response is not yet committed and still has the default
-        // status, we're going to override this and send an error instead.
-        if (!res.isCommitted() && res.getStatus() == SC_OK)
-        {
-            this.defaultChain.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
deleted file mode 100644
index b1398f2..0000000
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/NotFoundFilterChain.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.felix.http.base.internal.dispatch;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-public final class NotFoundFilterChain extends HttpFilterChain
-{
-    @Override
-    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/handler/FilterHandler.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/FilterHandler.java
index 951c2ec..a8eb611 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
@@ -16,9 +16,6 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
-import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -27,8 +24,8 @@
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 
 import org.apache.felix.http.base.internal.context.ExtServletContext;
 import org.apache.felix.http.base.internal.runtime.FilterInfo;
@@ -84,6 +81,7 @@
         return this.filter;
     }
 
+    @Override
     public FilterInfo getFilterInfo()
     {
         return this.filterInfo;
@@ -94,23 +92,9 @@
         return filterInfo.getRanking();
     }
 
-    public boolean handle(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException
+    public void handle(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException
     {
-        if (getContext().handleSecurity(req, res))
-        {
-            this.filter.doFilter(req, res, chain);
-
-            return true;
-        }
-
-        // FELIX-3988: If the response is not yet committed and still has the default
-        // status, we're going to override this and send an error instead.
-        if (!res.isCommitted() && (res.getStatus() == SC_OK || res.getStatus() == 0))
-        {
-            res.sendError(SC_FORBIDDEN);
-        }
-
-        return false;
+        this.filter.doFilter(req, res, chain);
     }
 
     @Override
@@ -150,6 +134,7 @@
         return this.patterns;
     }
 
+    @Override
     public long getContextServiceId()
     {
         return this.contextServiceId;
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 d435bee..f3c020d 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
@@ -37,8 +37,6 @@
 import org.apache.felix.http.base.internal.runtime.dto.ContextRuntime;
 import org.apache.felix.http.base.internal.runtime.dto.FailureRuntime;
 import org.apache.felix.http.base.internal.runtime.dto.HandlerRegistryRuntime;
-import org.apache.felix.http.base.internal.runtime.dto.InfoServletContextHelperRuntime;
-import org.apache.felix.http.base.internal.runtime.dto.ServletContextHelperRuntime;
 import org.apache.felix.http.base.internal.runtime.dto.ServletRegistryRuntime;
 import org.apache.felix.http.base.internal.whiteboard.RegistrationFailureException;
 
@@ -50,8 +48,6 @@
  */
 public final class HandlerRegistry
 {
-    private static final String HTTP_SERVICE_CONTEXT_NAME = "Http service context";
-
     private static FilterHandler[] EMPTY_FILTER_HANDLER = new FilterHandler[0];
 
     /** Current list of context registrations. */
@@ -252,12 +248,6 @@
         return EMPTY_FILTER_HANDLER;
     }
 
-    public ServletContextHelperRuntime getHttpServiceContextRuntime()
-    {
-        ServletContextHelperInfo info = new ServletContextHelperInfo(Integer.MAX_VALUE, 0, HTTP_SERVICE_CONTEXT_NAME, "/", null);
-        return new InfoServletContextHelperRuntime(info);
-    }
-
     public synchronized void addServlet(ServletHandler handler) throws RegistrationFailureException
     {
         Pattern[] patterns = handler.getPatterns();
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 10398f8..5d10a1a 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,16 +16,13 @@
  */
 package org.apache.felix.http.base.internal.handler;
 
-import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-
 import java.io.IOException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 
 import org.apache.felix.http.base.internal.context.ExtServletContext;
 import org.apache.felix.http.base.internal.runtime.ServletInfo;
@@ -69,23 +66,9 @@
         return this.servletInfo.compareTo(other.servletInfo);
     }
 
-    public boolean handle(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
+    public void handle(ServletRequest req, ServletResponse res) throws ServletException, IOException
     {
-        if (getContext().handleSecurity(req, res))
-        {
-            getServlet().service(req, res);
-
-            return true;
-        }
-
-        // FELIX-3988: If the response is not yet committed and still has the default
-        // status, we're going to override this and send an error instead.
-        if (!res.isCommitted() && (res.getStatus() == SC_OK || res.getStatus() == 0))
-        {
-            res.sendError(SC_FORBIDDEN);
-        }
-
-        return false;
+        getServlet().service(req, res);
     }
 
     public String determineServletPath(String uri)
@@ -114,6 +97,7 @@
         return this.patterns;
     }
 
+    @Override
     public ServletInfo getServletInfo()
     {
         return this.servletInfo;
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/InfoServletContextHelperRuntime.java b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/InfoServletContextHelperRuntime.java
index 8110c41..d938a8a 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/InfoServletContextHelperRuntime.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/InfoServletContextHelperRuntime.java
@@ -26,15 +26,23 @@
 {
     private final ServletContextHelperInfo info;
 
-    public InfoServletContextHelperRuntime(ServletContextHelperInfo info)
+    private final ServletContext servletContext;
+
+    public InfoServletContextHelperRuntime(final ServletContextHelperInfo info)
+    {
+        this(info, null);
+    }
+
+    public InfoServletContextHelperRuntime(final ServletContextHelperInfo info, final ServletContext ctx)
     {
         this.info = info;
+        this.servletContext = ctx;
     }
 
     @Override
     public ServletContext getSharedContext()
     {
-        return null;
+        return this.servletContext;
     }
 
     @Override
@@ -44,7 +52,7 @@
     }
 
     @Override
-    public int compareTo(InfoServletContextHelperRuntime other)
+    public int compareTo(final InfoServletContextHelperRuntime other)
     {
         return info.compareTo(other.info);
     }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/ServletContextDTOBuilder.java b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/ServletContextDTOBuilder.java
index 34d5bc4..a677c72 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/ServletContextDTOBuilder.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/ServletContextDTOBuilder.java
@@ -86,12 +86,12 @@
 
     ServletContextDTO build()
     {
-        ServletContext context  = contextRuntime.getSharedContext();
-        ServletContextHelperInfo contextInfo = contextRuntime.getContextInfo();
-        long contextId = contextInfo.getServiceId();
+        final ServletContext context  = contextRuntime.getSharedContext();
+        final ServletContextHelperInfo contextInfo = contextRuntime.getContextInfo();
+        final long contextId = contextInfo.getServiceId();
 
         contextDTO.attributes = getAttributes(context);
-        contextDTO.contextPath = context == null ? contextInfo.getPath() : context.getContextPath();
+        contextDTO.contextPath = context != null ? context.getContextPath() : contextInfo.getPath();
         contextDTO.errorPageDTOs = errorPageDTOs;
         contextDTO.filterDTOs = filterDTOs;
         contextDTO.initParams = contextInfo.getInitParameters();
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java
index 8f5032a..6aa33fe 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/whiteboard/WhiteboardManager.java
@@ -41,6 +41,7 @@
 import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSessionEvent;
 
+import org.apache.felix.http.base.internal.console.HttpServicePlugin;
 import org.apache.felix.http.base.internal.context.ExtServletContext;
 import org.apache.felix.http.base.internal.handler.HandlerRegistry;
 import org.apache.felix.http.base.internal.handler.HttpSessionWrapper;
@@ -60,6 +61,7 @@
 import org.apache.felix.http.base.internal.runtime.WhiteboardServiceInfo;
 import org.apache.felix.http.base.internal.runtime.dto.FailureRuntime;
 import org.apache.felix.http.base.internal.runtime.dto.HandlerRegistryRuntime;
+import org.apache.felix.http.base.internal.runtime.dto.InfoServletContextHelperRuntime;
 import org.apache.felix.http.base.internal.runtime.dto.RegistryRuntime;
 import org.apache.felix.http.base.internal.runtime.dto.ServletContextHelperRuntime;
 import org.apache.felix.http.base.internal.service.HttpServiceFactory;
@@ -115,6 +117,8 @@
 
     private volatile ServiceRegistration<HttpServiceRuntime> runtimeServiceReg;
 
+    private final HttpServicePlugin plugin;
+
     /**
      * Create a new whiteboard http manager
      * @param bundleContext
@@ -129,6 +133,7 @@
         this.httpServiceFactory = httpServiceFactory;
         this.httpService = new WhiteboardHttpService(this.bundleContext, registry);
         this.serviceRuntime = new HttpServiceRuntimeImpl(registry, this);
+        this.plugin = new HttpServicePlugin(bundleContext, this.serviceRuntime);
     }
 
     public void start(final ServletContext context)
@@ -191,6 +196,7 @@
 
         addTracker(new ServletRequestListenerTracker(this.bundleContext, this));
         addTracker(new ServletRequestAttributeListenerTracker(this.bundleContext, this));
+        this.plugin.register();
     }
 
     private void addTracker(ServiceTracker<?, ?> tracker)
@@ -204,6 +210,7 @@
      */
     public void stop()
     {
+        this.plugin.unregister();
         for(final ServiceTracker<?, ?> t : this.trackers)
         {
             t.close();
@@ -740,6 +747,8 @@
          return handlers;
     }
 
+    private static final String HTTP_SERVICE_CONTEXT_NAME = "Http Service context";
+
     public RegistryRuntime getRuntime(HandlerRegistry registry)
     {
         final Collection<ServletContextHelperRuntime> contextRuntimes = new TreeSet<ServletContextHelperRuntime>(ServletContextHelperRuntime.COMPARATOR);
@@ -759,7 +768,10 @@
                     listenerRuntimes.put(serviceId, handler.getListenerRegistry().getRuntime());
                 }
             }
-            contextRuntimes.add(registry.getHttpServiceContextRuntime());
+            // TODO - this is the wrong place, it adds the context for the http service
+            final ServletContextHelperInfo info = new ServletContextHelperInfo(Integer.MAX_VALUE, 0, HTTP_SERVICE_CONTEXT_NAME, "/", null);
+            contextRuntimes.add(new InfoServletContextHelperRuntime(info, this.webContext));
+
             handlerRuntimes = registry.getRuntime(failureRuntime);
             failureRuntime.add(serviceFailures);
         }
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 00096af..bc555b7 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
@@ -36,6 +36,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.felix.http.base.internal.dispatch.InvocationChain;
 import org.apache.felix.http.base.internal.runtime.FilterInfo;
 import org.junit.Before;
 import org.junit.Test;
@@ -116,9 +117,11 @@
     public void testHandleFoundForbidden() throws Exception
     {
         FilterHandler h1 = createHandler(0, "/a");
+        final ServletHandler sc = mock(ServletHandler.class);
+        when(sc.getContext()).thenReturn(this.context);
+        final InvocationChain ic = new InvocationChain(sc, new FilterHandler[] {h1});
         HttpServletRequest req = createServletRequest();
         HttpServletResponse res = createServletResponse();
-        FilterChain chain = mock(FilterChain.class);
 
         when(req.getRequestURI()).thenReturn("/a");
         // Default behaviour: uncomitted response and default status code...
@@ -127,10 +130,9 @@
 
         when(this.context.handleSecurity(req, res)).thenReturn(false);
 
-        h1.handle(req, res, chain);
+        ic.doFilter(req, res);
 
-        verify(this.filter, never()).doFilter(req, res, chain);
-        verify(chain, never()).doFilter(req, res);
+        verify(this.filter, never()).doFilter(req, res, ic);
         verify(res).sendError(SC_FORBIDDEN);
     }
 
@@ -141,9 +143,11 @@
     public void testHandleFoundForbiddenCommittedOwnResponse() throws Exception
     {
         FilterHandler h1 = createHandler(0, "/a");
+        final ServletHandler sc = mock(ServletHandler.class);
+        when(sc.getContext()).thenReturn(this.context);
+        final InvocationChain ic = new InvocationChain(sc, new FilterHandler[] {h1});
         HttpServletRequest req = createServletRequest();
         HttpServletResponse res = createServletResponse();
-        FilterChain chain = mock(FilterChain.class);
 
         when(req.getRequestURI()).thenReturn("/a");
         // Simulate an already committed response...
@@ -152,10 +156,9 @@
 
         when(this.context.handleSecurity(req, res)).thenReturn(false);
 
-        h1.handle(req, res, chain);
+        ic.doFilter(req, res);
 
-        verify(this.filter, never()).doFilter(req, res, chain);
-        verify(chain, never()).doFilter(req, res);
+        verify(this.filter, never()).doFilter(req, res, ic);
         // Should not be called from our handler...
         verify(res, never()).sendError(SC_FORBIDDEN);
     }
@@ -167,9 +170,11 @@
     public void testHandleFoundForbiddenCustomStatusCode() throws Exception
     {
         FilterHandler h1 = createHandler(0, "/a");
+        final ServletHandler sc = mock(ServletHandler.class);
+        when(sc.getContext()).thenReturn(this.context);
+        final InvocationChain ic = new InvocationChain(sc, new FilterHandler[] {h1});
         HttpServletRequest req = createServletRequest();
         HttpServletResponse res = createServletResponse();
-        FilterChain chain = mock(FilterChain.class);
 
         when(req.getRequestURI()).thenReturn("/a");
         // Simulate an uncommitted response with a non-default status code...
@@ -178,10 +183,9 @@
 
         when(this.context.handleSecurity(req, res)).thenReturn(false);
 
-        h1.handle(req, res, chain);
+        ic.doFilter(req, res);
 
-        verify(this.filter, never()).doFilter(req, res, chain);
-        verify(chain, never()).doFilter(req, res);
+        verify(this.filter, never()).doFilter(req, res, ic);
         // Should not be called from our handler...
         verify(res, never()).sendError(SC_FORBIDDEN);
     }
@@ -190,30 +194,32 @@
     public void testHandleNotFound() throws Exception
     {
         FilterHandler h1 = createHandler(0, "/a");
+        final ServletHandler sc = mock(ServletHandler.class);
+        when(sc.getContext()).thenReturn(this.context);
+        final InvocationChain ic = new InvocationChain(sc, new FilterHandler[] {h1});
         HttpServletRequest req = createServletRequest();
         HttpServletResponse res = createServletResponse();
-        FilterChain chain = mock(FilterChain.class);
 
         when(req.getRequestURI()).thenReturn("/");
-        h1.handle(req, res, chain);
+        ic.doFilter(req, res);
 
-        verify(this.filter, never()).doFilter(req, res, chain);
-        verify(chain, never()).doFilter(req, res);
+        verify(this.filter, never()).doFilter(req, res, ic);
     }
 
     @Test
     public void testHandleNotFoundContextRoot() throws Exception
     {
         FilterHandler h1 = createHandler(0, "/a");
+        final ServletHandler sc = mock(ServletHandler.class);
+        when(sc.getContext()).thenReturn(this.context);
+        final InvocationChain ic = new InvocationChain(sc, new FilterHandler[] {h1});
         HttpServletRequest req = createServletRequest();
         HttpServletResponse res = createServletResponse();
-        FilterChain chain = mock(FilterChain.class);
 
         when(req.getRequestURI()).thenReturn(null);
-        h1.handle(req, res, chain);
+        ic.doFilter(req, res);
 
-        verify(this.filter, never()).doFilter(req, res, chain);
-        verify(chain, never()).doFilter(req, res);
+        verify(this.filter, never()).doFilter(req, res, ic);
     }
 
     @Test
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/SimpleServletHandlerTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/SimpleServletHandlerTest.java
index d1eea17..9ba64a1 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/handler/SimpleServletHandlerTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/handler/SimpleServletHandlerTest.java
@@ -19,8 +19,7 @@
 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import static javax.servlet.http.HttpServletResponse.SC_PAYMENT_REQUIRED;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -35,6 +34,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.felix.http.base.internal.dispatch.InvocationChain;
 import org.apache.felix.http.base.internal.runtime.ServletInfo;
 import org.junit.Before;
 import org.junit.Test;
@@ -63,14 +63,15 @@
     public void testHandleFound() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
+        final InvocationChain ic = new InvocationChain(h1, new FilterHandler[0]);
         HttpServletRequest req = mock(HttpServletRequest.class);
         HttpServletResponse res = mock(HttpServletResponse.class);
         when(this.context.handleSecurity(req, res)).thenReturn(true);
 
         when(req.getPathInfo()).thenReturn("/a/b");
-        boolean result = h1.handle(req, res);
+        ic.doFilter(req, res);
 
-        assertTrue(result);
+        assertEquals(0, res.getStatus());
         verify(this.servlet).service(any(HttpServletRequest.class), any(HttpServletResponse.class));
     }
 
@@ -78,14 +79,15 @@
     public void testHandleFoundContextRoot() throws Exception
     {
         ServletHandler h1 = createHandler("/");
+        final InvocationChain ic = new InvocationChain(h1, new FilterHandler[0]);
         HttpServletRequest req = mock(HttpServletRequest.class);
         HttpServletResponse res = mock(HttpServletResponse.class);
         when(this.context.handleSecurity(req, res)).thenReturn(true);
 
         when(req.getPathInfo()).thenReturn(null);
-        boolean result = h1.handle(req, res);
+        ic.doFilter(req, res);
 
-        assertTrue(result);
+        assertEquals(0, res.getStatus());
         verify(this.servlet).service(any(HttpServletRequest.class), any(HttpServletResponse.class));
     }
 
@@ -96,6 +98,7 @@
     public void testHandleFoundForbidden() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
+        final InvocationChain ic = new InvocationChain(h1, new FilterHandler[0]);
         HttpServletRequest req = mock(HttpServletRequest.class);
         HttpServletResponse res = mock(HttpServletResponse.class);
 
@@ -107,9 +110,9 @@
         when(this.context.handleSecurity(req, res)).thenReturn(false);
 
         when(req.getPathInfo()).thenReturn("/a/b");
-        boolean result = h1.handle(req, res);
+        ic.doFilter(req, res);
 
-        assertFalse(result);
+        assertEquals(SC_OK, res.getStatus());
         verify(this.servlet, never()).service(req, res);
         verify(res).sendError(SC_FORBIDDEN);
     }
@@ -121,6 +124,7 @@
     public void testHandleFoundForbiddenCommittedOwnResponse() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
+        final InvocationChain ic = new InvocationChain(h1, new FilterHandler[0]);
         HttpServletRequest req = mock(HttpServletRequest.class);
         HttpServletResponse res = mock(HttpServletResponse.class);
 
@@ -132,9 +136,9 @@
         when(this.context.handleSecurity(req, res)).thenReturn(false);
 
         when(req.getPathInfo()).thenReturn("/a/b");
-        boolean result = h1.handle(req, res);
+        ic.doFilter(req, res);
 
-        assertFalse(result);
+        assertEquals(SC_OK, res.getStatus());
         verify(this.servlet, never()).service(req, res);
         verify(res, never()).sendError(SC_FORBIDDEN);
     }
@@ -146,6 +150,7 @@
     public void testHandleFoundForbiddenCustomStatusCode() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
+        final InvocationChain ic = new InvocationChain(h1, new FilterHandler[0]);
         HttpServletRequest req = mock(HttpServletRequest.class);
         HttpServletResponse res = mock(HttpServletResponse.class);
 
@@ -157,9 +162,9 @@
         when(this.context.handleSecurity(req, res)).thenReturn(false);
 
         when(req.getPathInfo()).thenReturn("/a/b");
-        boolean result = h1.handle(req, res);
+        ic.doFilter(req, res);
 
-        assertFalse(result);
+        assertEquals(SC_PAYMENT_REQUIRED, res.getStatus());
         verify(this.servlet, never()).service(req, res);
         verify(res, never()).sendError(SC_FORBIDDEN);
     }
@@ -168,13 +173,13 @@
     public void testHandleNotFound() throws Exception
     {
         ServletHandler h1 = createHandler("/a");
+        final InvocationChain ic = new InvocationChain(h1, new FilterHandler[0]);
         HttpServletRequest req = mock(HttpServletRequest.class);
         HttpServletResponse res = mock(HttpServletResponse.class);
 
         when(req.getPathInfo()).thenReturn("/");
-        boolean result = h1.handle(req, res);
+        ic.doFilter(req, res);
 
-        assertFalse(result);
         verify(this.servlet, never()).service(req, res);
     }
 
@@ -187,9 +192,8 @@
         when(this.context.handleSecurity(req, res)).thenReturn(true);
 
         when(req.getRequestURI()).thenReturn(null);
-        boolean result = h1.handle(req, res);
+        h1.handle(req, res);
 
-        assertTrue(result);
         verify(this.servlet).service(req, res);
     }