FELIX-4894 : Implement cross context shadowing

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1680558 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistry.java
index 3ca94be..d4a9b6c 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistry.java
@@ -160,6 +160,8 @@
                             final ServletHandler old = list.get(0);
                             old.destroy();
                             errorCodesMap.put(code, newList);
+                            final ErrorRegistrationStatus oldStatus = statusMapping.get(old.getServletInfo());
+                            oldStatus.errorCodeMapping.put(code, DTOConstants.FAILURE_REASON_SHADOWED_BY_OTHER_SERVICE);
                         }
                     }
                     else
@@ -197,6 +199,8 @@
                             final ServletHandler old = list.get(0);
                             old.destroy();
                             exceptionsMap.put(exception, newList);
+                            final ErrorRegistrationStatus oldStatus = statusMapping.get(old.getServletInfo());
+                            oldStatus.exceptionMapping.put(exception, DTOConstants.FAILURE_REASON_SHADOWED_BY_OTHER_SERVICE);
                         }
                     }
                     else
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterHandlerMapping.java b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterHandlerMapping.java
index 5c089c1..4e25065 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterHandlerMapping.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterHandlerMapping.java
@@ -121,7 +121,7 @@
         return add(mappings);
     }
 
-    FilterHandlerMapping add(@Nonnull final Map<Pattern, FilterHandler> mappings)
+    private FilterHandlerMapping add(@Nonnull final Map<Pattern, FilterHandler> mappings)
     {
         final Map<Pattern, Collection<FilterHandler>> newMappings = getAllMappings();
         addMappings(mappings, newMappings);
@@ -146,21 +146,13 @@
         return remove(mappings);
     }
 
-    FilterHandlerMapping remove(Map<Pattern, FilterHandler> mappings)
+    private FilterHandlerMapping remove(Map<Pattern, FilterHandler> mappings)
     {
         Map<Pattern, Collection<FilterHandler>> newMappings = getAllMappings();
         removeMappings(mappings, newMappings);
         return new FilterHandlerMapping(newMappings);
     }
 
-    FilterHandlerMapping update(Map<Pattern, FilterHandler> add, Map<Pattern, FilterHandler> remove)
-    {
-        Map<Pattern, Collection<FilterHandler>> newMappings = getAllMappings();
-        removeMappings(remove, newMappings);
-        addMappings(add, newMappings);
-        return new FilterHandlerMapping(newMappings);
-    }
-
     private void addMappings(Map<Pattern, FilterHandler> mappings, Map<Pattern, Collection<FilterHandler>> target)
     {
         for (Map.Entry<Pattern, FilterHandler> mapping : mappings.entrySet())
@@ -211,17 +203,6 @@
     }
 
     /**
-     * Returns whether this mapping contains the specified handler.
-     *
-     * @return <code>true</code> if the handlers contains the specified handler,
-     *         <code>false</code> otherwise
-     */
-    public boolean contains(FilterHandler handler)
-    {
-        return mappedHandlers.contains(handler);
-    }
-
-    /**
      * Returns all matching handlers for the given path.
      *
      * @param path the path that should match, cannot be <code>null</code>.
@@ -233,59 +214,6 @@
     }
 
     /**
-     * Returns the best matching handler for the given path, according to the rules defined in section 12.1 of Servlet 3.0 specification:
-     * <ul>
-     * <li>find an exact match of the path of the request to the path of the handler. A successful match selects the handler;</li>
-     * <li>recursively try to match the longest path-prefix. This is done by stepping down the path tree a directory at a time, using the
-     *     '/' character as a path separator. The longest match determines the servlet selected;</li>
-     * <li>if the last segment in the URL path contains an extension (e.g. .jsp), the servlet container will try to match a servlet that
-     *     handles requests for the extension. An extension is defined as the part of the last segment after the last '.' character.</li>
-     * </ul>
-     *
-     * @param path the path that should match, cannot be <code>null</code>.
-     * @return the best matching handler for the given path, or <code>null</code> in case no handler matched.
-     */
-    FilterHandler getBestMatch(String path)
-    {
-        List<FilterHandler> allMatches = getAllMatches(path, true /* firstOnly */);
-        return allMatches.isEmpty() ? null : allMatches.get(0);
-    }
-
-    /**
-     * Returns the (first) handler identified by the given name.
-     *
-     * @param name the name of the handler to return, can be <code>null</code> in which case this method will return <code>null</code>.
-     * @return the element with the given name, or <code>null</code> if not found, or the given argument was <code>null</code>.
-     */
-    FilterHandler getByName(String name)
-    {
-        if (name == null)
-        {
-            return null;
-        }
-
-        for (FilterHandler element : this.mappedHandlers)
-        {
-            if (name.equals(element.getName()))
-            {
-                return element;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Provides information on whether there are elements mapped or not.
-     *
-     * @return <code>false</code> if there is at least one element mapped, <code>true</code> otherwise.
-     */
-    boolean isEmpty()
-    {
-        return this.mappedHandlers.isEmpty();
-    }
-
-    /**
      * Performs the actual matching, yielding a list of either the first or all matching patterns.
      *
      * @param path the path to match, can be <code>null</code> in which case an empty string is
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PerContextHandlerRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PerContextHandlerRegistry.java
index a5888c7..c71e1c2 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PerContextHandlerRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PerContextHandlerRegistry.java
@@ -96,12 +96,6 @@
     @Override
     public int compareTo(@Nonnull final PerContextHandlerRegistry other)
     {
-        // the context of the HttpService is the least element
-        if (this.serviceId == 0 ^ other.serviceId == 0)
-        {
-            return this.serviceId == 0 ? -1 : 1;
-        }
-
         final int result = Integer.compare(other.path.length(), this.path.length());
         if ( result == 0 ) {
             if (this.ranking == other.ranking)
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistryTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistryTest.java
index f4cc50f..62dac22 100644
--- a/http/base/src/test/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistryTest.java
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/registry/ErrorPageRegistryTest.java
@@ -44,6 +44,7 @@
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.runtime.dto.DTOConstants;
 import org.osgi.service.http.runtime.dto.ServletContextDTO;
 import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
 
@@ -67,7 +68,7 @@
         holder.failedErrorPageDTOs.clear();
     }
 
-    @Test public void testSingleServlet() throws InvalidSyntaxException, ServletException
+    @Test public void testSingleErrorPage() throws InvalidSyntaxException, ServletException
     {
         final FailedDTOHolder holder = new FailedDTOHolder();
         final ServletContextDTO dto = new ServletContextDTO();
@@ -98,12 +99,11 @@
         reg.getRuntimeInfo(dto, holder.failedErrorPageDTOs);
         assertNull(dto.resourceDTOs);
         assertNull(dto.servletDTOs);
-        assertTrue(holder.failedResourceDTOs.isEmpty());
-        assertTrue(holder.failedServletDTOs.isEmpty());
         assertNotNull(dto.errorPageDTOs);
         assertEquals(1, dto.errorPageDTOs.length);
         assertEquals(1, dto.errorPageDTOs[0].errorCodes.length);
         assertEquals(404, dto.errorPageDTOs[0].errorCodes[0]);
+        assertTrue(holder.failedErrorPageDTOs.isEmpty());
 
         // test error handling
         assertNotNull(reg.get(new IOException(), 404));
@@ -125,6 +125,104 @@
         assertEmpty(dto, holder);
     }
 
+    @Test public void testSimpleHiding() throws InvalidSyntaxException, ServletException
+    {
+        final FailedDTOHolder holder = new FailedDTOHolder();
+        final ServletContextDTO dto = new ServletContextDTO();
+
+        final Map<ServletInfo, ErrorPageRegistry.ErrorRegistrationStatus> status = reg.getStatusMapping();
+        // empty reg
+        assertEquals(0, status.size());
+        // check DTO
+        reg.getRuntimeInfo(dto, holder.failedErrorPageDTOs);
+        assertEmpty(dto, holder);
+
+        // register error pages
+        final ServletHandler h1 = createServletHandler(1L, 0, "404", "java.io.IOException");
+        reg.addServlet(h1);
+        final ServletHandler h2 = createServletHandler(2L, 10, "404", "some.other.Exception");
+        reg.addServlet(h2);
+
+        verify(h1.getServlet()).init(Matchers.any(ServletConfig.class));
+        verify(h2.getServlet()).init(Matchers.any(ServletConfig.class));
+
+        // two entries in reg
+        assertEquals(2, status.size());
+        assertNotNull(status.get(h1.getServletInfo()));
+        assertEquals(1, status.get(h1.getServletInfo()).exceptionMapping.size());
+        assertEquals(-1, (int)status.get(h1.getServletInfo()).exceptionMapping.get("java.io.IOException"));
+        assertEquals(1, status.get(h1.getServletInfo()).errorCodeMapping.size());
+        assertEquals(DTOConstants.FAILURE_REASON_SHADOWED_BY_OTHER_SERVICE, (int)status.get(h1.getServletInfo()).errorCodeMapping.get(404L));
+        assertNotNull(status.get(h2.getServletInfo()));
+        assertEquals(1, status.get(h2.getServletInfo()).exceptionMapping.size());
+        assertEquals(-1, (int)status.get(h2.getServletInfo()).exceptionMapping.get("some.other.Exception"));
+        assertEquals(1, status.get(h2.getServletInfo()).errorCodeMapping.size());
+        assertEquals(-1, (int)status.get(h2.getServletInfo()).errorCodeMapping.get(404L));
+
+        // check DTO
+        clear(dto, holder);
+        reg.getRuntimeInfo(dto, holder.failedErrorPageDTOs);
+        assertNull(dto.resourceDTOs);
+        assertNull(dto.servletDTOs);
+        assertNotNull(dto.errorPageDTOs);
+        assertEquals(2, dto.errorPageDTOs.length);
+        assertEquals(0, dto.errorPageDTOs[0].errorCodes.length);
+        assertEquals(1, dto.errorPageDTOs[1].errorCodes.length);
+        assertEquals(404, dto.errorPageDTOs[1].errorCodes[0]);
+        assertEquals(1, dto.errorPageDTOs[0].exceptions.length);
+        assertEquals(1, dto.errorPageDTOs[1].exceptions.length);
+        assertEquals("java.io.IOException", dto.errorPageDTOs[0].exceptions[0]);
+        assertEquals("some.other.Exception", dto.errorPageDTOs[1].exceptions[0]);
+        assertEquals(1, holder.failedErrorPageDTOs.size());
+        assertEquals(1L, holder.failedErrorPageDTOs.iterator().next().serviceId);
+        assertEquals(1, holder.failedErrorPageDTOs.iterator().next().errorCodes.length);
+        assertEquals(404, holder.failedErrorPageDTOs.iterator().next().errorCodes[0]);
+        assertEquals(0, holder.failedErrorPageDTOs.iterator().next().exceptions.length);
+
+        // remove second page
+        final Servlet s2 = h2.getServlet();
+        reg.removeServlet(h2.getServletInfo(), true);
+        verify(s2).destroy();
+
+        // one entry in reg
+        assertEquals(1, status.size());
+        assertNotNull(status.get(h1.getServletInfo()));
+        assertEquals(1, status.get(h1.getServletInfo()).exceptionMapping.size());
+        assertEquals(-1, (int)status.get(h1.getServletInfo()).exceptionMapping.get("java.io.IOException"));
+        assertEquals(1, status.get(h1.getServletInfo()).errorCodeMapping.size());
+        assertEquals(-1, (int)status.get(h1.getServletInfo()).errorCodeMapping.get(404L));
+
+        // check DTO
+        clear(dto, holder);
+        reg.getRuntimeInfo(dto, holder.failedErrorPageDTOs);
+        assertNull(dto.resourceDTOs);
+        assertNull(dto.servletDTOs);
+        assertNotNull(dto.errorPageDTOs);
+        assertEquals(1, dto.errorPageDTOs.length);
+        assertEquals(1, dto.errorPageDTOs[0].errorCodes.length);
+        assertEquals(404, dto.errorPageDTOs[0].errorCodes[0]);
+        assertTrue(holder.failedErrorPageDTOs.isEmpty());
+
+        // test error handling
+        assertNotNull(reg.get(new IOException(), 404));
+        assertNotNull(reg.get(new RuntimeException(), 404));
+        assertNotNull(reg.get(new IOException(), 500));
+        assertNotNull(reg.get(new FileNotFoundException(), 500));
+        assertNull(reg.get(new RuntimeException(), 500));
+
+        // remove first page
+        final Servlet s1 = h1.getServlet();
+        reg.removeServlet(h1.getServletInfo(), true);
+        verify(s1).destroy();
+
+        // empty again
+        assertEquals(0, status.size());
+        // check DTO
+        clear(dto, holder);
+        reg.getRuntimeInfo(dto, holder.failedErrorPageDTOs);
+        assertEmpty(dto, holder);
+    }
+
     private static ServletInfo createServletInfo(final long id, final int ranking, final String... codes) throws InvalidSyntaxException
     {
         final BundleContext bCtx = mock(BundleContext.class);