View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.config.annotation;
20  
21  import java.io.DataInputStream;
22  import java.io.IOException;
23  import java.lang.annotation.Annotation;
24  import java.net.JarURLConnection;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.Enumeration;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.jar.JarEntry;
37  import java.util.jar.JarFile;
38  import java.util.logging.Level;
39  import java.util.logging.Logger;
40  
41  import javax.faces.FacesException;
42  import javax.faces.bean.ManagedBean;
43  import javax.faces.component.FacesComponent;
44  import javax.faces.component.behavior.FacesBehavior;
45  import javax.faces.context.ExternalContext;
46  import javax.faces.convert.FacesConverter;
47  import javax.faces.event.NamedEvent;
48  import javax.faces.render.FacesBehaviorRenderer;
49  import javax.faces.render.FacesRenderer;
50  import javax.faces.validator.FacesValidator;
51  
52  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
53  import org.apache.myfaces.shared.config.MyfacesConfig;
54  import org.apache.myfaces.shared.util.ClassUtils;
55  import org.apache.myfaces.spi.AnnotationProvider;
56  import org.apache.myfaces.spi.AnnotationProviderFactory;
57  import org.apache.myfaces.util.ContainerUtils;
58  import org.apache.myfaces.config.util.GAEUtils;
59  import org.apache.myfaces.config.util.JarUtils;
60  import org.apache.myfaces.shared.util.StringUtils;
61  import org.apache.myfaces.view.facelets.util.Classpath;
62  
63  /**
64   * 
65   * @since 2.0.2
66   * @author Leonardo Uribe
67   */
68  public class DefaultAnnotationProvider extends AnnotationProvider
69  {
70      private static final Logger log = Logger.getLogger(DefaultAnnotationProvider.class.getName());
71      
72      /**
73       * Servlet context init parameter which defines which packages to scan
74       * for beans, separated by commas.
75       */
76      @JSFWebConfigParam(since="2.0")
77      public static final String SCAN_PACKAGES = "org.apache.myfaces.annotation.SCAN_PACKAGES";
78  
79      /**
80       * <p>Prefix path used to locate web application classes for this
81       * web application.</p>
82       */
83      private static final String WEB_CLASSES_PREFIX = "/WEB-INF/classes/";
84      
85      /**
86       * <p>Prefix path used to locate web application libraries for this
87       * web application.</p>
88       */
89      private static final String WEB_LIB_PREFIX = "/WEB-INF/lib/";
90      
91      private static final String META_INF_PREFIX = "META-INF/";
92  
93      private static final String FACES_CONFIG_SUFFIX = ".faces-config.xml";
94      
95      private static final String STANDARD_FACES_CONFIG_RESOURCE = "META-INF/standard-faces-config.xml";
96  
97      /**
98       * <p>Resource path used to acquire implicit resources buried
99       * inside application JARs.</p>
100      */
101     private static final String FACES_CONFIG_IMPLICIT = "META-INF/faces-config.xml";
102     
103     private final _ClassByteCodeAnnotationFilter _filter;
104 
105     /**
106      * This set contains the annotation names that this AnnotationConfigurator is able to scan
107      * in the format that is read from .class file.
108      */
109     private static Set<String> byteCodeAnnotationsNames;
110 
111     static
112     {
113         Set<String> bcan = new HashSet<String>(10, 1f);
114         bcan.add("Ljavax/faces/component/FacesComponent;");
115         bcan.add("Ljavax/faces/component/behavior/FacesBehavior;");
116         bcan.add("Ljavax/faces/convert/FacesConverter;");
117         bcan.add("Ljavax/faces/validator/FacesValidator;");
118         bcan.add("Ljavax/faces/render/FacesRenderer;");
119         bcan.add("Ljavax/faces/bean/ManagedBean;");
120         bcan.add("Ljavax/faces/event/NamedEvent;");
121         //bcan.add("Ljavax/faces/event/ListenerFor;");
122         //bcan.add("Ljavax/faces/event/ListenersFor;");
123         bcan.add("Ljavax/faces/render/FacesBehaviorRenderer;");
124 
125         byteCodeAnnotationsNames = Collections.unmodifiableSet(bcan);
126     }
127     
128     private static final Set<Class<? extends Annotation>> JSF_ANNOTATION_CLASSES;
129     
130     static
131     {
132         Set<Class<? extends Annotation>> bcan = new HashSet<Class<? extends Annotation>>(10, 1f);
133         bcan.add(FacesComponent.class);
134         bcan.add(FacesBehavior.class);
135         bcan.add(FacesConverter.class);
136         bcan.add(FacesValidator.class);
137         bcan.add(FacesRenderer.class);
138         bcan.add(ManagedBean.class);
139         bcan.add(NamedEvent.class);
140         bcan.add(FacesBehaviorRenderer.class);
141         JSF_ANNOTATION_CLASSES = Collections.unmodifiableSet(bcan);
142     }
143     
144     public DefaultAnnotationProvider()
145     {
146         super();
147         _filter = new _ClassByteCodeAnnotationFilter();
148     }
149     
150     @Override
151     public Map<Class<? extends Annotation>, Set<Class<?>>> getAnnotatedClasses(ExternalContext ctx)
152     {
153         Map<Class<? extends Annotation>,Set<Class<?>>> map = new HashMap<Class<? extends Annotation>, Set<Class<?>>>();
154         Collection<Class<?>> classes = null;
155 
156         //1. Scan for annotations on /WEB-INF/classes
157         try
158         {
159             classes = getAnnotatedWebInfClasses(ctx);
160         }
161         catch (IOException e)
162         {
163             throw new FacesException(e);
164         }
165 
166         for (Class<?> clazz : classes)
167         {
168             processClass(map, clazz);
169         }
170         
171         //2. Scan for annotations on classpath
172         String jarAnnotationFilesToScanParam = MyfacesConfig.getCurrentInstance(ctx).getGaeJsfAnnotationsJarFiles();
173         jarAnnotationFilesToScanParam = jarAnnotationFilesToScanParam != null ? 
174                 jarAnnotationFilesToScanParam.trim() : null;
175         if (ContainerUtils.isRunningOnGoogleAppEngine(ctx) && 
176             jarAnnotationFilesToScanParam != null &&
177             jarAnnotationFilesToScanParam.length() > 0)
178         {
179             // Skip call AnnotationProvider.getBaseUrls(ctx), and instead use the value of the config parameter
180             // to find which classes needs to be scanned for annotations
181             classes = getGAEAnnotatedMetaInfClasses(ctx, jarAnnotationFilesToScanParam);
182         }
183         else
184         {
185             try
186             {
187                 AnnotationProvider provider
188                         = AnnotationProviderFactory.getAnnotationProviderFactory(ctx).getAnnotationProvider(ctx);
189                 classes = getAnnotatedMetaInfClasses(ctx, provider.getBaseUrls(ctx));
190             }
191             catch (IOException e)
192             {
193                 throw new FacesException(e);
194             }
195         }
196         
197         for (Class<?> clazz : classes)
198         {
199             processClass(map, clazz);
200         }
201         
202         //3. Scan on myfaces-impl for annotations available on myfaces-impl.
203         //Also scan jar including META-INF/standard-faces-config.xml
204         //(myfaces-impl jar file)
205         // -= Leonardo Uribe =- No annotations in MyFaces jars, code not
206         // necessary, because all config is already in standard-faces-config.xml
207         //URL url = getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
208         //if (url == null)
209         //{
210         //    url = getClass().getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
211         //}
212         //classes = getAnnotatedMyfacesImplClasses(ctx, url);
213         //for (Class<?> clazz : classes)
214         //{
215         //    processClass(map, clazz);
216         //}
217         
218         return map;
219     }
220     
221     @Override
222     public Set<URL> getBaseUrls() throws IOException
223     {
224         Set<URL> urlSet = new HashSet<URL>();
225         
226         //This usually happens when maven-jetty-plugin is used
227         //Scan jars looking for paths including META-INF/faces-config.xml
228         Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
229         while (resources.hasMoreElements())
230         {
231             urlSet.add(resources.nextElement());
232         }
233 
234         //Scan files inside META-INF ending with .faces-config.xml
235         URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACES_CONFIG_SUFFIX);
236         for (int i = 0; i < urls.length; i++)
237         {
238             urlSet.add(urls[i]);
239         }
240         
241         return urlSet;
242     }
243     
244     @Override
245     public Set<URL> getBaseUrls(ExternalContext context) throws IOException
246     {
247         String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
248         jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
249         if (ContainerUtils.isRunningOnGoogleAppEngine(context) && 
250             jarFilesToScanParam != null &&
251             jarFilesToScanParam.length() > 0)
252         {
253             Set<URL> urlSet = new HashSet<URL>();
254             
255             //This usually happens when maven-jetty-plugin is used
256             //Scan jars looking for paths including META-INF/faces-config.xml
257             Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
258             while (resources.hasMoreElements())
259             {
260                 urlSet.add(resources.nextElement());
261             }
262             
263             Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
264                     context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
265             if (urlsGAE != null)
266             {
267                 urlSet.addAll(urlsGAE);
268             }
269             return urlSet;
270         }
271         else
272         {
273             return getBaseUrls();
274         }
275     }
276 
277     protected Collection<Class<?>> getAnnotatedMetaInfClasses(ExternalContext ctx, Set<URL> urls)
278     {
279         if (urls != null && !urls.isEmpty())
280         {
281             List<Class<?>> list = new ArrayList<Class<?>>();
282             for (URL url : urls)
283             {
284                 try
285                 {
286                     JarFile jarFile = getJarFile(url);
287                     if (jarFile != null)
288                     {
289                         archiveClasses(ctx, jarFile, list);
290                     }
291                 }
292                 catch(IOException e)
293                 {
294                     log.log(Level.SEVERE, "cannot scan jar file for annotations:"+url, e);
295                 }
296             }
297             return list;
298         }
299         return Collections.emptyList();
300     }
301     
302     protected Collection<Class<?>> getGAEAnnotatedMetaInfClasses(ExternalContext context, String filter)
303     {
304         if (!filter.equals("none"))
305         {
306             String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
307             Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
308             if (paths != null)
309             {
310                 List<Class<?>> list = new ArrayList<Class<?>>();
311                 for (Object pathObject : paths)
312                 {
313                     String path = (String) pathObject;
314                     if (path.endsWith(".jar") && GAEUtils.wildcardMatch(path, jarFilesToScan, GAEUtils.WEB_LIB_PREFIX))
315                     {
316                         // GAE does not use WAR format, so the app is just uncompressed in a directory
317                         // What we need here is just take the path of the file, and open the file as a
318                         // jar file. Then, if the jar should be scanned, try to find the required file.
319                         try
320                         {
321                             URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/"); 
322                             JarFile jarFile = JarUtils.getJarFile(jarUrl);
323                             if (jarFile != null)
324                             {
325                                 archiveClasses(context, jarFile, list);
326                             }
327                         }
328                         catch(IOException e)
329                         {
330                             log.log(Level.SEVERE, 
331                                     "IOException when reading jar file for annotations using filter: "+filter, e);
332                         }
333                     }
334                 }
335                 return list;
336             }
337         }
338         return Collections.emptyList();
339     }
340 
341     protected Collection<Class<?>> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url)
342     {
343         return Collections.emptyList();
344         /*
345         try
346         {
347             List<Class<?>> list = new ArrayList<Class<?>>();
348             JarFile jarFile = getJarFile(url);
349             if (jarFile == null)
350             {
351                 return list;
352             }
353             else
354             {
355                 return archiveClasses(ctx, jarFile, list);
356             }
357         }
358         catch(IOException e)
359         {
360             throw new FacesException("cannot scan jar file for annotations:"+url, e);
361         }*/
362     }
363 
364     protected Collection<Class<?>> getAnnotatedWebInfClasses(ExternalContext ctx) throws IOException
365     {
366         String scanPackages = ctx.getInitParameter(SCAN_PACKAGES);
367         if (scanPackages != null)
368         {
369             try
370             {
371                 return packageClasses(ctx, scanPackages);
372             }
373             catch (ClassNotFoundException e)
374             {
375                 throw new FacesException(e);
376             }
377             catch (IOException e)
378             {
379                 throw new FacesException(e);
380             }
381         }
382         else
383         {
384             return webClasses(ctx);
385         }
386     }
387     
388     /**
389      * <p>Return a list of the classes defined within the given packages
390      * If there are no such classes, a zero-length list will be returned.</p>
391      *
392      * @param scanPackages the package configuration
393      *
394      * @exception ClassNotFoundException if a located class cannot be loaded
395      * @exception IOException if an input/output error occurs
396      */
397     private List<Class<?>> packageClasses(final ExternalContext externalContext,
398             final String scanPackages) throws ClassNotFoundException, IOException
399     {
400 
401         List<Class<?>> list = new ArrayList<Class<?>>();
402 
403         String[] scanPackageTokens = scanPackages.split(",");
404         for (String scanPackageToken : scanPackageTokens)
405         {
406             if (scanPackageToken.toLowerCase().endsWith(".jar"))
407             {
408                 URL jarResource = externalContext.getResource(WEB_LIB_PREFIX
409                         + scanPackageToken);
410                 String jarURLString = "jar:" + jarResource.toString() + "!/";
411                 URL url = new URL(jarURLString);
412                 JarFile jarFile = ((JarURLConnection) url.openConnection())
413                         .getJarFile();
414 
415                 archiveClasses(externalContext, jarFile, list);
416             }
417             else
418             {
419                 List<Class> list2 = new ArrayList<Class>();
420                 _PackageInfo.getInstance().getClasses(list2, scanPackageToken);
421                 for (Class c : list2)
422                 {
423                     list.add(c);                    
424                 }
425             }
426         }
427         return list;
428     }    
429     
430     /**
431      * <p>Return a list of classes to examine from the specified JAR archive.
432      * If this archive has no classes in it, a zero-length list is returned.</p>
433      *
434      * @param context <code>ExternalContext</code> instance for
435      *  this application
436      * @param jar <code>JarFile</code> for the archive to be scanned
437      *
438      * @exception ClassNotFoundException if a located class cannot be loaded
439      */
440     private List<Class<?>> archiveClasses(ExternalContext context, JarFile jar, List<Class<?>> list)
441     {
442 
443         // Accumulate and return a list of classes in this JAR file
444         ClassLoader loader = ClassUtils.getContextClassLoader();
445         if (loader == null)
446         {
447             loader = this.getClass().getClassLoader();
448         }
449         Enumeration<JarEntry> entries = jar.entries();
450         while (entries.hasMoreElements())
451         {
452             JarEntry entry = entries.nextElement();
453             if (entry.isDirectory())
454             {
455                 continue; // This is a directory
456             }
457             String name = entry.getName();
458             if (name.startsWith("META-INF/"))
459             {
460                 continue; // Attribute files
461             }
462             if (!name.endsWith(".class"))
463             {
464                 continue; // This is not a class
465             }
466 
467             DataInputStream in = null;
468             boolean couldContainAnnotation = false;
469             try
470             {
471                 in = new DataInputStream(jar.getInputStream(entry));
472                 couldContainAnnotation = _filter
473                         .couldContainAnnotationsOnClassDef(in,
474                                 byteCodeAnnotationsNames);
475             }
476             catch (IOException e)
477             {
478                 // Include this class - we can't scan this class using
479                 // the filter, but it could be valid, so we need to
480                 // load it using the classLoader. Anyway, log a debug
481                 // message.
482                 couldContainAnnotation = true;
483                 if (log.isLoggable(Level.FINE))
484                 {
485                     log.fine("IOException when filtering class " + name
486                             + " for annotations");
487                 }
488             }
489             finally
490             {
491                 if (in != null)
492                 {
493                     try
494                     {
495                         in.close();
496                     }
497                     catch (IOException e)
498                     {
499                         // No Op
500                     }
501                 }
502             }
503 
504             if (couldContainAnnotation)
505             {
506                 name = name.substring(0, name.length() - 6); // Trim ".class"
507                 Class<?> clazz = null;
508                 try
509                 {
510                     clazz = loader.loadClass(name.replace('/', '.'));
511                 }
512                 catch (NoClassDefFoundError e)
513                 {
514                     // Skip this class - we cannot analyze classes we cannot load
515                 }
516                 catch (Exception e)
517                 {
518                     // Skip this class - we cannot analyze classes we cannot load
519                 }
520                 if (clazz != null)
521                 {
522                     list.add(clazz);
523                 }
524             }
525         }
526         return list;
527 
528     }
529     
530     /**
531      * <p>Return a list of the classes defined under the
532      * <code>/WEB-INF/classes</code> directory of this web
533      * application.  If there are no such classes, a zero-length list
534      * will be returned.</p>
535      *
536      * @param externalContext <code>ExternalContext</code> instance for
537      *  this application
538      *
539      * @exception ClassNotFoundException if a located class cannot be loaded
540      */
541     private List<Class<?>> webClasses(ExternalContext externalContext)
542     {
543         List<Class<?>> list = new ArrayList<Class<?>>();
544         webClasses(externalContext, WEB_CLASSES_PREFIX, list);
545         return list;
546     }
547 
548     /**
549      * <p>Add classes found in the specified directory to the specified
550      * list, recursively calling this method when a directory is encountered.</p>
551      *
552      * @param externalContext <code>ExternalContext</code> instance for
553      *  this application
554      * @param prefix Prefix specifying the "directory path" to be searched
555      * @param list List to be appended to
556      *
557      * @exception ClassNotFoundException if a located class cannot be loaded
558      */
559     private void webClasses(ExternalContext externalContext, String prefix,
560             List<Class<?>> list)
561     {
562 
563         ClassLoader loader = getClassLoader();
564 
565         Set<String> paths = externalContext.getResourcePaths(prefix);
566         if(paths == null)
567         {
568             return; //need this in case there is no WEB-INF/classes directory
569         }
570         if (log.isLoggable(Level.FINEST))
571         {
572             log.finest("webClasses(" + prefix + ") - Received " + paths.size()
573                     + " paths to check");
574         }
575 
576         String path = null;
577 
578         if (paths.isEmpty())
579         {
580             if (log.isLoggable(Level.WARNING))
581             {
582                 log
583                         .warning("AnnotationConfigurator does not found classes "
584                                 + "for annotations in "
585                                 + prefix
586                                 + " ."
587                                 + " This could happen because maven jetty plugin is used"
588                                 + " (goal jetty:run). Try configure "
589                                 + SCAN_PACKAGES + " init parameter "
590                                 + "or use jetty:run-exploded instead.");
591             }
592         }
593         else
594         {
595             for (Object pathObject : paths)
596             {
597                 path = (String) pathObject;
598                 if (path.endsWith("/"))
599                 {
600                     webClasses(externalContext, path, list);
601                 }
602                 else if (path.endsWith(".class"))
603                 {
604                     DataInputStream in = null;
605                     boolean couldContainAnnotation = false;
606                     try
607                     {
608                         in = new DataInputStream(externalContext
609                                 .getResourceAsStream(path));
610                         couldContainAnnotation = _filter
611                                 .couldContainAnnotationsOnClassDef(in,
612                                         byteCodeAnnotationsNames);
613                     }
614                     catch (IOException e)
615                     {
616                         // Include this class - we can't scan this class using
617                         // the filter, but it could be valid, so we need to
618                         // load it using the classLoader. Anyway, log a debug
619                         // message.
620                         couldContainAnnotation = true;
621                         if (log.isLoggable(Level.FINE))
622                         {
623                             log.fine("IOException when filtering class " + path
624                                     + " for annotations");
625                         }
626                     }
627                     finally
628                     {
629                         if (in != null)
630                         {
631                             try
632                             {
633                                 in.close();
634                             }
635                             catch (IOException e)
636                             {
637                                 // No Op
638                             }
639                         }
640                     }
641 
642                     if (couldContainAnnotation)
643                     {
644                         //Load it and add it to list for later processing
645                         path = path.substring(WEB_CLASSES_PREFIX.length()); // Strip prefix
646                         path = path.substring(0, path.length() - 6); // Strip suffix
647                         path = path.replace('/', '.'); // Convert to FQCN
648 
649                         Class<?> clazz = null;
650                         try
651                         {
652                             clazz = loader.loadClass(path);
653                         }
654                         catch (NoClassDefFoundError e)
655                         {
656                             // Skip this class - we cannot analyze classes we cannot load
657                         }
658                         catch (Exception e)
659                         {
660                             // Skip this class - we cannot analyze classes we cannot load
661                         }
662                         if (clazz != null)
663                         {
664                             list.add(clazz);
665                         }
666                     }
667                 }
668             }
669         }
670     }
671     
672     private JarFile getJarFile(URL url) throws IOException
673     {
674         URLConnection conn = url.openConnection();
675         conn.setUseCaches(false);
676         conn.setDefaultUseCaches(false);
677 
678         JarFile jarFile;
679         if (conn instanceof JarURLConnection)
680         {
681             jarFile = ((JarURLConnection) conn).getJarFile();
682         }
683         else
684         {
685             jarFile = _getAlternativeJarFile(url);
686         }
687         return jarFile;
688     }
689     
690 
691     /**
692      * taken from org.apache.myfaces.view.facelets.util.Classpath
693      * 
694      * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile
695      * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full
696      * solution, since an unpacked WAR or EAR will not have JAR "files" as such.
697      */
698     private static JarFile _getAlternativeJarFile(URL url) throws IOException
699     {
700         String urlFile = url.getFile();
701 
702         // Trim off any suffix - which is prefixed by "!/" on Weblogic
703         int separatorIndex = urlFile.indexOf("!/");
704 
705         // OK, didn't find that. Try the less safe "!", used on OC4J
706         if (separatorIndex == -1)
707         {
708             separatorIndex = urlFile.indexOf('!');
709         }
710 
711         if (separatorIndex != -1)
712         {
713             String jarFileUrl = urlFile.substring(0, separatorIndex);
714             // And trim off any "file:" prefix.
715             if (jarFileUrl.startsWith("file:"))
716             {
717                 jarFileUrl = jarFileUrl.substring("file:".length());
718             }
719 
720             return new JarFile(jarFileUrl);
721         }
722 
723         return null;
724     }
725         
726     private ClassLoader getClassLoader()
727     {
728         ClassLoader loader = ClassUtils.getContextClassLoader();
729         if (loader == null)
730         {
731             loader = this.getClass().getClassLoader();
732         }
733         return loader;
734     }
735     
736     private void processClass(Map<Class<? extends Annotation>,Set<Class<?>>> map, Class<?> clazz)
737     {
738         Annotation[] annotations = clazz.getAnnotations();
739         for (Annotation anno : annotations)
740         {
741             Class<? extends Annotation> annotationClass = anno.annotationType();
742             if (JSF_ANNOTATION_CLASSES.contains(annotationClass))
743             {
744                 Set<Class<?>> set = map.get(annotationClass);
745                 if (set == null)
746                 {
747                     set = new HashSet<Class<?>>();
748                     set.add(clazz);
749                     map.put(annotationClass, set);
750                 }
751                 else
752                 {
753                     set.add(clazz);
754                 }
755 
756             }
757         }
758     }
759 }