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