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