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