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