Coverage Report - org.apache.myfaces.config.annotation.DefaultAnnotationProvider
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultAnnotationProvider
0%
0/240
0%
0/116
6.188
 
 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  0
     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  0
         Set<String> bcan = new HashSet<String>(10, 1f);
 116  0
         bcan.add("Ljavax/faces/component/FacesComponent;");
 117  0
         bcan.add("Ljavax/faces/component/behavior/FacesBehavior;");
 118  0
         bcan.add("Ljavax/faces/convert/FacesConverter;");
 119  0
         bcan.add("Ljavax/faces/validator/FacesValidator;");
 120  0
         bcan.add("Ljavax/faces/render/FacesRenderer;");
 121  0
         bcan.add("Ljavax/faces/bean/ManagedBean;");
 122  0
         bcan.add("Ljavax/faces/event/NamedEvent;");
 123  
         //bcan.add("Ljavax/faces/event/ListenerFor;");
 124  
         //bcan.add("Ljavax/faces/event/ListenersFor;");
 125  0
         bcan.add("Ljavax/faces/render/FacesBehaviorRenderer;");
 126  0
         bcan.add("Ljavax/faces/view/facelets/FaceletsResourceResolver;");
 127  
 
 128  0
         byteCodeAnnotationsNames = Collections.unmodifiableSet(bcan);
 129  
     }
 130  
     
 131  
     private static final Set<Class<? extends Annotation>> JSF_ANNOTATION_CLASSES;
 132  
     
 133  
     static
 134  
     {
 135  0
         Set<Class<? extends Annotation>> bcan = new HashSet<Class<? extends Annotation>>(10, 1f);
 136  0
         bcan.add(FacesComponent.class);
 137  0
         bcan.add(FacesBehavior.class);
 138  0
         bcan.add(FacesConverter.class);
 139  0
         bcan.add(FacesValidator.class);
 140  0
         bcan.add(FacesRenderer.class);
 141  0
         bcan.add(ManagedBean.class);
 142  0
         bcan.add(NamedEvent.class);
 143  0
         bcan.add(FacesBehaviorRenderer.class);
 144  0
         bcan.add(FaceletsResourceResolver.class);
 145  0
         JSF_ANNOTATION_CLASSES = Collections.unmodifiableSet(bcan);
 146  0
     }
 147  
     
 148  
     public DefaultAnnotationProvider()
 149  
     {
 150  0
         super();
 151  0
         _filter = new _ClassByteCodeAnnotationFilter();
 152  0
     }
 153  
     
 154  
     @Override
 155  
     public Map<Class<? extends Annotation>, Set<Class<?>>> getAnnotatedClasses(ExternalContext ctx)
 156  
     {
 157  0
         String useCdiForAnnotationScanning =
 158  
                 ctx.getInitParameter(CdiAnnotationProviderExtension.USE_CDI_FOR_ANNOTATION_SCANNING);
 159  0
         if (useCdiForAnnotationScanning != null && "true".equalsIgnoreCase(useCdiForAnnotationScanning.trim()))
 160  
         {
 161  0
             BeanManager beanManager = CDIUtils.getBeanManager(ctx);
 162  0
             CdiAnnotationProviderExtension extension =
 163  
                     BeanProvider.getContextualReference(beanManager, CdiAnnotationProviderExtension.class, false);
 164  0
             return extension.getMap();
 165  
         }
 166  
 
 167  0
         Map<Class<? extends Annotation>,Set<Class<?>>> map = new HashMap<Class<? extends Annotation>, Set<Class<?>>>();
 168  0
         Collection<Class<?>> classes = null;
 169  
 
 170  
         //1. Scan for annotations on /WEB-INF/classes
 171  
         try
 172  
         {
 173  0
             classes = getAnnotatedWebInfClasses(ctx);
 174  
         }
 175  0
         catch (IOException e)
 176  
         {
 177  0
             throw new FacesException(e);
 178  0
         }
 179  
 
 180  0
         for (Class<?> clazz : classes)
 181  
         {
 182  0
             processClass(map, clazz);
 183  0
         }
 184  
         
 185  
         //2. Scan for annotations on classpath
 186  0
         String jarAnnotationFilesToScanParam = MyfacesConfig.getCurrentInstance(ctx).getGaeJsfAnnotationsJarFiles();
 187  0
         jarAnnotationFilesToScanParam = jarAnnotationFilesToScanParam != null ? 
 188  
                 jarAnnotationFilesToScanParam.trim() : null;
 189  0
         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  0
             classes = getGAEAnnotatedMetaInfClasses(ctx, jarAnnotationFilesToScanParam);
 196  
         }
 197  
         else
 198  
         {
 199  
             try
 200  
             {
 201  0
                 AnnotationProvider provider
 202  
                         = AnnotationProviderFactory.getAnnotationProviderFactory(ctx).getAnnotationProvider(ctx);
 203  0
                 classes = getAnnotatedMetaInfClasses(ctx, provider.getBaseUrls(ctx));
 204  
             }
 205  0
             catch (IOException e)
 206  
             {
 207  0
                 throw new FacesException(e);
 208  0
             }
 209  
         }
 210  
         
 211  0
         for (Class<?> clazz : classes)
 212  
         {
 213  0
             processClass(map, clazz);
 214  0
         }
 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  0
         return map;
 233  
     }
 234  
     
 235  
     @Override
 236  
     public Set<URL> getBaseUrls() throws IOException
 237  
     {
 238  0
         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  0
         Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
 243  0
         while (resources.hasMoreElements())
 244  
         {
 245  0
             urlSet.add(resources.nextElement());
 246  
         }
 247  
 
 248  
         //Scan files inside META-INF ending with .faces-config.xml
 249  0
         URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACES_CONFIG_SUFFIX);
 250  0
         for (int i = 0; i < urls.length; i++)
 251  
         {
 252  0
             urlSet.add(urls[i]);
 253  
         }
 254  
         
 255  0
         return urlSet;
 256  
     }
 257  
     
 258  
     @Override
 259  
     public Set<URL> getBaseUrls(ExternalContext context) throws IOException
 260  
     {
 261  0
         String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
 262  0
         jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
 263  0
         if (ContainerUtils.isRunningOnGoogleAppEngine(context) && 
 264  
             jarFilesToScanParam != null &&
 265  
             jarFilesToScanParam.length() > 0)
 266  
         {
 267  0
             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  0
             Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
 272  0
             while (resources.hasMoreElements())
 273  
             {
 274  0
                 urlSet.add(resources.nextElement());
 275  
             }
 276  
             
 277  0
             Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
 278  
                     context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
 279  0
             if (urlsGAE != null)
 280  
             {
 281  0
                 urlSet.addAll(urlsGAE);
 282  
             }
 283  0
             return urlSet;
 284  
         }
 285  
         else
 286  
         {
 287  0
             return getBaseUrls();
 288  
         }
 289  
     }
 290  
 
 291  
     protected Collection<Class<?>> getAnnotatedMetaInfClasses(ExternalContext ctx, Set<URL> urls)
 292  
     {
 293  0
         if (urls != null && !urls.isEmpty())
 294  
         {
 295  0
             List<Class<?>> list = new ArrayList<Class<?>>();
 296  0
             for (URL url : urls)
 297  
             {
 298  
                 try
 299  
                 {
 300  0
                     JarFile jarFile = getJarFile(url);
 301  0
                     if (jarFile != null)
 302  
                     {
 303  0
                         archiveClasses(jarFile, list);
 304  
                     }
 305  
                 }
 306  0
                 catch(IOException e)
 307  
                 {
 308  0
                     log.log(Level.SEVERE, "cannot scan jar file for annotations:"+url, e);
 309  0
                 }
 310  0
             }
 311  0
             return list;
 312  
         }
 313  0
         return Collections.emptyList();
 314  
     }
 315  
     
 316  
     protected Collection<Class<?>> getGAEAnnotatedMetaInfClasses(ExternalContext context, String filter)
 317  
     {
 318  0
         if (!filter.equals("none"))
 319  
         {
 320  0
             String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
 321  0
             Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
 322  0
             if (paths != null)
 323  
             {
 324  0
                 List<Class<?>> list = new ArrayList<Class<?>>();
 325  0
                 for (Object pathObject : paths)
 326  
                 {
 327  0
                     String path = (String) pathObject;
 328  0
                     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  0
                             URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/"); 
 336  0
                             JarFile jarFile = JarUtils.getJarFile(jarUrl);
 337  0
                             if (jarFile != null)
 338  
                             {
 339  0
                                 archiveClasses(jarFile, list);
 340  
                             }
 341  
                         }
 342  0
                         catch(IOException e)
 343  
                         {
 344  0
                             log.log(Level.SEVERE, 
 345  
                                     "IOException when reading jar file for annotations using filter: "+filter, e);
 346  0
                         }
 347  
                     }
 348  0
                 }
 349  0
                 return list;
 350  
             }
 351  
         }
 352  0
         return Collections.emptyList();
 353  
     }
 354  
 
 355  
     protected Collection<Class<?>> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url)
 356  
     {
 357  0
         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  0
         String scanPackages = ctx.getInitParameter(SCAN_PACKAGES);
 381  0
         if (scanPackages != null)
 382  
         {
 383  
             try
 384  
             {
 385  0
                 return packageClasses(ctx, scanPackages);
 386  
             }
 387  0
             catch (ClassNotFoundException e)
 388  
             {
 389  0
                 throw new FacesException(e);
 390  
             }
 391  0
             catch (IOException e)
 392  
             {
 393  0
                 throw new FacesException(e);
 394  
             }
 395  
         }
 396  
         else
 397  
         {
 398  0
             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  0
         List<Class<?>> list = new ArrayList<Class<?>>();
 416  
 
 417  0
         String[] scanPackageTokens = scanPackages.split(",");
 418  0
         for (String scanPackageToken : scanPackageTokens)
 419  
         {
 420  0
             if (scanPackageToken.toLowerCase().endsWith(".jar"))
 421  
             {
 422  0
                 URL jarResource = externalContext.getResource(WEB_LIB_PREFIX
 423  
                         + scanPackageToken);
 424  0
                 String jarURLString = "jar:" + jarResource.toString() + "!/";
 425  0
                 URL url = new URL(jarURLString);
 426  0
                 JarFile jarFile = ((JarURLConnection) url.openConnection())
 427  
                         .getJarFile();
 428  
 
 429  0
                 archiveClasses(jarFile, list);
 430  0
             }
 431  
             else
 432  
             {
 433  0
                 List<Class> list2 = new ArrayList<Class>();
 434  0
                 _PackageInfo.getInstance().getClasses(list2, scanPackageToken);
 435  0
                 for (Class c : list2)
 436  
                 {
 437  0
                     list.add(c);                    
 438  0
                 }
 439  
             }
 440  
         }
 441  0
         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  0
         ClassLoader loader = ClassUtils.getContextClassLoader();
 458  0
         if (loader == null)
 459  
         {
 460  0
             loader = this.getClass().getClassLoader();
 461  
         }
 462  0
         Enumeration<JarEntry> entries = jar.entries();
 463  0
         while (entries.hasMoreElements())
 464  
         {
 465  0
             JarEntry entry = entries.nextElement();
 466  0
             if (entry.isDirectory())
 467  
             {
 468  0
                 continue; // This is a directory
 469  
             }
 470  0
             String name = entry.getName();
 471  0
             if (name.startsWith("META-INF/"))
 472  
             {
 473  0
                 continue; // Attribute files
 474  
             }
 475  0
             if (!name.endsWith(".class"))
 476  
             {
 477  0
                 continue; // This is not a class
 478  
             }
 479  
 
 480  0
             DataInputStream in = null;
 481  0
             boolean couldContainAnnotation = false;
 482  
             try
 483  
             {
 484  0
                 in = new DataInputStream(jar.getInputStream(entry));
 485  0
                 couldContainAnnotation = _filter
 486  
                         .couldContainAnnotationsOnClassDef(in,
 487  
                                 byteCodeAnnotationsNames);
 488  
             }
 489  0
             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  0
                 couldContainAnnotation = true;
 496  0
                 if (log.isLoggable(Level.FINE))
 497  
                 {
 498  0
                     log.fine("IOException when filtering class " + name
 499  
                             + " for annotations");
 500  
                 }
 501  
             }
 502  
             finally
 503  
             {
 504  0
                 if (in != null)
 505  
                 {
 506  
                     try
 507  
                     {
 508  0
                         in.close();
 509  
                     }
 510  0
                     catch (IOException e)
 511  
                     {
 512  
                         // No Op
 513  0
                     }
 514  
                 }
 515  
             }
 516  
 
 517  0
             if (couldContainAnnotation)
 518  
             {
 519  0
                 name = name.substring(0, name.length() - 6); // Trim ".class"
 520  0
                 Class<?> clazz = null;
 521  
                 try
 522  
                 {
 523  0
                     clazz = loader.loadClass(name.replace('/', '.'));
 524  
                 }
 525  0
                 catch (NoClassDefFoundError e)
 526  
                 {
 527  
                     // Skip this class - we cannot analyze classes we cannot load
 528  
                 }
 529  0
                 catch (Exception e)
 530  
                 {
 531  
                     // Skip this class - we cannot analyze classes we cannot load
 532  0
                 }
 533  0
                 if (clazz != null)
 534  
                 {
 535  0
                     list.add(clazz);
 536  
                 }
 537  
             }
 538  0
         }
 539  0
         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  0
         List<Class<?>> list = new ArrayList<Class<?>>();
 557  0
         webClasses(externalContext, WEB_CLASSES_PREFIX, list);
 558  0
         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  0
         ClassLoader loader = getClassLoader();
 577  
 
 578  0
         Set<String> paths = externalContext.getResourcePaths(prefix);
 579  0
         if(paths == null)
 580  
         {
 581  0
             return; //need this in case there is no WEB-INF/classes directory
 582  
         }
 583  0
         if (log.isLoggable(Level.FINEST))
 584  
         {
 585  0
             log.finest("webClasses(" + prefix + ") - Received " + paths.size()
 586  
                     + " paths to check");
 587  
         }
 588  
 
 589  0
         String path = null;
 590  
 
 591  0
         if (paths.isEmpty())
 592  
         {
 593  0
             if (log.isLoggable(Level.WARNING))
 594  
             {
 595  0
                 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  0
             for (Object pathObject : paths)
 609  
             {
 610  0
                 path = (String) pathObject;
 611  0
                 if (path.endsWith("/"))
 612  
                 {
 613  0
                     webClasses(externalContext, path, list);
 614  
                 }
 615  0
                 else if (path.endsWith(".class"))
 616  
                 {
 617  0
                     DataInputStream in = null;
 618  0
                     boolean couldContainAnnotation = false;
 619  
                     try
 620  
                     {
 621  0
                         in = new DataInputStream(externalContext
 622  
                                 .getResourceAsStream(path));
 623  0
                         couldContainAnnotation = _filter
 624  
                                 .couldContainAnnotationsOnClassDef(in,
 625  
                                         byteCodeAnnotationsNames);
 626  
                     }
 627  0
                     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  0
                         couldContainAnnotation = true;
 634  0
                         if (log.isLoggable(Level.FINE))
 635  
                         {
 636  0
                             log.fine("IOException when filtering class " + path
 637  
                                     + " for annotations");
 638  
                         }
 639  
                     }
 640  
                     finally
 641  
                     {
 642  0
                         if (in != null)
 643  
                         {
 644  
                             try
 645  
                             {
 646  0
                                 in.close();
 647  
                             }
 648  0
                             catch (IOException e)
 649  
                             {
 650  
                                 // No Op
 651  0
                             }
 652  
                         }
 653  
                     }
 654  
 
 655  0
                     if (couldContainAnnotation)
 656  
                     {
 657  
                         //Load it and add it to list for later processing
 658  0
                         path = path.substring(WEB_CLASSES_PREFIX.length()); // Strip prefix
 659  0
                         path = path.substring(0, path.length() - 6); // Strip suffix
 660  0
                         path = path.replace('/', '.'); // Convert to FQCN
 661  
 
 662  0
                         Class<?> clazz = null;
 663  
                         try
 664  
                         {
 665  0
                             clazz = loader.loadClass(path);
 666  
                         }
 667  0
                         catch (NoClassDefFoundError e)
 668  
                         {
 669  
                             // Skip this class - we cannot analyze classes we cannot load
 670  
                         }
 671  0
                         catch (Exception e)
 672  
                         {
 673  
                             // Skip this class - we cannot analyze classes we cannot load
 674  0
                         }
 675  0
                         if (clazz != null)
 676  
                         {
 677  0
                             list.add(clazz);
 678  
                         }
 679  
                     }
 680  
                 }
 681  0
             }
 682  
         }
 683  0
     }
 684  
     
 685  
     private JarFile getJarFile(URL url) throws IOException
 686  
     {
 687  0
         URLConnection conn = url.openConnection();
 688  0
         conn.setUseCaches(false);
 689  0
         conn.setDefaultUseCaches(false);
 690  
 
 691  
         JarFile jarFile;
 692  0
         if (conn instanceof JarURLConnection)
 693  
         {
 694  0
             jarFile = ((JarURLConnection) conn).getJarFile();
 695  
         }
 696  
         else
 697  
         {
 698  0
             jarFile = _getAlternativeJarFile(url);
 699  
         }
 700  0
         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  0
         String urlFile = url.getFile();
 714  
 
 715  
         // Trim off any suffix - which is prefixed by "!/" on Weblogic
 716  0
         int separatorIndex = urlFile.indexOf("!/");
 717  
 
 718  
         // OK, didn't find that. Try the less safe "!", used on OC4J
 719  0
         if (separatorIndex == -1)
 720  
         {
 721  0
             separatorIndex = urlFile.indexOf('!');
 722  
         }
 723  
 
 724  0
         if (separatorIndex != -1)
 725  
         {
 726  0
             String jarFileUrl = urlFile.substring(0, separatorIndex);
 727  
             // And trim off any "file:" prefix.
 728  0
             if (jarFileUrl.startsWith("file:"))
 729  
             {
 730  0
                 jarFileUrl = jarFileUrl.substring("file:".length());
 731  
             }
 732  
 
 733  0
             return new JarFile(jarFileUrl);
 734  
         }
 735  
 
 736  0
         return null;
 737  
     }
 738  
         
 739  
     private ClassLoader getClassLoader()
 740  
     {
 741  0
         ClassLoader loader = ClassUtils.getContextClassLoader();
 742  0
         if (loader == null)
 743  
         {
 744  0
             loader = this.getClass().getClassLoader();
 745  
         }
 746  0
         return loader;
 747  
     }
 748  
     
 749  
     private void processClass(Map<Class<? extends Annotation>,Set<Class<?>>> map, Class<?> clazz)
 750  
     {
 751  0
         Annotation[] annotations = clazz.getAnnotations();
 752  0
         for (Annotation anno : annotations)
 753  
         {
 754  0
             Class<? extends Annotation> annotationClass = anno.annotationType();
 755  0
             if (JSF_ANNOTATION_CLASSES.contains(annotationClass))
 756  
             {
 757  0
                 Set<Class<?>> set = map.get(annotationClass);
 758  0
                 if (set == null)
 759  
                 {
 760  0
                     set = new HashSet<Class<?>>();
 761  0
                     set.add(clazz);
 762  0
                     map.put(annotationClass, set);
 763  
                 }
 764  
                 else
 765  
                 {
 766  0
                     set.add(clazz);
 767  
                 }
 768  
 
 769  
             }
 770  
         }
 771  0
     }
 772  
 }