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         for (int i = 0; i < urls.length; i++)
251         {
252             urlSet.add(urls[i]);
253         }
254         
255         return urlSet;
256     }
257     
258     @Override
259     public Set<URL> getBaseUrls(ExternalContext context) throws IOException
260     {
261         String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
262         jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
263         if (ContainerUtils.isRunningOnGoogleAppEngine(context) && 
264             jarFilesToScanParam != null &&
265             jarFilesToScanParam.length() > 0)
266         {
267             Set<URL> urlSet = new HashSet<URL>();
268             
269             //This usually happens when maven-jetty-plugin is used
270             //Scan jars looking for paths including META-INF/faces-config.xml
271             Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
272             while (resources.hasMoreElements())
273             {
274                 urlSet.add(resources.nextElement());
275             }
276             
277             Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
278                     context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
279             if (urlsGAE != null)
280             {
281                 urlSet.addAll(urlsGAE);
282             }
283             return urlSet;
284         }
285         else
286         {
287             return getBaseUrls();
288         }
289     }
290 
291     protected Collection<Class<?>> getAnnotatedMetaInfClasses(ExternalContext ctx, Set<URL> urls)
292     {
293         if (urls != null && !urls.isEmpty())
294         {
295             List<Class<?>> list = new ArrayList<Class<?>>();
296             for (URL url : urls)
297             {
298                 try
299                 {
300                     JarFile jarFile = getJarFile(url);
301                     if (jarFile != null)
302                     {
303                         archiveClasses(jarFile, list);
304                     }
305                 }
306                 catch(IOException e)
307                 {
308                     log.log(Level.SEVERE, "cannot scan jar file for annotations:"+url, e);
309                 }
310             }
311             return list;
312         }
313         return Collections.emptyList();
314     }
315     
316     protected Collection<Class<?>> getGAEAnnotatedMetaInfClasses(ExternalContext context, String filter)
317     {
318         if (!filter.equals("none"))
319         {
320             String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
321             Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
322             if (paths != null)
323             {
324                 List<Class<?>> list = new ArrayList<Class<?>>();
325                 for (Object pathObject : paths)
326                 {
327                     String path = (String) pathObject;
328                     if (path.endsWith(".jar") && GAEUtils.wildcardMatch(path, jarFilesToScan, GAEUtils.WEB_LIB_PREFIX))
329                     {
330                         // GAE does not use WAR format, so the app is just uncompressed in a directory
331                         // What we need here is just take the path of the file, and open the file as a
332                         // jar file. Then, if the jar should be scanned, try to find the required file.
333                         try
334                         {
335                             URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/"); 
336                             JarFile jarFile = JarUtils.getJarFile(jarUrl);
337                             if (jarFile != null)
338                             {
339                                 archiveClasses(jarFile, list);
340                             }
341                         }
342                         catch(IOException e)
343                         {
344                             log.log(Level.SEVERE, 
345                                     "IOException when reading jar file for annotations using filter: "+filter, e);
346                         }
347                     }
348                 }
349                 return list;
350             }
351         }
352         return Collections.emptyList();
353     }
354 
355     protected Collection<Class<?>> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url)
356     {
357         return Collections.emptyList();
358         /*
359         try
360         {
361             List<Class<?>> list = new ArrayList<Class<?>>();
362             JarFile jarFile = getJarFile(url);
363             if (jarFile == null)
364             {
365                 return list;
366             }
367             else
368             {
369                 return archiveClasses(ctx, jarFile, list);
370             }
371         }
372         catch(IOException e)
373         {
374             throw new FacesException("cannot scan jar file for annotations:"+url, e);
375         }*/
376     }
377 
378     protected Collection<Class<?>> getAnnotatedWebInfClasses(ExternalContext ctx) throws IOException
379     {
380         String scanPackages = ctx.getInitParameter(SCAN_PACKAGES);
381         if (scanPackages != null)
382         {
383             try
384             {
385                 return packageClasses(ctx, scanPackages);
386             }
387             catch (ClassNotFoundException e)
388             {
389                 throw new FacesException(e);
390             }
391             catch (IOException e)
392             {
393                 throw new FacesException(e);
394             }
395         }
396         else
397         {
398             return webClasses(ctx);
399         }
400     }
401     
402     /**
403      * <p>Return a list of the classes defined within the given packages
404      * If there are no such classes, a zero-length list will be returned.</p>
405      *
406      * @param scanPackages the package configuration
407      *
408      * @exception ClassNotFoundException if a located class cannot be loaded
409      * @exception IOException if an input/output error occurs
410      */
411     private List<Class<?>> packageClasses(final ExternalContext externalContext,
412             final String scanPackages) throws ClassNotFoundException, IOException
413     {
414 
415         List<Class<?>> list = new ArrayList<Class<?>>();
416 
417         String[] scanPackageTokens = scanPackages.split(",");
418         for (String scanPackageToken : scanPackageTokens)
419         {
420             if (scanPackageToken.toLowerCase().endsWith(".jar"))
421             {
422                 URL jarResource = externalContext.getResource(WEB_LIB_PREFIX
423                         + scanPackageToken);
424                 String jarURLString = "jar:" + jarResource.toString() + "!/";
425                 URL url = new URL(jarURLString);
426                 JarFile jarFile = ((JarURLConnection) url.openConnection())
427                         .getJarFile();
428 
429                 archiveClasses(jarFile, list);
430             }
431             else
432             {
433                 List<Class> list2 = new ArrayList<Class>();
434                 _PackageInfo.getInstance().getClasses(list2, scanPackageToken);
435                 for (Class c : list2)
436                 {
437                     list.add(c);                    
438                 }
439             }
440         }
441         return list;
442     }    
443     
444     /**
445      * <p>Return a list of classes to examine from the specified JAR archive.
446      * If this archive has no classes in it, a zero-length list is returned.</p>
447      *
448      * @param context <code>ExternalContext</code> instance for
449      *  this application
450      * @param jar <code>JarFile</code> for the archive to be scanned
451      *
452      * @exception ClassNotFoundException if a located class cannot be loaded
453      */
454     private List<Class<?>> archiveClasses(JarFile jar, List<Class<?>> list)
455     {
456         // Accumulate and return a list of classes in this JAR file
457         ClassLoader loader = ClassUtils.getContextClassLoader();
458         if (loader == null)
459         {
460             loader = this.getClass().getClassLoader();
461         }
462         Enumeration<JarEntry> entries = jar.entries();
463         while (entries.hasMoreElements())
464         {
465             JarEntry entry = entries.nextElement();
466             if (entry.isDirectory())
467             {
468                 continue; // This is a directory
469             }
470             String name = entry.getName();
471             if (name.startsWith("META-INF/"))
472             {
473                 continue; // Attribute files
474             }
475             if (!name.endsWith(".class"))
476             {
477                 continue; // This is not a class
478             }
479 
480             DataInputStream in = null;
481             boolean couldContainAnnotation = false;
482             try
483             {
484                 in = new DataInputStream(jar.getInputStream(entry));
485                 couldContainAnnotation = _filter
486                         .couldContainAnnotationsOnClassDef(in,
487                                 byteCodeAnnotationsNames);
488             }
489             catch (IOException e)
490             {
491                 // Include this class - we can't scan this class using
492                 // the filter, but it could be valid, so we need to
493                 // load it using the classLoader. Anyway, log a debug
494                 // message.
495                 couldContainAnnotation = true;
496                 if (log.isLoggable(Level.FINE))
497                 {
498                     log.fine("IOException when filtering class " + name
499                             + " for annotations");
500                 }
501             }
502             finally
503             {
504                 if (in != null)
505                 {
506                     try
507                     {
508                         in.close();
509                     }
510                     catch (IOException e)
511                     {
512                         // No Op
513                     }
514                 }
515             }
516 
517             if (couldContainAnnotation)
518             {
519                 name = name.substring(0, name.length() - 6); // Trim ".class"
520                 Class<?> clazz = null;
521                 try
522                 {
523                     clazz = loader.loadClass(name.replace('/', '.'));
524                 }
525                 catch (NoClassDefFoundError e)
526                 {
527                     // Skip this class - we cannot analyze classes we cannot load
528                 }
529                 catch (Exception e)
530                 {
531                     // Skip this class - we cannot analyze classes we cannot load
532                 }
533                 if (clazz != null)
534                 {
535                     list.add(clazz);
536                 }
537             }
538         }
539         return list;
540 
541     }
542     
543     /**
544      * <p>Return a list of the classes defined under the
545      * <code>/WEB-INF/classes</code> directory of this web
546      * application.  If there are no such classes, a zero-length list
547      * will be returned.</p>
548      *
549      * @param externalContext <code>ExternalContext</code> instance for
550      *  this application
551      *
552      * @exception ClassNotFoundException if a located class cannot be loaded
553      */
554     private List<Class<?>> webClasses(ExternalContext externalContext)
555     {
556         List<Class<?>> list = new ArrayList<Class<?>>();
557         webClasses(externalContext, WEB_CLASSES_PREFIX, list);
558         return list;
559     }
560 
561     /**
562      * <p>Add classes found in the specified directory to the specified
563      * list, recursively calling this method when a directory is encountered.</p>
564      *
565      * @param externalContext <code>ExternalContext</code> instance for
566      *  this application
567      * @param prefix Prefix specifying the "directory path" to be searched
568      * @param list List to be appended to
569      *
570      * @exception ClassNotFoundException if a located class cannot be loaded
571      */
572     private void webClasses(ExternalContext externalContext, String prefix,
573             List<Class<?>> list)
574     {
575 
576         ClassLoader loader = getClassLoader();
577 
578         Set<String> paths = externalContext.getResourcePaths(prefix);
579         if(paths == null)
580         {
581             return; //need this in case there is no WEB-INF/classes directory
582         }
583         if (log.isLoggable(Level.FINEST))
584         {
585             log.finest("webClasses(" + prefix + ") - Received " + paths.size()
586                     + " paths to check");
587         }
588 
589         String path = null;
590 
591         if (paths.isEmpty())
592         {
593             if (log.isLoggable(Level.WARNING))
594             {
595                 log
596                         .warning("AnnotationConfigurator does not found classes "
597                                 + "for annotations in "
598                                 + prefix
599                                 + " ."
600                                 + " This could happen because maven jetty plugin is used"
601                                 + " (goal jetty:run). Try configure "
602                                 + SCAN_PACKAGES + " init parameter "
603                                 + "or use jetty:run-exploded instead.");
604             }
605         }
606         else
607         {
608             for (Object pathObject : paths)
609             {
610                 path = (String) pathObject;
611                 if (path.endsWith("/"))
612                 {
613                     webClasses(externalContext, path, list);
614                 }
615                 else if (path.endsWith(".class"))
616                 {
617                     DataInputStream in = null;
618                     boolean couldContainAnnotation = false;
619                     try
620                     {
621                         in = new DataInputStream(externalContext
622                                 .getResourceAsStream(path));
623                         couldContainAnnotation = _filter
624                                 .couldContainAnnotationsOnClassDef(in,
625                                         byteCodeAnnotationsNames);
626                     }
627                     catch (IOException e)
628                     {
629                         // Include this class - we can't scan this class using
630                         // the filter, but it could be valid, so we need to
631                         // load it using the classLoader. Anyway, log a debug
632                         // message.
633                         couldContainAnnotation = true;
634                         if (log.isLoggable(Level.FINE))
635                         {
636                             log.fine("IOException when filtering class " + path
637                                     + " for annotations");
638                         }
639                     }
640                     finally
641                     {
642                         if (in != null)
643                         {
644                             try
645                             {
646                                 in.close();
647                             }
648                             catch (IOException e)
649                             {
650                                 // No Op
651                             }
652                         }
653                     }
654 
655                     if (couldContainAnnotation)
656                     {
657                         //Load it and add it to list for later processing
658                         path = path.substring(WEB_CLASSES_PREFIX.length()); // Strip prefix
659                         path = path.substring(0, path.length() - 6); // Strip suffix
660                         path = path.replace('/', '.'); // Convert to FQCN
661 
662                         Class<?> clazz = null;
663                         try
664                         {
665                             clazz = loader.loadClass(path);
666                         }
667                         catch (NoClassDefFoundError e)
668                         {
669                             // Skip this class - we cannot analyze classes we cannot load
670                         }
671                         catch (Exception e)
672                         {
673                             // Skip this class - we cannot analyze classes we cannot load
674                         }
675                         if (clazz != null)
676                         {
677                             list.add(clazz);
678                         }
679                     }
680                 }
681             }
682         }
683     }
684     
685     private JarFile getJarFile(URL url) throws IOException
686     {
687         URLConnection conn = url.openConnection();
688         conn.setUseCaches(false);
689         conn.setDefaultUseCaches(false);
690 
691         JarFile jarFile;
692         if (conn instanceof JarURLConnection)
693         {
694             jarFile = ((JarURLConnection) conn).getJarFile();
695         }
696         else
697         {
698             jarFile = _getAlternativeJarFile(url);
699         }
700         return jarFile;
701     }
702     
703 
704     /**
705      * taken from org.apache.myfaces.view.facelets.util.Classpath
706      * 
707      * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile
708      * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full
709      * solution, since an unpacked WAR or EAR will not have JAR "files" as such.
710      */
711     private static JarFile _getAlternativeJarFile(URL url) throws IOException
712     {
713         String urlFile = url.getFile();
714 
715         // Trim off any suffix - which is prefixed by "!/" on Weblogic
716         int separatorIndex = urlFile.indexOf("!/");
717 
718         // OK, didn't find that. Try the less safe "!", used on OC4J
719         if (separatorIndex == -1)
720         {
721             separatorIndex = urlFile.indexOf('!');
722         }
723 
724         if (separatorIndex != -1)
725         {
726             String jarFileUrl = urlFile.substring(0, separatorIndex);
727             // And trim off any "file:" prefix.
728             if (jarFileUrl.startsWith("file:"))
729             {
730                 jarFileUrl = jarFileUrl.substring("file:".length());
731             }
732 
733             return new JarFile(jarFileUrl);
734         }
735 
736         return null;
737     }
738         
739     private ClassLoader getClassLoader()
740     {
741         ClassLoader loader = ClassUtils.getContextClassLoader();
742         if (loader == null)
743         {
744             loader = this.getClass().getClassLoader();
745         }
746         return loader;
747     }
748     
749     private void processClass(Map<Class<? extends Annotation>,Set<Class<?>>> map, Class<?> clazz)
750     {
751         Annotation[] annotations = clazz.getAnnotations();
752         for (Annotation anno : annotations)
753         {
754             Class<? extends Annotation> annotationClass = anno.annotationType();
755             if (JSF_ANNOTATION_CLASSES.contains(annotationClass))
756             {
757                 Set<Class<?>> set = map.get(annotationClass);
758                 if (set == null)
759                 {
760                     set = new HashSet<Class<?>>();
761                     set.add(clazz);
762                     map.put(annotationClass, set);
763                 }
764                 else
765                 {
766                     set.add(clazz);
767                 }
768 
769             }
770         }
771     }
772 }