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