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