View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.annotations;
20  
21  import java.io.IOException;
22  import java.net.MalformedURLException;
23  import java.net.URI;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.ServiceLoader;
33  import java.util.Set;
34  import java.util.concurrent.Callable;
35  import java.util.concurrent.ConcurrentHashMap;
36  import java.util.concurrent.CountDownLatch;
37  import java.util.concurrent.Semaphore;
38  import java.util.concurrent.TimeUnit;
39  
40  import javax.servlet.ServletContainerInitializer;
41  import javax.servlet.annotation.HandlesTypes;
42  
43  import org.eclipse.jetty.annotations.AnnotationParser.Handler;
44  import org.eclipse.jetty.plus.annotation.ContainerInitializer;
45  import org.eclipse.jetty.util.ConcurrentHashSet;
46  import org.eclipse.jetty.util.MultiException;
47  import org.eclipse.jetty.util.log.Log;
48  import org.eclipse.jetty.util.log.Logger;
49  import org.eclipse.jetty.util.resource.Resource;
50  import org.eclipse.jetty.webapp.AbstractConfiguration;
51  import org.eclipse.jetty.webapp.FragmentDescriptor;
52  import org.eclipse.jetty.webapp.MetaDataComplete;
53  import org.eclipse.jetty.webapp.WebAppContext;
54  import org.eclipse.jetty.webapp.WebDescriptor;
55  
56  /**
57   * Configuration for Annotations
58   *
59   *
60   */
61  public class AnnotationConfiguration extends AbstractConfiguration
62  {
63      private static final Logger LOG = Log.getLogger(AnnotationConfiguration.class);
64      
65      public static final String SERVLET_CONTAINER_INITIALIZER_ORDER = "org.eclipse.jetty.containerInitializerOrder";
66      public static final String CLASS_INHERITANCE_MAP  = "org.eclipse.jetty.classInheritanceMap";
67      public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers";
68      public static final String CONTAINER_INITIALIZER_STARTER = "org.eclipse.jetty.containerInitializerStarter";
69      public static final String MULTI_THREADED = "org.eclipse.jetty.annotations.multiThreaded";
70      public static final String MAX_SCAN_WAIT = "org.eclipse.jetty.annotations.maxWait";
71      
72      public static final int DEFAULT_MAX_SCAN_WAIT = 60; /* time in sec */  
73      public static final boolean DEFAULT_MULTI_THREADED = true;
74      
75      protected List<AbstractDiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<AbstractDiscoverableAnnotationHandler>();
76      protected ClassInheritanceHandler _classInheritanceHandler;
77      protected List<ContainerInitializerAnnotationHandler> _containerInitializerAnnotationHandlers = new ArrayList<ContainerInitializerAnnotationHandler>();
78     
79      protected List<ParserTask> _parserTasks;
80      protected WebAppClassNameResolver _webAppClassNameResolver;
81      protected ContainerClassNameResolver _containerClassNameResolver;
82      
83      
84      
85      /**
86       * ParserTask
87       *
88       * Task to executing scanning of a resource for annotations.
89       * 
90       */
91      public class ParserTask implements Callable<Void>
92      {
93          protected Exception _exception;
94          protected final AnnotationParser _parser;
95          protected final Set<? extends Handler> _handlers;
96          protected final ClassNameResolver _resolver;
97          protected final Resource _resource;
98        
99          
100         public ParserTask (AnnotationParser parser, Set<? extends Handler>handlers, Resource resource, ClassNameResolver resolver)
101         {
102             _parser = parser;
103             _handlers = handlers;
104             _resolver = resolver;
105             _resource = resource;
106         }
107 
108         public Void call() throws Exception
109         {
110             if (_parser != null)
111                 _parser.parse(_handlers, _resource, _resolver); 
112             return null;
113         }
114     }
115 
116     /**
117      * WebAppClassNameResolver
118      *
119      * Checks to see if a classname belongs to hidden or visible packages when scanning,
120      * and whether a classname that is a duplicate should override a previously
121      * scanned classname. 
122      * 
123      * This is analogous to the management of classes that the WebAppClassLoader is doing,
124      * however we don't want to load the classes at this point so we are doing it on
125      * the name only.
126      * 
127      */
128     public class WebAppClassNameResolver implements ClassNameResolver
129     {
130         private WebAppContext _context;
131 
132         public WebAppClassNameResolver (WebAppContext context)
133         {
134             _context = context;
135         }
136 
137         public boolean isExcluded (String name)
138         {
139             if (_context.isSystemClass(name)) return true;
140             if (_context.isServerClass(name)) return false;
141             return false;
142         }
143 
144         public boolean shouldOverride (String name)
145         {
146             //looking at webapp classpath, found already-parsed class 
147             //of same name - did it come from system or duplicate in webapp?
148             if (_context.isParentLoaderPriority())
149                 return false;
150             return true;
151         }
152     }
153 
154     
155     /**
156      * ContainerClassNameResolver
157      *
158      * Checks to see if a classname belongs to a hidden or visible package
159      * when scanning for annotations and thus whether it should be excluded from
160      * consideration or not.
161      * 
162      * This is analogous to the management of classes that the WebAppClassLoader is doing,
163      * however we don't want to load the classes at this point so we are doing it on
164      * the name only.
165      * 
166      */
167     public class ContainerClassNameResolver implements ClassNameResolver
168     { 
169         private WebAppContext _context;
170         
171         public ContainerClassNameResolver (WebAppContext context)
172         {
173             _context = context;
174         }
175         public boolean isExcluded (String name)
176         {
177             if (_context.isSystemClass(name)) return false;
178             if (_context.isServerClass(name)) return true;
179             return false;
180         }
181 
182         public boolean shouldOverride (String name)
183         {
184             //visiting the container classpath, 
185             if (_context.isParentLoaderPriority())
186                 return true;
187             return false;
188         }
189     }
190     
191     
192     /**
193      * ServletContainerInitializerOrdering
194      *
195      * A list of classnames of ServletContainerInitializers in the order in which
196      * they are to be called back. One name only in the list can be "*", which is a
197      * wildcard which matches any other ServletContainerInitializer name not already
198      * matched.
199      */
200     public class ServletContainerInitializerOrdering 
201     {
202         private Map<String, Integer> _indexMap = new HashMap<String, Integer>();
203         private Integer _star = null;
204         private String _ordering = null;
205         
206         public ServletContainerInitializerOrdering (String ordering)
207         {
208             if (ordering != null)
209             {
210                 _ordering = ordering;
211                 
212                 String[] tmp = ordering.split(",");
213                 
214                 for (int i=0; i<tmp.length; i++)
215                 {
216                     String s = tmp[i].trim();
217                     _indexMap.put(s, Integer.valueOf(i));
218                     if ("*".equals(s))
219                     {
220                         if (_star != null)
221                             throw new IllegalArgumentException("Duplicate wildcards in ServletContainerInitializer ordering "+ordering);
222                         _star = Integer.valueOf(i);
223                     }
224                     
225                 }
226             }
227         }
228         
229         /**
230          * True if "*" is one of the values.
231          * @return
232          */
233         public boolean hasWildcard()
234         {
235             return _star != null;
236         }
237         
238         /**
239          * Get the index of the "*" element, if it is specified. -1 otherwise.
240          * @return
241          */
242         public int getWildcardIndex()
243         {
244             if (!hasWildcard())
245                 return -1;
246             return _star.intValue();
247         }
248         
249         /**
250          * True if the ordering contains a single value of "*"
251          * @return
252          */
253         public boolean isDefaultOrder ()
254         {
255             return (getSize() == 1 && hasWildcard());
256         }
257         
258         /**
259          * Get the order index of the given classname
260          * @param name
261          * @return
262          */
263         public int getIndexOf (String name)
264         {
265             Integer i = _indexMap.get(name);
266             if (i == null)
267                 return -1;
268             return i.intValue();
269         }
270         
271         /**
272          * Get the number of elements of the ordering
273          * @return
274          */
275         public int getSize()
276         {
277             return _indexMap.size();
278         }
279         
280         public String toString()
281         {
282             if (_ordering == null)
283                 return "";
284             return _ordering;
285         }
286     }
287     
288     
289     
290     /**
291      * ServletContainerInitializerComparator
292      *
293      * Comparator impl that orders a set of ServletContainerInitializers according to the
294      * list of classnames (optionally containing a "*" wildcard character) established in a
295      * ServletContainerInitializerOrdering.
296      * @see ServletContainerInitializerOrdering
297      */
298     public class ServletContainerInitializerComparator implements Comparator<ServletContainerInitializer>
299     {
300         private ServletContainerInitializerOrdering _ordering;
301         
302         
303         public ServletContainerInitializerComparator (ServletContainerInitializerOrdering ordering)
304         {
305             _ordering = ordering;
306         }
307 
308         @Override
309         public int compare(ServletContainerInitializer sci1, ServletContainerInitializer sci2)
310         {
311             String c1 = (sci1 != null? sci1.getClass().getName() : null);
312             String c2 = (sci2 != null? sci2.getClass().getName() : null);
313 
314             if (c1 == null && c2 == null)
315                 return 0;
316             
317             int i1 = _ordering.getIndexOf(c1);
318             if (i1 < 0 && _ordering.hasWildcard())
319                 i1 = _ordering.getWildcardIndex();
320             int i2 = _ordering.getIndexOf(c2);
321             if (i2 < 0 && _ordering.hasWildcard())
322                 i2 = _ordering.getWildcardIndex();
323            
324             return Integer.compare(i1, i2);
325         }
326     }
327     
328     @Override
329     public void preConfigure(final WebAppContext context) throws Exception
330     {
331         _webAppClassNameResolver = new WebAppClassNameResolver(context);
332         _containerClassNameResolver = new ContainerClassNameResolver(context);
333     }
334 
335    
336     public void addDiscoverableAnnotationHandler(AbstractDiscoverableAnnotationHandler handler)
337     {
338         _discoverableAnnotationHandlers.add(handler);
339     }
340 
341     @Override
342     public void deconfigure(WebAppContext context) throws Exception
343     {
344         context.removeAttribute(CLASS_INHERITANCE_MAP);
345         context.removeAttribute(CONTAINER_INITIALIZERS);
346         ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
347         if (starter != null)
348         {
349             context.removeBean(starter);
350             context.removeAttribute(CONTAINER_INITIALIZER_STARTER);
351         }
352     }
353     
354     /** 
355      * @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
356      */
357     @Override
358     public void configure(WebAppContext context) throws Exception
359     {
360        context.addDecorator(new AnnotationDecorator(context));
361 
362        //Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any
363       
364        if (!context.getMetaData().isMetaDataComplete())
365        {
366            //If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
367            if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
368            {
369                _discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
370                _discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
371                _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
372            }
373        }
374 
375        //Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the
376        //classes so we can call their onStartup() methods correctly
377        createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
378 
379        if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
380            scanForAnnotations(context);      
381     }
382 
383 
384 
385     /** 
386      * @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
387      */
388     @Override
389     public void postConfigure(WebAppContext context) throws Exception
390     {
391         ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap = (ConcurrentHashMap<String, ConcurrentHashSet<String>>)context.getAttribute(CLASS_INHERITANCE_MAP);
392         List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
393         
394         context.removeAttribute(CLASS_INHERITANCE_MAP);
395         if (classMap != null)
396             classMap.clear();
397         
398         context.removeAttribute(CONTAINER_INITIALIZERS);
399         if (initializers != null)
400             initializers.clear();
401         
402         if (_discoverableAnnotationHandlers != null)
403             _discoverableAnnotationHandlers.clear();
404 
405         _classInheritanceHandler = null;
406         if (_containerInitializerAnnotationHandlers != null)
407             _containerInitializerAnnotationHandlers.clear();
408 
409         if (_parserTasks != null)
410         {
411             _parserTasks.clear();
412             _parserTasks = null;
413         }
414         
415         super.postConfigure(context);
416     }
417     
418     
419     
420     /**
421      * Perform scanning of classes for annotations
422      * 
423      * @param context
424      * @throws Exception
425      */
426     protected void scanForAnnotations (WebAppContext context)
427     throws Exception
428     {
429         AnnotationParser parser = createAnnotationParser();
430         _parserTasks = new ArrayList<ParserTask>();
431 
432         long start = 0; 
433         
434         if (LOG.isDebugEnabled()) 
435         {
436             LOG.debug("Scanning for annotations: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}, maxScanWait={}", 
437                       context.getServletContext().getEffectiveMajorVersion(), 
438                       context.getMetaData().isMetaDataComplete(),
439                       context.isConfigurationDiscovered(),
440                       isUseMultiThreading(context),
441                       getMaxScanWait(context));
442         }
443              
444         parseContainerPath(context, parser);
445         //email from Rajiv Mordani jsrs 315 7 April 2010
446         //    If there is a <others/> then the ordering should be
447         //          WEB-INF/classes the order of the declared elements + others.
448         //    In case there is no others then it is
449         //          WEB-INF/classes + order of the elements.
450         parseWebInfClasses(context, parser);
451         parseWebInfLib (context, parser); 
452         
453 
454         if (LOG.isDebugEnabled())
455             start = System.nanoTime();
456         
457         //execute scan, either effectively synchronously (1 thread only), or asychronously (limited by number of processors available) 
458         final Semaphore task_limit = (isUseMultiThreading(context)? new Semaphore(Runtime.getRuntime().availableProcessors()):new Semaphore(1));     
459         final CountDownLatch latch = new CountDownLatch(_parserTasks.size());
460         final MultiException me = new MultiException();
461     
462         for (final ParserTask p:_parserTasks)
463         {
464             task_limit.acquire();
465             context.getServer().getThreadPool().execute(new Runnable()
466             {
467                 @Override
468                 public void run()
469                 {
470                    try
471                    {
472                        p.call();
473                    }
474                    catch (Exception e)
475                    {
476                        me.add(e);
477                    }
478                    finally
479                    {
480                        task_limit.release();
481                        latch.countDown();
482                    }
483                 }         
484             });
485         }
486        
487         boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);
488         
489         if (LOG.isDebugEnabled())
490             LOG.debug("Annotation parsing millisec={}",(TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS)));
491         
492         if (timeout)
493             me.add(new Exception("Timeout scanning annotations"));
494         me.ifExceptionThrow();   
495     }
496 
497     
498     
499     /**
500      * @return a new AnnotationParser. This method can be overridden to use a different implementation of
501      * the AnnotationParser. Note that this is considered internal API.
502      */
503     protected AnnotationParser createAnnotationParser()
504     {
505         return new AnnotationParser();
506     }
507     
508     /**
509      * Check if we should use multiple threads to scan for annotations or not
510      * @param context
511      * @return
512      */
513     protected boolean isUseMultiThreading(WebAppContext context)
514     {
515         //try context attribute to see if we should use multithreading
516         Object o = context.getAttribute(MULTI_THREADED);
517         if (o instanceof Boolean)
518         {
519             return ((Boolean)o).booleanValue();
520         }
521         //try server attribute to see if we should use multithreading
522         o = context.getServer().getAttribute(MULTI_THREADED);
523         if (o instanceof Boolean)
524         {
525             return ((Boolean)o).booleanValue();
526         }
527         //try system property to see if we should use multithreading
528         return Boolean.valueOf(System.getProperty(MULTI_THREADED, Boolean.toString(DEFAULT_MULTI_THREADED)));
529     }
530 
531    
532     
533     /**
534      * Work out how long we should wait for the async scanning to occur.
535      * 
536      * @param context
537      * @return
538      */
539     protected int getMaxScanWait (WebAppContext context)
540     {
541         //try context attribute to get max time in sec to wait for scan completion
542         Object o = context.getAttribute(MAX_SCAN_WAIT);
543         if (o != null && o instanceof Number)
544         {
545             return ((Number)o).intValue();
546         }
547         //try server attribute to get max time in sec to wait for scan completion
548         o = context.getServer().getAttribute(MAX_SCAN_WAIT);
549         if (o != null && o instanceof Number)
550         {
551             return ((Number)o).intValue();
552         }
553         //try system property to get max time in sec to wait for scan completion
554         return Integer.getInteger(MAX_SCAN_WAIT, DEFAULT_MAX_SCAN_WAIT).intValue();
555     }
556     
557     
558     
559     /** 
560      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
561      */
562     @Override
563     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
564     {
565         context.addDecorator(new AnnotationDecorator(context));
566     }
567 
568 
569     
570     /**
571      * @param context
572      * @param scis
573      * @throws Exception
574      */
575     public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis)
576     throws Exception
577     {
578         if (scis == null || scis.isEmpty())
579             return; // nothing to do
580 
581         List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
582         context.setAttribute(CONTAINER_INITIALIZERS, initializers);
583 
584         for (ServletContainerInitializer service : scis)
585         {
586             HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class);
587             ContainerInitializer initializer = null;
588             if (annotation != null)
589             {    
590                 //There is a HandlesTypes annotation on the on the ServletContainerInitializer
591                 Class[] classes = annotation.value();
592                 if (classes != null)
593                 {
594                     initializer = new ContainerInitializer(service, classes);
595 
596                     //If we haven't already done so, we need to register a handler that will
597                     //process the whole class hierarchy to satisfy the ServletContainerInitializer
598                     if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
599                     {
600                         //MultiMap<String> map = new MultiMap<>();
601                         ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ConcurrentHashMap<String, ConcurrentHashSet<String>>();
602                         context.setAttribute(CLASS_INHERITANCE_MAP, map);
603                         _classInheritanceHandler = new ClassInheritanceHandler(map);
604                     }
605 
606                     for (Class c: classes)
607                     {
608                         //The value of one of the HandlesTypes classes is actually an Annotation itself so
609                         //register a handler for it
610                         if (c.isAnnotation())
611                         {
612                             if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName());
613                            _containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
614                         }
615                     }
616                 }
617                 else
618                 {
619                     initializer = new ContainerInitializer(service, null);
620                     if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass());
621                 }
622             }
623             else
624             {
625                 initializer = new ContainerInitializer(service, null);
626                 if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass());
627             }
628             
629             initializers.add(initializer);
630         }
631         
632         
633         //add a bean to the context which will call the servletcontainerinitializers when appropriate
634         ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
635         if (starter != null)
636             throw new IllegalStateException("ServletContainerInitializersStarter already exists");
637         starter = new ServletContainerInitializersStarter(context);
638         context.setAttribute(CONTAINER_INITIALIZER_STARTER, starter);
639         context.addBean(starter, true);
640     }
641 
642     
643     public Resource getJarFor (ServletContainerInitializer service) 
644     throws MalformedURLException, IOException
645     {
646         String loadingJarName = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class").toString();
647 
648         int i = loadingJarName.indexOf(".jar");
649         if (i < 0)
650             return null; //not from a jar
651         
652         loadingJarName = loadingJarName.substring(0,i+4);
653         loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
654         return Resource.newResource(loadingJarName);
655     }
656     
657 
658     /**
659      * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
660      * from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
661      * @param context
662      * @param sci
663      * @return true if excluded
664      */
665     public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer sci, Resource sciResource)
666     throws Exception
667     {
668         if (sci == null)
669             throw new IllegalArgumentException("ServletContainerInitializer null");
670         if (context == null)
671             throw new IllegalArgumentException("WebAppContext null");
672         
673         //A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
674         //of WEB-INF/lib jars
675         if (sci.getClass().getClassLoader()==context.getClassLoader().getParent())
676             return false;
677         
678         List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
679 
680         //If no ordering, nothing is excluded
681         if (context.getMetaData().getOrdering() == null)
682             return false;
683 
684         //there is an ordering, but there are no jars resulting from the ordering, everything excluded
685         if (orderedJars.isEmpty())
686             return true;
687 
688         if (sciResource == null)
689             return false; //not from a jar therefore not from WEB-INF so not excludable
690         
691         URI loadingJarURI = sciResource.getURI();
692         boolean found = false;
693         Iterator<Resource> itor = orderedJars.iterator();
694         while (!found && itor.hasNext())
695         {
696             Resource r = itor.next();
697             found = r.getURI().equals(loadingJarURI);
698         }
699 
700         return !found;
701     }
702 
703 
704 
705     /**
706      * @param context
707      * @return list of non-excluded {@link ServletContainerInitializer}s
708      * @throws Exception
709      */
710     public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
711     throws Exception
712     {
713         ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
714 
715         
716         //We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
717         long start = 0;
718         if (LOG.isDebugEnabled())
719             start = System.nanoTime();
720         ServiceLoader<ServletContainerInitializer> loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class, context.getClassLoader());
721         if (LOG.isDebugEnabled())
722             LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
723         
724         //no ServletContainerInitializers found
725         if (loadedInitializers == null)
726             return Collections.emptyList();
727 
728         ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
729        
730         if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
731         {
732             if (LOG.isDebugEnabled())
733                 LOG.debug("Ordering ServletContainerInitializers with "+initializerOrdering);
734             
735             //There is an ordering that is not just "*".
736             //Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
737             for (ServletContainerInitializer sci:loadedInitializers)
738             {
739                 Resource sciResource = getJarFor(sci);
740                 if (!isFromExcludedJar(context, sci, sciResource))
741                 {
742                     String name = sci.getClass().getName();
743                     if (initializerOrdering.getIndexOf(name) >= 0 || initializerOrdering.hasWildcard())
744                         nonExcludedInitializers.add(sci);
745                 }
746             }
747       
748             //apply the ordering
749             Collections.sort(nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
750         }
751         else
752         {
753             //No ordering specified, or just the wildcard value "*" specified.
754             //Fallback to ordering the ServletContainerInitializers according to:
755             //container classpath first, WEB-INF/clases then WEB-INF/lib (obeying any web.xml jar ordering)
756             if (LOG.isDebugEnabled())
757                 LOG.debug("Ordering ServletContainerInitializers as container path, webapp path");
758             
759             Map<ServletContainerInitializer,Resource> webappPathInitializerResourceMap = new HashMap<ServletContainerInitializer,Resource>();
760             for (ServletContainerInitializer sci : loadedInitializers)
761             {
762                 //if its on the container's classpath then add it
763                 if (sci.getClass().getClassLoader() == context.getClassLoader().getParent())
764                 {
765                     nonExcludedInitializers.add(sci);
766                 }
767                 else
768                 {
769                     //if on the webapp's classpath then check the containing jar is not excluded from consideration
770                     Resource sciResource = getJarFor(sci);        
771                     if (!isFromExcludedJar(context, sci, sciResource))
772                     {   
773                         webappPathInitializerResourceMap.put(sci, sciResource);                       
774                     }
775                 }
776             }            
777 
778             //add the webapp classpath ones according to any web.xml ordering
779             if (context.getMetaData().getOrdering() == null)
780                 nonExcludedInitializers.addAll(webappPathInitializerResourceMap.keySet()); //no ordering, just add them
781             else
782             {
783                 //add in any ServletContainerInitializers which are not in a jar, as they must be from WEB-INF/classes
784                 for (Map.Entry<ServletContainerInitializer, Resource> entry:webappPathInitializerResourceMap.entrySet())
785                 {
786                     if (entry.getValue() == null)
787                         nonExcludedInitializers.add(entry.getKey());
788                 }
789                 
790                 //add ServletContainerInitializers according to the ordering of its containing jar
791                 for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
792                 {
793                     for (Map.Entry<ServletContainerInitializer, Resource> entry:webappPathInitializerResourceMap.entrySet())
794                     {
795                         if (webInfJar.equals(entry.getValue()))
796                             nonExcludedInitializers.add(entry.getKey());
797                     }
798                 }
799             }
800         }   
801         
802         return nonExcludedInitializers;
803     }
804 
805 
806     /**
807      * Jetty-specific extension that allows an ordering to be applied across ALL ServletContainerInitializers.
808      * 
809      * @return
810      */
811     public ServletContainerInitializerOrdering getInitializerOrdering (WebAppContext context)
812     {
813         if (context == null)
814             return null;
815         
816         String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
817         if (tmp == null || "".equals(tmp.trim()))
818             return null;
819         
820         return new ServletContainerInitializerOrdering(tmp);
821     }
822 
823 
824     /**
825      * Scan jars on container path.
826      * 
827      * @param context
828      * @param parser
829      * @throws Exception
830      */
831     public void parseContainerPath (final WebAppContext context, final AnnotationParser parser) throws Exception
832     {
833         //if no pattern for the container path is defined, then by default scan NOTHING
834         LOG.debug("Scanning container jars");   
835         
836         //always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
837         final Set<Handler> handlers = new HashSet<Handler>();
838         handlers.addAll(_discoverableAnnotationHandlers);
839         handlers.addAll(_containerInitializerAnnotationHandlers);
840         if (_classInheritanceHandler != null)
841             handlers.add(_classInheritanceHandler);
842 
843         for (Resource r : context.getMetaData().getContainerResources())
844         {
845             //queue it up for scanning if using multithreaded mode
846             if (_parserTasks != null)
847                 _parserTasks.add(new ParserTask(parser, handlers, r, _containerClassNameResolver));  
848         } 
849     }
850 
851 
852     /**
853      * Scan jars in WEB-INF/lib
854      * 
855      * @param context
856      * @param parser
857      * @throws Exception
858      */
859     public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser) throws Exception
860     {   
861         LOG.debug("Scanning WEB-INF/lib jars");
862         
863         List<FragmentDescriptor> frags = context.getMetaData().getFragments();
864 
865         //email from Rajiv Mordani jsrs 315 7 April 2010
866         //jars that do not have a web-fragment.xml are still considered fragments
867         //they have to participate in the ordering
868         ArrayList<URI> webInfUris = new ArrayList<URI>();
869 
870         List<Resource> jars = context.getMetaData().getOrderedWebInfJars();
871 
872         //No ordering just use the jars in any order
873         if (jars == null || jars.isEmpty())
874             jars = context.getMetaData().getWebInfJars();
875 
876         for (Resource r : jars)
877         {
878             //for each jar, we decide which set of annotations we need to parse for
879             final Set<Handler> handlers = new HashSet<Handler>();
880 
881             FragmentDescriptor f = getFragmentFromJar(r, frags);
882 
883             //if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
884             // but yet we still need to do the scanning for the classes on behalf of  the servletcontainerinitializers
885             //if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
886             //or if it has a fragment we scan it if it is not metadata complete
887             if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null ||  !_containerInitializerAnnotationHandlers.isEmpty())
888             {
889                 //register the classinheritance handler if there is one
890                 if (_classInheritanceHandler != null)
891                     handlers.add(_classInheritanceHandler);
892 
893                 //register the handlers for the @HandlesTypes values that are themselves annotations if there are any
894                 handlers.addAll(_containerInitializerAnnotationHandlers);
895 
896                 //only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
897                 if (f == null || !isMetaDataComplete(f))
898                     handlers.addAll(_discoverableAnnotationHandlers);
899 
900                 if (_parserTasks != null)
901                     _parserTasks.add (new ParserTask(parser, handlers,r, _webAppClassNameResolver));
902             }
903         }
904 
905     }
906 
907 
908     /**
909      * Scan classes in WEB-INF/classes
910      * 
911      * @param context
912      * @param parser
913      * @throws Exception
914      */
915     public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser)
916     throws Exception
917     {
918         LOG.debug("Scanning WEB-INF/classes");
919        
920         Set<Handler> handlers = new HashSet<Handler>();
921         handlers.addAll(_discoverableAnnotationHandlers);
922         if (_classInheritanceHandler != null)
923             handlers.add(_classInheritanceHandler);
924         handlers.addAll(_containerInitializerAnnotationHandlers);
925 
926         for (Resource dir : context.getMetaData().getWebInfClassesDirs())
927         {
928             if (_parserTasks != null)
929                 _parserTasks.add(new ParserTask(parser, handlers, dir, _webAppClassNameResolver));
930         }
931     }
932 
933 
934 
935     /**
936      * Get the web-fragment.xml from a jar
937      * 
938      * @param jar
939      * @param frags
940      * @return the fragment if found, or null of not found
941      * @throws Exception
942      */
943     public FragmentDescriptor getFragmentFromJar (Resource jar,  List<FragmentDescriptor> frags)
944     throws Exception
945     {
946         //check if the jar has a web-fragment.xml
947         FragmentDescriptor d = null;
948         for (FragmentDescriptor frag: frags)
949         {
950             Resource fragResource = frag.getResource(); //eg jar:file:///a/b/c/foo.jar!/META-INF/web-fragment.xml
951             if (Resource.isContainedIn(fragResource,jar))
952             {
953                 d = frag;
954                 break;
955             }
956         }
957         return d;
958     }
959 
960     public boolean isMetaDataComplete (WebDescriptor d)
961     {
962         return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
963     }
964 }