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      protected ServiceLoader<ServletContainerInitializer> _loadedInitializers = null;
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         if (_loadedInitializers != null)
418             _loadedInitializers.reload();
419     }
420     
421     /** 
422      * @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
423      */
424     @Override
425     public void configure(WebAppContext context) throws Exception
426     {
427        context.getObjectFactory().addDecorator(new AnnotationDecorator(context));
428 
429        //Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any
430       
431        if (!context.getMetaData().isMetaDataComplete())
432        {
433            //If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
434            if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
435            {
436                _discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
437                _discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
438                _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
439            }
440        }
441 
442        //Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the
443        //classes so we can call their onStartup() methods correctly
444        createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
445 
446        if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
447            scanForAnnotations(context);   
448        
449        // Resolve container initializers
450        List<ContainerInitializer> initializers = 
451                    (List<ContainerInitializer>)context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
452        if (initializers != null && initializers.size()>0)
453        {
454            Map<String, Set<String>> map = ( Map<String, Set<String>>) context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
455            if (map == null)
456                LOG.warn ("ServletContainerInitializers: detected. Class hierarchy: empty");
457            for (ContainerInitializer i : initializers)
458                    i.resolveClasses(context,map);
459        }
460     }
461 
462 
463     /** 
464      * @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
465      */
466     @Override
467     public void postConfigure(WebAppContext context) throws Exception
468     {
469         ConcurrentHashMap<String, ConcurrentHashSet<String>> classMap = (ClassInheritanceMap)context.getAttribute(CLASS_INHERITANCE_MAP);
470         List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
471         
472         context.removeAttribute(CLASS_INHERITANCE_MAP);
473         if (classMap != null)
474             classMap.clear();
475         
476         context.removeAttribute(CONTAINER_INITIALIZERS);
477         if (initializers != null)
478             initializers.clear();
479         
480         if (_discoverableAnnotationHandlers != null)
481             _discoverableAnnotationHandlers.clear();
482 
483         _classInheritanceHandler = null;
484         if (_containerInitializerAnnotationHandlers != null)
485             _containerInitializerAnnotationHandlers.clear();
486 
487         if (_parserTasks != null)
488         {
489             _parserTasks.clear();
490             _parserTasks = null;
491         }
492         
493         super.postConfigure(context);
494     }
495     
496     
497     
498     /**
499      * Perform scanning of classes for annotations
500      * 
501      * @param context the context for the scan
502      * @throws Exception if unable to scan
503      */
504     protected void scanForAnnotations (WebAppContext context)
505     throws Exception
506     {
507         AnnotationParser parser = createAnnotationParser();
508         _parserTasks = new ArrayList<ParserTask>();
509 
510         long start = 0; 
511 
512 
513         if (LOG.isDebugEnabled())
514             LOG.debug("Annotation scanning commencing: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}, maxScanWait={}", 
515                       context.getServletContext().getEffectiveMajorVersion(), 
516                       context.getMetaData().isMetaDataComplete(),
517                       context.isConfigurationDiscovered(),
518                       isUseMultiThreading(context),
519                       getMaxScanWait(context));
520 
521              
522         parseContainerPath(context, parser);
523         //email from Rajiv Mordani jsrs 315 7 April 2010
524         //    If there is a <others/> then the ordering should be
525         //          WEB-INF/classes the order of the declared elements + others.
526         //    In case there is no others then it is
527         //          WEB-INF/classes + order of the elements.
528         parseWebInfClasses(context, parser);
529         parseWebInfLib (context, parser); 
530         
531         start = System.nanoTime();
532         
533         //execute scan, either effectively synchronously (1 thread only), or asynchronously (limited by number of processors available) 
534         final Semaphore task_limit = (isUseMultiThreading(context)? new Semaphore(Runtime.getRuntime().availableProcessors()):new Semaphore(1));     
535         final CountDownLatch latch = new CountDownLatch(_parserTasks.size());
536         final MultiException me = new MultiException();
537     
538         for (final ParserTask p:_parserTasks)
539         {
540             task_limit.acquire();
541             context.getServer().getThreadPool().execute(new Runnable()
542             {
543                 @Override
544                 public void run()
545                 {
546                    try
547                    {
548                        p.call();
549                    }
550                    catch (Exception e)
551                    {
552                        me.add(e);
553                    }
554                    finally
555                    {
556                        task_limit.release();
557                        latch.countDown();
558                    }
559                 }         
560             });
561         }
562        
563         boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);
564           
565         if (LOG.isDebugEnabled())
566         {
567             for (ParserTask p:_parserTasks)
568                 LOG.debug("Scanned {} in {}ms", p.getResource(), TimeUnit.MILLISECONDS.convert(p.getStatistic().getElapsed(), TimeUnit.NANOSECONDS));
569 
570             LOG.debug("Scanned {} container path jars, {} WEB-INF/lib jars, {} WEB-INF/classes dirs in {}ms for context {}",
571                     _containerPathStats.getTotal(), _webInfLibStats.getTotal(), _webInfClassesStats.getTotal(),
572                     (TimeUnit.MILLISECONDS.convert(System.nanoTime()-start, TimeUnit.NANOSECONDS)),
573                     context);
574         }
575 
576         if (timeout)
577             me.add(new Exception("Timeout scanning annotations"));
578         me.ifExceptionThrow();   
579     }
580 
581     
582     
583     /**
584      * @return a new AnnotationParser. This method can be overridden to use a different implementation of
585      * the AnnotationParser. Note that this is considered internal API.
586      */
587     protected AnnotationParser createAnnotationParser()
588     {
589         return new AnnotationParser();
590     }
591     
592     /**
593      * Check if we should use multiple threads to scan for annotations or not
594      * @param context the context of the multi threaded setting
595      * @return true if multi threading is enabled on the context, server, or via a System property.
596      * @see #MULTI_THREADED
597      */
598     protected boolean isUseMultiThreading(WebAppContext context)
599     {
600         //try context attribute to see if we should use multithreading
601         Object o = context.getAttribute(MULTI_THREADED);
602         if (o instanceof Boolean)
603         {
604             return ((Boolean)o).booleanValue();
605         }
606         //try server attribute to see if we should use multithreading
607         o = context.getServer().getAttribute(MULTI_THREADED);
608         if (o instanceof Boolean)
609         {
610             return ((Boolean)o).booleanValue();
611         }
612         //try system property to see if we should use multithreading
613         return Boolean.valueOf(System.getProperty(MULTI_THREADED, Boolean.toString(DEFAULT_MULTI_THREADED)));
614     }
615 
616    
617     
618     /**
619      * Work out how long we should wait for the async scanning to occur.
620      * 
621      * @param context the context of the max scan wait setting
622      * @return the max scan wait setting on the context, or server, or via a System property.
623      * @see #MAX_SCAN_WAIT
624      */
625     protected int getMaxScanWait (WebAppContext context)
626     {
627         //try context attribute to get max time in sec to wait for scan completion
628         Object o = context.getAttribute(MAX_SCAN_WAIT);
629         if (o != null && o instanceof Number)
630         {
631             return ((Number)o).intValue();
632         }
633         //try server attribute to get max time in sec to wait for scan completion
634         o = context.getServer().getAttribute(MAX_SCAN_WAIT);
635         if (o != null && o instanceof Number)
636         {
637             return ((Number)o).intValue();
638         }
639         //try system property to get max time in sec to wait for scan completion
640         return Integer.getInteger(MAX_SCAN_WAIT, DEFAULT_MAX_SCAN_WAIT).intValue();
641     }
642     
643     /** 
644      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
645      */
646     @Override
647     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
648     {
649         context.getObjectFactory().addDecorator(new AnnotationDecorator(context));
650     }
651 
652     public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis)
653     throws Exception
654     {
655         if (scis == null || scis.isEmpty())
656             return; // nothing to do
657 
658         List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
659         context.setAttribute(CONTAINER_INITIALIZERS, initializers);
660 
661         for (ServletContainerInitializer service : scis)
662         {
663             HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class);
664             ContainerInitializer initializer = null;
665             if (annotation != null)
666             {    
667                 //There is a HandlesTypes annotation on the on the ServletContainerInitializer
668                 Class<?>[] classes = annotation.value();
669                 if (classes != null)
670                 {
671 
672                     if (LOG.isDebugEnabled()){LOG.debug("HandlesTypes {} on initializer {}",Arrays.asList(classes),service.getClass());}
673                     
674                     initializer = new ContainerInitializer(service, classes);
675 
676                     //If we haven't already done so, we need to register a handler that will
677                     //process the whole class hierarchy to satisfy the ServletContainerInitializer
678                     if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
679                     {
680                         //MultiMap<String> map = new MultiMap<>();
681                         ConcurrentHashMap<String, ConcurrentHashSet<String>> map = new ClassInheritanceMap();
682                         context.setAttribute(CLASS_INHERITANCE_MAP, map);
683                         _classInheritanceHandler = new ClassInheritanceHandler(map);
684                     }
685 
686                     for (Class<?> c: classes)
687                     {
688                         //The value of one of the HandlesTypes classes is actually an Annotation itself so
689                         //register a handler for it
690                         if (c.isAnnotation())
691                         {
692                             if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName());
693                            _containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
694                         }
695                     }
696                 }
697                 else
698                 {
699                     initializer = new ContainerInitializer(service, null);
700                     if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass());
701                 }
702             }
703             else
704             {
705                 initializer = new ContainerInitializer(service, null);
706                 if (LOG.isDebugEnabled()) LOG.debug("No HandlesTypes annotation on initializer "+service.getClass());
707             }
708             
709             initializers.add(initializer);
710         }
711         
712         
713         //add a bean to the context which will call the servletcontainerinitializers when appropriate
714         ServletContainerInitializersStarter starter = (ServletContainerInitializersStarter)context.getAttribute(CONTAINER_INITIALIZER_STARTER);
715         if (starter != null)
716             throw new IllegalStateException("ServletContainerInitializersStarter already exists");
717         starter = new ServletContainerInitializersStarter(context);
718         context.setAttribute(CONTAINER_INITIALIZER_STARTER, starter);
719         context.addBean(starter, true);
720     }
721 
722     public Resource getJarFor (ServletContainerInitializer service) 
723     throws MalformedURLException, IOException
724     {
725         //try the thread context classloader to get the jar that loaded the class
726         URL jarURL = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class");
727         
728         //if for some reason that failed (eg we're in osgi and the TCCL does not know about the service) try the classloader that
729         //loaded the class
730         if (jarURL == null)
731             jarURL = service.getClass().getClassLoader().getResource(service.getClass().getName().replace('.','/')+".class");
732   
733         String loadingJarName = jarURL.toString();
734 
735         int i = loadingJarName.indexOf(".jar");
736         if (i < 0)
737             return null; //not from a jar
738         
739         loadingJarName = loadingJarName.substring(0,i+4);
740         loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
741         return Resource.newResource(loadingJarName, false);
742     }
743     
744     /**
745      * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
746      * from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
747      * 
748      * @param context the context for the jars
749      * @param sci the servlet container initializer
750      * @param sciResource  the resource for the servlet container initializer
751      * @return true if excluded
752      * @throws Exception if unable to determine exclusion
753      */
754     public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer sci, Resource sciResource)
755     throws Exception
756     {
757         if (sci == null)
758             throw new IllegalArgumentException("ServletContainerInitializer null");
759         if (context == null)
760             throw new IllegalArgumentException("WebAppContext null");
761         
762         if (LOG.isDebugEnabled()) LOG.debug("Checking {} for jar exclusion", sci);
763         
764         //A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
765         //of WEB-INF/lib jars
766         if (isFromContainerClassPath(context, sci))
767             return false;
768         
769         //If no ordering, nothing is excluded
770         if (context.getMetaData().getOrdering() == null)
771             return false;
772         
773         
774         List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
775 
776         //there is an ordering, but there are no jars resulting from the ordering, everything excluded
777         if (orderedJars.isEmpty())
778             return true;
779 
780         if (sciResource == null)
781             return false; //not from a jar therefore not from WEB-INF so not excludable
782         
783         URI loadingJarURI = sciResource.getURI();
784         boolean found = false;
785         Iterator<Resource> itor = orderedJars.iterator();
786         while (!found && itor.hasNext())
787         {
788             Resource r = itor.next();
789             found = r.getURI().equals(loadingJarURI);
790         }
791 
792         return !found;
793     }
794 
795 
796     /**
797      * Test if the ServletContainerIntializer is excluded by the 
798      * o.e.j.containerInitializerExclusionPattern
799      * 
800      * @param sci the ServletContainerIntializer
801      * @return true if the ServletContainerIntializer is excluded
802      */
803     public boolean matchesExclusionPattern(ServletContainerInitializer sci)
804     {
805         //no exclusion pattern, no SCI is excluded by it
806         if (_sciExcludePattern == null)
807             return false;
808         
809         //test if name of class matches the regex
810         if (LOG.isDebugEnabled()) LOG.debug("Checking {} against containerInitializerExclusionPattern",sci.getClass().getName());
811         return _sciExcludePattern.matcher(sci.getClass().getName()).matches();
812     }
813     
814     
815     /**
816      * Test if the ServletContainerInitializer is from the container classpath
817      * 
818      * @param context the context for the webapp classpath
819      * @param sci the ServletContainerIntializer
820      * @return true if ServletContainerIntializer is from container classpath
821      */
822     public boolean isFromContainerClassPath (WebAppContext context, ServletContainerInitializer sci)
823     {
824         if (sci == null)
825             return false;
826         return sci.getClass().getClassLoader()==context.getClassLoader().getParent();
827     }
828 
829     /**
830      * Get SCIs that are not excluded from consideration
831      * @param context the web app context 
832      * @return the list of non-excluded servlet container initializers
833      * @throws Exception if unable to get list 
834      */
835     public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
836     throws Exception
837     {
838         ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
839       
840         //We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
841         long start = 0;
842 
843         ClassLoader old = Thread.currentThread().getContextClassLoader();
844        
845         try
846         {        
847             if (LOG.isDebugEnabled())
848                 start = System.nanoTime();
849             Thread.currentThread().setContextClassLoader(context.getClassLoader());
850             _loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
851         }
852         finally
853         {
854             Thread.currentThread().setContextClassLoader(old);
855         }
856         
857         if (LOG.isDebugEnabled())
858             LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
859      
860         
861         Map<ServletContainerInitializer,Resource> sciResourceMap = new HashMap<ServletContainerInitializer,Resource>();
862         ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
863 
864         //Get initial set of SCIs that aren't from excluded jars or excluded by the containerExclusionPattern, or excluded
865         //because containerInitializerOrdering omits it
866         for (ServletContainerInitializer sci:_loadedInitializers)
867         { 
868             
869             if (matchesExclusionPattern(sci)) 
870             {
871                 if (LOG.isDebugEnabled()) LOG.debug("{} excluded by pattern", sci);
872                 continue;
873             }
874 
875             Resource sciResource = getJarFor(sci);
876             if (isFromExcludedJar(context, sci, sciResource)) 
877             { 
878                 if (LOG.isDebugEnabled()) LOG.debug("{} is from excluded jar", sci);
879                 continue;
880             }
881             
882             //check containerInitializerOrdering doesn't exclude it
883             String name = sci.getClass().getName();
884             if (initializerOrdering != null
885                 && (!initializerOrdering.hasWildcard() && initializerOrdering.getIndexOf(name) < 0))
886             {
887                 if (LOG.isDebugEnabled()) LOG.debug("{} is excluded by ordering", sci);
888                 continue;
889             }
890             
891             sciResourceMap.put(sci, sciResource);
892         }
893 
894         //Order the SCIs that are included
895         if (initializerOrdering != null && !initializerOrdering.isDefaultOrder())
896         {
897             if (LOG.isDebugEnabled())
898                 LOG.debug("Ordering ServletContainerInitializers with "+initializerOrdering);
899 
900             //There is an ordering that is not just "*".
901             //Arrange ServletContainerInitializers according to the ordering of classnames given, irrespective of coming from container or webapp classpaths
902             nonExcludedInitializers.addAll(sciResourceMap.keySet());
903             Collections.sort(nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
904         }
905         else
906         {
907             //No jetty-specific ordering specified, or just the wildcard value "*" specified.
908             //Fallback to ordering the ServletContainerInitializers according to:
909             //container classpath first, WEB-INF/classes then WEB-INF/lib (obeying any web.xml jar ordering)
910              
911             //no web.xml ordering defined, add SCIs in any order
912             if (context.getMetaData().getOrdering() == null)
913             {
914                 if (LOG.isDebugEnabled())  LOG.debug("No web.xml ordering, ServletContainerInitializers in random order");
915                 nonExcludedInitializers.addAll(sciResourceMap.keySet());
916             }
917             else
918             {
919                 if (LOG.isDebugEnabled())  LOG.debug("Ordering ServletContainerInitializers with ordering {}",context.getMetaData().getOrdering());
920                 for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
921                 {
922                     //add in SCIs from the container classpath
923                     if (entry.getKey().getClass().getClassLoader()==context.getClassLoader().getParent())
924                         nonExcludedInitializers.add(entry.getKey());
925                     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
926                         nonExcludedInitializers.add(entry.getKey());
927                 }
928               
929                 //add SCIs according to the ordering of its containing jar
930                 for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
931                 {
932                     for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
933                     {
934                       if (webInfJar.equals(entry.getValue()))
935                             nonExcludedInitializers.add(entry.getKey());
936                     }
937                 }
938             }
939         }   
940 
941         if (LOG.isDebugEnabled()) 
942         {
943             int i=0;
944             for (ServletContainerInitializer sci:nonExcludedInitializers)
945                 LOG.debug("ServletContainerInitializer: {} {} from {}",(++i), sci.getClass().getName(), sciResourceMap.get(sci));
946         }    
947         
948         return nonExcludedInitializers;
949     }
950 
951 
952     /**
953      * Jetty-specific extension that allows an ordering to be applied across ALL ServletContainerInitializers.
954      * 
955      * @param context the context for the initializer ordering configuration 
956      * @return the ordering of the ServletContainerIntializer's
957      */
958     public ServletContainerInitializerOrdering getInitializerOrdering (WebAppContext context)
959     {
960         if (context == null)
961             return null;
962         
963         String tmp = (String)context.getAttribute(SERVLET_CONTAINER_INITIALIZER_ORDER);
964         if (tmp == null || "".equals(tmp.trim()))
965             return null;
966         
967         return new ServletContainerInitializerOrdering(tmp);
968     }
969 
970 
971     /**
972      * Scan jars on container path.
973      * 
974      * @param context the context for the scan
975      * @param parser the parser to scan with
976      * @throws Exception if unable to scan
977      */
978     public void parseContainerPath (final WebAppContext context, final AnnotationParser parser) throws Exception
979     {
980         //always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
981         final Set<Handler> handlers = new HashSet<Handler>();
982         handlers.addAll(_discoverableAnnotationHandlers);
983         handlers.addAll(_containerInitializerAnnotationHandlers);
984         if (_classInheritanceHandler != null)
985             handlers.add(_classInheritanceHandler);
986 
987         _containerPathStats = new CounterStatistic();
988 
989         for (Resource r : context.getMetaData().getContainerResources())
990         {
991             //queue it up for scanning if using multithreaded mode
992             if (_parserTasks != null)
993             {
994                 ParserTask task = new ParserTask(parser, handlers, r, _containerClassNameResolver);
995                 _parserTasks.add(task);  
996                 _containerPathStats.increment();
997                 if (LOG.isDebugEnabled())
998                     task.setStatistic(new TimeStatistic());
999             }
1000         } 
1001     }
1002 
1003 
1004     /**
1005      * Scan jars in WEB-INF/lib
1006      * 
1007      * @param context the context for the scan
1008      * @param parser the annotation parser to use
1009      * @throws Exception if unable to scan and/or parse
1010      */
1011     public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser) throws Exception
1012     {   
1013         List<FragmentDescriptor> frags = context.getMetaData().getFragments();
1014 
1015         //email from Rajiv Mordani jsrs 315 7 April 2010
1016         //jars that do not have a web-fragment.xml are still considered fragments
1017         //they have to participate in the ordering
1018         ArrayList<URI> webInfUris = new ArrayList<URI>();
1019 
1020         List<Resource> jars = null;
1021         
1022         if (context.getMetaData().getOrdering() != null)
1023             jars = context.getMetaData().getOrderedWebInfJars();
1024         else
1025             //No ordering just use the jars in any order
1026             jars = context.getMetaData().getWebInfJars();
1027 
1028         _webInfLibStats = new CounterStatistic();
1029 
1030         for (Resource r : jars)
1031         {
1032             //for each jar, we decide which set of annotations we need to parse for
1033             final Set<Handler> handlers = new HashSet<Handler>();
1034 
1035             FragmentDescriptor f = getFragmentFromJar(r, frags);
1036 
1037             //if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
1038             // but yet we still need to do the scanning for the classes on behalf of  the servletcontainerinitializers
1039             //if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
1040             //or if it has a fragment we scan it if it is not metadata complete
1041             if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null ||  !_containerInitializerAnnotationHandlers.isEmpty())
1042             {
1043                 //register the classinheritance handler if there is one
1044                 if (_classInheritanceHandler != null)
1045                     handlers.add(_classInheritanceHandler);
1046 
1047                 //register the handlers for the @HandlesTypes values that are themselves annotations if there are any
1048                 handlers.addAll(_containerInitializerAnnotationHandlers);
1049 
1050                 //only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
1051                 if (f == null || !isMetaDataComplete(f))
1052                     handlers.addAll(_discoverableAnnotationHandlers);
1053 
1054                 if (_parserTasks != null)
1055                 {
1056                     ParserTask task = new ParserTask(parser, handlers,r, _webAppClassNameResolver);
1057                     _parserTasks.add (task);
1058                     _webInfLibStats.increment();
1059                     if (LOG.isDebugEnabled())
1060                         task.setStatistic(new TimeStatistic());
1061                 }
1062             }
1063         }
1064     }
1065 
1066 
1067     /**
1068      * Scan classes in WEB-INF/classes
1069      * 
1070      * @param context the context for the scan
1071      * @param parser the annotation parser to use
1072      * @throws Exception if unable to scan and/or parse
1073      */
1074     public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser)
1075     throws Exception
1076     {
1077         Set<Handler> handlers = new HashSet<Handler>();
1078         handlers.addAll(_discoverableAnnotationHandlers);
1079         if (_classInheritanceHandler != null)
1080             handlers.add(_classInheritanceHandler);
1081         handlers.addAll(_containerInitializerAnnotationHandlers);
1082 
1083         _webInfClassesStats = new CounterStatistic();
1084 
1085         for (Resource dir : context.getMetaData().getWebInfClassesDirs())
1086         {
1087             if (_parserTasks != null)
1088             {
1089                 ParserTask task = new ParserTask(parser, handlers, dir, _webAppClassNameResolver);
1090                 _parserTasks.add(task);
1091                 _webInfClassesStats.increment();
1092                 if (LOG.isDebugEnabled())
1093                     task.setStatistic(new TimeStatistic());
1094             }
1095         }
1096     }
1097 
1098 
1099 
1100     /**
1101      * Get the web-fragment.xml from a jar
1102      * 
1103      * @param jar the jar to look in for a fragment
1104      * @param frags the fragments previously found
1105      * @return true if the fragment if found, or null of not found
1106      * @throws Exception if unable to determine the the fragment contains
1107      */
1108     public FragmentDescriptor getFragmentFromJar (Resource jar,  List<FragmentDescriptor> frags)
1109     throws Exception
1110     {
1111         //check if the jar has a web-fragment.xml
1112         FragmentDescriptor d = null;
1113         for (FragmentDescriptor frag: frags)
1114         {
1115             Resource fragResource = frag.getResource(); //eg jar:file:///a/b/c/foo.jar!/META-INF/web-fragment.xml
1116             if (Resource.isContainedIn(fragResource,jar))
1117             {
1118                 d = frag;
1119                 break;
1120             }
1121         }
1122         return d;
1123     }
1124 
1125     public boolean isMetaDataComplete (WebDescriptor d)
1126     {
1127         return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
1128     }
1129 
1130     public static class ClassInheritanceMap extends ConcurrentHashMap<String, ConcurrentHashSet<String>>
1131     {
1132         
1133         @Override
1134         public String toString()
1135         {
1136             return String.format("ClassInheritanceMap@%x{size=%d}",hashCode(),size());
1137         }
1138     }
1139 }
1140 
1141