View Javadoc

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