FELIX-4551 : Add support for multiple patterns in servlet/filter registration. Apply patch from Thomas Baier

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1673686 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRegistry.java
index 7d7e5d0..ab8daa9 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ServletHandlerRegistry.java
@@ -48,50 +48,43 @@
 import org.apache.felix.http.base.internal.util.PatternUtil.PatternComparator;
 import org.apache.felix.http.base.internal.whiteboard.RegistrationFailureException;
 
-public final class ServletHandlerRegistry
+final class ServletHandlerRegistry
 {
-    private volatile HandlerMapping<ServletHandler> servletMapping = new HandlerMapping<ServletHandler>();
     private final HandlerRankingMultimap<String> registeredServletHandlers;
     private final SortedSet<ServletHandler> allServletHandlers = new TreeSet<ServletHandler>();
 
-    private final ContextServletHandlerComparator handlerComparator;
+    private volatile ContextRegistry contextRegistry;
 
-    public ServletHandlerRegistry()
+    ServletHandlerRegistry()
     {
-        PatternComparator keyComparator = PatternComparator.INSTANCE;
-        handlerComparator = new ContextServletHandlerComparator();
-        this.registeredServletHandlers = new HandlerRankingMultimap<String>(null, handlerComparator);
+        this.contextRegistry = new ContextRegistry();
+        this.registeredServletHandlers = new HandlerRankingMultimap<String>(null, new ServletHandlerComparator());
     }
 
     /**
      * Register default context registry for Http Service
      */
-    public void init()
+    synchronized void init()
     {
-        handlerComparator.contextRankings.put(0L, new ContextRanking());
+        contextRegistry = contextRegistry.add(0L, new ContextRanking());
     }
 
-    public void shutdown()
+    synchronized void shutdown()
     {
         removeAll();
     }
 
-    public void add(@Nonnull ServletContextHelperInfo info)
+    synchronized void add(@Nonnull ServletContextHelperInfo info)
     {
-        handlerComparator.contextRankings.put(info.getServiceId(), new ContextRanking(info));
+        contextRegistry = contextRegistry.add(info);
     }
 
-    public void remove(@Nonnull ServletContextHelperInfo info)
+    synchronized void remove(@Nonnull ServletContextHelperInfo info)
     {
-        handlerComparator.contextRankings.remove(info.getServiceId());
+        contextRegistry = contextRegistry.remove(info);
     }
 
-    public ServletHandler getServletHandlerByName(final Long contextId, @Nonnull final String name)
-    {
-        return servletMapping.getByName(name);
-    }
-
-    public synchronized void addServlet(final ServletHandler handler) throws RegistrationFailureException
+    synchronized void addServlet(final ServletHandler handler) throws RegistrationFailureException
     {
         if (this.allServletHandlers.contains(handler))
         {
@@ -106,7 +99,7 @@
 
     private void registerServlet(ServletHandler handler) throws RegistrationFailureException
     {
-        String contextPath = handlerComparator.getPath(handler.getContextServiceId());
+        String contextPath = contextRegistry.getPath(handler.getContextServiceId());
         if (contextPath == null)
         {
             throw new RegistrationFailureException(handler.getServletInfo(), FAILURE_REASON_SERVLET_CONTEXT_FAILURE);
@@ -119,26 +112,15 @@
         }
         Update<String> update = this.registeredServletHandlers.add(patterns, handler);
         initHandlers(update.getInit());
-        this.servletMapping = this.servletMapping.update(convert(update.getActivated()), convert(update.getDeactivated()));
+        contextRegistry = contextRegistry.updateServletMapping(update, handler.getContextServiceId());
         destroyHandlers(update.getDestroy());
     }
 
-    private Map<Pattern, ServletHandler> convert(Map<String, ServletHandler> mapping)
+    synchronized void removeAll()
     {
-        TreeMap<Pattern, ServletHandler> converted = new TreeMap<Pattern, ServletHandler>(PatternComparator.INSTANCE);
-        for (Map.Entry<String, ServletHandler> entry : mapping.entrySet())
-        {
-            Pattern pattern = Pattern.compile(PatternUtil.convertToRegEx(entry.getKey()));
-            converted.put(pattern, entry.getValue());
-        }
-        return converted;
-    }
+        Collection<ServletHandler> servletHandlers = this.contextRegistry.getServletHandlers();
 
-    public synchronized void removeAll()
-    {
-        Collection<ServletHandler> servletHandlers = this.servletMapping.values();
-
-        this.servletMapping = new HandlerMapping<ServletHandler>();
+        this.contextRegistry = new ContextRegistry();
 
         destroyHandlers(servletHandlers);
 
@@ -151,12 +133,12 @@
         return removeServlet(0L, servletInfo, true);
     }
 
-    public synchronized Servlet removeServlet(Long contextId, ServletInfo servletInfo) throws RegistrationFailureException
+    synchronized Servlet removeServlet(Long contextId, ServletInfo servletInfo) throws RegistrationFailureException
     {
         return removeServlet(contextId, servletInfo, true);
     }
 
-    public synchronized Servlet removeServlet(Long contextId, ServletInfo servletInfo, final boolean destroy) throws RegistrationFailureException
+    synchronized Servlet removeServlet(Long contextId, ServletInfo servletInfo, final boolean destroy) throws RegistrationFailureException
     {
         ServletHandler handler = getServletHandler(servletInfo);
         if (handler == null)
@@ -177,6 +159,20 @@
         return servlet;
     }
 
+    private ServletHandler getServletHandler(final ServletInfo servletInfo)
+    {
+        Iterator<ServletHandler> it = this.allServletHandlers.iterator();
+        while (it.hasNext())
+        {
+            ServletHandler handler = it.next();
+            if (handler.getServletInfo().compareTo(servletInfo) == 0)
+            {
+                return handler;
+            }
+        }
+        return null;
+    }
+
     synchronized void removeServlet(Servlet servlet, final boolean destroy) throws RegistrationFailureException
     {
         Iterator<ServletHandler> it = this.allServletHandlers.iterator();
@@ -198,7 +194,7 @@
 
     private void removeServlet(ServletHandler handler, boolean destroy) throws RegistrationFailureException
     {
-        String contextPath = handlerComparator.getPath(handler.getContextServiceId());
+        String contextPath = contextRegistry.getPath(handler.getContextServiceId());
         contextPath = contextPath.equals("/") ? "" : contextPath;
         List<String> patterns = new ArrayList<String>(asList(handler.getServletInfo().getPatterns()));
         for (int i = 0; i < patterns.size(); i++)
@@ -207,7 +203,7 @@
         }
         Update<String> update = this.registeredServletHandlers.remove(patterns, handler);
         initHandlers(update.getInit());
-        this.servletMapping = this.servletMapping.update(convert(update.getActivated()), convert(update.getDeactivated()));
+        contextRegistry = contextRegistry.updateServletMapping(update, handler.getContextServiceId());
         if (destroy)
         {
             destroyHandlers(update.getDestroy());
@@ -239,32 +235,18 @@
         }
     }
 
-    public ServletHandler getServletHandler(String requestURI)
+    ServletHandler getServletHandler(String requestURI)
     {
-        return this.servletMapping.getBestMatch(requestURI);
+        return contextRegistry.getServletHandler(requestURI);
     }
 
-    public ServletHandler getServletHandlerByName(String name)
+    ServletHandler getServletHandlerByName(final Long contextId, @Nonnull final String name)
     {
-        return this.servletMapping.getByName(name);
+        HandlerMapping<ServletHandler> servletMapping = contextRegistry.getServletMapping(contextId);
+        return servletMapping != null ? servletMapping.getByName(name) : null;
     }
 
-    private ServletHandler getServletHandler(final ServletInfo servletInfo)
-    {
-        Iterator<ServletHandler> it = this.allServletHandlers.iterator();
-        while (it.hasNext())
-        {
-            ServletHandler handler = it.next();
-            if (handler.getServletInfo().compareTo(servletInfo) == 0)
-            {
-                return handler;
-            }
-        }
-        return null;
-    }
-
-
-    public synchronized ServletRegistryRuntime getRuntime(FailureRuntime.Builder failureRuntimeBuilder)
+    synchronized ServletRegistryRuntime getRuntime(FailureRuntime.Builder failureRuntimeBuilder)
     {
         addShadowedHandlers(failureRuntimeBuilder, this.registeredServletHandlers.getShadowedValues());
 
@@ -292,23 +274,146 @@
         }
     }
 
-    private static class ContextServletHandlerComparator implements Comparator<ServletHandler>
+    private class ServletHandlerComparator implements Comparator<ServletHandler>
     {
-        private final Map<Long, ContextRanking> contextRankings = new HashMap<Long, ContextRanking>();
-
         @Override
         public int compare(ServletHandler o1, ServletHandler o2)
         {
-            ContextRanking contextRankingOne = contextRankings.get(o1.getContextServiceId());
-            ContextRanking contextRankingTwo = contextRankings.get(o2.getContextServiceId());
+            ContextRanking contextRankingOne = contextRegistry.getContextRanking(o1.getContextServiceId());
+            ContextRanking contextRankingTwo = contextRegistry.getContextRanking(o2.getContextServiceId());
             int contextComparison = contextRankingOne.compareTo(contextRankingTwo);
             return contextComparison == 0 ? o1.compareTo(o2) : contextComparison;
         }
+    }
 
-       String getPath(long contextId)
-       {
-           return contextRankings.get(contextId).path;
-       }
+    private static class ContextRegistry
+    {
+        private final Map<Long, ContextRanking> contextRankingsPerId;
+        private final TreeSet<ContextRanking> contextRankings;
+        private final Map<Long, HandlerMapping<ServletHandler>> servletMappingsPerContext;
+
+        ContextRegistry()
+        {
+            this(new HashMap<Long, ContextRanking>(),
+                new TreeSet<ContextRanking>(),
+                new HashMap<Long, HandlerMapping<ServletHandler>>());
+        }
+
+        ContextRegistry(Map<Long, ContextRanking> contextRankingsPerId, TreeSet<ContextRanking> contextRankings, Map<Long, HandlerMapping<ServletHandler>> servletMappingsPerContext)
+        {
+            this.contextRankingsPerId = contextRankingsPerId;
+            this.contextRankings = contextRankings;
+            this.servletMappingsPerContext = servletMappingsPerContext;
+        }
+
+        ContextRanking getContextRanking(long contextServiceId)
+        {
+            return contextRankingsPerId.get(contextServiceId);
+        }
+
+        ServletHandler getServletHandler(String requestURI)
+        {
+            List<Long> contextIds = getContextId(requestURI);
+            for (Long contextId : contextIds)
+            {
+                HandlerMapping<ServletHandler> servletMapping = this.servletMappingsPerContext.get(contextId);
+                if (servletMapping != null)
+                {
+                    ServletHandler bestMatch = servletMapping.getBestMatch(requestURI);
+                    if (bestMatch != null)
+                    {
+                        return bestMatch;
+                    }
+                }
+            }
+            return null;
+        }
+
+        HandlerMapping<ServletHandler> getServletMapping(Long contextId)
+        {
+            return servletMappingsPerContext.get(contextId);
+        }
+
+        ContextRegistry add(long id, ContextRanking contextRanking)
+        {
+            Map<Long, ContextRanking> newContextRankingsPerId = new HashMap<Long, ContextRanking>(contextRankingsPerId);
+            TreeSet<ContextRanking> newContextRankings = new TreeSet<ContextRanking>(contextRankings);
+            Map<Long, HandlerMapping<ServletHandler>> newServletMappingsPerContext = new HashMap<Long, HandlerMapping<ServletHandler>>(servletMappingsPerContext);
+            newContextRankingsPerId.put(id, contextRanking);
+            newContextRankings.add(contextRanking);
+            newServletMappingsPerContext.put(id, new HandlerMapping<ServletHandler>());
+
+            return new ContextRegistry(newContextRankingsPerId, newContextRankings, newServletMappingsPerContext);
+        }
+
+        ContextRegistry add(ServletContextHelperInfo info)
+        {
+            return add(info.getServiceId(), new ContextRanking(info));
+        }
+
+        ContextRegistry remove(ServletContextHelperInfo info)
+        {
+            Map<Long, ContextRanking> newContextRankingsPerId = new HashMap<Long, ContextRanking>(contextRankingsPerId);
+            TreeSet<ContextRanking> newContextRankings = new TreeSet<ContextRanking>(contextRankings);
+            Map<Long, HandlerMapping<ServletHandler>> newServletMappingsPerContext = new HashMap<Long, HandlerMapping<ServletHandler>>(servletMappingsPerContext);
+            newContextRankingsPerId.remove(info.getServiceId());
+            newContextRankings.remove(new ContextRanking(info));
+            newServletMappingsPerContext.remove(info.getServiceId());
+
+            return new ContextRegistry(newContextRankingsPerId, newContextRankings, newServletMappingsPerContext);
+        }
+
+        String getPath(long contextId)
+        {
+            return contextRankingsPerId.get(contextId).path;
+        }
+
+        private List<Long> getContextId(String path)
+        {
+            List<Long> ids = new ArrayList<Long>();
+            for (ContextRanking contextRanking : contextRankings)
+            {
+                if (contextRanking.isMatching(path))
+                {
+                    ids.add(contextRanking.serviceId);
+                }
+            }
+            return ids;
+        }
+
+        ContextRegistry updateServletMapping(Update<String> update, long contextId)
+        {
+            Map<Long, HandlerMapping<ServletHandler>> newServletMappingsPerContext = new HashMap<Long, HandlerMapping<ServletHandler>>(servletMappingsPerContext);
+            HandlerMapping<ServletHandler> servletMapping = newServletMappingsPerContext.get(contextId);
+            if (servletMapping == null)
+            {
+                servletMapping = new HandlerMapping<ServletHandler>();
+            }
+            newServletMappingsPerContext.put(contextId, servletMapping.update(convert(update.getActivated()), convert(update.getDeactivated())));
+            return new ContextRegistry(contextRankingsPerId, contextRankings, newServletMappingsPerContext);
+        }
+
+        private Map<Pattern, ServletHandler> convert(Map<String, ServletHandler> mapping)
+        {
+            TreeMap<Pattern, ServletHandler> converted = new TreeMap<Pattern, ServletHandler>(PatternComparator.INSTANCE);
+            for (Map.Entry<String, ServletHandler> entry : mapping.entrySet())
+            {
+                Pattern pattern = Pattern.compile(PatternUtil.convertToRegEx(entry.getKey()));
+                converted.put(pattern, entry.getValue());
+            }
+            return converted;
+        }
+
+        Collection<ServletHandler> getServletHandlers()
+        {
+            Collection<ServletHandler> servletHandlers = new ArrayList<ServletHandler>();
+            for (HandlerMapping<ServletHandler> servletMapping : this.servletMappingsPerContext.values())
+            {
+                servletHandlers.addAll(servletMapping.values());
+            }
+
+            return servletHandlers;
+        }
     }
 
     // TODO combine with PerContextHandlerRegistry
@@ -354,5 +459,10 @@
             }
             return result;
         }
+
+        boolean isMatching(final String requestURI)
+        {
+            return "".equals(path) || "/".equals(path) || requestURI.startsWith(path);
+        }
     }
 }
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/ServletPatternTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/ServletPatternTest.java
index 931d203..49657b1 100644
--- a/http/itest/src/test/java/org/apache/felix/http/itest/ServletPatternTest.java
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/ServletPatternTest.java
@@ -19,17 +19,23 @@
 
 package org.apache.felix.http.itest;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.osgi.framework.Constants.SERVICE_RANKING;
 import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME;
 import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH;
 import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT;
-import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_INIT_PARAM_PREFIX;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_RESOURCE_PATTERN;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_RESOURCE_PREFIX;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME;
 import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -37,109 +43,107 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.felix.http.itest.HttpServiceRuntimeTest.TestResource;
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.ExamReactorStrategy;
 import org.ops4j.pax.exam.junit.JUnit4TestRunner;
-import org.osgi.framework.Constants;
+import org.ops4j.pax.exam.spi.reactors.EagerSingleStagedReactorFactory;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.http.context.ServletContextHelper;
 
 @RunWith(JUnit4TestRunner.class)
+@ExamReactorStrategy( EagerSingleStagedReactorFactory.class )
 public class ServletPatternTest extends BaseIntegrationTest
 {
+    private List<ServiceRegistration<?>> registrations = new ArrayList<ServiceRegistration<?>>();
+
+    private CountDownLatch initLatch;
+    private CountDownLatch destroyLatch;
+
+    public void setupLatches(int count)
+    {
+        initLatch = new CountDownLatch(count);
+        destroyLatch = new CountDownLatch(count);
+    }
+
+    public void setupServlet(final String name, String[] path, int rank, String context) throws Exception
+    {
+        Dictionary<String, Object> servletProps = new Hashtable<String, Object>();
+        servletProps.put(HTTP_WHITEBOARD_SERVLET_NAME, name);
+        servletProps.put(HTTP_WHITEBOARD_SERVLET_PATTERN, path);
+        servletProps.put(SERVICE_RANKING, rank);
+        if (context != null)
+        {
+            servletProps.put(HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=" + context + ")");
+        }
+
+        TestServlet servletWithErrorCode = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws IOException
+            {
+                resp.getWriter().print(name);
+                resp.flushBuffer();
+            }
+        };
+
+        registrations.add(m_context.registerService(Servlet.class.getName(), servletWithErrorCode, servletProps));
+    }
+
+    private void setupContext(String name, String path) throws InterruptedException
+    {
+        Dictionary<String, ?> properties = createDictionary(
+                HTTP_WHITEBOARD_CONTEXT_NAME, name,
+                HTTP_WHITEBOARD_CONTEXT_PATH, path);
+
+        ServletContextHelper servletContextHelper = new ServletContextHelper(m_context.getBundle()){
+            // test helper
+        };
+        registrations.add(m_context.registerService(ServletContextHelper.class.getName(), servletContextHelper, properties));
+
+        Thread.sleep(500);
+    }
+
+    @After
+    public void unregisterServices() throws InterruptedException
+    {
+        for (ServiceRegistration<?> serviceRegistration : registrations)
+        {
+            serviceRegistration.unregister();
+        }
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+        Thread.sleep(500);
+    }
 
     @Test
     public void testHighRankReplaces() throws Exception
     {
-        CountDownLatch initLatch = new CountDownLatch(2);
-        CountDownLatch destroyLatch = new CountDownLatch(2);
+        setupLatches(2);
 
-        TestServlet lowRankServlet = new TestServlet(initLatch, destroyLatch)
-        {
-            private static final long serialVersionUID = 1L;
+        setupServlet("lowRankServlet", new String[] { "/foo", "/bar" }, 1, null);
+        setupServlet("highRankServlet", new String[] { "/foo", "/baz" }, 2, null);
 
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-                throws IOException
-            {
-                resp.getWriter().print("lowRankServlet");
-                resp.flushBuffer();
-            }
-        };
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
 
-        TestServlet highRankServlet = new TestServlet(initLatch, destroyLatch)
-        {
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp)
-                throws IOException
-            {
-                resp.getWriter().print("highRankServlet");
-                resp.flushBuffer();
-            }
-        };
-
-        Dictionary<String, Object> lowRankProps = new Hashtable<String, Object>();
-        String lowRankPattern[] = { "/foo", "/bar" };
-        lowRankProps.put(HTTP_WHITEBOARD_SERVLET_PATTERN, lowRankPattern);
-        lowRankProps.put(HTTP_WHITEBOARD_FILTER_INIT_PARAM_PREFIX + ".myname", "lowRankServlet");
-        lowRankProps.put(SERVICE_RANKING, 1);
-
-        ServiceRegistration<?> lowRankReg = m_context.registerService(Servlet.class.getName(),
-            lowRankServlet, lowRankProps);
-
-        Dictionary<String, Object> highRankProps = new Hashtable<String, Object>();
-        String highRankPattern[] = { "/foo", "/baz" };
-        highRankProps.put(HTTP_WHITEBOARD_SERVLET_PATTERN, highRankPattern);
-        highRankProps.put(HTTP_WHITEBOARD_FILTER_INIT_PARAM_PREFIX + ".myname", "highRankServlet");
-        highRankProps.put(Constants.SERVICE_RANKING, 2);
-
-        ServiceRegistration<?> highRankReg = m_context.registerService(Servlet.class.getName(),
-            highRankServlet, highRankProps);
-
-        try
-        {
-            assertTrue(initLatch.await(5, TimeUnit.SECONDS));
-
-            assertContent("highRankServlet", createURL("/foo"));
-            assertContent("lowRankServlet", createURL("/bar"));
-            assertContent("highRankServlet", createURL("/baz"));
-
-        }
-        finally
-        {
-            lowRankReg.unregister();
-            highRankReg.unregister();
-        }
-
-        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+        assertContent("highRankServlet", createURL("/foo"));
+        assertContent("lowRankServlet", createURL("/bar"));
+        assertContent("highRankServlet", createURL("/baz"));
     }
 
     @Test
     public void testHttpServiceReplaces() throws Exception
     {
-        Dictionary<String, ?> properties = createDictionary(
-            HTTP_WHITEBOARD_CONTEXT_NAME, "test",
-            HTTP_WHITEBOARD_CONTEXT_PATH, "/test");
+        setupLatches(2);
 
-        ServletContextHelper servletContextHelper = new ServletContextHelper(m_context.getBundle()) {};
-        m_context.registerService(ServletContextHelper.class.getName(), servletContextHelper, properties);
-
-        CountDownLatch initLatch = new CountDownLatch(2);
-        CountDownLatch destroyLatch = new CountDownLatch(2);
-
-        TestServlet whiteboardServlet = new TestServlet(initLatch, destroyLatch)
-        {
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
-            {
-                resp.getWriter().print("whiteboardServlet");
-                resp.flushBuffer();
-            }
-        };
+        setupContext("contextA", "/test");
+        setupServlet("whiteboardServlet", new String[]{ "/foo", "/bar" }, Integer.MAX_VALUE, "contextA");
 
         TestServlet httpServiceServlet = new TestServlet(initLatch, destroyLatch)
         {
@@ -153,13 +157,6 @@
             }
         };
 
-        String whiteboardPattern[] = { "/foo", "/bar" };
-        Dictionary<String, Object> whiteboardProps = new Hashtable<String, Object>();
-        whiteboardProps.put(HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=test)");
-        whiteboardProps.put(HTTP_WHITEBOARD_SERVLET_PATTERN, whiteboardPattern);
-        whiteboardProps.put(SERVICE_RANKING, Integer.MAX_VALUE);
-        ServiceRegistration<?> serviceRegistration = m_context.registerService(Servlet.class.getName(), whiteboardServlet, whiteboardProps);
-
         register("/test/foo", httpServiceServlet);
 
         try
@@ -171,7 +168,6 @@
         }
         finally
         {
-            serviceRegistration.unregister();
             unregister(httpServiceServlet);
         }
     }
@@ -179,66 +175,86 @@
     @Test
     public void testSameRankDoesNotReplace() throws Exception
     {
-        CountDownLatch initLatch = new CountDownLatch(2);
-        CountDownLatch destroyLatch = new CountDownLatch(2);
+        setupLatches(2);
 
-        TestServlet servlet1 = new TestServlet(initLatch, destroyLatch)
-        {
-            private static final long serialVersionUID = 1L;
+        setupServlet("servlet1", new String[]{ "/foo", "/bar" }, 2, null);
+        setupServlet("servlet2", new String[]{ "/foo", "/baz" }, 2, null);
 
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
-            {
-                resp.getWriter().print("servlet1");
-                resp.flushBuffer();
-            }
-        };
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
 
-        TestServlet servlet2 = new TestServlet(initLatch, destroyLatch)
-        {
-            private static final long serialVersionUID = 1L;
+        assertContent("servlet1", createURL("/foo"));
+        assertContent("servlet1", createURL("/bar"));
+        assertContent("servlet2", createURL("/baz"));
+    }
 
-            @Override
-            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
-            {
-                resp.getWriter().print("servlet2");
-                resp.flushBuffer();
-            }
-        };
+    @Test
+    public void testHighRankResourceReplaces() throws Exception
+    {
+        setupLatches(1);
 
-        Dictionary<String, Object> props1 = new Hashtable<String, Object>();
-        String lowRankPattern[] = { "/foo", "/bar" };
-        props1.put(HTTP_WHITEBOARD_SERVLET_PATTERN, lowRankPattern);
-        props1.put(HTTP_WHITEBOARD_FILTER_INIT_PARAM_PREFIX + ".myname", "lowRankServlet");
-        props1.put(SERVICE_RANKING, 2);
+        setupServlet("lowRankServlet", new String[]{ "/foo" }, 1, null);
 
-        ServiceRegistration<?> reg1 = m_context.registerService(Servlet.class.getName(),
-            servlet1, props1);
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+        assertContent("lowRankServlet", createURL("/foo"));
 
-        Dictionary<String, Object> props2 = new Hashtable<String, Object>();
-        String highRankPattern[] = { "/foo", "/baz" };
-        props2.put(HTTP_WHITEBOARD_SERVLET_PATTERN, highRankPattern);
-        props2.put(HTTP_WHITEBOARD_FILTER_INIT_PARAM_PREFIX + ".myname", "highRankServlet");
-        props2.put(SERVICE_RANKING, 2);
+        Dictionary<String, Object> resourceProps = new Hashtable<String, Object>();
+        String highRankPattern[] = { "/foo" };
+        resourceProps.put(HTTP_WHITEBOARD_RESOURCE_PATTERN, highRankPattern);
+        resourceProps.put(HTTP_WHITEBOARD_RESOURCE_PREFIX, "/resource/test.html");
+        resourceProps.put(SERVICE_RANKING, 2);
 
-        ServiceRegistration<?> reg2 = m_context.registerService(Servlet.class.getName(),
-            servlet2, props2);
-
-        try
-        {
-            assertTrue(initLatch.await(5, TimeUnit.SECONDS));
-
-            assertContent("servlet1", createURL("/foo"));
-            assertContent("servlet1", createURL("/bar"));
-            assertContent("servlet2", createURL("/baz"));
-
-        }
-        finally
-        {
-            reg1.unregister();
-            reg2.unregister();
-        }
+        registrations.add(m_context.registerService(TestResource.class.getName(),
+            new TestResource(), resourceProps));
 
         assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+        Thread.sleep(500);
+
+        assertContent(getTestHtmlContent(), createURL("/foo"));
+    }
+
+    private String getTestHtmlContent() throws IOException
+    {
+        InputStream resourceAsStream = this.getClass().getResourceAsStream("/resource/test.html");
+        return slurpAsString(resourceAsStream);
+    }
+
+    @Test
+    public void contextWithLongerPrefixIsChosen() throws Exception
+    {
+        setupLatches(2);
+
+        setupContext("contextA", "/a");
+        setupContext("contextB", "/a/b");
+
+        setupServlet("servlet1", new String[]{ "/b/test" }, 1, "contextA");
+
+        Thread.sleep(500);
+        assertEquals(1, initLatch.getCount());
+        assertContent("servlet1", createURL("/a/b/test"));
+
+        setupServlet("servlet2", new String[]{ "/test" }, 1, "contextB");
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+        assertContent("servlet2", createURL("/a/b/test"));
+    }
+
+    @Test
+    public void contextWithLongerPrefixIsChosenWithWildcard() throws Exception
+    {
+        setupLatches(2);
+
+        setupContext("contextA", "/a");
+        setupContext("contextB", "/a/b");
+
+        setupServlet("servlet1", new String[]{ "/b/test/servlet" }, 1, "contextA");
+
+        Thread.sleep(500);
+        assertEquals(1, initLatch.getCount());
+        assertContent("servlet1", createURL("/a/b/test/servlet"));
+
+        setupServlet("servlet2", new String[]{ "/test/*" }, 1, "contextB");
+
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+        assertContent("servlet2", createURL("/a/b/test/servlet"));
     }
 }