blob: e8492780747afb80b6a5bc811b1ea3e11ba48ecb [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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.apache.felix.http.base.internal.handler.ServletHandler;
import org.apache.felix.http.base.internal.runtime.ServletInfo;
import org.apache.felix.http.base.internal.runtime.dto.BuilderConstants;
import org.apache.felix.http.base.internal.runtime.dto.ResourceDTOBuilder;
import org.apache.felix.http.base.internal.runtime.dto.ServletDTOBuilder;
import org.osgi.service.http.runtime.dto.DTOConstants;
import org.osgi.service.http.runtime.dto.FailedResourceDTO;
import org.osgi.service.http.runtime.dto.FailedServletDTO;
import org.osgi.service.http.runtime.dto.ResourceDTO;
import org.osgi.service.http.runtime.dto.ServletContextDTO;
import org.osgi.service.http.runtime.dto.ServletDTO;
/**
* The servlet registry keeps the mappings for all servlets (by using their pattern)
* for a single servlet context.
*/
public final class ServletRegistry
{
private static final String NAMED_SERVLET_PATTERN = ":::";
private volatile List<PathResolver> activeResolvers = Collections.emptyList();
private final Map<String, List<ServletHandler>> inactiveServletMappings = new HashMap<String, List<ServletHandler>>();
private final Map<String, List<ServletHandler>> servletsByName = new ConcurrentHashMap<String, List<ServletHandler>>();
private static final class RegistrationStatus
{
public ServletHandler handler;
public Map<Integer, String[]> statusToPath = new HashMap<Integer, String[]>();
}
private volatile Map<ServletInfo, RegistrationStatus> mapping = Collections.emptyMap();
/**
* Resolve a request uri
*
* @param relativeRequestURI The request uri
* @return A path resolution if a servlet matched, {@code null} otherwise
*/
public PathResolution resolve(@NotNull final String relativeRequestURI)
{
final List<PathResolver> resolvers = this.activeResolvers;
for(final PathResolver entry : resolvers)
{
final PathResolution pr = entry.resolve(relativeRequestURI);
if ( pr != null )
{
// TODO - we should have all patterns under which this servlet is actively registered
pr.patterns = new String[] {entry.getPattern()};
return pr;
}
}
return null;
}
private PathResolver findResolver(final List<PathResolver> resolvers, final String pattern)
{
for(final PathResolver pr : resolvers)
{
if ( pr.getPattern().equals(pattern) )
{
return pr;
}
}
return null;
}
/**
* Add a servlet.
*
* @param handler The servlet handler
*/
public synchronized void addServlet(@NotNull final ServletHandler handler)
{
// we have to check for every pattern in the info
// Can be null in case of error-handling servlets and named servlets
if ( handler.getServletInfo().getPatterns() != null )
{
final Map<ServletInfo, RegistrationStatus> newMap = new TreeMap<ServletInfo, ServletRegistry.RegistrationStatus>(this.mapping);
final List<PathResolver> resolvers = new ArrayList<PathResolver>(this.activeResolvers);
final RegistrationStatus status = new RegistrationStatus();
status.handler = handler;
boolean isActive = false;
// used for detecting duplicates
final Set<String> patterns = new HashSet<String>();
for(final String pattern : handler.getServletInfo().getPatterns())
{
if ( patterns.contains(pattern) )
{
continue;
}
patterns.add(pattern);
final PathResolver regHandler = findResolver(resolvers, pattern);
if ( regHandler != null )
{
if ( regHandler.getServletHandler().getServletInfo().compareTo(handler.getServletInfo()) > 0 )
{
// replace if no error with new servlet
if ( this.tryToActivate(resolvers, pattern, handler, status, regHandler) )
{
isActive = true;
final String oldName = regHandler.getServletHandler().getName();
regHandler.getServletHandler().destroy();
final RegistrationStatus oldStatus = newMap.get(regHandler.getServletHandler().getServletInfo());
final RegistrationStatus newOldStatus = new RegistrationStatus();
newOldStatus.handler = oldStatus.handler;
newOldStatus.statusToPath = new HashMap<Integer, String[]>(oldStatus.statusToPath);
newMap.put(regHandler.getServletHandler().getServletInfo(), newOldStatus);
this.addToInactiveList(pattern, regHandler.getServletHandler(), newOldStatus);
if ( regHandler.getServletHandler().getServlet() == null )
{
removeFromNameMapping(oldName, regHandler.getServletHandler());
}
}
}
else
{
// add to inactive
this.addToInactiveList(pattern, handler, status);
}
}
else
{
// add to active
if ( this.tryToActivate(resolvers, pattern, handler, status, null) )
{
isActive = true;
}
}
}
newMap.put(handler.getServletInfo(), status);
if ( isActive )
{
addToNameMapping(handler);
}
Collections.sort(resolvers);
this.activeResolvers = resolvers;
this.mapping = newMap;
}
else if ( !handler.getServletInfo().isResource() && handler.getServletInfo().getName() != null )
{
final Map<ServletInfo, RegistrationStatus> newMap = new TreeMap<ServletInfo, ServletRegistry.RegistrationStatus>(this.mapping);
final RegistrationStatus status = new RegistrationStatus();
status.handler = handler;
// if a servlet has only a name we always try to activate
// this is not very efficient, but works
// add to active
final int result = handler.init();
if ( result == -1 )
{
addToNameMapping(handler);
}
addPattern(status, result, NAMED_SERVLET_PATTERN);
newMap.put(handler.getServletInfo(), status);
this.mapping = newMap;
}
}
private void addToNameMapping(final ServletHandler handler)
{
if ( !handler.getServletInfo().isResource() )
{
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);
}
}
private void removeFromNameMapping(final String servletName, final ServletHandler handler)
{
if ( !handler.getServletInfo().isResource() )
{
List<ServletHandler> list = this.servletsByName.get(servletName);
if ( list != null )
{
final List<ServletHandler> newList = new ArrayList<ServletHandler>(list);
final Iterator<ServletHandler> i = newList.iterator();
while ( i.hasNext() )
{
final ServletHandler s = i.next();
if ( s == handler )
{
i.remove();
break;
}
}
if ( newList.isEmpty() )
{
this.servletsByName.remove(servletName);
}
else
{
this.servletsByName.put(servletName, newList);
}
}
}
}
/**
* Remove a servlet
* @param info The servlet info
*/
public synchronized void removeServlet(@NotNull final ServletInfo info, final boolean destroy)
{
if ( info.getPatterns() != null )
{
final List<PathResolver> resolvers = new ArrayList<PathResolver>(this.activeResolvers);
final Map<ServletInfo, RegistrationStatus> newMap = new TreeMap<ServletInfo, ServletRegistry.RegistrationStatus>(this.mapping);
newMap.remove(info);
ServletHandler cleanupHandler = null;
// used for detecting duplicates
final Set<String> patterns = new HashSet<String>();
for(final String pattern : info.getPatterns())
{
if ( patterns.contains(pattern) )
{
continue;
}
patterns.add(pattern);
final PathResolver regHandler = this.findResolver(resolvers, pattern);
if ( regHandler != null && regHandler.getServletHandler().getServletInfo().equals(info) )
{
cleanupHandler = regHandler.getServletHandler();
removeFromNameMapping(cleanupHandler.getName(), cleanupHandler);
final List<ServletHandler> inactiveList = this.inactiveServletMappings.get(pattern);
if ( inactiveList == null )
{
resolvers.remove(regHandler);
}
else
{
boolean done = false;
while ( !done )
{
final ServletHandler h = inactiveList.remove(0);
boolean activate = h.getServlet() == null;
final RegistrationStatus oldStatus = newMap.get(h.getServletInfo());
if ( oldStatus != null ) {
final RegistrationStatus newOldStatus = new RegistrationStatus();
newOldStatus.handler = oldStatus.handler;
newOldStatus.statusToPath = new HashMap<Integer, String[]>(oldStatus.statusToPath);
removePattern(newOldStatus, DTOConstants.FAILURE_REASON_SHADOWED_BY_OTHER_SERVICE, pattern);
newMap.put(h.getServletInfo(), newOldStatus);
done = this.tryToActivate(resolvers, pattern, h, newOldStatus, regHandler);
if ( done && activate ) {
this.addToNameMapping(h);
}
}
if ( !done )
{
done = inactiveList.isEmpty();
}
}
if ( inactiveList.isEmpty() )
{
this.inactiveServletMappings.remove(pattern);
}
}
}
else
{
final List<ServletHandler> inactiveList = this.inactiveServletMappings.get(pattern);
if ( inactiveList != null )
{
final Iterator<ServletHandler> i = inactiveList.iterator();
while ( i.hasNext() )
{
final ServletHandler h = i.next();
if ( h.getServletInfo().equals(info) )
{
i.remove();
cleanupHandler = h;
break;
}
}
if ( inactiveList.isEmpty() )
{
this.inactiveServletMappings.remove(pattern);
}
}
}
}
Collections.sort(resolvers);
this.activeResolvers = resolvers;
this.mapping = newMap;
if ( cleanupHandler != null )
{
cleanupHandler.dispose();
}
}
else if ( !info.isResource() && info.getName() != null )
{
final Map<ServletInfo, RegistrationStatus> newMap = new TreeMap<ServletInfo, ServletRegistry.RegistrationStatus>(this.mapping);
final RegistrationStatus status = newMap.remove(info);
if ( status != null )
{
removeFromNameMapping(info.getName(), status.handler);
this.mapping = newMap;
status.handler.dispose();
}
}
}
public synchronized void cleanup()
{
this.activeResolvers = Collections.emptyList();
this.inactiveServletMappings.clear();
this.servletsByName.clear();
this.mapping = Collections.emptyMap();
}
private void addToInactiveList(final String pattern, final ServletHandler handler, final RegistrationStatus status)
{
List<ServletHandler> inactiveList = this.inactiveServletMappings.get(pattern);
if ( inactiveList == null )
{
inactiveList = new ArrayList<ServletHandler>();
this.inactiveServletMappings.put(pattern, inactiveList);
}
inactiveList.add(handler);
Collections.sort(inactiveList);
removePattern(status, -1, pattern);
addPattern(status, DTOConstants.FAILURE_REASON_SHADOWED_BY_OTHER_SERVICE, pattern);
}
private boolean tryToActivate(final List<PathResolver> resolvers,
final String pattern,
final ServletHandler handler,
final RegistrationStatus status,
final PathResolver oldResolver)
{
// add to active
final int result = handler.init();
if ( result == -1 )
{
if ( oldResolver != null )
{
resolvers.remove(oldResolver);
}
final PathResolver resolver = PathResolverFactory.createPatternMatcher(handler, pattern);
resolvers.add(resolver);
}
// update status
addPattern(status, result, pattern);
return result == -1;
}
private void addPattern(final RegistrationStatus status, final int failureCode, final String pattern)
{
String[] paths = status.statusToPath.get(failureCode);
if ( paths == null )
{
status.statusToPath.put(failureCode, new String[] {pattern});
}
else
{
final String[] newPaths = new String[paths.length + 1];
System.arraycopy(paths, 0, newPaths, 0, paths.length);
newPaths[paths.length] = pattern;
status.statusToPath.put(failureCode, newPaths);
}
}
private void removePattern(final RegistrationStatus status, final int failureCode, final String pattern)
{
String[] paths = status.statusToPath.get(failureCode);
if ( paths != null )
{
final List<String> array = new ArrayList<String>(Arrays.asList(paths));
if ( array.remove(pattern) )
{
if ( array.isEmpty() )
{
status.statusToPath.remove(failureCode);
}
else
{
status.statusToPath.put(failureCode, array.toArray(new String[array.size()]));
}
}
}
}
public ServletHandler resolveByName(final @NotNull String name)
{
final List<ServletHandler> handlerList = this.servletsByName.get(name);
if ( handlerList != null )
{
return handlerList.get(0);
}
return null;
}
public void getRuntimeInfo(
final ServletContextDTO servletContextDTO,
final Collection<FailedServletDTO> allFailedServletDTOs,
final Collection<FailedResourceDTO> allFailedResourceDTOs)
{
final Map<Long, ServletDTO> servletDTOs = new HashMap<Long, ServletDTO>();
final Map<Long, ResourceDTO> resourceDTOs = new HashMap<Long, ResourceDTO>();
final Map<Long, FailedServletDTO> failedServletDTOs = new HashMap<Long, FailedServletDTO>();
final Map<Long, FailedResourceDTO> failedResourceDTOs = new HashMap<Long, FailedResourceDTO>();
for(final Map.Entry<ServletInfo, RegistrationStatus> entry : mapping.entrySet())
{
final long serviceId = entry.getKey().getServiceId();
for(final Map.Entry<Integer, String[]> map : entry.getValue().statusToPath.entrySet())
{
if ( entry.getKey().isResource() )
{
final ResourceDTO state = ResourceDTOBuilder.build(entry.getValue().handler, map.getKey());
if ( map.getValue().length == 1 && NAMED_SERVLET_PATTERN == map.getValue()[0] )
{
state.patterns = BuilderConstants.EMPTY_STRING_ARRAY;
}
else
{
state.patterns = Arrays.copyOf(map.getValue(), map.getValue().length);
}
if ( map.getKey() == -1 )
{
resourceDTOs.put(serviceId, state);
}
else
{
failedResourceDTOs.put(serviceId, (FailedResourceDTO)state);
}
}
else
{
final ServletDTO state = ServletDTOBuilder.build(entry.getValue().handler, map.getKey());
if ( map.getValue().length == 1 && NAMED_SERVLET_PATTERN == map.getValue()[0] )
{
state.patterns = BuilderConstants.EMPTY_STRING_ARRAY;
}
else
{
state.patterns = Arrays.copyOf(map.getValue(), map.getValue().length);
}
if ( map.getKey() == -1 )
{
servletDTOs.put(serviceId, state);
}
else
{
failedServletDTOs.put(serviceId, (FailedServletDTO)state);
}
}
}
}
final Collection<ServletDTO> servletDTOsArray = servletDTOs.values();
if ( !servletDTOsArray.isEmpty() )
{
servletContextDTO.servletDTOs = servletDTOsArray.toArray(new ServletDTO[servletDTOsArray.size()]);
}
final Collection<ResourceDTO> resourceDTOsArray = resourceDTOs.values();
if ( !resourceDTOsArray.isEmpty() )
{
servletContextDTO.resourceDTOs = resourceDTOsArray.toArray(new ResourceDTO[resourceDTOsArray.size()]);
}
allFailedResourceDTOs.addAll(failedResourceDTOs.values());
allFailedServletDTOs.addAll(failedServletDTOs.values());
}
}