blob: 48d5b93f2ea98036240f55741ffe0a115880105c [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.annotations;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.annotation.HandlesTypes;
import org.eclipse.jetty.annotations.AnnotationParser.Handler;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.statistic.CounterStatistic;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.MetaDataComplete;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebDescriptor;
/**
* Configuration for Annotations
*/
public class AnnotationConfiguration extends AbstractConfiguration
{
private static final Logger LOG = Log.getLogger(AnnotationConfiguration.class);
public static final String SERVLET_CONTAINER_INITIALIZER_EXCLUSION_PATTERN = "org.eclipse.jetty.containerInitializerExclusionPattern";
public static final String SERVLET_CONTAINER_INITIALIZER_ORDER = "org.eclipse.jetty.containerInitializerOrder";
public static final String CLASS_INHERITANCE_MAP = "org.eclipse.jetty.classInheritanceMap";
public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers";
public static final String CONTAINER_INITIALIZER_STARTER = "org.eclipse.jetty.containerInitializerStarter";
public static final String MULTI_THREADED = "org.eclipse.jetty.annotations.multiThreaded";
public static final String MAX_SCAN_WAIT = "org.eclipse.jetty.annotations.maxWait";
public static final int DEFAULT_MAX_SCAN_WAIT = 60; /* time in sec */
public static final boolean DEFAULT_MULTI_THREADED = true;
protected List<AbstractDiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<AbstractDiscoverableAnnotationHandler>();
protected ClassInheritanceHandler _classInheritanceHandler;
protected List<ContainerInitializerAnnotationHandler> _containerInitializerAnnotationHandlers = new ArrayList<ContainerInitializerAnnotationHandler>();
protected List<ParserTask> _parserTasks;
protected WebAppClassNameResolver _webAppClassNameResolver;
protected ContainerClassNameResolver _containerClassNameResolver;
protected CounterStatistic _containerPathStats;
protected CounterStatistic _webInfLibStats;
protected CounterStatistic _webInfClassesStats;
protected Pattern _sciExcludePattern;
protected ServiceLoader<ServletContainerInitializer> _loadedInitializers = null;
/**
* TimeStatistic
*
* Simple class to capture elapsed time of an operation.
*
*/
public class TimeStatistic
{
public long _start = 0;
public long _end = 0;
public void start ()
{
_start = System.nanoTime();
}
public void end ()
{
_end = System.nanoTime();
}
public long getStart()
{
return _start;
}
public long getEnd ()
{
return _end;
}
public long getElapsed ()
{
return (_end > _start?(_end-_start):0);
}
}
/**
* ParserTask
*
* Task to executing scanning of a resource for annotations.
*
*/
public class ParserTask implements Callable<Void>
{
protected Exception _exception;
protected final AnnotationParser _parser;
protected final Set<? extends Handler> _handlers;
protected final ClassNameResolver _resolver;
protected final Resource _resource;
protected TimeStatistic _stat;
public ParserTask (AnnotationParser parser, Set<? extends Handler>handlers, Resource resource, ClassNameResolver resolver)
{
_parser = parser;
_handlers = handlers;
_resolver = resolver;
_resource = resource;
}
public void setStatistic(TimeStatistic stat)
{
_stat = stat;
}
public Void call() throws Exception
{
if (_stat != null)
_stat.start();
if (_parser != null)
_parser.parse(_handlers, _resource, _resolver);
if (_stat != null)
_stat.end();
return null;
}
public TimeStatistic getStatistic()
{
return _stat;
}
public Resource getResource()
{
return _resource;
}
}
/**
* WebAppClassNameResolver
*
* Checks to see if a classname belongs to hidden or visible packages when scanning,
* and whether a classname that is a duplicate should override a previously
* scanned classname.
*
* This is analogous to the management of classes that the WebAppClassLoader is doing,
* however we don't want to load the classes at this point so we are doing it on
* the name only.
*
*/
public class WebAppClassNameResolver implements ClassNameResolver
{
private WebAppContext _context;
public WebAppClassNameResolver (WebAppContext context)
{
_context = context;
}
public boolean isExcluded (String name)
{
if (_context.isSystemClass(name)) return true;
if (_context.isServerClass(name)) return false;
return false;
}
public boolean shouldOverride (String name)
{
//looking at webapp classpath, found already-parsed class
//of same name - did it come from system or duplicate in webapp?
if (_context.isParentLoaderPriority())
return false;
return true;
}
}
/**
* ContainerClassNameResolver
*
* Checks to see if a classname belongs to a hidden or visible package
* when scanning for annotations and thus whether it should be excluded from
* consideration or not.
*
* This is analogous to the management of classes that the WebAppClassLoader is doing,
* however we don't want to load the classes at this point so we are doing it on
* the name only.
*
*/
public class ContainerClassNameResolver implements ClassNameResolver
{
private WebAppContext _context;
public ContainerClassNameResolver (WebAppContext context)
{
_context = context;
}
public boolean isExcluded (String name)
{
if (_context.isSystemClass(name)) return false;
if (_context.isServerClass(name)) return true;
return false;
}
public boolean shouldOverride (String name)
{
//visiting the container classpath,
if (_context.isParentLoaderPriority())
return true;
return false;
}
}
/**
* ServletContainerInitializerOrdering
*
* A list of classnames of ServletContainerInitializers in the order in which
* they are to be called back. One name only in the list can be "*", which is a
* wildcard which matches any other ServletContainerInitializer name not already
* matched.
*/
public class ServletContainerInitializerOrdering
{
private Map<String, Integer> _indexMap = new HashMap<String, Integer>();
private Integer _star = null;
private String _ordering = null;
public ServletContainerInitializerOrdering (String ordering)
{
if (ordering != null)
{
_ordering = ordering;
String[] tmp = StringUtil.csvSplit(ordering);
for (int i=0; i<tmp.length; i++)
{
String s = tmp[i].trim();
_indexMap.put(s, Integer.valueOf(i));
if ("*".equals(s))
{
if (_star != null)
throw new IllegalArgumentException("Duplicate wildcards in ServletContainerInitializer ordering "+ordering);
_star = Integer.valueOf(i);
}
}
}
}
/**
* @return true if "*" is one of the values.
*/
public boolean hasWildcard()
{
return _star != null;
}
/**
* @return the index of the "*" element, if it is specified. -1 otherwise.
*/
public int getWildcardIndex()
{
if (!hasWildcard())
return -1;
return _star.intValue();
}
/**
* @return true if the ordering contains a single value of "*"
*/
public boolean isDefaultOrder ()
{
return (getSize() == 1 && hasWildcard());
}
/**
* Get the order index of the given classname
* @param name the classname to look up
* @return the index of the class name (or -1 if not found)
*/
public int getIndexOf (String name)
{
Integer i = _indexMap.get(name);
if (i == null)
return -1;
return i.intValue();
}
/**
* Get the number of elements of the ordering
* @return the size of the index
*/
public int getSize()
{
return _indexMap.size();
}
public String toString()
{
if (_ordering == null)
return "";
return _ordering;
}
}
/**
* ServletContainerInitializerComparator
*
* Comparator impl that orders a set of ServletContainerInitializers according to the
* list of classnames (optionally containing a "*" wildcard character) established in a
* ServletContainerInitializerOrdering.
* @see ServletContainerInitializerOrdering
*/
public class ServletContainerInitializerComparator implements Comparator<ServletContainerInitializer>
{
private ServletContainerInitializerOrdering _ordering;
public ServletContainerInitializerComparator (ServletContainerInitializerOrdering ordering)
{
_ordering = ordering;
}
@Override
public int compare(ServletContainerInitializer sci1, ServletContainerInitializer sci2)
{
String c1 = (sci1 != null? sci1.getClass().getName() : null);
String c2 = (sci2 != null? sci2.getClass().getName() : null);
if (c1 == null && c2 == null)
return 0;
int i1 = _ordering.getIndexOf(c1);
if (i1 < 0 && _ordering.hasWildcard())
i1 = _ordering.getWildcardIndex();
int i2 = _ordering.getIndexOf(c2);
if (i2 < 0 && _ordering.hasWildcard())
i2 = _ordering.getWildcardIndex();
return Integer.compare(i1, i2);
}
}
@Override
public void preConfigure(final WebAppContext context) throws Exception
{
_webAppClassNameResolver = new WebAppClassNameResolver(context);
_containerClassNameResolver = new ContainerClassNameResolver(context);
String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_EXCLUSION_PATTERN);
_sciExcludePattern = (tmp==null?null:Pattern.compile(tmp));
}
public void addDiscoverableAnnotationHandler(AbstractDiscoverableAnnotationHandler handler)
{
_discoverableAnnotationHandlers.add(handler);
}
@Override
public void deconfigure(WebAppContext context) throws Exception
{
context.removeAttribute(CLASS_INHERITANCE_MAP);
context.removeAttribute(CONTAINER_INITIALIZERS);
ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
if (starter != null)
{
context.removeBean(starter);
context.removeAttribute(CONTAINER_INITIALIZER_STARTER);
}
if (_loadedInitializers != null)
_loadedInitializers.reload();
}
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void configure(WebAppContext context) throws Exception
{
context.getObjectFactory().addDecorator(new AnnotationDecorator(context));
//Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any
if (!context.getMetaData().isMetaDataComplete())
{
//If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
{
_discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
_discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
_discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
}
}
//Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the
//classes so we can call their onStartup() methods correctly
createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
scanForAnnotations(context);
// Resolve container initializers
List<ContainerInitializer> initializers =
(List<ContainerInitializer>)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
if (initializers != null && initializers.size()>0)
{
Map<String, Set<String>> map = ( Map<String, Set<String>>) context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
if (map == null)
LOG.warn ("ServletContainerInitializers: detected. Class hierarchy: empty");
for (ContainerInitializer i : initializers)
i.resolveClasses(context,map);
}
}
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void postConfigure(WebAppContext context) throws Exception
{
ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap = (ClassInheritanceMap)context.getAttribute(CLASS_INHERITANCE_MAP);
List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
context.removeAttribute(CLASS_INHERITANCE_MAP);
if (classMap != null)
classMap.clear();
context.removeAttribute(CONTAINER_INITIALIZERS);
if (initializers != null)
initializers.clear();
if (_discoverableAnnotationHandlers != null)
_discoverableAnnotationHandlers.clear();
_classInheritanceHandler = null;
if (_containerInitializerAnnotationHandlers != null)
_containerInitializerAnnotationHandlers.clear();
if (_parserTasks != null)
{
_parserTasks.clear();
_parserTasks = null;
}
super.postConfigure(context);
}
/**
* Perform scanning of classes for annotations
*
* @param context the context for the scan
* @throws Exception if unable to scan
*/
protected void scanForAnnotations (WebAppContext context)
throws Exception
{
AnnotationParser parser = createAnnotationParser();
_parserTasks = new ArrayList<ParserTask>();
long start = 0;
if (LOG.isDebugEnabled())
LOG.debug("Annotation scanning commencing: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}, maxScanWait={}",
context.getServletContext().getEffectiveMajorVersion(),
context.getMetaData().isMetaDataComplete(),
context.isConfigurationDiscovered(),
isUseMultiThreading(context),
getMaxScanWait(context));
parseContainerPath(context, parser);
//email from Rajiv Mordani jsrs 315 7 April 2010
// If there is a <others/> then the ordering should be
// WEB-INF/classes the order of the declared elements + others.
// In case there is no others then it is
// WEB-INF/classes + order of the elements.
parseWebInfClasses(context, parser);
parseWebInfLib (context, parser);
start = System.nanoTime();
//execute scan, either effectively synchronously (1 thread only), or asynchronously (limited by number of processors available)
final Semaphore task_limit = (isUseMultiThreading(context)? new Semaphore(Runtime.getRuntime().availableProcessors()):new Semaphore(1));
final CountDownLatch latch = new CountDownLatch(_parserTasks.size());
final MultiException me = new MultiException();
for (final ParserTask p:_parserTasks)
{
task_limit.acquire();
context.getServer().getThreadPool().execute(new Runnable()
{
@Override
public void run()
{
try
{
p.call();
}
catch (Exception e)
{
me.add(e);
}
finally
{
task_limit.release();
latch.countDown();
}
}
});
}
boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);
if (LOG.isDebugEnabled())
{
for (ParserTask p:_parserTasks)
LOG.debug("Scanned {} in {}ms", p.getResource(), TimeUnit.MILLISECONDS.convert(p.getStatistic().getElapsed(), TimeUnit.NANOSECONDS));
LOG.debug("Scanned {} container path jars, {} WEB-INF/lib jars, {} WEB-INF/classes dirs in {}ms for context {}",
_containerPathStats.getTotal(), _webInfLibStats.getTotal(), _webInfClassesStats.getTotal(),
(TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS)),
context);
}
if (timeout)
me.add(new Exception("Timeout scanning annotations"));
me.ifExceptionThrow();
}
/**
* @return a new AnnotationParser. This method can be overridden to use a different implementation of
* the AnnotationParser. Note that this is considered internal API.
*/
protected AnnotationParser createAnnotationParser()
{
return new AnnotationParser();
}
/**
* Check if we should use multiple threads to scan for annotations or not
* @param context the context of the multi threaded setting
* @return true if multi threading is enabled on the context, server, or via a System property.
* @see #MULTI_THREADED
*/
protected boolean isUseMultiThreading(WebAppContext context)
{
//try context attribute to see if we should use multithreading
Object o = context.getAttribute(MULTI_THREADED);
if (o instanceof Boolean)
{
return ((Boolean)o).booleanValue();
}
//try server attribute to see if we should use multithreading
o = context.getServer().getAttribute(MULTI_THREADED);
if (o instanceof Boolean)
{
return ((Boolean)o).booleanValue();
}
//try system property to see if we should use multithreading
return Boolean.valueOf(System.getProperty(MULTI_THREADED, Boolean.toString(DEFAULT_MULTI_THREADED)));
}
/**
* Work out how long we should wait for the async scanning to occur.
*
* @param context the context of the max scan wait setting
* @return the max scan wait setting on the context, or server, or via a System property.
* @see #MAX_SCAN_WAIT
*/
protected int getMaxScanWait (WebAppContext context)
{
//try context attribute to get max time in sec to wait for scan completion
Object o = context.getAttribute(MAX_SCAN_WAIT);
if (o != null && o instanceof Number)
{
return ((Number)o).intValue();
}
//try server attribute to get max time in sec to wait for scan completion
o = context.getServer().getAttribute(MAX_SCAN_WAIT);
if (o != null && o instanceof Number)
{
return ((Number)o).intValue();
}
//try system property to get max time in sec to wait for scan completion
return Integer.getInteger(MAX_SCAN_WAIT, DEFAULT_MAX_SCAN_WAIT).intValue();
}
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
{
context.getObjectFactory().addDecorator(new AnnotationDecorator(context));
}
public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis)
throws Exception
{
if (scis == null || scis.isEmpty())
return; // nothing to do
List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
context.setAttribute(CONTAINER_INITIALIZERS, initializers);
for (ServletContainerInitializer service : scis)
{
HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class);
ContainerInitializer initializer = null;
if (annotation != null)
{
//There is a HandlesTypes annotation on the on the ServletContainerInitializer
Class<?>[] classes = annotation.value();
if (classes != null)
{
if (LOG.isDebugEnabled()){LOG.debug("HandlesTypes {} on initializer {}",Arrays.asList(classes),service.getClass());}
initializer = new ContainerInitializer(service, classes);
//If we haven't already done so, we need to register a handler that will
//process the whole class hierarchy to satisfy the ServletContainerInitializer
if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
{
//MultiMap<String> map = new MultiMap<>();
ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ClassInheritanceMap();
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
for (Class<?> c: classes)
{
//The value of one of the HandlesTypes classes is actually an Annotation itself so
//register a handler for it
if (c.isAnnotation())
{
if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName());
_containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
}
}
}
else
{
initializer = new ContainerInitializer(service, null);
if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass());
}
}
else
{
initializer = new ContainerInitializer(service, null);
if (LOG.isDebugEnabled()) LOG.debug("No HandlesTypes annotation on initializer "+service.getClass());
}
initializers.add(initializer);
}
//add a bean to the context which will call the servletcontainerinitializers when appropriate
ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
if (starter != null)
throw new IllegalStateException("ServletContainerInitializersStarter already exists");
starter = new ServletContainerInitializersStarter(context);
context.setAttribute(CONTAINER_INITIALIZER_STARTER, starter);
context.addBean(starter, true);
}
public Resource getJarFor (ServletContainerInitializer service)
throws MalformedURLException, IOException
{
//try the thread context classloader to get the jar that loaded the class
URL jarURL = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class");
//if for some reason that failed (eg we're in osgi and the TCCL does not know about the service) try the classloader that
//loaded the class
if (jarURL == null)
jarURL = service.getClass().getClassLoader().getResource(service.getClass().getName().replace('.','/')+".class");
String loadingJarName = jarURL.toString();
int i = loadingJarName.indexOf(".jar");
if (i < 0)
return null; //not from a jar
loadingJarName = loadingJarName.substring(0,i+4);
loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
return Resource.newResource(loadingJarName);
}
/**
* Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
* from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
*
* @param context the context for the jars
* @param sci the servlet container initializer
* @param sciResource the resource for the servlet container initializer
* @return true if excluded
* @throws Exception if unable to determine exclusion
*/
public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer sci, Resource sciResource)
throws Exception
{
if (sci == null)
throw new IllegalArgumentException("ServletContainerInitializer null");
if (context == null)
throw new IllegalArgumentException("WebAppContext null");
if (LOG.isDebugEnabled()) LOG.debug("Checking {} for jar exclusion", sci);
//A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
//of WEB-INF/lib jars
if (isFromContainerClassPath(context, sci))
return false;
//If no ordering, nothing is excluded
if (context.getMetaData().getOrdering() == null)
return false;
List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
//there is an ordering, but there are no jars resulting from the ordering, everything excluded
if (orderedJars.isEmpty())
return true;
if (sciResource == null)
return false; //not from a jar therefore not from WEB-INF so not excludable
URI loadingJarURI = sciResource.getURI();
boolean found = false;
Iterator<Resource> itor = orderedJars.iterator();
while (!found && itor.hasNext())
{
Resource r = itor.next();
found = r.getURI().equals(loadingJarURI);
}
return !found;
}
/**
* Test if the ServletContainerIntializer is excluded by the
* o.e.j.containerInitializerExclusionPattern
*
* @param sci the ServletContainerIntializer
* @return true if the ServletContainerIntializer is excluded
*/
public boolean matchesExclusionPattern(ServletContainerInitializer sci)
{
//no exclusion pattern, no SCI is excluded by it
if (_sciExcludePattern == null)
return false;
//test if name of class matches the regex
if (LOG.isDebugEnabled()) LOG.debug("Checking {} against containerInitializerExclusionPattern",sci.getClass().getName());
return _sciExcludePattern.matcher(sci.getClass().getName()).matches();
}
/**
* Test if the ServletContainerInitializer is from the container classpath
*
* @param context the context for the webapp classpath
* @param sci the ServletContainerIntializer
* @return true if ServletContainerIntializer is from container classpath
*/
public boolean isFromContainerClassPath (WebAppContext context, ServletContainerInitializer sci)
{
if (sci == null)
return false;
return sci.getClass().getClassLoader()==context.getClassLoader().getParent();
}
/**
* Get SCIs that are not excluded from consideration
* @param context the web app context
* @return the list of non-excluded servlet container initializers
* @throws Exception if unable to get list
*/
public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
throws Exception
{
ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
//We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
long start = 0;
ClassLoader old = Thread.currentThread().getContextClassLoader();
try
{
if (LOG.isDebugEnabled())
start = System.nanoTime();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
_loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
if (LOG.isDebugEnabled())
LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
Map<ServletContainerInitializer,Resource> sciResourceMap = new HashMap<ServletContainerInitializer,Resource>();
ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
//Get initial set of SCIs that aren't from excluded jars or excluded by the containerExclusionPattern, or excluded
//because containerInitializerOrdering omits it
for (ServletContainerInitializer sci:_loadedInitializers)
{
if (matchesExclusionPattern(sci))
{
if (LOG.isDebugEnabled()) LOG.debug("{} excluded by pattern", sci);
continue;
}
Resource sciResource = getJarFor(sci);
if (isFromExcludedJar(context, sci, sciResource))
{
if (LOG.isDebugEnabled()) LOG.debug("{} is from excluded jar", sci);
continue;
}
//check containerInitializerOrdering doesn't exclude it
String name = sci.getClass().getName();
if (initializerOrdering != null
&& (!initializerOrdering.hasWildcard() && initializerOrdering.getIndexOf(name) < 0))
{
if (LOG.isDebugEnabled()) LOG.debug("{} is excluded by ordering", sci);
continue;
}
sciResourceMap.put(sci, sciResource);
}
//Order the SCIs that are included
if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
{
if (LOG.isDebugEnabled())
LOG.debug("Ordering ServletContainerInitializers with "+initializerOrdering);
//There is an ordering that is not just "*".
//Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
nonExcludedInitializers.addAll(sciResourceMap.keySet());
Collections.sort(nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
}
else
{
//No jetty-specific ordering specified, or just the wildcard value "*" specified.
//Fallback to ordering the ServletContainerInitializers according to:
//container classpath first, WEB-INF/classes then WEB-INF/lib (obeying any web.xml jar ordering)
//no web.xml ordering defined, add SCIs in any order
if (context.getMetaData().getOrdering() == null)
{
if (LOG.isDebugEnabled()) LOG.debug("No web.xml ordering, ServletContainerInitializers in random order");
nonExcludedInitializers.addAll(sciResourceMap.keySet());
}
else
{
if (LOG.isDebugEnabled()) LOG.debug("Ordering ServletContainerInitializers with ordering {}",context.getMetaData().getOrdering());
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
{
//add in SCIs from the container classpath
if (entry.getKey().getClass().getClassLoader()==context.getClassLoader().getParent())
nonExcludedInitializers.add(entry.getKey());
else if (entry.getValue() == null) //add in SCIs not in a jar, as they must be from WEB-INF/classes and can't be ordered
nonExcludedInitializers.add(entry.getKey());
}
//add SCIs according to the ordering of its containing jar
for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
{
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
{
if (webInfJar.equals(entry.getValue()))
nonExcludedInitializers.add(entry.getKey());
}
}
}
}
if (LOG.isDebugEnabled())
{
int i=0;
for (ServletContainerInitializer sci:nonExcludedInitializers)
LOG.debug("ServletContainerInitializer: {} {} from {}",(++i), sci.getClass().getName(), sciResourceMap.get(sci));
}
return nonExcludedInitializers;
}
/**
* Jetty-specific extension that allows an ordering to be applied across ALL ServletContainerInitializers.
*
* @param context the context for the initializer ordering configuration
* @return the ordering of the ServletContainerIntializer's
*/
public ServletContainerInitializerOrdering getInitializerOrdering (WebAppContext context)
{
if (context == null)
return null;
String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
if (tmp == null || "".equals(tmp.trim()))
return null;
return new ServletContainerInitializerOrdering(tmp);
}
/**
* Scan jars on container path.
*
* @param context the context for the scan
* @param parser the parser to scan with
* @throws Exception if unable to scan
*/
public void parseContainerPath (final WebAppContext context, final AnnotationParser parser) throws Exception
{
//always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
final Set<Handler> handlers = new HashSet<Handler>();
handlers.addAll(_discoverableAnnotationHandlers);
handlers.addAll(_containerInitializerAnnotationHandlers);
if (_classInheritanceHandler != null)
handlers.add(_classInheritanceHandler);
_containerPathStats = new CounterStatistic();
for (Resource r : context.getMetaData().getContainerResources())
{
//queue it up for scanning if using multithreaded mode
if (_parserTasks != null)
{
ParserTask task = new ParserTask(parser, handlers, r, _containerClassNameResolver);
_parserTasks.add(task);
_containerPathStats.increment();
if (LOG.isDebugEnabled())
task.setStatistic(new TimeStatistic());
}
}
}
/**
* Scan jars in WEB-INF/lib
*
* @param context the context for the scan
* @param parser the annotation parser to use
* @throws Exception if unable to scan and/or parse
*/
public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser) throws Exception
{
List<FragmentDescriptor> frags = context.getMetaData().getFragments();
//email from Rajiv Mordani jsrs 315 7 April 2010
//jars that do not have a web-fragment.xml are still considered fragments
//they have to participate in the ordering
ArrayList<URI> webInfUris = new ArrayList<URI>();
List<Resource> jars = null;
if (context.getMetaData().getOrdering() != null)
jars = context.getMetaData().getOrderedWebInfJars();
else
//No ordering just use the jars in any order
jars = context.getMetaData().getWebInfJars();
_webInfLibStats = new CounterStatistic();
for (Resource r : jars)
{
//for each jar, we decide which set of annotations we need to parse for
final Set<Handler> handlers = new HashSet<Handler>();
FragmentDescriptor f = getFragmentFromJar(r, frags);
//if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
// but yet we still need to do the scanning for the classes on behalf of the servletcontainerinitializers
//if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
//or if it has a fragment we scan it if it is not metadata complete
if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
{
//register the classinheritance handler if there is one
if (_classInheritanceHandler != null)
handlers.add(_classInheritanceHandler);
//register the handlers for the @HandlesTypes values that are themselves annotations if there are any
handlers.addAll(_containerInitializerAnnotationHandlers);
//only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
if (f == null || !isMetaDataComplete(f))
handlers.addAll(_discoverableAnnotationHandlers);
if (_parserTasks != null)
{
ParserTask task = new ParserTask(parser, handlers,r, _webAppClassNameResolver);
_parserTasks.add (task);
_webInfLibStats.increment();
if (LOG.isDebugEnabled())
task.setStatistic(new TimeStatistic());
}
}
}
}
/**
* Scan classes in WEB-INF/classes
*
* @param context the context for the scan
* @param parser the annotation parser to use
* @throws Exception if unable to scan and/or parse
*/
public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser)
throws Exception
{
Set<Handler> handlers = new HashSet<Handler>();
handlers.addAll(_discoverableAnnotationHandlers);
if (_classInheritanceHandler != null)
handlers.add(_classInheritanceHandler);
handlers.addAll(_containerInitializerAnnotationHandlers);
_webInfClassesStats = new CounterStatistic();
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
{
if (_parserTasks != null)
{
ParserTask task = new ParserTask(parser, handlers, dir, _webAppClassNameResolver);
_parserTasks.add(task);
_webInfClassesStats.increment();
if (LOG.isDebugEnabled())
task.setStatistic(new TimeStatistic());
}
}
}
/**
* Get the web-fragment.xml from a jar
*
* @param jar the jar to look in for a fragment
* @param frags the fragments previously found
* @return true if the fragment if found, or null of not found
* @throws Exception if unable to determine the the fragment contains
*/
public FragmentDescriptor getFragmentFromJar (Resource jar, List<FragmentDescriptor> frags)
throws Exception
{
//check if the jar has a web-fragment.xml
FragmentDescriptor d = null;
for (FragmentDescriptor frag: frags)
{
Resource fragResource = frag.getResource(); //eg jar:file:///a/b/c/foo.jar!/META-INF/web-fragment.xml
if (Resource.isContainedIn(fragResource,jar))
{
d = frag;
break;
}
}
return d;
}
public boolean isMetaDataComplete (WebDescriptor d)
{
return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
}
public static class ClassInheritanceMap extends ConcurrentHashMap<String, ConcurrentHashSet<String>>
{
@Override
public String toString()
{
return String.format("ClassInheritanceMap@%x{size=%d}",hashCode(),size());
}
}
}