View Javadoc

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