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);
+ }
+}