blob: 79554354ba8ee5c4bfd6292c35d83387d6a51052 [file] [log] [blame]
/*
* 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 java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import javax.servlet.DispatcherType;
import org.apache.felix.http.base.internal.handler.FilterHandler;
import org.apache.felix.http.base.internal.handler.ServletHandler;
import org.apache.felix.http.base.internal.runtime.FilterInfo;
import org.apache.felix.http.base.internal.runtime.dto.FilterDTOBuilder;
import org.osgi.service.http.runtime.dto.FailedFilterDTO;
import org.osgi.service.http.runtime.dto.FilterDTO;
import org.osgi.service.http.runtime.dto.ServletContextDTO;
/**
* The filter registry keeps track of all filter mappings for a single servlet context.
*
*/
public final class FilterRegistry
{
/** List of all filter registrations. These are sorted by the status objects. */
private volatile List<FilterRegistrationStatus> filters = Collections.emptyList();
/**
* The status object keeps track of the registration status of a filter and holds
* the resolvers to match against a uri.
* The status objects are sorted by result first, followed by ranking. The active
* filters ( result == -1) are first, followed by the inactive ones. This sorting
* allows to only traverse over the active ones and also avoids any sorting
* as the filters are processed in the correct order already.
*/
private static final class FilterRegistrationStatus implements Comparable<FilterRegistrationStatus>
{
private final int result;
private final FilterHandler handler;
private final PathResolver[] resolvers;
public FilterRegistrationStatus(@NotNull final FilterHandler handler, @Nullable final PathResolver[] resolvers, final int result)
{
this.handler = handler;
this.resolvers = resolvers;
this.result = result;
}
public int getResult()
{
return this.result;
}
public @NotNull FilterHandler getHandler()
{
return this.handler;
}
public @Nullable PathResolver[] getResolvers()
{
return this.resolvers;
}
@Override
public int compareTo(final FilterRegistrationStatus o) {
int result = this.result - o.result;
if ( result == 0 )
{
result = this.handler.compareTo(o.handler);
}
return result;
}
}
/**
* Add a filter.
* @param handler The handler for the filter
*/
public synchronized void addFilter(@NotNull final FilterHandler handler)
{
final int result = handler.init();
PathResolver[] prs = null;
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);
prs = resolvers.toArray(new PathResolver[resolvers.size()]);
}
final FilterRegistrationStatus status = new FilterRegistrationStatus(handler, prs, result);
final List<FilterRegistrationStatus> newList = new ArrayList<FilterRegistry.FilterRegistrationStatus>(this.filters);
newList.add(status);
Collections.sort(newList);
this.filters = newList;
}
/**
* Remove a filter
* @param filterInfo The filter info
* @param destroy boolean flag indicating whether to call destroy on the filter.
*/
public synchronized void removeFilter(@NotNull final FilterInfo filterInfo, final boolean destroy)
{
FilterRegistrationStatus found = null;
final List<FilterRegistrationStatus> newList = new ArrayList<FilterRegistry.FilterRegistrationStatus>(this.filters);
final Iterator<FilterRegistrationStatus> i = newList.iterator();
while ( i.hasNext() )
{
final FilterRegistrationStatus status = i.next();
if ( status.getHandler().getFilterInfo().equals(filterInfo) )
{
found = status;
i.remove();
break;
}
}
if ( found != null )
{
this.filters = newList;
if ( found.getResult() == -1 && destroy )
{
found.getHandler().dispose();
}
}
}
public synchronized void cleanup()
{
this.filters = Collections.emptyList();
}
/**
* 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 @NotNull FilterHandler[] getFilterHandlers(@Nullable final ServletHandler handler,
@NotNull final DispatcherType dispatcherType,
@NotNull final String requestURI)
{
final List<FilterHandler> result = new ArrayList<FilterHandler>();
final List<FilterRegistrationStatus> allFilters = this.filters;
for(final FilterRegistrationStatus status : allFilters)
{
// as soon as we encounter a failing filter, we can stop
if ( status.getResult() != -1 )
{
break;
}
if (referencesDispatcherType(status.getHandler(), dispatcherType) )
{
boolean added = false;
for(final PathResolver resolver : status.getResolvers())
{
if ( resolver.resolve(requestURI) != null )
{
result.add(status.getHandler());
added = true;
break;
}
}
// check for servlet name if it's not a resource
final String servletName = (handler != null && !handler.getServletInfo().isResource()) ? handler.getName() : null;
if ( !added && servletName != null && status.getHandler().getFilterInfo().getServletNames() != null )
{
for(final String name : status.getHandler().getFilterInfo().getServletNames())
{
if ( servletName.equals(name) )
{
result.add(status.getHandler());
added = true;
break;
}
}
}
}
}
return result.toArray(new FilterHandler[result.size()]);
}
/**
* 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)
{
for(final DispatcherType dt : handler.getFilterInfo().getDispatcher())
{
if ( dt == dispatcherType )
{
return true;
}
}
return false;
}
/**
* Get the runtime information about filters
* @param servletContextDTO The servlet context DTO
* @param failedFilterDTOs The collection holding the failed filters.
*/
public void getRuntimeInfo(final ServletContextDTO servletContextDTO,
final Collection<FailedFilterDTO> failedFilterDTOs)
{
final List<FilterDTO> filterDTOs = new ArrayList<FilterDTO>();
final List<FilterRegistrationStatus> allFilters = this.filters;
for(final FilterRegistrationStatus status : allFilters)
{
if ( status.getResult() != -1 )
{
failedFilterDTOs.add((FailedFilterDTO)FilterDTOBuilder.build(status.getHandler(), status.getResult()));
}
else
{
filterDTOs.add(FilterDTOBuilder.build(status.getHandler(), status.getResult()));
}
}
if ( !filterDTOs.isEmpty() )
{
servletContextDTO.filterDTOs = filterDTOs.toArray(new FilterDTO[filterDTOs.size()]);
}
}
}