FELIX-4893 : Replace filter registry with path resolvers

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1680752 13f79535-47bb-0310-9956-ffa450edef68
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
deleted file mode 100644
index 4e25065..0000000
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterHandlerMapping.java
+++ /dev/null
@@ -1,270 +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.registry;
-
-import static java.util.Collections.unmodifiableCollection;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.annotation.Nonnull;
-
-import org.apache.felix.http.base.internal.handler.FilterHandler;
-import org.apache.felix.http.base.internal.util.PatternUtil;
-import org.apache.felix.http.base.internal.util.PatternUtil.PatternComparator;
-
-/**
- * Represents a Map-like structure that can map path-patterns to servlet/filter handlers, allowing
- * for easy access to those handlers, based on the match rules defined in section 12.1 of Servlet
- * 3.0 specification.
- * <p>
- * {@link FilterHandlerMapping} instances are immutable.
- *
- * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
- */
-public final class FilterHandlerMapping
-{
-    private final SortedMap<Pattern, Set<FilterHandler>> exactMap;
-    private final SortedMap<Pattern, Set<FilterHandler>> wildcardMap;
-    private final Set<FilterHandler> mappedHandlers;
-
-    /**
-     * Creates a new, empty, {@link FilterHandlerMapping} instance.
-     */
-    public FilterHandlerMapping()
-    {
-        this(Collections.<Pattern, Collection<FilterHandler>>emptyMap());
-    }
-
-    /**
-     * Creates a new {@link FilterHandlerMapping} instance for the given elements.
-     *
-     * @param mappings the elements to map.
-     */
-    private FilterHandlerMapping(@Nonnull final Map<Pattern, Collection<FilterHandler>> mappings)
-    {
-        this.exactMap = new TreeMap<Pattern, Set<FilterHandler>>(PatternComparator.INSTANCE);
-        this.wildcardMap = new TreeMap<Pattern, Set<FilterHandler>>(PatternComparator.INSTANCE);
-        this.mappedHandlers = new TreeSet<FilterHandler>();
-
-        for (Map.Entry<Pattern, Collection<FilterHandler>> mapping : mappings.entrySet())
-        {
-            Pattern pattern = mapping.getKey();
-            Collection<FilterHandler> handlers = mapping.getValue();
-
-            mappedHandlers.addAll(handlers);
-
-            if (PatternUtil.isWildcardPattern(pattern))
-            {
-                Set<FilterHandler> vs = this.wildcardMap.get(pattern);
-                if (vs == null)
-                {
-                    vs = new TreeSet<FilterHandler>();
-                    this.wildcardMap.put(pattern, vs);
-                }
-                vs.addAll(handlers);
-            }
-            else
-            {
-                Set<FilterHandler> vs = this.exactMap.get(pattern);
-                if (vs == null)
-                {
-                    vs = new TreeSet<FilterHandler>();
-                    this.exactMap.put(pattern, vs);
-                }
-                vs.addAll(handlers);
-            }
-        }
-    }
-
-    /**
-     * Returns a new {@link FilterHandlerMapping} instance with a mapping for the
-     * given handler.
-     *
-     * @param handler the handler to be added to the mapping.
-     * @return a new {@link FilterHandlerMapping} instance with a mapping for the
-     *         given handler.
-     */
-    public FilterHandlerMapping add(@Nonnull final FilterHandler handler)
-    {
-        final Map<Pattern, FilterHandler> mappings = new TreeMap<Pattern, FilterHandler>(PatternComparator.INSTANCE);
-        for (final Pattern pattern : handler.getPatterns())
-        {
-            mappings.put(pattern, handler);
-        }
-        return add(mappings);
-    }
-
-    private FilterHandlerMapping add(@Nonnull final Map<Pattern, FilterHandler> mappings)
-    {
-        final Map<Pattern, Collection<FilterHandler>> newMappings = getAllMappings();
-        addMappings(mappings, newMappings);
-        return new FilterHandlerMapping(newMappings);
-    }
-
-    /**
-     * Returns a new {@link FilterHandlerMapping} instance without a mapping for the
-     * given handler.
-     *
-     * @param subject the handled element to be removed from the mapping
-     * @return a new {@link FilterHandlerMapping} instance without a mapping for the
-     *         given handler.
-     */
-    public FilterHandlerMapping remove(FilterHandler handler)
-    {
-        Map<Pattern, FilterHandler> mappings = new TreeMap<Pattern, FilterHandler>(PatternComparator.INSTANCE);
-        for (Pattern pattern : handler.getPatterns())
-        {
-            mappings.put(pattern, handler);
-        }
-        return remove(mappings);
-    }
-
-    private FilterHandlerMapping remove(Map<Pattern, FilterHandler> mappings)
-    {
-        Map<Pattern, Collection<FilterHandler>> newMappings = getAllMappings();
-        removeMappings(mappings, 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())
-        {
-            if (!target.containsKey(mapping.getKey()))
-            {
-                target.put(mapping.getKey(), new TreeSet<FilterHandler>());
-            }
-            target.get(mapping.getKey()).add(mapping.getValue());
-        }
-    }
-
-    private void removeMappings(Map<Pattern, FilterHandler> mappings, Map<Pattern, Collection<FilterHandler>> target)
-    {
-        for (Map.Entry<Pattern, FilterHandler> mapping : mappings.entrySet())
-        {
-            Collection<FilterHandler> mappedHandlers = target.get(mapping.getKey());
-            if (mappedHandlers == null)
-            {
-                continue;
-            }
-            mappedHandlers.remove(mapping.getValue());
-            if (mappedHandlers.isEmpty())
-            {
-                target.remove(mapping.getKey());
-            }
-        }
-    }
-
-    private Map<Pattern, Collection<FilterHandler>> getAllMappings()
-    {
-        Map<Pattern, Collection<FilterHandler>> newMappings = new TreeMap<Pattern, Collection<FilterHandler>>(PatternComparator.INSTANCE);
-        newMappings.putAll(exactMap);
-        newMappings.putAll(wildcardMap);
-        return newMappings;
-    }
-
-    /**
-     * Returns all mapped handlers.
-     *
-     * @return the handlers contained in this mapping. The returned
-     *         <code>Collection</code> is unmodifiable and never
-     *         <code>null</code>.
-     */
-    public Collection<FilterHandler> values()
-    {
-        return unmodifiableCollection(mappedHandlers);
-    }
-
-    /**
-     * Returns all matching handlers for the given path.
-     *
-     * @param path the path that should match, cannot be <code>null</code>.
-     * @return a {@link Collection} of all matching handlers, never <code>null</code>.
-     */
-    public List<FilterHandler> getAllMatches(String path)
-    {
-        return getAllMatches(path, false /* firstOnly */);
-    }
-
-    /**
-     * 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
-     *        used;
-     * @param firstOnly <code>true</code> if only the first matching pattern should be returned,
-     *        <code>false</code> if all matching patterns should be returned.
-     * @return a list with matching elements, never <code>null</code>.
-     */
-    private List<FilterHandler> getAllMatches(String path, boolean firstOnly)
-    {
-        path = (path == null) ? "" : path.trim();
-
-        Set<FilterHandler> result = new TreeSet<FilterHandler>();
-        // Look for exact matches only, that is, those patterns without wildcards...
-        for (Entry<Pattern, Set<FilterHandler>> entry : this.exactMap.entrySet())
-        {
-            Matcher matcher = entry.getKey().matcher(path);
-            // !!! we should always match the *entire* pattern, instead of the longest prefix...
-            if (matcher.matches())
-            {
-                Set<FilterHandler> vs = entry.getValue();
-                for (FilterHandler v : vs)
-                {
-                    result.add(v);
-                    if (firstOnly)
-                    {
-                        return new ArrayList<FilterHandler>(result);
-                    }
-                }
-            }
-        }
-
-        // Try to apply the wildcard patterns...
-        for (Entry<Pattern, Set<FilterHandler>> entry : this.wildcardMap.entrySet())
-        {
-            Matcher matcher = entry.getKey().matcher(path);
-            if (matcher.find(0))
-            {
-                Set<FilterHandler> vs = entry.getValue();
-                for (FilterHandler v : vs)
-                {
-                    result.add(v);
-
-                    if (firstOnly)
-                    {
-                        break;
-                    }
-                }
-            }
-        }
-
-        return new ArrayList<FilterHandler>(result);
-    }
-}
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterRegistry.java
index 96e624a..d402343 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/FilterRegistry.java
@@ -17,12 +17,13 @@
 package org.apache.felix.http.base.internal.registry;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Iterator;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
 import javax.annotation.CheckForNull;
@@ -39,30 +40,49 @@
 
 /**
  * The filter registry keeps track of all filter mappings for a single servlet context.
+ *
+ * TODO - we should sort the statusMapping by result and ranking, keeping the active filters first,
+ *        highest ranking first. This would allow to stop iterating and avoid sorting the result.
  */
 public final class FilterRegistry
 {
-    private volatile FilterHandlerMapping filterMapping = new FilterHandlerMapping();
-
+    /** Map of all filter registrations. */
     private final Map<FilterInfo, FilterRegistrationStatus> statusMapping = new ConcurrentHashMap<FilterInfo, FilterRegistrationStatus>();
 
     private static final class FilterRegistrationStatus
     {
         public int result;
         public FilterHandler handler;
+        public PathResolver[] resolvers;
     }
 
     public synchronized void addFilter(@Nonnull final FilterHandler handler)
     {
         final int result = handler.init();
-        if ( result == -1 )
-        {
-            this.filterMapping = this.filterMapping.add(handler);
-        }
         final FilterRegistrationStatus status = new FilterRegistrationStatus();
         status.result = result;
         status.handler = handler;
 
+        if ( result == -1 )
+        {
+            final List<PathResolver> resolvers = new ArrayList<PathResolver>();
+            if ( handler.getFilterInfo().getPatterns() != null )
+            {
+                for(final String pattern : handler.getFilterInfo().getPatterns() ) {
+                    resolvers.add(PathResolverFactory.createPatternMatcher(null, pattern));
+                }
+            }
+            if ( handler.getFilterInfo().getRegexs() != null )
+            {
+                for(final String regex : handler.getFilterInfo().getRegexs() ) {
+                    resolvers.add(PathResolverFactory.createRegexMatcher(regex));
+                }
+            }
+            Collections.sort(resolvers);
+
+            status.resolvers = resolvers.toArray(new PathResolver[resolvers.size()]);
+        }
+
         statusMapping.put(handler.getFilterInfo(), status);
     }
 
@@ -73,7 +93,6 @@
         {
             if ( status.result == -1 )
             {
-                this.filterMapping = this.filterMapping.remove(status.handler);
                 if (destroy)
                 {
                     status.handler.dispose();
@@ -82,56 +101,69 @@
         }
     }
 
-    public FilterHandler[] getFilterHandlers(@CheckForNull final ServletHandler handler,
-            @CheckForNull DispatcherType dispatcherType,
-            @Nonnull String requestURI)
+    /**
+     * Get all filters handling the request.
+     * Filters are applied to the url and/or the servlet
+     * @param handler Optional servlet handler
+     * @param dispatcherType The dispatcher type
+     * @param requestURI The request uri
+     * @return The array of filter handlers, might be empty.
+     */
+    public @Nonnull FilterHandler[] getFilterHandlers(@CheckForNull final ServletHandler handler,
+            @Nonnull final DispatcherType dispatcherType,
+            @Nonnull final String requestURI)
     {
-        // See Servlet 3.0 specification, section 6.2.4...
-        final List<FilterHandler> result = new ArrayList<FilterHandler>();
-        result.addAll(this.filterMapping.getAllMatches(requestURI));
+        final Set<FilterHandler> result = new TreeSet<FilterHandler>();
 
-        // TODO this is not the most efficient/fastest way of doing this...
-        Iterator<FilterHandler> iter = result.iterator();
-        while (iter.hasNext())
+        for(final FilterRegistrationStatus status : this.statusMapping.values())
         {
-            if (!referencesDispatcherType(iter.next(), dispatcherType))
+            if (referencesDispatcherType(status.handler, dispatcherType) )
             {
-                iter.remove();
-            }
-        }
+                boolean added = false;
+                for(final PathResolver resolver : status.resolvers)
+                {
+                    if ( resolver.resolve(requestURI) != null )
+                    {
+                        result.add(status.handler);
+                        added = true;
+                        break;
+                    }
+                }
+                // check for servlet name
+                final String servletName = (handler != null) ? handler.getName() : null;
+                if ( !added && servletName != null && status.handler.getFilterInfo().getServletNames() != null )
+                {
+                    for(final String name : status.handler.getFilterInfo().getServletNames())
+                    {
+                        if ( servletName.equals(name) )
+                        {
+                            result.add(status.handler);
+                            added = true;
+                            break;
+                        }
+                    }
+                }
 
-        final String servletName = (handler != null) ? handler.getName() : null;
-        // TODO this is not the most efficient/fastest way of doing this...
-        for (FilterHandler filterHandler : this.filterMapping.values())
-        {
-            if (referencesServletByName(filterHandler, servletName))
-            {
-                result.add(filterHandler);
             }
         }
 
         return result.toArray(new FilterHandler[result.size()]);
     }
 
-    private boolean referencesDispatcherType(FilterHandler handler, DispatcherType dispatcherType)
+    /**
+     * Check if the filter is registered for the required dispatcher type
+     * @param handler The filter handler
+     * @param dispatcherType The requested dispatcher type
+     * @return {@code true} if the filter can be applied.
+     */
+    private boolean referencesDispatcherType(final FilterHandler handler, final DispatcherType dispatcherType)
     {
-        if (dispatcherType == null)
+        for(final DispatcherType dt : handler.getFilterInfo().getDispatcher())
         {
-            return true;
-        }
-        return Arrays.asList(handler.getFilterInfo().getDispatcher()).contains(dispatcherType);
-    }
-
-    private boolean referencesServletByName(FilterHandler handler, String servletName)
-    {
-        if (servletName == null)
-        {
-            return false;
-        }
-        String[] names = handler.getFilterInfo().getServletNames();
-        if (names != null && names.length > 0)
-        {
-            return Arrays.asList(names).contains(servletName);
+            if ( dt == dispatcherType )
+            {
+                return true;
+            }
         }
         return false;
     }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/HandlerRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/HandlerRegistry.java
index 1cc83f2..f1c01d5 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/HandlerRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/HandlerRegistry.java
@@ -198,7 +198,7 @@
     }
 
     public FilterHandler[] getFilters(@Nonnull final ServletResolution pr,
-            final DispatcherType dispatcherType,
+            @Nonnull final DispatcherType dispatcherType,
             @Nonnull String requestURI)
     {
         if ( pr != null && pr.handlerRegistry != null )
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PathResolverFactory.java b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PathResolverFactory.java
index 1e14490..da607f9 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PathResolverFactory.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/PathResolverFactory.java
@@ -16,6 +16,11 @@
  */
 package org.apache.felix.http.base.internal.registry;
 
+import java.util.regex.Pattern;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
 import org.apache.felix.http.base.internal.handler.ServletHandler;
 
 /**
@@ -32,7 +37,7 @@
  */
 public abstract class PathResolverFactory {
 
-    public static PathResolver create(final ServletHandler handler, final String pattern)
+    public static @Nonnull PathResolver createPatternMatcher(@CheckForNull final ServletHandler handler, @Nonnull final String pattern)
     {
         if ( pattern.length() == 0 )
         {
@@ -53,6 +58,11 @@
         return new ExactAndPathMatcher(handler, pattern);
     }
 
+    public static @Nonnull PathResolver createRegexMatcher(@Nonnull final String regex)
+    {
+        return new RegexMatcher(regex);
+    }
+
     public static abstract class AbstractMatcher implements PathResolver
     {
         private final int ranking;
@@ -100,12 +110,12 @@
 
         @Override
         public PathResolution resolve(final String uri) {
-            if ( uri.length() == 0 )
+            if ( uri.length() == 0 || uri.equals("/") )
             {
                 final PathResolution pr = new PathResolution();
                 pr.pathInfo = "/";
                 pr.servletPath = "";
-                pr.requestURI = "";
+                pr.requestURI = uri;
                 pr.handler = this.getServletHandler();
 
                 return pr;
@@ -241,4 +251,35 @@
             return this.extension.length();
         }
     }
+
+    public static final class RegexMatcher extends AbstractMatcher
+    {
+        private final Pattern pattern;
+
+        public RegexMatcher(final String regex)
+        {
+            super(null, 0);
+            this.pattern = Pattern.compile(regex);
+        }
+
+        @Override
+        public @CheckForNull PathResolution resolve(@Nonnull final String uri) {
+            if ( pattern.matcher(uri).matches() )
+            {
+                final PathResolution pr = new PathResolution();
+                pr.pathInfo = null;
+                pr.servletPath = uri;
+                pr.requestURI = uri;
+
+                return pr;
+            }
+            return null;
+        }
+
+        @Override
+        public int getOrdering()
+        {
+            return this.pattern.toString().length();
+        }
+    }
 }
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 c71e1c2..26ca121 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
@@ -16,6 +16,7 @@
  */
 package org.apache.felix.http.base.internal.registry;
 
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.servlet.DispatcherType;
 
@@ -167,8 +168,9 @@
         this.filterRegistry.removeFilter(info, destroy);
     }
 
-    public FilterHandler[] getFilterHandlers(final ServletHandler servletHandler,
-            DispatcherType dispatcherType, String requestURI)
+    public FilterHandler[] getFilterHandlers(@CheckForNull final ServletHandler servletHandler,
+            @Nonnull final DispatcherType dispatcherType,
+            @Nonnull final String requestURI)
     {
         return this.filterRegistry.getFilterHandlers(servletHandler, dispatcherType, requestURI);
     }
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ServletRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ServletRegistry.java
index 3ecfe76..e059c9d 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ServletRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/registry/ServletRegistry.java
@@ -147,45 +147,51 @@
 
     private void addToNameMapping(final ServletHandler handler)
     {
-        final String servletName = handler.getName();
-        List<ServletHandler> list = this.servletsByName.get(servletName);
-        if ( list == null )
+        if ( !handler.getServletInfo().isResource() )
         {
-            list = new ArrayList<ServletHandler>();
-            list.add(handler);
+            final String servletName = handler.getName();
+            List<ServletHandler> list = this.servletsByName.get(servletName);
+            if ( list == null )
+            {
+                list = new ArrayList<ServletHandler>();
+                list.add(handler);
+            }
+            else
+            {
+                list = new ArrayList<ServletHandler>(list);
+                list.add(handler);
+                Collections.sort(list);
+            }
+            this.servletsByName.put(servletName, list);
         }
-        else
-        {
-            list = new ArrayList<ServletHandler>(list);
-            list.add(handler);
-            Collections.sort(list);
-        }
-        this.servletsByName.put(servletName, list);
     }
 
     private synchronized void removeFromNameMapping(final String servletName, final ServletHandler handler)
     {
-        List<ServletHandler> list = this.servletsByName.get(servletName);
-        if ( list != null )
+        if ( !handler.getServletInfo().isResource() )
         {
-            final List<ServletHandler> newList = new ArrayList<ServletHandler>(list);
-            final Iterator<ServletHandler> i = newList.iterator();
-            while ( i.hasNext() )
+            List<ServletHandler> list = this.servletsByName.get(servletName);
+            if ( list != null )
             {
-                final ServletHandler s = i.next();
-                if ( s == handler )
+                final List<ServletHandler> newList = new ArrayList<ServletHandler>(list);
+                final Iterator<ServletHandler> i = newList.iterator();
+                while ( i.hasNext() )
                 {
-                    i.remove();
-                    break;
+                    final ServletHandler s = i.next();
+                    if ( s == handler )
+                    {
+                        i.remove();
+                        break;
+                    }
                 }
-            }
-            if ( newList.isEmpty() )
-            {
-                this.servletsByName.remove(servletName);
-            }
-            else
-            {
-                this.servletsByName.put(servletName, newList);
+                if ( newList.isEmpty() )
+                {
+                    this.servletsByName.remove(servletName);
+                }
+                else
+                {
+                    this.servletsByName.put(servletName, newList);
+                }
             }
         }
     }
@@ -290,7 +296,7 @@
         final int result = handler.init();
         if ( result == -1 )
         {
-            final PathResolver reg = PathResolverFactory.create(handler, pattern);
+            final PathResolver reg = PathResolverFactory.createPatternMatcher(handler, pattern);
             this.activeServletMappings.put(pattern, reg);
 
             // add ok
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/RequestInfoDTOBuilder.java b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/RequestInfoDTOBuilder.java
index 542f584..9c15e89 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/RequestInfoDTOBuilder.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/runtime/dto/RequestInfoDTOBuilder.java
@@ -16,6 +16,8 @@
  */
 package org.apache.felix.http.base.internal.runtime.dto;
 
+import javax.servlet.DispatcherType;
+
 import org.apache.felix.http.base.internal.handler.FilterHandler;
 import org.apache.felix.http.base.internal.registry.HandlerRegistry;
 import org.apache.felix.http.base.internal.registry.PathResolution;
@@ -59,7 +61,7 @@
             requestInfoDTO.servletDTO.patterns = pr.patterns;
         }
 
-        final FilterHandler[] filterHandlers = registry.getFilters(pr, null, path);
+        final FilterHandler[] filterHandlers = registry.getFilters(pr, DispatcherType.REQUEST, path);
         requestInfoDTO.filterDTOs = FilterDTOBuilder.build(filterHandlers);
 
         return requestInfoDTO;
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/registry/FilterRegistryTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/registry/FilterRegistryTest.java
new file mode 100644
index 0000000..e74146c
--- /dev/null
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/registry/FilterRegistryTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.registry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+
+import org.apache.felix.http.base.internal.context.ExtServletContext;
+import org.apache.felix.http.base.internal.handler.FilterHandler;
+import org.apache.felix.http.base.internal.handler.HttpServiceFilterHandler;
+import org.apache.felix.http.base.internal.runtime.FilterInfo;
+import org.apache.felix.http.base.internal.runtime.dto.FailedDTOHolder;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.runtime.dto.ServletContextDTO;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+
+public class FilterRegistryTest {
+
+    private final FilterRegistry reg = new FilterRegistry();
+
+    private void assertEmpty(final ServletContextDTO dto, final FailedDTOHolder holder)
+    {
+        assertNull(dto.filterDTOs);
+        assertTrue(holder.failedFilterDTOs.isEmpty());
+    }
+
+    private void clear(final ServletContextDTO dto, final FailedDTOHolder holder)
+    {
+        dto.filterDTOs = null;
+        holder.failedFilterDTOs.clear();
+    }
+
+    @Test public void testSingleFilter() throws InvalidSyntaxException, ServletException
+    {
+        final FailedDTOHolder holder = new FailedDTOHolder();
+        final ServletContextDTO dto = new ServletContextDTO();
+
+        // check DTO
+        reg.getRuntimeInfo(dto, holder.failedFilterDTOs);
+        assertEmpty(dto, holder);
+
+        // register filter
+        final FilterHandler h1 = createFilterHandler(1L, 0, "/foo");
+        reg.addFilter(h1);
+
+        verify(h1.getFilter()).init(Matchers.any(FilterConfig.class));
+
+        // one entry in DTO
+        clear(dto, holder);
+        reg.getRuntimeInfo(dto, holder.failedFilterDTOs);
+        assertTrue(holder.failedFilterDTOs.isEmpty());
+        assertNotNull(dto.filterDTOs);
+        assertEquals(1, dto.filterDTOs.length);
+        assertEquals(1, dto.filterDTOs[0].patterns.length);
+        assertEquals("/foo", dto.filterDTOs[0].patterns[0]);
+
+        // remove filter
+        final Filter f = h1.getFilter();
+        reg.removeFilter(h1.getFilterInfo(), true);
+        verify(f).destroy();
+
+        // empty again
+        clear(dto, holder);
+        reg.getRuntimeInfo(dto, holder.failedFilterDTOs);
+        assertEmpty(dto, holder);
+    }
+
+    @Test public void testFilterOrdering() throws InvalidSyntaxException
+    {
+        final FilterHandler h1 = createFilterHandler(1L, 20, "/foo");
+        reg.addFilter(h1);
+        final FilterHandler h2 = createFilterHandler(2L, 10, "/foo");
+        reg.addFilter(h2);
+        final FilterHandler h3 = createFilterHandler(3L, 30, "/foo");
+        reg.addFilter(h3);
+        final FilterHandler h4 = createFilterHandler(4L, 0, "/other");
+        reg.addFilter(h4);
+        final FilterHandler h5 = createFilterHandler(5L, 90, "/foo");
+        reg.addFilter(h5);
+
+        final FilterHandler[] handlers = reg.getFilterHandlers(null, DispatcherType.REQUEST, "/foo");
+        assertEquals(4, handlers.length);
+        assertEquals(h5.getFilterInfo(), handlers[0].getFilterInfo());
+        assertEquals(h3.getFilterInfo(), handlers[1].getFilterInfo());
+        assertEquals(h1.getFilterInfo(), handlers[2].getFilterInfo());
+        assertEquals(h2.getFilterInfo(), handlers[3].getFilterInfo());
+
+        // cleanup
+        reg.removeFilter(h1.getFilterInfo(), true);
+        reg.removeFilter(h2.getFilterInfo(), true);
+        reg.removeFilter(h3.getFilterInfo(), true);
+        reg.removeFilter(h4.getFilterInfo(), true);
+        reg.removeFilter(h5.getFilterInfo(), true);
+    }
+
+    private static FilterInfo createFilterInfo(final long id, final int ranking, final String... paths) throws InvalidSyntaxException
+    {
+        final BundleContext bCtx = mock(BundleContext.class);
+        when(bCtx.createFilter(Matchers.anyString())).thenReturn(null);
+        final Bundle bundle = mock(Bundle.class);
+        when(bundle.getBundleContext()).thenReturn(bCtx);
+
+        final ServiceReference<Filter> ref = mock(ServiceReference.class);
+        when(ref.getBundle()).thenReturn(bundle);
+        when(ref.getProperty(Constants.SERVICE_ID)).thenReturn(id);
+        when(ref.getProperty(Constants.SERVICE_RANKING)).thenReturn(ranking);
+        when(ref.getProperty(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN)).thenReturn(paths);
+        when(ref.getPropertyKeys()).thenReturn(new String[0]);
+        final FilterInfo si = new FilterInfo(ref);
+
+        return si;
+    }
+
+    private static FilterHandler createFilterHandler(final long id, final int ranking, final String... paths) throws InvalidSyntaxException
+    {
+        final FilterInfo si = createFilterInfo(id, ranking, paths);
+        final ExtServletContext ctx = mock(ExtServletContext.class);
+        final Filter filter = mock(Filter.class);
+
+        return new HttpServiceFilterHandler(7, ctx, si, filter);
+    }
+}
diff --git a/http/base/src/test/java/org/apache/felix/http/base/internal/registry/PathResolverFactoryTest.java b/http/base/src/test/java/org/apache/felix/http/base/internal/registry/PathResolverFactoryTest.java
new file mode 100644
index 0000000..d110440
--- /dev/null
+++ b/http/base/src/test/java/org/apache/felix/http/base/internal/registry/PathResolverFactoryTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.registry;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class PathResolverFactoryTest {
+
+    private void assertResult(final PathResolver resolver,
+            final String path,
+            final String expectedServletPath,
+            final String expectedPathInfo)
+    {
+        final PathResolution pr = resolver.resolve(path);
+        assertNotNull(pr);
+        assertEquals(path, pr.requestURI);
+        assertEquals(expectedServletPath, pr.servletPath);
+        if ( expectedPathInfo == null )
+        {
+            assertNull(pr.pathInfo);
+        }
+        else
+        {
+            assertEquals(expectedPathInfo, pr.pathInfo);
+        }
+    }
+
+    @Test public void testRootMatching()
+    {
+        final PathResolver pr = PathResolverFactory.createPatternMatcher(null, "");
+        assertNotNull(pr);
+
+        assertResult(pr, "/", "", "/");
+        assertResult(pr, "", "", "/");
+
+        assertNull(pr.resolve("/foo"));
+    }
+
+    @Test public void testDefaultMatcher()
+    {
+        final PathResolver pr = PathResolverFactory.createPatternMatcher(null, "/");
+        assertNotNull(pr);
+
+        assertResult(pr, "/foo/bar", "/foo/bar", null);
+        assertResult(pr, "/foo", "/foo", null);
+    }
+}