View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.annotations;
20  
21  import java.net.URI;
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.ServiceLoader;
26  import java.util.Set;
27  
28  import javax.servlet.ServletContainerInitializer;
29  import javax.servlet.ServletContext;
30  import javax.servlet.ServletException;
31  import javax.servlet.annotation.HandlesTypes;
32  
33  
34  import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
35  import org.eclipse.jetty.plus.annotation.ContainerInitializer;
36  import org.eclipse.jetty.util.MultiMap;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.eclipse.jetty.util.resource.Resource;
40  import org.eclipse.jetty.webapp.Configuration;
41  import org.eclipse.jetty.webapp.AbstractConfiguration;
42  import org.eclipse.jetty.webapp.Descriptor;
43  import org.eclipse.jetty.webapp.DiscoveredAnnotation;
44  import org.eclipse.jetty.webapp.FragmentDescriptor;
45  import org.eclipse.jetty.webapp.MetaDataComplete;
46  import org.eclipse.jetty.webapp.WebAppContext;
47  import org.eclipse.jetty.webapp.WebDescriptor;
48  
49  /**
50   * Configuration for Annotations
51   *
52   *
53   */
54  public class AnnotationConfiguration extends AbstractConfiguration
55  {
56      private static final Logger LOG = Log.getLogger(AnnotationConfiguration.class);
57      public static final String CLASS_INHERITANCE_MAP  = "org.eclipse.jetty.classInheritanceMap";    
58      public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers";
59    
60      
61      protected List<DiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
62      protected ClassInheritanceHandler _classInheritanceHandler;
63      protected List<ContainerInitializerAnnotationHandler> _containerInitializerAnnotationHandlers = new ArrayList<ContainerInitializerAnnotationHandler>();
64      
65      
66      public void preConfigure(final WebAppContext context) throws Exception
67      {
68      }
69     
70      
71      
72      /** 
73       * @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
74       */
75      @Override
76      public void configure(WebAppContext context) throws Exception
77      {
78         boolean metadataComplete = context.getMetaData().isMetaDataComplete();
79         context.addDecorator(new AnnotationDecorator(context));   
80        
81         
82         //Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any
83         AnnotationParser parser = null;
84         if (!metadataComplete)
85         {
86             //If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
87             if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
88             {
89                 _discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
90                 _discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
91                 _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
92             }
93         }
94         else
95             if (LOG.isDebugEnabled()) LOG.debug("Metadata-complete==true,  not processing discoverable servlet annotations for context "+context);
96         
97         
98         
99         //Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the
100        //classes so we can call their onStartup() methods correctly
101        createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
102        
103        if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
104        {           
105            parser = createAnnotationParser();
106            if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered());
107            parseContainerPath(context, parser);
108            //email from Rajiv Mordani jsrs 315 7 April 2010
109            //    If there is a <others/> then the ordering should be 
110            //          WEB-INF/classes the order of the declared elements + others.
111            //    In case there is no others then it is 
112            //          WEB-INF/classes + order of the elements.
113            parseWebInfClasses(context, parser);
114            parseWebInfLib (context, parser);
115            
116            for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
117                context.getMetaData().addDiscoveredAnnotations(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList());      
118        }
119     }
120     
121     
122     
123     /** 
124      * @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
125      */
126     @Override
127     public void postConfigure(WebAppContext context) throws Exception
128     {
129         MultiMap map = (MultiMap)context.getAttribute(CLASS_INHERITANCE_MAP);
130         if (map != null)
131             map.clear();
132         
133         context.removeAttribute(CLASS_INHERITANCE_MAP);
134         
135         List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
136         if (initializers != null)
137             initializers.clear();
138         if (_discoverableAnnotationHandlers != null)
139             _discoverableAnnotationHandlers.clear();
140       
141         _classInheritanceHandler = null;
142         if (_containerInitializerAnnotationHandlers != null)
143             _containerInitializerAnnotationHandlers.clear();
144   
145         super.postConfigure(context);
146     }
147 
148     /**
149      * @return a new AnnotationParser. This method can be overridden to use a different impleemntation of
150      * the AnnotationParser. Note that this is considered internal API.
151      */
152     protected AnnotationParser createAnnotationParser()
153     {
154         return new AnnotationParser();
155     }
156     
157     /** 
158      * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
159      */
160     @Override
161     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
162     {
163         context.addDecorator(new AnnotationDecorator(context));   
164     }
165     
166     
167     
168     /**
169      * @param context
170      * @param scis
171      * @throws Exception
172      */
173     public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis)
174     throws Exception
175     {
176         
177         if (scis == null || scis.isEmpty())
178             return; // nothing to do
179         
180 
181         List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
182         context.setAttribute(CONTAINER_INITIALIZERS, initializers);
183 
184         for (ServletContainerInitializer service : scis)
185         {
186             HandlesTypes annotation = service.getClass().getAnnotation(HandlesTypes.class);
187             ContainerInitializer initializer = new ContainerInitializer();
188             initializer.setTarget(service);
189             initializers.add(initializer);
190             if (annotation != null)
191             {
192                 //There is a HandlesTypes annotation on the on the ServletContainerInitializer
193                 Class[] classes = annotation.value();
194                 if (classes != null)
195                 {
196                     initializer.setInterestedTypes(classes);
197 
198                     //If we haven't already done so, we need to register a handler that will
199                     //process the whole class hierarchy to satisfy the ServletContainerInitializer
200                     if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
201                     {
202                         MultiMap map = new MultiMap();
203                         context.setAttribute(CLASS_INHERITANCE_MAP, map);
204                         _classInheritanceHandler = new ClassInheritanceHandler(map);
205                     }
206                                      
207                     for (Class c: classes)
208                     {
209                         //The value of one of the HandlesTypes classes is actually an Annotation itself so
210                         //register a handler for it
211                         if (c.isAnnotation())
212                         {
213                             if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName());
214                            
215                            _containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
216                         }
217                     }
218                 }
219                 else
220                     if (LOG.isDebugEnabled()) LOG.debug("No classes in HandlesTypes on initializer "+service.getClass());
221             }
222             else
223                 if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass());
224         }
225         
226 
227         //add a listener which will call the servletcontainerinitializers when appropriate
228         ServletContainerInitializerListener listener = new ServletContainerInitializerListener();
229         listener.setWebAppContext(context);
230         context.addEventListener(listener);
231     }
232 
233     
234     
235     /**
236      * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
237      * from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
238      * @param orderedJars
239      * @param service
240      * @return
241      */
242     public boolean isFromExcludedJar (WebAppContext context, ServletContainerInitializer service)
243     throws Exception
244     {
245         List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();
246 
247         //If no ordering, nothing is excluded
248         if (context.getMetaData().getOrdering() == null)
249             return false;
250 
251         //there is an ordering, but there are no jars resulting from the ordering, everything excluded
252         if (orderedJars.isEmpty())
253             return true; 
254 
255         String loadingJarName = Thread.currentThread().getContextClassLoader().getResource(service.getClass().getName().replace('.','/')+".class").toString();
256 
257         int i = loadingJarName.indexOf(".jar");  
258         if (i < 0)
259             return false; //not from a jar therefore not from WEB-INF so not excludable
260 
261         loadingJarName = loadingJarName.substring(0,i+4);
262         loadingJarName = (loadingJarName.startsWith("jar:")?loadingJarName.substring(4):loadingJarName);
263         URI loadingJarURI = Resource.newResource(loadingJarName).getURI();
264         boolean found = false;
265         Iterator<Resource> itor = orderedJars.iterator();
266         while (!found && itor.hasNext())
267         {
268             Resource r = itor.next();         
269             found = r.getURI().equals(loadingJarURI);
270         }
271 
272         return !found;
273     }
274    
275     
276     
277     /**
278      * @param context
279      * @return
280      * @throws Exception
281      */
282     public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
283     throws Exception
284     {
285         List<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
286         
287         //We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
288         ServiceLoader<ServletContainerInitializer> loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class, context.getClassLoader());
289        
290         if (loadedInitializers != null)
291         {  
292             for (ServletContainerInitializer service : loadedInitializers)
293             {
294                 if (!isFromExcludedJar(context, service))
295                     nonExcludedInitializers.add(service);
296             }
297         }
298         return nonExcludedInitializers;
299     }
300     
301     
302     
303     
304     /**
305      * Scan jars on container path.
306      * 
307      * @param context
308      * @param parser
309      * @throws Exception
310      */
311     public void parseContainerPath (final WebAppContext context, final AnnotationParser parser)
312     throws Exception
313     {
314         //if no pattern for the container path is defined, then by default scan NOTHING
315         LOG.debug("Scanning container jars");
316         
317         //always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
318         parser.clearHandlers();
319         for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
320         {
321             if (h instanceof AbstractDiscoverableAnnotationHandler)
322                 ((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
323         }
324         parser.registerHandlers(_discoverableAnnotationHandlers);
325         parser.registerHandler(_classInheritanceHandler);
326         parser.registerHandlers(_containerInitializerAnnotationHandlers);
327         
328         //Convert from Resource to URI
329         ArrayList<URI> containerUris = new ArrayList<URI>();
330         for (Resource r : context.getMetaData().getOrderedContainerJars())
331         {
332             URI uri = r.getURI();
333                 containerUris.add(uri);          
334         }
335         
336         parser.parse (containerUris.toArray(new URI[containerUris.size()]),
337                 new ClassNameResolver ()
338                 {
339                     public boolean isExcluded (String name)
340                     {
341                         if (context.isSystemClass(name)) return false;
342                         if (context.isServerClass(name)) return true;
343                         return false;
344                     }
345 
346                     public boolean shouldOverride (String name)
347                     { 
348                         //looking at system classpath
349                         if (context.isParentLoaderPriority())
350                             return true;
351                         return false;
352                     }
353                 });   
354         
355          
356     }
357     
358     
359     /**
360      * Scan jars in WEB-INF/lib
361      * 
362      * @param context
363      * @param parser
364      * @throws Exception
365      */
366     public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser)
367     throws Exception
368     {  
369         List<FragmentDescriptor> frags = context.getMetaData().getFragments();
370         
371         //email from Rajiv Mordani jsrs 315 7 April 2010
372         //jars that do not have a web-fragment.xml are still considered fragments
373         //they have to participate in the ordering
374         ArrayList<URI> webInfUris = new ArrayList<URI>();
375         
376         List<Resource> jars = context.getMetaData().getOrderedWebInfJars();
377         
378         //No ordering just use the jars in any order
379         if (jars == null || jars.isEmpty())
380             jars = context.getMetaData().getWebInfJars();
381    
382         for (Resource r : jars)
383         {          
384             //for each jar, we decide which set of annotations we need to parse for
385             parser.clearHandlers();
386             URI uri  = r.getURI();
387             FragmentDescriptor f = getFragmentFromJar(r, frags);
388            
389             //if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
390             // but yet we still need to do the scanning for the classes on behalf of  the servletcontainerinitializers
391             //if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
392             //or if it has a fragment we scan it if it is not metadata complete
393             if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null ||  !_containerInitializerAnnotationHandlers.isEmpty())
394             {
395                 //register the classinheritance handler if there is one
396                 parser.registerHandler(_classInheritanceHandler);
397                 
398                 //register the handlers for the @HandlesTypes values that are themselves annotations if there are any
399                 parser.registerHandlers(_containerInitializerAnnotationHandlers);
400                 
401                 //only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
402                 if (f == null || !isMetaDataComplete(f))
403                 {
404                     for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
405                     {
406                         if (h instanceof AbstractDiscoverableAnnotationHandler)
407                             ((AbstractDiscoverableAnnotationHandler)h).setResource(r);
408                     }
409                     parser.registerHandlers(_discoverableAnnotationHandlers);
410                 }
411 
412                 parser.parse(uri, 
413                              new ClassNameResolver()
414                              {
415                                  public boolean isExcluded (String name)
416                                  {    
417                                      if (context.isSystemClass(name)) return true;
418                                      if (context.isServerClass(name)) return false;
419                                      return false;
420                                  }
421 
422                                  public boolean shouldOverride (String name)
423                                  {
424                                     //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
425                                     if (context.isParentLoaderPriority())
426                                         return false;
427                                     return true;
428                                  }
429                              });   
430             }
431         }
432     }
433      
434     /**
435      * Scan classes in WEB-INF/classes
436      * 
437      * @param context
438      * @param parser
439      * @throws Exception
440      */
441     public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser)
442     throws Exception
443     {
444         LOG.debug("Scanning classes in WEB-INF/classes");
445         if (context.getWebInf() != null)
446         {
447             Resource classesDir = context.getWebInf().addPath("classes/");
448             if (classesDir.exists())
449             {
450                 parser.clearHandlers();
451                 for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
452                 {
453                     if (h instanceof AbstractDiscoverableAnnotationHandler)
454                         ((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
455                 }
456                 parser.registerHandlers(_discoverableAnnotationHandlers);
457                 parser.registerHandler(_classInheritanceHandler);
458                 parser.registerHandlers(_containerInitializerAnnotationHandlers);
459                 
460                 parser.parse(classesDir, 
461                              new ClassNameResolver()
462                 {
463                     public boolean isExcluded (String name)
464                     {
465                         if (context.isSystemClass(name)) return true;
466                         if (context.isServerClass(name)) return false;
467                         return false;
468                     }
469 
470                     public boolean shouldOverride (String name)
471                     {
472                         //looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
473                         if (context.isParentLoaderPriority())
474                             return false;
475                         return true;
476                     }
477                 });
478             }
479         }
480     }
481     
482  
483     
484     /**
485      * Get the web-fragment.xml from a jar
486      * 
487      * @param jar
488      * @param frags
489      * @return
490      * @throws Exception
491      */
492     public FragmentDescriptor getFragmentFromJar (Resource jar,  List<FragmentDescriptor> frags)
493     throws Exception
494     {
495         //check if the jar has a web-fragment.xml
496         FragmentDescriptor d = null;
497         for (FragmentDescriptor frag: frags)
498         {
499             Resource fragResource = frag.getResource(); //eg jar:file:///a/b/c/foo.jar!/META-INF/web-fragment.xml
500             if (Resource.isContainedIn(fragResource,jar))
501             {
502                 d = frag;
503                 break;
504             }
505         }
506         return d;
507     }
508     
509     public boolean isMetaDataComplete (WebDescriptor d)
510     {
511         return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
512     }
513 }