View Javadoc

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