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  import javax.faces.view.facelets.FaceletsResourceResolver;
52  
53  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
54  import org.apache.myfaces.shared.config.MyfacesConfig;
55  import org.apache.myfaces.shared.util.ClassUtils;
56  import org.apache.myfaces.spi.AnnotationProvider;
57  import org.apache.myfaces.spi.AnnotationProviderFactory;
58  import org.apache.myfaces.util.ContainerUtils;
59  import org.apache.myfaces.config.util.GAEUtils;
60  import org.apache.myfaces.config.util.JarUtils;
61  import org.apache.myfaces.shared.util.StringUtils;
62  import org.apache.myfaces.view.facelets.util.Classpath;
63  
64  /**
65   * 
66   * @since 2.0.2
67   * @author Leonardo Uribe
68   */
69  public class DefaultAnnotationProvider extends AnnotationProvider
70  {
71      private static final Logger log = Logger.getLogger(DefaultAnnotationProvider.class.getName());
72      
73      /**
74       * Servlet context init parameter which defines which packages to scan
75       * for beans, separated by commas.
76       */
77      @JSFWebConfigParam(since="2.0")
78      public static final String SCAN_PACKAGES = "org.apache.myfaces.annotation.SCAN_PACKAGES";
79  
80      /**
81       * <p>Prefix path used to locate web application classes for this
82       * web application.</p>
83       */
84      private static final String WEB_CLASSES_PREFIX = "/WEB-INF/classes/";
85      
86      /**
87       * <p>Prefix path used to locate web application libraries for this
88       * web application.</p>
89       */
90      private static final String WEB_LIB_PREFIX = "/WEB-INF/lib/";
91      
92      private static final String META_INF_PREFIX = "META-INF/";
93  
94      private static final String FACES_CONFIG_SUFFIX = ".faces-config.xml";
95  
96      /**
97       * <p>Resource path used to acquire implicit resources buried
98       * inside application JARs.</p>
99       */
100     private static final String FACES_CONFIG_IMPLICIT = "META-INF/faces-config.xml";
101     
102     private final _ClassByteCodeAnnotationFilter _filter;
103 
104     /**
105      * This set contains the annotation names that this AnnotationConfigurator is able to scan
106      * in the format that is read from .class file.
107      */
108     private static Set<String> byteCodeAnnotationsNames;
109 
110     static
111     {
112         Set<String> bcan = new HashSet<String>(10, 1f);
113         bcan.add("Ljavax/faces/component/FacesComponent;");
114         bcan.add("Ljavax/faces/component/behavior/FacesBehavior;");
115         bcan.add("Ljavax/faces/convert/FacesConverter;");
116         bcan.add("Ljavax/faces/validator/FacesValidator;");
117         bcan.add("Ljavax/faces/render/FacesRenderer;");
118         bcan.add("Ljavax/faces/bean/ManagedBean;");
119         bcan.add("Ljavax/faces/event/NamedEvent;");
120         //bcan.add("Ljavax/faces/event/ListenerFor;");
121         //bcan.add("Ljavax/faces/event/ListenersFor;");
122         bcan.add("Ljavax/faces/render/FacesBehaviorRenderer;");
123         bcan.add("Ljavax/faces/view/facelets/FaceletsResourceResolver;");
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         bcan.add(FaceletsResourceResolver.class);
142         JSF_ANNOTATION_CLASSES = Collections.unmodifiableSet(bcan);
143     }
144     
145     public DefaultAnnotationProvider()
146     {
147         super();
148         _filter = new _ClassByteCodeAnnotationFilter();
149     }
150     
151     @Override
152     public Map<Class<? extends Annotation>, Set<Class<?>>> getAnnotatedClasses(ExternalContext ctx)
153     {
154         Map<Class<? extends Annotation>,Set<Class<?>>> map = new HashMap<Class<? extends Annotation>, Set<Class<?>>>();
155         Collection<Class<?>> classes = null;
156 
157         //1. Scan for annotations on /WEB-INF/classes
158         try
159         {
160             classes = getAnnotatedWebInfClasses(ctx);
161         }
162         catch (IOException e)
163         {
164             throw new FacesException(e);
165         }
166 
167         for (Class<?> clazz : classes)
168         {
169             processClass(map, clazz);
170         }
171         
172         //2. Scan for annotations on classpath
173         String jarAnnotationFilesToScanParam = MyfacesConfig.getCurrentInstance(ctx).getGaeJsfAnnotationsJarFiles();
174         jarAnnotationFilesToScanParam = jarAnnotationFilesToScanParam != null ? 
175                 jarAnnotationFilesToScanParam.trim() : null;
176         if (ContainerUtils.isRunningOnGoogleAppEngine(ctx) && 
177             jarAnnotationFilesToScanParam != null &&
178             jarAnnotationFilesToScanParam.length() > 0)
179         {
180             // Skip call AnnotationProvider.getBaseUrls(ctx), and instead use the value of the config parameter
181             // to find which classes needs to be scanned for annotations
182             classes = getGAEAnnotatedMetaInfClasses(ctx, jarAnnotationFilesToScanParam);
183         }
184         else
185         {
186             try
187             {
188                 AnnotationProvider provider
189                         = AnnotationProviderFactory.getAnnotationProviderFactory(ctx).getAnnotationProvider(ctx);
190                 classes = getAnnotatedMetaInfClasses(ctx, provider.getBaseUrls(ctx));
191             }
192             catch (IOException e)
193             {
194                 throw new FacesException(e);
195             }
196         }
197         
198         for (Class<?> clazz : classes)
199         {
200             processClass(map, clazz);
201         }
202         
203         //3. Scan on myfaces-impl for annotations available on myfaces-impl.
204         //Also scan jar including META-INF/standard-faces-config.xml
205         //(myfaces-impl jar file)
206         // -= Leonardo Uribe =- No annotations in MyFaces jars, code not
207         // necessary, because all config is already in standard-faces-config.xml
208         //URL url = getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
209         //if (url == null)
210         //{
211         //    url = getClass().getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
212         //}
213         //classes = getAnnotatedMyfacesImplClasses(ctx, url);
214         //for (Class<?> clazz : classes)
215         //{
216         //    processClass(map, clazz);
217         //}
218         
219         return map;
220     }
221     
222     @Override
223     public Set<URL> getBaseUrls() throws IOException
224     {
225         Set<URL> urlSet = new HashSet<URL>();
226         
227         //This usually happens when maven-jetty-plugin is used
228         //Scan jars looking for paths including META-INF/faces-config.xml
229         Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
230         while (resources.hasMoreElements())
231         {
232             urlSet.add(resources.nextElement());
233         }
234 
235         //Scan files inside META-INF ending with .faces-config.xml
236         URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACES_CONFIG_SUFFIX);
237         for (int i = 0; i < urls.length; i++)
238         {
239             urlSet.add(urls[i]);
240         }
241         
242         return urlSet;
243     }
244     
245     @Override
246     public Set<URL> getBaseUrls(ExternalContext context) throws IOException
247     {
248         String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
249         jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
250         if (ContainerUtils.isRunningOnGoogleAppEngine(context) && 
251             jarFilesToScanParam != null &&
252             jarFilesToScanParam.length() > 0)
253         {
254             Set<URL> urlSet = new HashSet<URL>();
255             
256             //This usually happens when maven-jetty-plugin is used
257             //Scan jars looking for paths including META-INF/faces-config.xml
258             Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
259             while (resources.hasMoreElements())
260             {
261                 urlSet.add(resources.nextElement());
262             }
263             
264             Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
265                     context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
266             if (urlsGAE != null)
267             {
268                 urlSet.addAll(urlsGAE);
269             }
270             return urlSet;
271         }
272         else
273         {
274             return getBaseUrls();
275         }
276     }
277 
278     protected Collection<Class<?>> getAnnotatedMetaInfClasses(ExternalContext ctx, Set<URL> urls)
279     {
280         if (urls != null && !urls.isEmpty())
281         {
282             List<Class<?>> list = new ArrayList<Class<?>>();
283             for (URL url : urls)
284             {
285                 try
286                 {
287                     JarFile jarFile = getJarFile(url);
288                     if (jarFile != null)
289                     {
290                         archiveClasses(jarFile, list);
291                     }
292                 }
293                 catch(IOException e)
294                 {
295                     log.log(Level.SEVERE, "cannot scan jar file for annotations:"+url, e);
296                 }
297             }
298             return list;
299         }
300         return Collections.emptyList();
301     }
302     
303     protected Collection<Class<?>> getGAEAnnotatedMetaInfClasses(ExternalContext context, String filter)
304     {
305         if (!filter.equals("none"))
306         {
307             String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
308             Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
309             if (paths != null)
310             {
311                 List<Class<?>> list = new ArrayList<Class<?>>();
312                 for (Object pathObject : paths)
313                 {
314                     String path = (String) pathObject;
315                     if (path.endsWith(".jar") && GAEUtils.wildcardMatch(path, jarFilesToScan, GAEUtils.WEB_LIB_PREFIX))
316                     {
317                         // GAE does not use WAR format, so the app is just uncompressed in a directory
318                         // What we need here is just take the path of the file, and open the file as a
319                         // jar file. Then, if the jar should be scanned, try to find the required file.
320                         try
321                         {
322                             URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/"); 
323                             JarFile jarFile = JarUtils.getJarFile(jarUrl);
324                             if (jarFile != null)
325                             {
326                                 archiveClasses(jarFile, list);
327                             }
328                         }
329                         catch(IOException e)
330                         {
331                             log.log(Level.SEVERE, 
332                                     "IOException when reading jar file for annotations using filter: "+filter, e);
333                         }
334                     }
335                 }
336                 return list;
337             }
338         }
339         return Collections.emptyList();
340     }
341 
342     protected Collection<Class<?>> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url)
343     {
344         return Collections.emptyList();
345         /*
346         try
347         {
348             List<Class<?>> list = new ArrayList<Class<?>>();
349             JarFile jarFile = getJarFile(url);
350             if (jarFile == null)
351             {
352                 return list;
353             }
354             else
355             {
356                 return archiveClasses(ctx, jarFile, list);
357             }
358         }
359         catch(IOException e)
360         {
361             throw new FacesException("cannot scan jar file for annotations:"+url, e);
362         }*/
363     }
364 
365     protected Collection<Class<?>> getAnnotatedWebInfClasses(ExternalContext ctx) throws IOException
366     {
367         String scanPackages = ctx.getInitParameter(SCAN_PACKAGES);
368         if (scanPackages != null)
369         {
370             try
371             {
372                 return packageClasses(ctx, scanPackages);
373             }
374             catch (ClassNotFoundException e)
375             {
376                 throw new FacesException(e);
377             }
378             catch (IOException e)
379             {
380                 throw new FacesException(e);
381             }
382         }
383         else
384         {
385             return webClasses(ctx);
386         }
387     }
388     
389     /**
390      * <p>Return a list of the classes defined within the given packages
391      * If there are no such classes, a zero-length list will be returned.</p>
392      *
393      * @param scanPackages the package configuration
394      *
395      * @exception ClassNotFoundException if a located class cannot be loaded
396      * @exception IOException if an input/output error occurs
397      */
398     private List<Class<?>> packageClasses(final ExternalContext externalContext,
399             final String scanPackages) throws ClassNotFoundException, IOException
400     {
401 
402         List<Class<?>> list = new ArrayList<Class<?>>();
403 
404         String[] scanPackageTokens = scanPackages.split(",");
405         for (String scanPackageToken : scanPackageTokens)
406         {
407             if (scanPackageToken.toLowerCase().endsWith(".jar"))
408             {
409                 URL jarResource = externalContext.getResource(WEB_LIB_PREFIX
410                         + scanPackageToken);
411                 String jarURLString = "jar:" + jarResource.toString() + "!/";
412                 URL url = new URL(jarURLString);
413                 JarFile jarFile = ((JarURLConnection) url.openConnection())
414                         .getJarFile();
415 
416                 archiveClasses(jarFile, list);
417             }
418             else
419             {
420                 List<Class> list2 = new ArrayList<Class>();
421                 _PackageInfo.getInstance().getClasses(list2, scanPackageToken);
422                 for (Class c : list2)
423                 {
424                     list.add(c);                    
425                 }
426             }
427         }
428         return list;
429     }    
430     
431     /**
432      * <p>Return a list of classes to examine from the specified JAR archive.
433      * If this archive has no classes in it, a zero-length list is returned.</p>
434      *
435      * @param context <code>ExternalContext</code> instance for
436      *  this application
437      * @param jar <code>JarFile</code> for the archive to be scanned
438      *
439      * @exception ClassNotFoundException if a located class cannot be loaded
440      */
441     private List<Class<?>> archiveClasses(JarFile jar, List<Class<?>> list)
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 }