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