Save This Page
Home » apache-tomcat-6.0.26-src » org.apache » catalina » loader » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    * 
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    * 
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   
   18   
   19   package org.apache.catalina.loader;
   20   
   21   import java.io.ByteArrayInputStream;
   22   import java.io.File;
   23   import java.io.FileOutputStream;
   24   import java.io.FilePermission;
   25   import java.io.IOException;
   26   import java.io.InputStream;
   27   import java.lang.ref.Reference;
   28   import java.lang.ref.WeakReference;
   29   import java.lang.reflect.Field;
   30   import java.lang.reflect.InvocationTargetException;
   31   import java.lang.reflect.Method;
   32   import java.lang.reflect.Modifier;
   33   import java.net.MalformedURLException;
   34   import java.net.URL;
   35   import java.net.URLClassLoader;
   36   import java.security.AccessControlException;
   37   import java.security.AccessController;
   38   import java.security.CodeSource;
   39   import java.security.Permission;
   40   import java.security.PermissionCollection;
   41   import java.security.Policy;
   42   import java.security.PrivilegedAction;
   43   import java.util.ArrayList;
   44   import java.util.Collection;
   45   import java.util.Enumeration;
   46   import java.util.HashMap;
   47   import java.util.Iterator;
   48   import java.util.LinkedHashMap;
   49   import java.util.List;
   50   import java.util.Map;
   51   import java.util.ResourceBundle;
   52   import java.util.Set;
   53   import java.util.Vector;
   54   import java.util.concurrent.ThreadPoolExecutor;
   55   import java.util.jar.Attributes;
   56   import java.util.jar.JarEntry;
   57   import java.util.jar.JarFile;
   58   import java.util.jar.Manifest;
   59   import java.util.jar.Attributes.Name;
   60   
   61   import javax.naming.NameClassPair;
   62   import javax.naming.NamingEnumeration;
   63   import javax.naming.NamingException;
   64   import javax.naming.directory.DirContext;
   65   
   66   import org.apache.catalina.Globals;
   67   import org.apache.catalina.Lifecycle;
   68   import org.apache.catalina.LifecycleException;
   69   import org.apache.catalina.LifecycleListener;
   70   import org.apache.catalina.util.StringManager;
   71   import org.apache.jasper.servlet.JasperLoader;
   72   import org.apache.naming.JndiPermission;
   73   import org.apache.naming.resources.Resource;
   74   import org.apache.naming.resources.ResourceAttributes;
   75   import org.apache.tomcat.util.IntrospectionUtils;
   76   
   77   /**
   78    * Specialized web application class loader.
   79    * <p>
   80    * This class loader is a full reimplementation of the 
   81    * <code>URLClassLoader</code> from the JDK. It is designed to be fully
   82    * compatible with a normal <code>URLClassLoader</code>, although its internal
   83    * behavior may be completely different.
   84    * <p>
   85    * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows 
   86    * the delegation model recommended in the specification. The system class 
   87    * loader will be queried first, then the local repositories, and only then 
   88    * delegation to the parent class loader will occur. This allows the web 
   89    * application to override any shared class except the classes from J2SE.
   90    * Special handling is provided from the JAXP XML parser interfaces, the JNDI
   91    * interfaces, and the classes from the servlet API, which are never loaded 
   92    * from the webapp repository.
   93    * <p>
   94    * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper 
   95    * compilation technology, any repository which contains classes from 
   96    * the servlet API will be ignored by the class loader.
   97    * <p>
   98    * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
   99    * URLs which include the full JAR URL when a class is loaded from a JAR file,
  100    * which allows setting security permission at the class level, even when a
  101    * class is contained inside a JAR.
  102    * <p>
  103    * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
  104    * the order they are added via the initial constructor and/or any subsequent
  105    * calls to <code>addRepository()</code> or <code>addJar()</code>.
  106    * <p>
  107    * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
  108    * security is made unless a security manager is present.
  109    *
  110    * @author Remy Maucherat
  111    * @author Craig R. McClanahan
  112    * @version $Revision: 915603 $ $Date: 2010-02-24 01:07:06 +0100 (Wed, 24 Feb 2010) $
  113    */
  114   public class WebappClassLoader
  115       extends URLClassLoader
  116       implements Reloader, Lifecycle
  117    {
  118   
  119       protected static org.apache.juli.logging.Log log=
  120           org.apache.juli.logging.LogFactory.getLog( WebappClassLoader.class );
  121   
  122       /**
  123        * List of ThreadGroup names to ignore when scanning for web application
  124        * started threads that need to be shut down.
  125        */
  126       private static final List<String> JVM_THREAD_GROUP_NAMES =
  127           new ArrayList<String>();
  128   
  129       public static final boolean ENABLE_CLEAR_REFERENCES = 
  130           Boolean.valueOf(System.getProperty("org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES", "true")).booleanValue();
  131   
  132       /**
  133        * @deprecated Not used
  134        */
  135       protected class PrivilegedFindResource
  136           implements PrivilegedAction {
  137   
  138           protected File file;
  139           protected String path;
  140   
  141           PrivilegedFindResource(File file, String path) {
  142               this.file = file;
  143               this.path = path;
  144           }
  145   
  146           public Object run() {
  147               return findResourceInternal(file, path);
  148           }
  149   
  150       }
  151   
  152       static {
  153           JVM_THREAD_GROUP_NAMES.add("system");
  154           JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
  155       }
  156   
  157       protected class PrivilegedFindResourceByName
  158           implements PrivilegedAction<ResourceEntry> {
  159   
  160           protected String name;
  161           protected String path;
  162   
  163           PrivilegedFindResourceByName(String name, String path) {
  164               this.name = name;
  165               this.path = path;
  166           }
  167   
  168           public ResourceEntry run() {
  169               return findResourceInternal(name, path);
  170           }
  171   
  172       }
  173   
  174       
  175       protected final class PrivilegedGetClassLoader
  176           implements PrivilegedAction<ClassLoader> {
  177   
  178           public Class<?> clazz;
  179   
  180           public PrivilegedGetClassLoader(Class<?> clazz){
  181               this.clazz = clazz;
  182           }
  183   
  184           public ClassLoader run() {       
  185               return clazz.getClassLoader();
  186           }           
  187       }
  188   
  189       
  190   
  191   
  192       // ------------------------------------------------------- Static Variables
  193   
  194   
  195       /**
  196        * The set of trigger classes that will cause a proposed repository not
  197        * to be added if this class is visible to the class loader that loaded
  198        * this factory class.  Typically, trigger classes will be listed for
  199        * components that have been integrated into the JDK for later versions,
  200        * but where the corresponding JAR files are required to run on
  201        * earlier versions.
  202        */
  203       protected static final String[] triggers = {
  204           "javax.servlet.Servlet"                     // Servlet API
  205       };
  206   
  207   
  208       /**
  209        * Set of package names which are not allowed to be loaded from a webapp
  210        * class loader without delegating first.
  211        */
  212       protected static final String[] packageTriggers = {
  213       };
  214   
  215   
  216       /**
  217        * The string manager for this package.
  218        */
  219       protected static final StringManager sm =
  220           StringManager.getManager(Constants.Package);
  221   
  222       
  223       /**
  224        * Use anti JAR locking code, which does URL rerouting when accessing
  225        * resources.
  226        */
  227       boolean antiJARLocking = false; 
  228       
  229   
  230       // ----------------------------------------------------------- Constructors
  231   
  232   
  233       /**
  234        * Construct a new ClassLoader with no defined repositories and no
  235        * parent ClassLoader.
  236        */
  237       public WebappClassLoader() {
  238   
  239           super(new URL[0]);
  240           this.parent = getParent();
  241           system = getSystemClassLoader();
  242           securityManager = System.getSecurityManager();
  243   
  244           if (securityManager != null) {
  245               refreshPolicy();
  246           }
  247   
  248       }
  249   
  250   
  251       /**
  252        * Construct a new ClassLoader with no defined repositories and no
  253        * parent ClassLoader.
  254        */
  255       public WebappClassLoader(ClassLoader parent) {
  256   
  257           super(new URL[0], parent);
  258                   
  259           this.parent = getParent();
  260           
  261           system = getSystemClassLoader();
  262           securityManager = System.getSecurityManager();
  263   
  264           if (securityManager != null) {
  265               refreshPolicy();
  266           }
  267       }
  268   
  269   
  270       // ----------------------------------------------------- Instance Variables
  271   
  272   
  273       /**
  274        * Associated directory context giving access to the resources in this
  275        * webapp.
  276        */
  277       protected DirContext resources = null;
  278   
  279   
  280       /**
  281        * The cache of ResourceEntry for classes and resources we have loaded,
  282        * keyed by resource name.
  283        */
  284       protected HashMap resourceEntries = new HashMap();
  285   
  286   
  287       /**
  288        * The list of not found resources.
  289        */
  290       protected HashMap<String, String> notFoundResources =
  291           new LinkedHashMap<String, String>() {
  292           private static final long serialVersionUID = 1L;
  293           protected boolean removeEldestEntry(
  294                   Map.Entry<String, String> eldest) {
  295               return size() > 1000;
  296           }
  297       };
  298   
  299   
  300       /**
  301        * Should this class loader delegate to the parent class loader
  302        * <strong>before</strong> searching its own repositories (i.e. the
  303        * usual Java2 delegation model)?  If set to <code>false</code>,
  304        * this class loader will search its own repositories first, and
  305        * delegate to the parent only if the class or resource is not
  306        * found locally.
  307        */
  308       protected boolean delegate = false;
  309   
  310   
  311       /**
  312        * Last time a JAR was accessed.
  313        */
  314       protected long lastJarAccessed = 0L;
  315   
  316   
  317       /**
  318        * The list of local repositories, in the order they should be searched
  319        * for locally loaded classes or resources.
  320        */
  321       protected String[] repositories = new String[0];
  322   
  323   
  324        /**
  325         * Repositories URLs, used to cache the result of getURLs.
  326         */
  327        protected URL[] repositoryURLs = null;
  328   
  329   
  330       /**
  331        * Repositories translated as path in the work directory (for Jasper
  332        * originally), but which is used to generate fake URLs should getURLs be
  333        * called.
  334        */
  335       protected File[] files = new File[0];
  336   
  337   
  338       /**
  339        * The list of JARs, in the order they should be searched
  340        * for locally loaded classes or resources.
  341        */
  342       protected JarFile[] jarFiles = new JarFile[0];
  343   
  344   
  345       /**
  346        * The list of JARs, in the order they should be searched
  347        * for locally loaded classes or resources.
  348        */
  349       protected File[] jarRealFiles = new File[0];
  350   
  351   
  352       /**
  353        * The path which will be monitored for added Jar files.
  354        */
  355       protected String jarPath = null;
  356   
  357   
  358       /**
  359        * The list of JARs, in the order they should be searched
  360        * for locally loaded classes or resources.
  361        */
  362       protected String[] jarNames = new String[0];
  363   
  364   
  365       /**
  366        * The list of JARs last modified dates, in the order they should be
  367        * searched for locally loaded classes or resources.
  368        */
  369       protected long[] lastModifiedDates = new long[0];
  370   
  371   
  372       /**
  373        * The list of resources which should be checked when checking for
  374        * modifications.
  375        */
  376       protected String[] paths = new String[0];
  377   
  378   
  379       /**
  380        * A list of read File and Jndi Permission's required if this loader
  381        * is for a web application context.
  382        */
  383       protected ArrayList permissionList = new ArrayList();
  384   
  385   
  386       /**
  387        * Path where resources loaded from JARs will be extracted.
  388        */
  389       protected File loaderDir = null;
  390       protected String canonicalLoaderDir = null;
  391   
  392       /**
  393        * The PermissionCollection for each CodeSource for a web
  394        * application context.
  395        */
  396       protected HashMap loaderPC = new HashMap();
  397   
  398   
  399       /**
  400        * Instance of the SecurityManager installed.
  401        */
  402       protected SecurityManager securityManager = null;
  403   
  404   
  405       /**
  406        * The parent class loader.
  407        */
  408       protected ClassLoader parent = null;
  409   
  410   
  411       /**
  412        * The system class loader.
  413        */
  414       protected ClassLoader system = null;
  415   
  416   
  417       /**
  418        * Has this component been started?
  419        */
  420       protected boolean started = false;
  421   
  422   
  423       /**
  424        * Has external repositories.
  425        */
  426       protected boolean hasExternalRepositories = false;
  427   
  428       /**
  429        * need conversion for properties files
  430        */
  431       protected boolean needConvert = false;
  432   
  433   
  434       /**
  435        * All permission.
  436        */
  437       protected Permission allPermission = new java.security.AllPermission();
  438   
  439   
  440       /**
  441        * Should Tomcat attempt to terminate threads that have been started by the
  442        * web application? Stopping threads is performed via the deprecated (for
  443        * good reason) <code>Thread.stop()</code> method and is likely to result in
  444        * instability. As such, enabling this should be viewed as an option of last
  445        * resort in a development environment and is not recommended in a
  446        * production environment. If not specified, the default value of
  447        * <code>false</code> will be used. Note that instances of
  448        * java.util.TimerThread will always be terminate since a safe method exists
  449        * to do so.
  450        */
  451       private boolean clearReferencesStopThreads = false;
  452   
  453       /**
  454        * Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()}
  455        * when the class loader is stopped? If not specified, the default value
  456        * of <code>true</code> is used. Changing the default setting is likely to
  457        * lead to memory leaks and other issues.
  458        */
  459       private boolean clearReferencesLogFactoryRelease = true;
  460   
  461       // ------------------------------------------------------------- Properties
  462   
  463   
  464       /**
  465        * Get associated resources.
  466        */
  467       public DirContext getResources() {
  468   
  469           return this.resources;
  470   
  471       }
  472   
  473   
  474       /**
  475        * Set associated resources.
  476        */
  477       public void setResources(DirContext resources) {
  478   
  479           this.resources = resources;
  480   
  481       }
  482   
  483   
  484       /**
  485        * Return the "delegate first" flag for this class loader.
  486        */
  487       public boolean getDelegate() {
  488   
  489           return (this.delegate);
  490   
  491       }
  492   
  493   
  494       /**
  495        * Set the "delegate first" flag for this class loader.
  496        *
  497        * @param delegate The new "delegate first" flag
  498        */
  499       public void setDelegate(boolean delegate) {
  500   
  501           this.delegate = delegate;
  502   
  503       }
  504   
  505   
  506       /**
  507        * @return Returns the antiJARLocking.
  508        */
  509       public boolean getAntiJARLocking() {
  510           return antiJARLocking;
  511       }
  512       
  513       
  514       /**
  515        * @param antiJARLocking The antiJARLocking to set.
  516        */
  517       public void setAntiJARLocking(boolean antiJARLocking) {
  518           this.antiJARLocking = antiJARLocking;
  519       }
  520   
  521       
  522       /**
  523        * If there is a Java SecurityManager create a read FilePermission
  524        * or JndiPermission for the file directory path.
  525        *
  526        * @param path file directory path
  527        */
  528       public void addPermission(String path) {
  529           if (path == null) {
  530               return;
  531           }
  532   
  533           if (securityManager != null) {
  534               Permission permission = null;
  535               if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) {
  536                   if (!path.endsWith("/")) {
  537                       path = path + "/";
  538                   }
  539                   permission = new JndiPermission(path + "*");
  540                   addPermission(permission);
  541               } else {
  542                   if (!path.endsWith(File.separator)) {
  543                       permission = new FilePermission(path, "read");
  544                       addPermission(permission);
  545                       path = path + File.separator;
  546                   }
  547                   permission = new FilePermission(path + "-", "read");
  548                   addPermission(permission);
  549               }
  550           }
  551       }
  552   
  553   
  554       /**
  555        * If there is a Java SecurityManager create a read FilePermission
  556        * or JndiPermission for URL.
  557        *
  558        * @param url URL for a file or directory on local system
  559        */
  560       public void addPermission(URL url) {
  561           if (url != null) {
  562               addPermission(url.toString());
  563           }
  564       }
  565   
  566   
  567       /**
  568        * If there is a Java SecurityManager create a Permission.
  569        *
  570        * @param permission The permission
  571        */
  572       public void addPermission(Permission permission) {
  573           if ((securityManager != null) && (permission != null)) {
  574               permissionList.add(permission);
  575           }
  576       }
  577   
  578   
  579       /**
  580        * Return the JAR path.
  581        */
  582       public String getJarPath() {
  583   
  584           return this.jarPath;
  585   
  586       }
  587   
  588   
  589       /**
  590        * Change the Jar path.
  591        */
  592       public void setJarPath(String jarPath) {
  593   
  594           this.jarPath = jarPath;
  595   
  596       }
  597   
  598   
  599       /**
  600        * Change the work directory.
  601        */
  602       public void setWorkDir(File workDir) {
  603           this.loaderDir = new File(workDir, "loader");
  604           if (loaderDir == null) {
  605               canonicalLoaderDir = null;
  606           } else { 
  607               try {
  608                   canonicalLoaderDir = loaderDir.getCanonicalPath();
  609                   if (!canonicalLoaderDir.endsWith(File.separator)) {
  610                       canonicalLoaderDir += File.separator;
  611                   }
  612               } catch (IOException ioe) {
  613                   canonicalLoaderDir = null;
  614               }
  615           }
  616       }
  617   
  618        /**
  619         * Utility method for use in subclasses.
  620         * Must be called before Lifecycle methods to have any effect.
  621         */
  622        protected void setParentClassLoader(ClassLoader pcl) {
  623            parent = pcl;
  624        }
  625   
  626        /**
  627         * Return the clearReferencesStopThreads flag for this Context.
  628         */
  629        public boolean getClearReferencesStopThreads() {
  630            return (this.clearReferencesStopThreads);
  631        }
  632   
  633        
  634        /**
  635         * Set the clearReferencesStopThreads feature for this Context.
  636         *
  637         * @param clearReferencesStopThreads The new flag value
  638         */
  639        public void setClearReferencesStopThreads(
  640                boolean clearReferencesStopThreads) {
  641            this.clearReferencesStopThreads = clearReferencesStopThreads;
  642        }
  643   
  644   
  645        /**
  646         * Return the clearReferencesLogFactoryRelease flag for this Context.
  647         */
  648        public boolean getClearReferencesLogFactoryRelease() {
  649            return (this.clearReferencesLogFactoryRelease);
  650        }
  651   
  652   
  653        /**
  654         * Set the clearReferencesLogFactoryRelease feature for this Context.
  655         *
  656         * @param clearReferencesLogFactoryRelease The new flag value
  657         */
  658        public void setClearReferencesLogFactoryRelease(
  659                boolean clearReferencesLogFactoryRelease) {
  660            this.clearReferencesLogFactoryRelease =
  661                clearReferencesLogFactoryRelease;
  662        }
  663   
  664   
  665       // ------------------------------------------------------- Reloader Methods
  666   
  667   
  668       /**
  669        * Add a new repository to the set of places this ClassLoader can look for
  670        * classes to be loaded.
  671        *
  672        * @param repository Name of a source of classes to be loaded, such as a
  673        *  directory pathname, a JAR file pathname, or a ZIP file pathname
  674        *
  675        * @exception IllegalArgumentException if the specified repository is
  676        *  invalid or does not exist
  677        */
  678       public void addRepository(String repository) {
  679   
  680           // Ignore any of the standard repositories, as they are set up using
  681           // either addJar or addRepository
  682           if (repository.startsWith("/WEB-INF/lib")
  683               || repository.startsWith("/WEB-INF/classes"))
  684               return;
  685   
  686           // Add this repository to our underlying class loader
  687           try {
  688               URL url = new URL(repository);
  689               super.addURL(url);
  690               hasExternalRepositories = true;
  691               repositoryURLs = null;
  692           } catch (MalformedURLException e) {
  693               IllegalArgumentException iae = new IllegalArgumentException
  694                   ("Invalid repository: " + repository); 
  695               iae.initCause(e);
  696               throw iae;
  697           }
  698   
  699       }
  700   
  701   
  702       /**
  703        * Add a new repository to the set of places this ClassLoader can look for
  704        * classes to be loaded.
  705        *
  706        * @param repository Name of a source of classes to be loaded, such as a
  707        *  directory pathname, a JAR file pathname, or a ZIP file pathname
  708        *
  709        * @exception IllegalArgumentException if the specified repository is
  710        *  invalid or does not exist
  711        */
  712       synchronized void addRepository(String repository, File file) {
  713   
  714           // Note : There should be only one (of course), but I think we should
  715           // keep this a bit generic
  716   
  717           if (repository == null)
  718               return;
  719   
  720           if (log.isDebugEnabled())
  721               log.debug("addRepository(" + repository + ")");
  722   
  723           int i;
  724   
  725           // Add this repository to our internal list
  726           String[] result = new String[repositories.length + 1];
  727           for (i = 0; i < repositories.length; i++) {
  728               result[i] = repositories[i];
  729           }
  730           result[repositories.length] = repository;
  731           repositories = result;
  732   
  733           // Add the file to the list
  734           File[] result2 = new File[files.length + 1];
  735           for (i = 0; i < files.length; i++) {
  736               result2[i] = files[i];
  737           }
  738           result2[files.length] = file;
  739           files = result2;
  740   
  741       }
  742   
  743   
  744       synchronized void addJar(String jar, JarFile jarFile, File file)
  745           throws IOException {
  746   
  747           if (jar == null)
  748               return;
  749           if (jarFile == null)
  750               return;
  751           if (file == null)
  752               return;
  753   
  754           if (log.isDebugEnabled())
  755               log.debug("addJar(" + jar + ")");
  756   
  757           int i;
  758   
  759           if ((jarPath != null) && (jar.startsWith(jarPath))) {
  760   
  761               String jarName = jar.substring(jarPath.length());
  762               while (jarName.startsWith("/"))
  763                   jarName = jarName.substring(1);
  764   
  765               String[] result = new String[jarNames.length + 1];
  766               for (i = 0; i < jarNames.length; i++) {
  767                   result[i] = jarNames[i];
  768               }
  769               result[jarNames.length] = jarName;
  770               jarNames = result;
  771   
  772           }
  773   
  774           try {
  775   
  776               // Register the JAR for tracking
  777   
  778               long lastModified =
  779                   ((ResourceAttributes) resources.getAttributes(jar))
  780                   .getLastModified();
  781   
  782               String[] result = new String[paths.length + 1];
  783               for (i = 0; i < paths.length; i++) {
  784                   result[i] = paths[i];
  785               }
  786               result[paths.length] = jar;
  787               paths = result;
  788   
  789               long[] result3 = new long[lastModifiedDates.length + 1];
  790               for (i = 0; i < lastModifiedDates.length; i++) {
  791                   result3[i] = lastModifiedDates[i];
  792               }
  793               result3[lastModifiedDates.length] = lastModified;
  794               lastModifiedDates = result3;
  795   
  796           } catch (NamingException e) {
  797               // Ignore
  798           }
  799   
  800           // If the JAR currently contains invalid classes, don't actually use it
  801           // for classloading
  802           if (!validateJarFile(file))
  803               return;
  804   
  805           JarFile[] result2 = new JarFile[jarFiles.length + 1];
  806           for (i = 0; i < jarFiles.length; i++) {
  807               result2[i] = jarFiles[i];
  808           }
  809           result2[jarFiles.length] = jarFile;
  810           jarFiles = result2;
  811   
  812           // Add the file to the list
  813           File[] result4 = new File[jarRealFiles.length + 1];
  814           for (i = 0; i < jarRealFiles.length; i++) {
  815               result4[i] = jarRealFiles[i];
  816           }
  817           result4[jarRealFiles.length] = file;
  818           jarRealFiles = result4;
  819       }
  820   
  821   
  822       /**
  823        * Return a String array of the current repositories for this class
  824        * loader.  If there are no repositories, a zero-length array is
  825        * returned.For security reason, returns a clone of the Array (since 
  826        * String are immutable).
  827        */
  828       public String[] findRepositories() {
  829   
  830           return ((String[])repositories.clone());
  831   
  832       }
  833   
  834   
  835       /**
  836        * Have one or more classes or resources been modified so that a reload
  837        * is appropriate?
  838        */
  839       public boolean modified() {
  840   
  841           if (log.isDebugEnabled())
  842               log.debug("modified()");
  843   
  844           // Checking for modified loaded resources
  845           int length = paths.length;
  846   
  847           // A rare race condition can occur in the updates of the two arrays
  848           // It's totally ok if the latest class added is not checked (it will
  849           // be checked the next time
  850           int length2 = lastModifiedDates.length;
  851           if (length > length2)
  852               length = length2;
  853   
  854           for (int i = 0; i < length; i++) {
  855               try {
  856                   long lastModified =
  857                       ((ResourceAttributes) resources.getAttributes(paths[i]))
  858                       .getLastModified();
  859                   if (lastModified != lastModifiedDates[i]) {
  860                       if( log.isDebugEnabled() ) 
  861                           log.debug("  Resource '" + paths[i]
  862                                     + "' was modified; Date is now: "
  863                                     + new java.util.Date(lastModified) + " Was: "
  864                                     + new java.util.Date(lastModifiedDates[i]));
  865                       return (true);
  866                   }
  867               } catch (NamingException e) {
  868                   log.error("    Resource '" + paths[i] + "' is missing");
  869                   return (true);
  870               }
  871           }
  872   
  873           length = jarNames.length;
  874   
  875           // Check if JARs have been added or removed
  876           if (getJarPath() != null) {
  877   
  878               try {
  879                   NamingEnumeration enumeration = resources.listBindings(getJarPath());
  880                   int i = 0;
  881                   while (enumeration.hasMoreElements() && (i < length)) {
  882                       NameClassPair ncPair = (NameClassPair) enumeration.nextElement();
  883                       String name = ncPair.getName();
  884                       // Ignore non JARs present in the lib folder
  885                       if (!name.endsWith(".jar"))
  886                           continue;
  887                       if (!name.equals(jarNames[i])) {
  888                           // Missing JAR
  889                           log.info("    Additional JARs have been added : '" 
  890                                    + name + "'");
  891                           return (true);
  892                       }
  893                       i++;
  894                   }
  895                   if (enumeration.hasMoreElements()) {
  896                       while (enumeration.hasMoreElements()) {
  897                           NameClassPair ncPair = 
  898                               (NameClassPair) enumeration.nextElement();
  899                           String name = ncPair.getName();
  900                           // Additional non-JAR files are allowed
  901                           if (name.endsWith(".jar")) {
  902                               // There was more JARs
  903                               log.info("    Additional JARs have been added");
  904                               return (true);
  905                           }
  906                       }
  907                   } else if (i < jarNames.length) {
  908                       // There was less JARs
  909                       log.info("    Additional JARs have been added");
  910                       return (true);
  911                   }
  912               } catch (NamingException e) {
  913                   if (log.isDebugEnabled())
  914                       log.debug("    Failed tracking modifications of '"
  915                           + getJarPath() + "'");
  916               } catch (ClassCastException e) {
  917                   log.error("    Failed tracking modifications of '"
  918                             + getJarPath() + "' : " + e.getMessage());
  919               }
  920   
  921           }
  922   
  923           // No classes have been modified
  924           return (false);
  925   
  926       }
  927   
  928   
  929       /**
  930        * Render a String representation of this object.
  931        */
  932       public String toString() {
  933   
  934           StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
  935           sb.append("  delegate: ");
  936           sb.append(delegate);
  937           sb.append("\r\n");
  938           sb.append("  repositories:\r\n");
  939           if (repositories != null) {
  940               for (int i = 0; i < repositories.length; i++) {
  941                   sb.append("    ");
  942                   sb.append(repositories[i]);
  943                   sb.append("\r\n");
  944               }
  945           }
  946           if (this.parent != null) {
  947               sb.append("----------> Parent Classloader:\r\n");
  948               sb.append(this.parent.toString());
  949               sb.append("\r\n");
  950           }
  951           return (sb.toString());
  952   
  953       }
  954   
  955   
  956       // ---------------------------------------------------- ClassLoader Methods
  957   
  958   
  959        /**
  960         * Add the specified URL to the classloader.
  961         */
  962        protected void addURL(URL url) {
  963            super.addURL(url);
  964            hasExternalRepositories = true;
  965            repositoryURLs = null;
  966        }
  967   
  968   
  969       /**
  970        * Find the specified class in our local repositories, if possible.  If
  971        * not found, throw <code>ClassNotFoundException</code>.
  972        *
  973        * @param name Name of the class to be loaded
  974        *
  975        * @exception ClassNotFoundException if the class was not found
  976        */
  977       public Class findClass(String name) throws ClassNotFoundException {
  978   
  979           if (log.isDebugEnabled())
  980               log.debug("    findClass(" + name + ")");
  981   
  982           // Cannot load anything from local repositories if class loader is stopped
  983           if (!started) {
  984               throw new ClassNotFoundException(name);
  985           }
  986   
  987           // (1) Permission to define this class when using a SecurityManager
  988           if (securityManager != null) {
  989               int i = name.lastIndexOf('.');
  990               if (i >= 0) {
  991                   try {
  992                       if (log.isTraceEnabled())
  993                           log.trace("      securityManager.checkPackageDefinition");
  994                       securityManager.checkPackageDefinition(name.substring(0,i));
  995                   } catch (Exception se) {
  996                       if (log.isTraceEnabled())
  997                           log.trace("      -->Exception-->ClassNotFoundException", se);
  998                       throw new ClassNotFoundException(name, se);
  999                   }
 1000               }
 1001           }
 1002   
 1003           // Ask our superclass to locate this class, if possible
 1004           // (throws ClassNotFoundException if it is not found)
 1005           Class clazz = null;
 1006           try {
 1007               if (log.isTraceEnabled())
 1008                   log.trace("      findClassInternal(" + name + ")");
 1009               try {
 1010                   clazz = findClassInternal(name);
 1011               } catch(ClassNotFoundException cnfe) {
 1012                   if (!hasExternalRepositories) {
 1013                       throw cnfe;
 1014                   }
 1015               } catch(AccessControlException ace) {
 1016                   log.warn("WebappClassLoader.findClassInternal(" + name
 1017                           + ") security exception: " + ace.getMessage(), ace);
 1018                   throw new ClassNotFoundException(name, ace);
 1019               } catch (RuntimeException e) {
 1020                   if (log.isTraceEnabled())
 1021                       log.trace("      -->RuntimeException Rethrown", e);
 1022                   throw e;
 1023               }
 1024               if ((clazz == null) && hasExternalRepositories) {
 1025                   try {
 1026                       clazz = super.findClass(name);
 1027                   } catch(AccessControlException ace) {
 1028                       log.warn("WebappClassLoader.findClassInternal(" + name
 1029                               + ") security exception: " + ace.getMessage(), ace);
 1030                       throw new ClassNotFoundException(name, ace);
 1031                   } catch (RuntimeException e) {
 1032                       if (log.isTraceEnabled())
 1033                           log.trace("      -->RuntimeException Rethrown", e);
 1034                       throw e;
 1035                   }
 1036               }
 1037               if (clazz == null) {
 1038                   if (log.isDebugEnabled())
 1039                       log.debug("    --> Returning ClassNotFoundException");
 1040                   throw new ClassNotFoundException(name);
 1041               }
 1042           } catch (ClassNotFoundException e) {
 1043               if (log.isTraceEnabled())
 1044                   log.trace("    --> Passing on ClassNotFoundException");
 1045               throw e;
 1046           }
 1047   
 1048           // Return the class we have located
 1049           if (log.isTraceEnabled())
 1050               log.debug("      Returning class " + clazz);
 1051           
 1052           if ((log.isTraceEnabled()) && (clazz != null)) {
 1053               ClassLoader cl;
 1054               if (Globals.IS_SECURITY_ENABLED){
 1055                   cl = AccessController.doPrivileged(
 1056                       new PrivilegedGetClassLoader(clazz));
 1057               } else {
 1058                   cl = clazz.getClassLoader();
 1059               }
 1060               log.debug("      Loaded by " + cl.toString());
 1061           }
 1062           return (clazz);
 1063   
 1064       }
 1065   
 1066   
 1067       /**
 1068        * Find the specified resource in our local repository, and return a
 1069        * <code>URL</code> refering to it, or <code>null</code> if this resource
 1070        * cannot be found.
 1071        *
 1072        * @param name Name of the resource to be found
 1073        */
 1074       public URL findResource(final String name) {
 1075   
 1076           if (log.isDebugEnabled())
 1077               log.debug("    findResource(" + name + ")");
 1078   
 1079           URL url = null;
 1080   
 1081           ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
 1082           if (entry == null) {
 1083               if (securityManager != null) {
 1084                   PrivilegedAction<ResourceEntry> dp =
 1085                       new PrivilegedFindResourceByName(name, name);
 1086                   entry = AccessController.doPrivileged(dp);
 1087               } else {
 1088                   entry = findResourceInternal(name, name);
 1089               }
 1090           }
 1091           if (entry != null) {
 1092               url = entry.source;
 1093           }
 1094   
 1095           if ((url == null) && hasExternalRepositories)
 1096               url = super.findResource(name);
 1097   
 1098           if (log.isDebugEnabled()) {
 1099               if (url != null)
 1100                   log.debug("    --> Returning '" + url.toString() + "'");
 1101               else
 1102                   log.debug("    --> Resource not found, returning null");
 1103           }
 1104           return (url);
 1105   
 1106       }
 1107   
 1108   
 1109       /**
 1110        * Return an enumeration of <code>URLs</code> representing all of the
 1111        * resources with the given name.  If no resources with this name are
 1112        * found, return an empty enumeration.
 1113        *
 1114        * @param name Name of the resources to be found
 1115        *
 1116        * @exception IOException if an input/output error occurs
 1117        */
 1118       public Enumeration findResources(String name) throws IOException {
 1119   
 1120           if (log.isDebugEnabled())
 1121               log.debug("    findResources(" + name + ")");
 1122   
 1123           Vector result = new Vector();
 1124   
 1125           int jarFilesLength = jarFiles.length;
 1126           int repositoriesLength = repositories.length;
 1127   
 1128           int i;
 1129   
 1130           // Looking at the repositories
 1131           for (i = 0; i < repositoriesLength; i++) {
 1132               try {
 1133                   String fullPath = repositories[i] + name;
 1134                   resources.lookup(fullPath);
 1135                   // Note : Not getting an exception here means the resource was
 1136                   // found
 1137                   try {
 1138                       result.addElement(getURI(new File(files[i], name)));
 1139                   } catch (MalformedURLException e) {
 1140                       // Ignore
 1141                   }
 1142               } catch (NamingException e) {
 1143               }
 1144           }
 1145   
 1146           // Looking at the JAR files
 1147           synchronized (jarFiles) {
 1148               if (openJARs()) {
 1149                   for (i = 0; i < jarFilesLength; i++) {
 1150                       JarEntry jarEntry = jarFiles[i].getJarEntry(name);
 1151                       if (jarEntry != null) {
 1152                           try {
 1153                               String jarFakeUrl = getURI(jarRealFiles[i]).toString();
 1154                               jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
 1155                               result.addElement(new URL(jarFakeUrl));
 1156                           } catch (MalformedURLException e) {
 1157                               // Ignore
 1158                           }
 1159                       }
 1160                   }
 1161               }
 1162           }
 1163   
 1164           // Adding the results of a call to the superclass
 1165           if (hasExternalRepositories) {
 1166   
 1167               Enumeration otherResourcePaths = super.findResources(name);
 1168   
 1169               while (otherResourcePaths.hasMoreElements()) {
 1170                   result.addElement(otherResourcePaths.nextElement());
 1171               }
 1172   
 1173           }
 1174   
 1175           return result.elements();
 1176   
 1177       }
 1178   
 1179   
 1180       /**
 1181        * Find the resource with the given name.  A resource is some data
 1182        * (images, audio, text, etc.) that can be accessed by class code in a
 1183        * way that is independent of the location of the code.  The name of a
 1184        * resource is a "/"-separated path name that identifies the resource.
 1185        * If the resource cannot be found, return <code>null</code>.
 1186        * <p>
 1187        * This method searches according to the following algorithm, returning
 1188        * as soon as it finds the appropriate URL.  If the resource cannot be
 1189        * found, returns <code>null</code>.
 1190        * <ul>
 1191        * <li>If the <code>delegate</code> property is set to <code>true</code>,
 1192        *     call the <code>getResource()</code> method of the parent class
 1193        *     loader, if any.</li>
 1194        * <li>Call <code>findResource()</code> to find this resource in our
 1195        *     locally defined repositories.</li>
 1196        * <li>Call the <code>getResource()</code> method of the parent class
 1197        *     loader, if any.</li>
 1198        * </ul>
 1199        *
 1200        * @param name Name of the resource to return a URL for
 1201        */
 1202       public URL getResource(String name) {
 1203   
 1204           if (log.isDebugEnabled())
 1205               log.debug("getResource(" + name + ")");
 1206           URL url = null;
 1207   
 1208           // (1) Delegate to parent if requested
 1209           if (delegate) {
 1210               if (log.isDebugEnabled())
 1211                   log.debug("  Delegating to parent classloader " + parent);
 1212               ClassLoader loader = parent;
 1213               if (loader == null)
 1214                   loader = system;
 1215               url = loader.getResource(name);
 1216               if (url != null) {
 1217                   if (log.isDebugEnabled())
 1218                       log.debug("  --> Returning '" + url.toString() + "'");
 1219                   return (url);
 1220               }
 1221           }
 1222   
 1223           // (2) Search local repositories
 1224           url = findResource(name);
 1225           if (url != null) {
 1226               // Locating the repository for special handling in the case 
 1227               // of a JAR
 1228               if (antiJARLocking) {
 1229                   ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
 1230                   try {
 1231                       String repository = entry.codeBase.toString();
 1232                       if ((repository.endsWith(".jar")) 
 1233                               && (!(name.endsWith(".class")))) {
 1234                           // Copy binary content to the work directory if not present
 1235                           File resourceFile = new File(loaderDir, name);
 1236                           url = getURI(resourceFile);
 1237                       }
 1238                   } catch (Exception e) {
 1239                       // Ignore
 1240                   }
 1241               }
 1242               if (log.isDebugEnabled())
 1243                   log.debug("  --> Returning '" + url.toString() + "'");
 1244               return (url);
 1245           }
 1246   
 1247           // (3) Delegate to parent unconditionally if not already attempted
 1248           if( !delegate ) {
 1249               ClassLoader loader = parent;
 1250               if (loader == null)
 1251                   loader = system;
 1252               url = loader.getResource(name);
 1253               if (url != null) {
 1254                   if (log.isDebugEnabled())
 1255                       log.debug("  --> Returning '" + url.toString() + "'");
 1256                   return (url);
 1257               }
 1258           }
 1259   
 1260           // (4) Resource was not found
 1261           if (log.isDebugEnabled())
 1262               log.debug("  --> Resource not found, returning null");
 1263           return (null);
 1264   
 1265       }
 1266   
 1267   
 1268       /**
 1269        * Find the resource with the given name, and return an input stream
 1270        * that can be used for reading it.  The search order is as described
 1271        * for <code>getResource()</code>, after checking to see if the resource
 1272        * data has been previously cached.  If the resource cannot be found,
 1273        * return <code>null</code>.
 1274        *
 1275        * @param name Name of the resource to return an input stream for
 1276        */
 1277       public InputStream getResourceAsStream(String name) {
 1278   
 1279           if (log.isDebugEnabled())
 1280               log.debug("getResourceAsStream(" + name + ")");
 1281           InputStream stream = null;
 1282   
 1283           // (0) Check for a cached copy of this resource
 1284           stream = findLoadedResource(name);
 1285           if (stream != null) {
 1286               if (log.isDebugEnabled())
 1287                   log.debug("  --> Returning stream from cache");
 1288               return (stream);
 1289           }
 1290   
 1291           // (1) Delegate to parent if requested
 1292           if (delegate) {
 1293               if (log.isDebugEnabled())
 1294                   log.debug("  Delegating to parent classloader " + parent);
 1295               ClassLoader loader = parent;
 1296               if (loader == null)
 1297                   loader = system;
 1298               stream = loader.getResourceAsStream(name);
 1299               if (stream != null) {
 1300                   // FIXME - cache???
 1301                   if (log.isDebugEnabled())
 1302                       log.debug("  --> Returning stream from parent");
 1303                   return (stream);
 1304               }
 1305           }
 1306   
 1307           // (2) Search local repositories
 1308           if (log.isDebugEnabled())
 1309               log.debug("  Searching local repositories");
 1310           URL url = findResource(name);
 1311           if (url != null) {
 1312               // FIXME - cache???
 1313               if (log.isDebugEnabled())
 1314                   log.debug("  --> Returning stream from local");
 1315               stream = findLoadedResource(name);
 1316               try {
 1317                   if (hasExternalRepositories && (stream == null))
 1318                       stream = url.openStream();
 1319               } catch (IOException e) {
 1320                   ; // Ignore
 1321               }
 1322               if (stream != null)
 1323                   return (stream);
 1324           }
 1325   
 1326           // (3) Delegate to parent unconditionally
 1327           if (!delegate) {
 1328               if (log.isDebugEnabled())
 1329                   log.debug("  Delegating to parent classloader unconditionally " + parent);
 1330               ClassLoader loader = parent;
 1331               if (loader == null)
 1332                   loader = system;
 1333               stream = loader.getResourceAsStream(name);
 1334               if (stream != null) {
 1335                   // FIXME - cache???
 1336                   if (log.isDebugEnabled())
 1337                       log.debug("  --> Returning stream from parent");
 1338                   return (stream);
 1339               }
 1340           }
 1341   
 1342           // (4) Resource was not found
 1343           if (log.isDebugEnabled())
 1344               log.debug("  --> Resource not found, returning null");
 1345           return (null);
 1346   
 1347       }
 1348   
 1349   
 1350       /**
 1351        * Load the class with the specified name.  This method searches for
 1352        * classes in the same manner as <code>loadClass(String, boolean)</code>
 1353        * with <code>false</code> as the second argument.
 1354        *
 1355        * @param name Name of the class to be loaded
 1356        *
 1357        * @exception ClassNotFoundException if the class was not found
 1358        */
 1359       public Class loadClass(String name) throws ClassNotFoundException {
 1360   
 1361           return (loadClass(name, false));
 1362   
 1363       }
 1364   
 1365   
 1366       /**
 1367        * Load the class with the specified name, searching using the following
 1368        * algorithm until it finds and returns the class.  If the class cannot
 1369        * be found, returns <code>ClassNotFoundException</code>.
 1370        * <ul>
 1371        * <li>Call <code>findLoadedClass(String)</code> to check if the
 1372        *     class has already been loaded.  If it has, the same
 1373        *     <code>Class</code> object is returned.</li>
 1374        * <li>If the <code>delegate</code> property is set to <code>true</code>,
 1375        *     call the <code>loadClass()</code> method of the parent class
 1376        *     loader, if any.</li>
 1377        * <li>Call <code>findClass()</code> to find this class in our locally
 1378        *     defined repositories.</li>
 1379        * <li>Call the <code>loadClass()</code> method of our parent
 1380        *     class loader, if any.</li>
 1381        * </ul>
 1382        * If the class was found using the above steps, and the
 1383        * <code>resolve</code> flag is <code>true</code>, this method will then
 1384        * call <code>resolveClass(Class)</code> on the resulting Class object.
 1385        *
 1386        * @param name Name of the class to be loaded
 1387        * @param resolve If <code>true</code> then resolve the class
 1388        *
 1389        * @exception ClassNotFoundException if the class was not found
 1390        */
 1391       public Class loadClass(String name, boolean resolve)
 1392           throws ClassNotFoundException {
 1393   
 1394           synchronized (name.intern()) {
 1395               if (log.isDebugEnabled())
 1396                   log.debug("loadClass(" + name + ", " + resolve + ")");
 1397               Class clazz = null;
 1398       
 1399               // Log access to stopped classloader
 1400               if (!started) {
 1401                   try {
 1402                       throw new IllegalStateException();
 1403                   } catch (IllegalStateException e) {
 1404                       log.info(sm.getString("webappClassLoader.stopped", name), e);
 1405                   }
 1406               }
 1407       
 1408               // (0) Check our previously loaded local class cache
 1409               clazz = findLoadedClass0(name);
 1410               if (clazz != null) {
 1411                   if (log.isDebugEnabled())
 1412                       log.debug("  Returning class from cache");
 1413                   if (resolve)
 1414                       resolveClass(clazz);
 1415                   return (clazz);
 1416               }
 1417       
 1418               // (0.1) Check our previously loaded class cache
 1419               clazz = findLoadedClass(name);
 1420               if (clazz != null) {
 1421                   if (log.isDebugEnabled())
 1422                       log.debug("  Returning class from cache");
 1423                   if (resolve)
 1424                       resolveClass(clazz);
 1425                   return (clazz);
 1426               }
 1427       
 1428               // (0.2) Try loading the class with the system class loader, to prevent
 1429               //       the webapp from overriding J2SE classes
 1430               try {
 1431                   clazz = system.loadClass(name);
 1432                   if (clazz != null) {
 1433                       if (resolve)
 1434                           resolveClass(clazz);
 1435                       return (clazz);
 1436                   }
 1437               } catch (ClassNotFoundException e) {
 1438                   // Ignore
 1439               }
 1440       
 1441               // (0.5) Permission to access this class when using a SecurityManager
 1442               if (securityManager != null) {
 1443                   int i = name.lastIndexOf('.');
 1444                   if (i >= 0) {
 1445                       try {
 1446                           securityManager.checkPackageAccess(name.substring(0,i));
 1447                       } catch (SecurityException se) {
 1448                           String error = "Security Violation, attempt to use " +
 1449                               "Restricted Class: " + name;
 1450                           log.info(error, se);
 1451                           throw new ClassNotFoundException(error, se);
 1452                       }
 1453                   }
 1454               }
 1455       
 1456               boolean delegateLoad = delegate || filter(name);
 1457       
 1458               // (1) Delegate to our parent if requested
 1459               if (delegateLoad) {
 1460                   if (log.isDebugEnabled())
 1461                       log.debug("  Delegating to parent classloader1 " + parent);
 1462                   ClassLoader loader = parent;
 1463                   if (loader == null)
 1464                       loader = system;
 1465                   try {
 1466                       clazz = loader.loadClass(name);
 1467                       if (clazz != null) {
 1468                           if (log.isDebugEnabled())
 1469                               log.debug("  Loading class from parent");
 1470                           if (resolve)
 1471                               resolveClass(clazz);
 1472                           return (clazz);
 1473                       }
 1474                   } catch (ClassNotFoundException e) {
 1475                       ;
 1476                   }
 1477               }
 1478       
 1479               // (2) Search local repositories
 1480               if (log.isDebugEnabled())
 1481                   log.debug("  Searching local repositories");
 1482               try {
 1483                   clazz = findClass(name);
 1484                   if (clazz != null) {
 1485                       if (log.isDebugEnabled())
 1486                           log.debug("  Loading class from local repository");
 1487                       if (resolve)
 1488                           resolveClass(clazz);
 1489                       return (clazz);
 1490                   }
 1491               } catch (ClassNotFoundException e) {
 1492                   ;
 1493               }
 1494       
 1495               // (3) Delegate to parent unconditionally
 1496               if (!delegateLoad) {
 1497                   if (log.isDebugEnabled())
 1498                       log.debug("  Delegating to parent classloader at end: " + parent);
 1499                   ClassLoader loader = parent;
 1500                   if (loader == null)
 1501                       loader = system;
 1502                   try {
 1503                       clazz = loader.loadClass(name);
 1504                       if (clazz != null) {
 1505                           if (log.isDebugEnabled())
 1506                               log.debug("  Loading class from parent");
 1507                           if (resolve)
 1508                               resolveClass(clazz);
 1509                           return (clazz);
 1510                       }
 1511                   } catch (ClassNotFoundException e) {
 1512                       ;
 1513                   }
 1514               }
 1515       
 1516               throw new ClassNotFoundException(name);
 1517           }
 1518       }
 1519   
 1520   
 1521       /**
 1522        * Get the Permissions for a CodeSource.  If this instance
 1523        * of WebappClassLoader is for a web application context,
 1524        * add read FilePermission or JndiPermissions for the base
 1525        * directory (if unpacked),
 1526        * the context URL, and jar file resources.
 1527        *
 1528        * @param codeSource where the code was loaded from
 1529        * @return PermissionCollection for CodeSource
 1530        */
 1531       protected PermissionCollection getPermissions(CodeSource codeSource) {
 1532   
 1533           String codeUrl = codeSource.getLocation().toString();
 1534           PermissionCollection pc;
 1535           if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) {
 1536               pc = super.getPermissions(codeSource);
 1537               if (pc != null) {
 1538                   Iterator perms = permissionList.iterator();
 1539                   while (perms.hasNext()) {
 1540                       Permission p = (Permission)perms.next();
 1541                       pc.add(p);
 1542                   }
 1543                   loaderPC.put(codeUrl,pc);
 1544               }
 1545           }
 1546           return (pc);
 1547   
 1548       }
 1549   
 1550   
 1551       /**
 1552        * Returns the search path of URLs for loading classes and resources.
 1553        * This includes the original list of URLs specified to the constructor,
 1554        * along with any URLs subsequently appended by the addURL() method.
 1555        * @return the search path of URLs for loading classes and resources.
 1556        */
 1557       public URL[] getURLs() {
 1558   
 1559           if (repositoryURLs != null) {
 1560               return repositoryURLs;
 1561           }
 1562   
 1563           URL[] external = super.getURLs();
 1564   
 1565           int filesLength = files.length;
 1566           int jarFilesLength = jarRealFiles.length;
 1567           int length = filesLength + jarFilesLength + external.length;
 1568           int i;
 1569   
 1570           try {
 1571   
 1572               URL[] urls = new URL[length];
 1573               for (i = 0; i < length; i++) {
 1574                   if (i < filesLength) {
 1575                       urls[i] = getURL(files[i], true);
 1576                   } else if (i < filesLength + jarFilesLength) {
 1577                       urls[i] = getURL(jarRealFiles[i - filesLength], true);
 1578                   } else {
 1579                       urls[i] = external[i - filesLength - jarFilesLength];
 1580                   }
 1581               }
 1582   
 1583               repositoryURLs = urls;
 1584   
 1585           } catch (MalformedURLException e) {
 1586               repositoryURLs = new URL[0];
 1587           }
 1588   
 1589           return repositoryURLs;
 1590   
 1591       }
 1592   
 1593   
 1594       // ------------------------------------------------------ Lifecycle Methods
 1595   
 1596   
 1597       /**
 1598        * Add a lifecycle event listener to this component.
 1599        *
 1600        * @param listener The listener to add
 1601        */
 1602       public void addLifecycleListener(LifecycleListener listener) {
 1603       }
 1604   
 1605   
 1606       /**
 1607        * Get the lifecycle listeners associated with this lifecycle. If this 
 1608        * Lifecycle has no listeners registered, a zero-length array is returned.
 1609        */
 1610       public LifecycleListener[] findLifecycleListeners() {
 1611           return new LifecycleListener[0];
 1612       }
 1613   
 1614   
 1615       /**
 1616        * Remove a lifecycle event listener from this component.
 1617        *
 1618        * @param listener The listener to remove
 1619        */
 1620       public void removeLifecycleListener(LifecycleListener listener) {
 1621       }
 1622   
 1623   
 1624       /**
 1625        * Start the class loader.
 1626        *
 1627        * @exception LifecycleException if a lifecycle error occurs
 1628        */
 1629       public void start() throws LifecycleException {
 1630   
 1631           started = true;
 1632           String encoding = null;
 1633           try {
 1634               encoding = System.getProperty("file.encoding");
 1635           } catch (Exception e) {
 1636               return;
 1637           }
 1638           if (encoding.indexOf("EBCDIC")!=-1) {
 1639               needConvert = true;
 1640           }
 1641   
 1642       }
 1643   
 1644   
 1645       public boolean isStarted() {
 1646           return started;
 1647       }
 1648   
 1649       /**
 1650        * Stop the class loader.
 1651        *
 1652        * @exception LifecycleException if a lifecycle error occurs
 1653        */
 1654       public void stop() throws LifecycleException {
 1655   
 1656           // Clearing references should be done before setting started to
 1657           // false, due to possible side effects
 1658           clearReferences();
 1659   
 1660           started = false;
 1661           
 1662           int length = files.length;
 1663           for (int i = 0; i < length; i++) {
 1664               files[i] = null;
 1665           }
 1666   
 1667           length = jarFiles.length;
 1668           for (int i = 0; i < length; i++) {
 1669               try {
 1670                   if (jarFiles[i] != null) {
 1671                       jarFiles[i].close();
 1672                   }
 1673               } catch (IOException e) {
 1674                   // Ignore
 1675               }
 1676               jarFiles[i] = null;
 1677           }
 1678   
 1679           notFoundResources.clear();
 1680           resourceEntries.clear();
 1681           resources = null;
 1682           repositories = null;
 1683           repositoryURLs = null;
 1684           files = null;
 1685           jarFiles = null;
 1686           jarRealFiles = null;
 1687           jarPath = null;
 1688           jarNames = null;
 1689           lastModifiedDates = null;
 1690           paths = null;
 1691           hasExternalRepositories = false;
 1692           parent = null;
 1693   
 1694           permissionList.clear();
 1695           loaderPC.clear();
 1696   
 1697           if (loaderDir != null) {
 1698               deleteDir(loaderDir);
 1699           }
 1700   
 1701       }
 1702   
 1703   
 1704       /**
 1705        * Used to periodically signal to the classloader to release 
 1706        * JAR resources.
 1707        */
 1708       public void closeJARs(boolean force) {
 1709           if (jarFiles.length > 0) {
 1710                   synchronized (jarFiles) {
 1711                       if (force || (System.currentTimeMillis() 
 1712                                     > (lastJarAccessed + 90000))) {
 1713                           for (int i = 0; i < jarFiles.length; i++) {
 1714                               try {
 1715                                   if (jarFiles[i] != null) {
 1716                                       jarFiles[i].close();
 1717                                       jarFiles[i] = null;
 1718                                   }
 1719                               } catch (IOException e) {
 1720                                   if (log.isDebugEnabled()) {
 1721                                       log.debug("Failed to close JAR", e);
 1722                                   }
 1723                               }
 1724                           }
 1725                       }
 1726                   }
 1727           }
 1728       }
 1729   
 1730   
 1731       // ------------------------------------------------------ Protected Methods
 1732   
 1733       
 1734       /**
 1735        * Clear references.
 1736        */
 1737       protected void clearReferences() {
 1738   
 1739           // De-register any remaining JDBC drivers
 1740           clearReferencesJdbc();
 1741   
 1742           // Stop any threads the web application started
 1743           clearReferencesThreads();
 1744           
 1745           // Clear any ThreadLocals loaded by this class loader
 1746           clearReferencesThreadLocals();
 1747           
 1748           // Clear RMI Targets loaded by this class loader
 1749           clearReferencesRmiTargets();
 1750   
 1751           // Null out any static or final fields from loaded classes,
 1752           // as a workaround for apparent garbage collection bugs
 1753           if (ENABLE_CLEAR_REFERENCES) {
 1754               clearReferencesStaticFinal();
 1755           }
 1756           
 1757            // Clear the IntrospectionUtils cache.
 1758           IntrospectionUtils.clear();
 1759           
 1760           // Clear the classloader reference in common-logging
 1761           if (clearReferencesLogFactoryRelease) {
 1762               org.apache.juli.logging.LogFactory.release(this);
 1763           }
 1764           
 1765           // Clear the resource bundle cache
 1766           // This shouldn't be necessary, the cache uses weak references but
 1767           // it has caused leaks. Oddly, using the leak detection code in
 1768           // standard host allows the class loader to be GC'd. This has been seen
 1769           // on Sun but not IBM JREs. Maybe a bug in Sun's GC impl?
 1770           clearReferencesResourceBundles();
 1771   
 1772           // Clear the classloader reference in the VM's bean introspector
 1773           java.beans.Introspector.flushCaches();
 1774   
 1775       }
 1776   
 1777   
 1778       /**
 1779        * Deregister any JDBC drivers registered by the webapp that the webapp
 1780        * forgot. This is made unnecessary complex because a) DriverManager
 1781        * checks the class loader of the calling class (it would be much easier
 1782        * if it checked the context class loader) b) using reflection would
 1783        * create a dependency on the DriverManager implementation which can,
 1784        * and has, changed.
 1785        * 
 1786        * We can't just create an instance of JdbcLeakPrevention as it will be
 1787        * loaded by the common class loader (since it's .class file is in the
 1788        * $CATALINA_HOME/lib directory). This would fail DriverManager's check
 1789        * on the class loader of the calling class. So, we load the bytes via
 1790        * our parent class loader but define the class with this class loader
 1791        * so the JdbcLeakPrevention looks like a webapp class to the
 1792        * DriverManager.
 1793        * 
 1794        * If only apps cleaned up after themselves...
 1795        */
 1796       private final void clearReferencesJdbc() {
 1797           InputStream is = getResourceAsStream(
 1798                   "org/apache/catalina/loader/JdbcLeakPrevention.class");
 1799           // We know roughly how big the class will be (~ 1K) so allow 2k as a
 1800           // starting point
 1801           byte[] classBytes = new byte[2048];
 1802           int offset = 0;
 1803           try {
 1804               int read = is.read(classBytes, offset, classBytes.length-offset);
 1805               while (read > -1) {
 1806                   offset += read;
 1807                   if (offset == classBytes.length) {
 1808                       // Buffer full - double size
 1809                       byte[] tmp = new byte[classBytes.length * 2];
 1810                       System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
 1811                       classBytes = tmp;
 1812                   }
 1813                   read = is.read(classBytes, offset, classBytes.length-offset);
 1814               }
 1815               Class<?> lpClass =
 1816                   defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
 1817                       classBytes, 0, offset);
 1818               Object obj = lpClass.newInstance();
 1819               @SuppressWarnings("unchecked")
 1820               List<String> driverNames = (List<String>) obj.getClass().getMethod(
 1821                       "clearJdbcDriverRegistrations").invoke(obj);
 1822               for (String name : driverNames) {
 1823                   log.error(sm.getString("webappClassLoader.clearJbdc", name));
 1824               }
 1825           } catch (Exception e) {
 1826               // So many things to go wrong above...
 1827               log.warn(sm.getString("webappClassLoader.jdbcRemoveFailed"), e);
 1828           } finally {
 1829               if (is != null) {
 1830                   try {
 1831                       is.close();
 1832                   } catch (IOException ioe) {
 1833                       log.warn(sm.getString(
 1834                               "webappClassLoader.jdbcRemoveStreamError"), ioe);
 1835                   }
 1836               }
 1837           }
 1838       }
 1839   
 1840   
 1841       private final void clearReferencesStaticFinal() {
 1842           
 1843           @SuppressWarnings("unchecked")
 1844           Collection<ResourceEntry> values =
 1845               ((HashMap<String,ResourceEntry>) resourceEntries.clone()).values();
 1846           Iterator<ResourceEntry> loadedClasses = values.iterator();
 1847           //
 1848           // walk through all loaded class to trigger initialization for
 1849           //    any uninitialized classes, otherwise initialization of
 1850           //    one class may call a previously cleared class.
 1851           while(loadedClasses.hasNext()) {
 1852               ResourceEntry entry = loadedClasses.next();
 1853               if (entry.loadedClass != null) {
 1854                   Class<?> clazz = entry.loadedClass;
 1855                   try {
 1856                       Field[] fields = clazz.getDeclaredFields();
 1857                       for (int i = 0; i < fields.length; i++) {
 1858                           if(Modifier.isStatic(fields[i].getModifiers())) {
 1859                               fields[i].get(null);
 1860                               break;
 1861                           }
 1862                       }
 1863                   } catch(Throwable t) {
 1864                       // Ignore
 1865                   }
 1866               }
 1867           }
 1868           loadedClasses = values.iterator();
 1869           while (loadedClasses.hasNext()) {
 1870               ResourceEntry entry = loadedClasses.next();
 1871               if (entry.loadedClass != null) {
 1872                   Class<?> clazz = entry.loadedClass;
 1873                   try {
 1874                       Field[] fields = clazz.getDeclaredFields();
 1875                       for (int i = 0; i < fields.length; i++) {
 1876                           Field field = fields[i];
 1877                           int mods = field.getModifiers();
 1878                           if (field.getType().isPrimitive() 
 1879                                   || (field.getName().indexOf("$") != -1)) {
 1880                               continue;
 1881                           }
 1882                           if (Modifier.isStatic(mods)) {
 1883                               try {
 1884                                   field.setAccessible(true);
 1885                                   if (Modifier.isFinal(mods)) {
 1886                                       if (!((field.getType().getName().startsWith("java."))
 1887                                               || (field.getType().getName().startsWith("javax.")))) {
 1888                                           nullInstance(field.get(null));
 1889                                       }
 1890                                   } else {
 1891                                       field.set(null, null);
 1892                                       if (log.isDebugEnabled()) {
 1893                                           log.debug("Set field " + field.getName() 
 1894                                                   + " to null in class " + clazz.getName());
 1895                                       }
 1896                                   }
 1897                               } catch (Throwable t) {
 1898                                   if (log.isDebugEnabled()) {
 1899                                       log.debug("Could not set field " + field.getName() 
 1900                                               + " to null in class " + clazz.getName(), t);
 1901                                   }
 1902                               }
 1903                           }
 1904                       }
 1905                   } catch (Throwable t) {
 1906                       if (log.isDebugEnabled()) {
 1907                           log.debug("Could not clean fields for class " + clazz.getName(), t);
 1908                       }
 1909                   }
 1910               }
 1911           }
 1912           
 1913       }
 1914   
 1915   
 1916       private void nullInstance(Object instance) {
 1917           if (instance == null) {
 1918               return;
 1919           }
 1920           Field[] fields = instance.getClass().getDeclaredFields();
 1921           for (int i = 0; i < fields.length; i++) {
 1922               Field field = fields[i];
 1923               int mods = field.getModifiers();
 1924               if (field.getType().isPrimitive() 
 1925                       || (field.getName().indexOf("$") != -1)) {
 1926                   continue;
 1927               }
 1928               try {
 1929                   field.setAccessible(true);
 1930                   if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
 1931                       // Doing something recursively is too risky
 1932                       continue;
 1933                   }
 1934                   Object value = field.get(instance);
 1935                   if (null != value) {
 1936                       Class<? extends Object> valueClass = value.getClass();
 1937                       if (!loadedByThisOrChild(valueClass)) {
 1938                           if (log.isDebugEnabled()) {
 1939                               log.debug("Not setting field " + field.getName() +
 1940                                       " to null in object of class " + 
 1941                                       instance.getClass().getName() +
 1942                                       " because the referenced object was of type " +
 1943                                       valueClass.getName() + 
 1944                                       " which was not loaded by this WebappClassLoader.");
 1945                           }
 1946                       } else {
 1947                           field.set(instance, null);
 1948                           if (log.isDebugEnabled()) {
 1949                               log.debug("Set field " + field.getName() 
 1950                                       + " to null in class " + instance.getClass().getName());
 1951                           }
 1952                       }
 1953                   }
 1954               } catch (Throwable t) {
 1955                   if (log.isDebugEnabled()) {
 1956                       log.debug("Could not set field " + field.getName() 
 1957                               + " to null in object instance of class " 
 1958                               + instance.getClass().getName(), t);
 1959                   }
 1960               }
 1961           }
 1962       }
 1963   
 1964   
 1965       @SuppressWarnings("deprecation")
 1966       private void clearReferencesThreads() {
 1967           Thread[] threads = getThreads();
 1968           
 1969           // Iterate over the set of threads
 1970           for (Thread thread : threads) {
 1971               if (thread != null) {
 1972                   ClassLoader ccl = thread.getContextClassLoader();
 1973                   if (ccl != null && ccl == this) {
 1974                       // Don't warn about this thread
 1975                       if (thread == Thread.currentThread()) {
 1976                           continue;
 1977                       }
 1978                       
 1979                       // Skip threads that have already died
 1980                       if (!thread.isAlive()) {
 1981                           continue;
 1982                       }
 1983   
 1984                       // Don't warn about JVM controlled threads
 1985                       ThreadGroup tg = thread.getThreadGroup();
 1986                       if (tg != null &&
 1987                               JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
 1988                           continue;
 1989                       }
 1990                      
 1991                       // TimerThread is not normally visible
 1992                       if (thread.getClass().getName().equals(
 1993                               "java.util.TimerThread")) {
 1994                           clearReferencesStopTimerThread(thread);
 1995                           continue;
 1996                       }
 1997   
 1998                       log.error(sm.getString("webappClassLoader.warnThread",
 1999                               thread.getName()));
 2000                       
 2001                       // Don't try an stop the threads unless explicitly
 2002                       // configured to do so
 2003                       if (!clearReferencesStopThreads) {
 2004                           continue;
 2005                       }
 2006                       
 2007                       // If the thread has been started via an executor, try
 2008                       // shutting down the executor
 2009                       try {
 2010                           Field targetField =
 2011                               thread.getClass().getDeclaredField("target");
 2012                           targetField.setAccessible(true);
 2013                           Object target = targetField.get(thread);
 2014                           
 2015                           if (target != null &&
 2016                                   target.getClass().getCanonicalName().equals(
 2017                                   "java.util.concurrent.ThreadPoolExecutor.Worker")) {
 2018                               Field executorField =
 2019                                   target.getClass().getDeclaredField("this$0");
 2020                               executorField.setAccessible(true);
 2021                               Object executor = executorField.get(target);
 2022                               if (executor instanceof ThreadPoolExecutor) {
 2023                                   ((ThreadPoolExecutor) executor).shutdownNow();
 2024                               }
 2025                           }
 2026                       } catch (SecurityException e) {
 2027                           log.warn(sm.getString(
 2028                                   "webappClassLoader.stopThreadFail",
 2029                                   thread.getName()), e);
 2030                       } catch (NoSuchFieldException e) {
 2031                           log.warn(sm.getString(
 2032                                   "webappClassLoader.stopThreadFail",
 2033                                   thread.getName()), e);
 2034                       } catch (IllegalArgumentException e) {
 2035                           log.warn(sm.getString(
 2036                                   "webappClassLoader.stopThreadFail",
 2037                                   thread.getName()), e);
 2038                       } catch (IllegalAccessException e) {
 2039                           log.warn(sm.getString(
 2040                                   "webappClassLoader.stopThreadFail",
 2041                                   thread.getName()), e);
 2042                       }
 2043   
 2044                       // This method is deprecated and for good reason. This is
 2045                       // very risky code but is the only option at this point.
 2046                       // A *very* good reason for apps to do this clean-up
 2047                       // themselves.
 2048                       thread.stop();
 2049                   }
 2050               }
 2051           }
 2052       }
 2053   
 2054       
 2055       private void clearReferencesStopTimerThread(Thread thread) {
 2056           
 2057           // Need to get references to:
 2058           // - newTasksMayBeScheduled field
 2059           // - queue field
 2060           // - queue.clear()
 2061           
 2062           try {
 2063               Field newTasksMayBeScheduledField =
 2064                   thread.getClass().getDeclaredField("newTasksMayBeScheduled");
 2065               newTasksMayBeScheduledField.setAccessible(true);
 2066               Field queueField = thread.getClass().getDeclaredField("queue");
 2067               queueField.setAccessible(true);
 2068       
 2069               Object queue = queueField.get(thread);
 2070               
 2071               Method clearMethod = queue.getClass().getDeclaredMethod("clear");
 2072               clearMethod.setAccessible(true);
 2073               
 2074               synchronized(queue) {
 2075                   newTasksMayBeScheduledField.setBoolean(thread, false);
 2076                   clearMethod.invoke(queue);
 2077                   queue.notify();  // In case queue was already empty.
 2078               }
 2079               
 2080               log.error(sm.getString("webappClassLoader.warnTimerThread",
 2081                       thread.getName()));
 2082   
 2083           } catch (NoSuchFieldException e) {
 2084               log.warn(sm.getString(
 2085                       "webappClassLoader.stopTimerThreadFail",
 2086                       thread.getName()), e);
 2087           } catch (IllegalAccessException e) {
 2088               log.warn(sm.getString(
 2089                       "webappClassLoader.stopTimerThreadFail",
 2090                       thread.getName()), e);
 2091           } catch (NoSuchMethodException e) {
 2092               log.warn(sm.getString(
 2093                       "webappClassLoader.stopTimerThreadFail",
 2094                       thread.getName()), e);
 2095           } catch (InvocationTargetException e) {
 2096               log.warn(sm.getString(
 2097                       "webappClassLoader.stopTimerThreadFail",
 2098                       thread.getName()), e);
 2099           }
 2100       }
 2101   
 2102       private void clearReferencesThreadLocals() {
 2103           Thread[] threads = getThreads();
 2104   
 2105           try {
 2106               // Make the fields in the Thread class that store ThreadLocals
 2107               // accessible
 2108               Field threadLocalsField =
 2109                   Thread.class.getDeclaredField("threadLocals");
 2110               threadLocalsField.setAccessible(true);
 2111               Field inheritableThreadLocalsField =
 2112                   Thread.class.getDeclaredField("inheritableThreadLocals");
 2113               inheritableThreadLocalsField.setAccessible(true);
 2114               // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
 2115               // accessible
 2116               Class<?> tlmClass =
 2117                   Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
 2118               Field tableField = tlmClass.getDeclaredField("table");
 2119               tableField.setAccessible(true);
 2120               
 2121               for (int i = 0; i < threads.length; i++) {
 2122                   Object threadLocalMap;
 2123                   if (threads[i] != null) {
 2124                       // Clear the first map
 2125                       threadLocalMap = threadLocalsField.get(threads[i]);
 2126                       clearThreadLocalMap(threadLocalMap, tableField);
 2127                       // Clear the second map
 2128                       threadLocalMap =
 2129                           inheritableThreadLocalsField.get(threads[i]);
 2130                       clearThreadLocalMap(threadLocalMap, tableField);
 2131                   }
 2132               }
 2133           } catch (SecurityException e) {
 2134               log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
 2135           } catch (NoSuchFieldException e) {
 2136               log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
 2137           } catch (ClassNotFoundException e) {
 2138               log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
 2139           } catch (IllegalArgumentException e) {
 2140               log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
 2141           } catch (IllegalAccessException e) {
 2142               log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
 2143           } catch (NoSuchMethodException e) {
 2144               log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
 2145           } catch (InvocationTargetException e) {
 2146               log.warn(sm.getString("webappClassLoader.clearThreadLocalFail"), e);
 2147           }       
 2148       }
 2149   
 2150   
 2151       /*
 2152        * Clears the given thread local map object. Also pass in the field that
 2153        * points to the internal table to save re-calculating it on every
 2154        * call to this method.
 2155        */
 2156       private void clearThreadLocalMap(Object map, Field internalTableField)
 2157               throws NoSuchMethodException, IllegalAccessException,
 2158               NoSuchFieldException, InvocationTargetException {
 2159           if (map != null) {
 2160               Method mapRemove =
 2161                   map.getClass().getDeclaredMethod("remove",
 2162                           ThreadLocal.class);
 2163               mapRemove.setAccessible(true);
 2164               Object[] table = (Object[]) internalTableField.get(map);
 2165               int staleEntriesCount = 0;
 2166               if (table != null) {
 2167                   for (int j =0; j < table.length; j++) {
 2168                       if (table[j] != null) {
 2169                           boolean remove = false;
 2170                           // Check the key
 2171                           Object key = ((Reference<?>) table[j]).get();
 2172                           if (this.equals(key) || (key != null &&
 2173                                   this == key.getClass().getClassLoader())) {
 2174                               remove = true;
 2175                           }
 2176                           // Check the value
 2177                           Field valueField =
 2178                               table[j].getClass().getDeclaredField("value");
 2179                           valueField.setAccessible(true);
 2180                           Object value = valueField.get(table[j]);
 2181                           if (this.equals(value) || (value != null &&
 2182                                   this == value.getClass().getClassLoader())) {
 2183                               remove = true;
 2184                           }
 2185                           if (remove) {
 2186                               Object[] args = new Object[4];
 2187                               if (key != null) {
 2188                                   args[0] = key.getClass().getCanonicalName();
 2189                                   args[1] = key.toString();
 2190                               }
 2191                               if (value != null) {
 2192                                   args[2] = value.getClass().getCanonicalName();
 2193                                   args[3] = value.toString();
 2194                               }
 2195                               if (value == null) {
 2196                                   if (log.isDebugEnabled()) {
 2197                                       log.debug(sm.getString(
 2198                                               "webappClassLoader.clearThreadLocalDebug",
 2199                                               args));
 2200                                   }
 2201                               } else {
 2202                                   log.error(sm.getString(
 2203                                           "webappClassLoader.clearThreadLocal",
 2204                                           args));
 2205                               }
 2206                               if (key == null) {
 2207                                 staleEntriesCount++;
 2208                               } else {
 2209                                 mapRemove.invoke(map, key);
 2210                               }
 2211                           }
 2212                       }
 2213                   }
 2214               }
 2215               if (staleEntriesCount > 0) {
 2216                   Method mapRemoveStale =
 2217                       map.getClass().getDeclaredMethod("expungeStaleEntries");
 2218                   mapRemoveStale.setAccessible(true);
 2219                   mapRemoveStale.invoke(map);
 2220               }
 2221           }
 2222       }
 2223   
 2224       /*
 2225        * Get the set of current threads as an array.
 2226        */
 2227       private Thread[] getThreads() {
 2228           // Get the current thread group 
 2229           ThreadGroup tg = Thread.currentThread( ).getThreadGroup( );
 2230           // Find the root thread group
 2231           while (tg.getParent() != null) {
 2232               tg = tg.getParent();
 2233           }
 2234           
 2235           int threadCountGuess = tg.activeCount() + 50;
 2236           Thread[] threads = new Thread[threadCountGuess];
 2237           int threadCountActual = tg.enumerate(threads);
 2238           // Make sure we don't miss any threads
 2239           while (threadCountActual == threadCountGuess) {
 2240               threadCountGuess *=2;
 2241               threads = new Thread[threadCountGuess];
 2242               // Note tg.enumerate(Thread[]) silently ignores any threads that
 2243               // can't fit into the array 
 2244               threadCountActual = tg.enumerate(threads);
 2245           }
 2246           
 2247           return threads;
 2248       }
 2249   
 2250   
 2251       /**
 2252        * This depends on the internals of the Sun JVM so it does everything by
 2253        * reflection.
 2254        */
 2255       private void clearReferencesRmiTargets() {
 2256           try {
 2257               // Need access to the ccl field of sun.rmi.transport.Target
 2258               Class<?> objectTargetClass =
 2259                   Class.forName("sun.rmi.transport.Target");
 2260               Field cclField = objectTargetClass.getDeclaredField("ccl");
 2261               cclField.setAccessible(true);
 2262   
 2263               // Clear the objTable map
 2264               Class<?> objectTableClass =
 2265                   Class.forName("sun.rmi.transport.ObjectTable");
 2266               Field objTableField = objectTableClass.getDeclaredField("objTable");
 2267               objTableField.setAccessible(true);
 2268               Object objTable = objTableField.get(null);
 2269               if (objTable == null) {
 2270                   return;
 2271               }
 2272               
 2273               // Iterate over the values in the table
 2274               if (objTable instanceof Map<?,?>) {
 2275                   Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
 2276                   while (iter.hasNext()) {
 2277                       Object obj = iter.next();
 2278                       Object cclObject = cclField.get(obj);
 2279                       if (this == cclObject) {
 2280                           iter.remove();
 2281                       }
 2282                   }
 2283               }
 2284   
 2285               // Clear the implTable map
 2286               Field implTableField = objectTableClass.getDeclaredField("implTable");
 2287               implTableField.setAccessible(true);
 2288               Object implTable = implTableField.get(null);
 2289               if (implTable == null) {
 2290                   return;
 2291               }
 2292               
 2293               // Iterate over the values in the table
 2294               if (implTable instanceof Map<?,?>) {
 2295                   Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
 2296                   while (iter.hasNext()) {
 2297                       Object obj = iter.next();
 2298                       Object cclObject = cclField.get(obj);
 2299                       if (this == cclObject) {
 2300                           iter.remove();
 2301                       }
 2302                   }
 2303               }
 2304           } catch (ClassNotFoundException e) {
 2305               log.info(sm.getString("webappClassLoader.clearRmiInfo"), e);
 2306           } catch (SecurityException e) {
 2307               log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
 2308           } catch (NoSuchFieldException e) {
 2309               log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
 2310           } catch (IllegalArgumentException e) {
 2311               log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
 2312           } catch (IllegalAccessException e) {
 2313               log.warn(sm.getString("webappClassLoader.clearRmiFail"), e);
 2314           }
 2315       }
 2316       
 2317       
 2318       /**
 2319        * Clear the {@link ResourceBundle} cache of any bundles loaded by this
 2320        * class loader or any class loader where this loader is a parent class
 2321        * loader. Whilst {@link ResourceBundle#clearCache()} could be used there
 2322        * are complications around the {@link JasperLoader} that mean a reflection
 2323        * based approach is more likely to be complete.
 2324        * 
 2325        * The ResourceBundle is using WeakReferences so it shouldn't be pinning the
 2326        * class loader in memory. However, it is. Therefore clear ou the
 2327        * references.
 2328        */
 2329       private void clearReferencesResourceBundles() {
 2330           // Get a reference to the cache
 2331           try {
 2332               Field cacheListField =
 2333                   ResourceBundle.class.getDeclaredField("cacheList");
 2334               cacheListField.setAccessible(true);
 2335   
 2336               // Java 6 uses ConcurrentMap
 2337               // Java 5 uses SoftCache extends Abstract Map
 2338               // So use Map and it *should* work with both
 2339               Map<?,?> cacheList = (Map<?,?>) cacheListField.get(null);
 2340               
 2341               // Get the keys (loader references are in the key)
 2342               Set<?> keys = cacheList.keySet();
 2343               
 2344               Field loaderRefField = null;
 2345               
 2346               // Iterate over the keys looking at the loader instances
 2347               Iterator<?> keysIter = keys.iterator();
 2348               
 2349               int countRemoved = 0;
 2350               
 2351               while (keysIter.hasNext()) {
 2352                   Object key = keysIter.next();
 2353                   
 2354                   if (loaderRefField == null) {
 2355                       loaderRefField =
 2356                           key.getClass().getDeclaredField("loaderRef");
 2357                       loaderRefField.setAccessible(true);
 2358                   }
 2359                   WeakReference<?> loaderRef =
 2360                       (WeakReference<?>) loaderRefField.get(key);
 2361                   
 2362                   ClassLoader loader = (ClassLoader) loaderRef.get();
 2363                   
 2364                   while (loader != null && loader != this) {
 2365                       loader = loader.getParent();
 2366                   }
 2367                   
 2368                   if (loader != null) {
 2369                       keysIter.remove();
 2370                       countRemoved++;
 2371                   }
 2372               }
 2373               
 2374               if (countRemoved > 0 && log.isDebugEnabled()) {
 2375                   log.debug(sm.getString(
 2376                           "webappClassLoader.clearReferencesResourceBundlesCount",
 2377                           Integer.valueOf(countRemoved)));
 2378               }
 2379           } catch (SecurityException e) {
 2380               log.error(sm.getString(
 2381                       "webappClassLoader.clearReferencesResourceBundlesFail"), e);
 2382           } catch (NoSuchFieldException e) {
 2383               if (System.getProperty("java.vendor").startsWith("Sun")) {
 2384                   log.error(sm.getString(
 2385                   "webappClassLoader.clearReferencesResourceBundlesFail"), e);
 2386               } else {
 2387                   log.debug(sm.getString(
 2388                   "webappClassLoader.clearReferencesResourceBundlesFail"), e);
 2389               }
 2390           } catch (IllegalArgumentException e) {
 2391               log.error(sm.getString(
 2392                       "webappClassLoader.clearReferencesResourceBundlesFail"), e);
 2393           } catch (IllegalAccessException e) {
 2394               log.error(sm.getString(
 2395                       "webappClassLoader.clearReferencesResourceBundlesFail"), e);
 2396           }
 2397       }
 2398   
 2399   
 2400       /**
 2401        * Determine whether a class was loaded by this class loader or one of
 2402        * its child class loaders.
 2403        */
 2404       protected boolean loadedByThisOrChild(Class clazz)
 2405       {
 2406           boolean result = false;
 2407           for (ClassLoader classLoader = clazz.getClassLoader();
 2408                   null != classLoader; classLoader = classLoader.getParent()) {
 2409               if (classLoader.equals(this)) {
 2410                   result = true;
 2411                   break;
 2412               }
 2413           }
 2414           return result;
 2415       }    
 2416   
 2417   
 2418       /**
 2419        * Used to periodically signal to the classloader to release JAR resources.
 2420        */
 2421       protected boolean openJARs() {
 2422           if (started && (jarFiles.length > 0)) {
 2423               lastJarAccessed = System.currentTimeMillis();
 2424               if (jarFiles[0] == null) {
 2425                   for (int i = 0; i < jarFiles.length; i++) {
 2426                       try {
 2427                           jarFiles[i] = new JarFile(jarRealFiles[i]);
 2428                       } catch (IOException e) {
 2429                           if (log.isDebugEnabled()) {
 2430                               log.debug("Failed to open JAR", e);
 2431                           }
 2432                           return false;
 2433                       }
 2434                   }
 2435               }
 2436           }
 2437           return true;
 2438       }
 2439   
 2440   
 2441       /**
 2442        * Find specified class in local repositories.
 2443        *
 2444        * @return the loaded class, or null if the class isn't found
 2445        */
 2446       protected Class findClassInternal(String name)
 2447           throws ClassNotFoundException {
 2448   
 2449           if (!validate(name))
 2450               throw new ClassNotFoundException(name);
 2451   
 2452           String tempPath = name.replace('.', '/');
 2453           String classPath = tempPath + ".class";
 2454   
 2455           ResourceEntry entry = null;
 2456   
 2457           if (securityManager != null) {
 2458               PrivilegedAction<ResourceEntry> dp =
 2459                   new PrivilegedFindResourceByName(name, classPath);
 2460               entry = AccessController.doPrivileged(dp);
 2461           } else {
 2462               entry = findResourceInternal(name, classPath);
 2463           }
 2464   
 2465           if (entry == null)
 2466               throw new ClassNotFoundException(name);
 2467   
 2468           Class clazz = entry.loadedClass;
 2469           if (clazz != null)
 2470               return clazz;
 2471   
 2472           synchronized (name.intern()) {
 2473               clazz = entry.loadedClass;
 2474               if (clazz != null)
 2475                   return clazz;
 2476   
 2477               if (entry.binaryContent == null)
 2478                   throw new ClassNotFoundException(name);
 2479   
 2480               // Looking up the package
 2481               String packageName = null;
 2482               int pos = name.lastIndexOf('.');
 2483               if (pos != -1)
 2484                   packageName = name.substring(0, pos);
 2485           
 2486               Package pkg = null;
 2487           
 2488               if (packageName != null) {
 2489                   pkg = getPackage(packageName);
 2490                   // Define the package (if null)
 2491                   if (pkg == null) {
 2492                       try {
 2493                           if (entry.manifest == null) {
 2494                               definePackage(packageName, null, null, null, null,
 2495                                       null, null, null);
 2496                           } else {
 2497                               definePackage(packageName, entry.manifest,
 2498                                       entry.codeBase);
 2499                           }
 2500                       } catch (IllegalArgumentException e) {
 2501                           // Ignore: normal error due to dual definition of package
 2502                       }
 2503                       pkg = getPackage(packageName);
 2504                   }
 2505               }
 2506       
 2507               if (securityManager != null) {
 2508   
 2509                   // Checking sealing
 2510                   if (pkg != null) {
 2511                       boolean sealCheck = true;
 2512                       if (pkg.isSealed()) {
 2513                           sealCheck = pkg.isSealed(entry.codeBase);
 2514                       } else {
 2515                           sealCheck = (entry.manifest == null)
 2516                               || !isPackageSealed(packageName, entry.manifest);
 2517                       }
 2518                       if (!sealCheck)
 2519                           throw new SecurityException
 2520                               ("Sealing violation loading " + name + " : Package "
 2521                                + packageName + " is sealed.");
 2522                   }
 2523       
 2524               }
 2525   
 2526               try {
 2527                   clazz = defineClass(name, entry.binaryContent, 0,
 2528                           entry.binaryContent.length, 
 2529                           new CodeSource(entry.codeBase, entry.certificates));
 2530               } catch (UnsupportedClassVersionError ucve) {
 2531                   throw new UnsupportedClassVersionError(
 2532                           ucve.getLocalizedMessage() + " " +
 2533                           sm.getString("webappClassLoader.wrongVersion",
 2534                                   name));
 2535               }
 2536               entry.loadedClass = clazz;
 2537               entry.binaryContent = null;
 2538               entry.source = null;
 2539               entry.codeBase = null;
 2540               entry.manifest = null;
 2541               entry.certificates = null;
 2542           }
 2543           
 2544           return clazz;
 2545   
 2546       }
 2547   
 2548       /**
 2549        * Find specified resource in local repositories.
 2550        *
 2551        * @return the loaded resource, or null if the resource isn't found
 2552        */
 2553       protected ResourceEntry findResourceInternal(File file, String path){
 2554           ResourceEntry entry = new ResourceEntry();
 2555           try {
 2556               entry.source = getURI(new File(file, path));
 2557               entry.codeBase = getURL(new File(file, path), false);
 2558           } catch (MalformedURLException e) {
 2559               return null;
 2560           }   
 2561           return entry;
 2562       }
 2563       
 2564   
 2565       /**
 2566        * Find specified resource in local repositories.
 2567        *
 2568        * @return the loaded resource, or null if the resource isn't found
 2569        */
 2570       protected ResourceEntry findResourceInternal(String name, String path) {
 2571   
 2572           if (!started) {
 2573               log.info(sm.getString("webappClassLoader.stopped", name));
 2574               return null;
 2575           }
 2576   
 2577           if ((name == null) || (path == null))
 2578               return null;
 2579   
 2580           ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
 2581           if (entry != null)
 2582               return entry;
 2583   
 2584           int contentLength = -1;
 2585           InputStream binaryStream = null;
 2586   
 2587           int jarFilesLength = jarFiles.length;
 2588           int repositoriesLength = repositories.length;
 2589   
 2590           int i;
 2591   
 2592           Resource resource = null;
 2593   
 2594           boolean fileNeedConvert = false;
 2595   
 2596           for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
 2597               try {
 2598   
 2599                   String fullPath = repositories[i] + path;
 2600   
 2601                   Object lookupResult = resources.lookup(fullPath);
 2602                   if (lookupResult instanceof Resource) {
 2603                       resource = (Resource) lookupResult;
 2604                   }
 2605   
 2606                   // Note : Not getting an exception here means the resource was
 2607                   // found
 2608                   entry = findResourceInternal(files[i], path);
 2609   
 2610                   ResourceAttributes attributes =
 2611                       (ResourceAttributes) resources.getAttributes(fullPath);
 2612                   contentLength = (int) attributes.getContentLength();
 2613                   entry.lastModified = attributes.getLastModified();
 2614   
 2615                   if (resource != null) {
 2616   
 2617   
 2618                       try {
 2619                           binaryStream = resource.streamContent();
 2620                       } catch (IOException e) {
 2621                           return null;
 2622                       }
 2623   
 2624                       if (needConvert) {
 2625                           if (path.endsWith(".properties")) {
 2626                               fileNeedConvert = true;
 2627                           }
 2628                       }
 2629   
 2630                       // Register the full path for modification checking
 2631                       // Note: Only syncing on a 'constant' object is needed
 2632                       synchronized (allPermission) {
 2633   
 2634                           int j;
 2635   
 2636                           long[] result2 = 
 2637                               new long[lastModifiedDates.length + 1];
 2638                           for (j = 0; j < lastModifiedDates.length; j++) {
 2639                               result2[j] = lastModifiedDates[j];
 2640                           }
 2641                           result2[lastModifiedDates.length] = entry.lastModified;
 2642                           lastModifiedDates = result2;
 2643   
 2644                           String[] result = new String[paths.length + 1];
 2645                           for (j = 0; j < paths.length; j++) {
 2646                               result[j] = paths[j];
 2647                           }
 2648                           result[paths.length] = fullPath;
 2649                           paths = result;
 2650   
 2651                       }
 2652   
 2653                   }
 2654   
 2655               } catch (NamingException e) {
 2656               }
 2657           }
 2658   
 2659           if ((entry == null) && (notFoundResources.containsKey(name)))
 2660               return null;
 2661   
 2662           JarEntry jarEntry = null;
 2663   
 2664           synchronized (jarFiles) {
 2665   
 2666               try {
 2667                   if (!openJARs()) {
 2668                       return null;
 2669                   }
 2670                   for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
 2671       
 2672                       jarEntry = jarFiles[i].getJarEntry(path);
 2673       
 2674                       if (jarEntry != null) {
 2675       
 2676                           entry = new ResourceEntry();
 2677                           try {
 2678                               entry.codeBase = getURL(jarRealFiles[i], false);
 2679                               String jarFakeUrl = getURI(jarRealFiles[i]).toString();
 2680                               jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
 2681                               entry.source = new URL(jarFakeUrl);
 2682                               entry.lastModified = jarRealFiles[i].lastModified();
 2683                           } catch (MalformedURLException e) {
 2684                               return null;
 2685                           }
 2686                           contentLength = (int) jarEntry.getSize();
 2687                           try {
 2688                               entry.manifest = jarFiles[i].getManifest();
 2689                               binaryStream = jarFiles[i].getInputStream(jarEntry);
 2690                           } catch (IOException e) {
 2691                               return null;
 2692                           }
 2693       
 2694                           // Extract resources contained in JAR to the workdir
 2695                           if (antiJARLocking && !(path.endsWith(".class"))) {
 2696                               byte[] buf = new byte[1024];
 2697                               File resourceFile = new File
 2698                                   (loaderDir, jarEntry.getName());
 2699                               if (!resourceFile.exists()) {
 2700                                   Enumeration<JarEntry> entries =
 2701                                       jarFiles[i].entries();
 2702                                   while (entries.hasMoreElements()) {
 2703                                       JarEntry jarEntry2 =  entries.nextElement();
 2704                                       if (!(jarEntry2.isDirectory()) 
 2705                                           && (!jarEntry2.getName().endsWith
 2706                                               (".class"))) {
 2707                                           resourceFile = new File
 2708                                               (loaderDir, jarEntry2.getName());
 2709                                           try {
 2710                                               if (!resourceFile.getCanonicalPath().startsWith(
 2711                                                       canonicalLoaderDir)) {
 2712                                                   throw new IllegalArgumentException(
 2713                                                           sm.getString("webappClassLoader.illegalJarPath",
 2714                                                       jarEntry2.getName()));
 2715                                               }
 2716                                           } catch (IOException ioe) {
 2717                                               throw new IllegalArgumentException(
 2718                                                       sm.getString("webappClassLoader.validationErrorJarPath",
 2719                                                               jarEntry2.getName()), ioe);
 2720                                           }                                 
 2721                                           resourceFile.getParentFile().mkdirs();
 2722                                           FileOutputStream os = null;
 2723                                           InputStream is = null;
 2724                                           try {
 2725                                               is = jarFiles[i].getInputStream
 2726                                                   (jarEntry2);
 2727                                               os = new FileOutputStream
 2728                                                   (resourceFile);
 2729                                               while (true) {
 2730                                                   int n = is.read(buf);
 2731                                                   if (n <= 0) {
 2732                                                       break;
 2733                                                   }
 2734                                                   os.write(buf, 0, n);
 2735                                               }
 2736                                           } catch (IOException e) {
 2737                                               // Ignore
 2738                                           } finally {
 2739                                               try {
 2740                                                   if (is != null) {
 2741                                                       is.close();
 2742                                                   }
 2743                                               } catch (IOException e) {
 2744                                               }
 2745                                               try {
 2746                                                   if (os != null) {
 2747                                                       os.close();
 2748                                                   }
 2749                                               } catch (IOException e) {
 2750                                               }
 2751                                           }
 2752                                       }
 2753                                   }
 2754                               }
 2755                           }
 2756       
 2757                       }
 2758       
 2759                   }
 2760       
 2761                   if (entry == null) {
 2762                       synchronized (notFoundResources) {
 2763                           notFoundResources.put(name, name);
 2764                       }
 2765                       return null;
 2766                   }
 2767       
 2768                   if (binaryStream != null) {
 2769       
 2770                       byte[] binaryContent = new byte[contentLength];
 2771       
 2772                       int pos = 0;
 2773                       try {
 2774       
 2775                           while (true) {
 2776                               int n = binaryStream.read(binaryContent, pos,
 2777                                                         binaryContent.length - pos);
 2778                               if (n <= 0)
 2779                                   break;
 2780                               pos += n;
 2781                           }
 2782                       } catch (IOException e) {
 2783                           log.error(sm.getString("webappClassLoader.readError", name), e);
 2784                           return null;
 2785                       }
 2786                       if (fileNeedConvert) {
 2787                           // Workaround for certain files on platforms that use
 2788                           // EBCDIC encoding, when they are read through FileInputStream.
 2789                           // See commit message of rev.303915 for details
 2790                           // http://svn.apache.org/viewvc?view=revision&revision=303915
 2791                           String str = new String(binaryContent,0,pos);
 2792                           try {
 2793                               binaryContent = str.getBytes("UTF-8");
 2794                           } catch (Exception e) {
 2795                               return null;
 2796                           }
 2797                       }
 2798                       entry.binaryContent = binaryContent;
 2799       
 2800                       // The certificates are only available after the JarEntry 
 2801                       // associated input stream has been fully read
 2802                       if (jarEntry != null) {
 2803                           entry.certificates = jarEntry.getCertificates();
 2804                       }
 2805       
 2806                   }
 2807               } finally {
 2808                   if (binaryStream != null) {
 2809                       try {
 2810                           binaryStream.close();
 2811                       } catch (IOException e) { /* Ignore */}
 2812                   }
 2813               }
 2814           }
 2815   
 2816           // Add the entry in the local resource repository
 2817           synchronized (resourceEntries) {
 2818               // Ensures that all the threads which may be in a race to load
 2819               // a particular class all end up with the same ResourceEntry
 2820               // instance
 2821               ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
 2822               if (entry2 == null) {
 2823                   resourceEntries.put(name, entry);
 2824               } else {
 2825                   entry = entry2;
 2826               }
 2827           }
 2828   
 2829           return entry;
 2830   
 2831       }
 2832   
 2833   
 2834       /**
 2835        * Returns true if the specified package name is sealed according to the
 2836        * given manifest.
 2837        */
 2838       protected boolean isPackageSealed(String name, Manifest man) {
 2839   
 2840           String path = name.replace('.', '/') + '/';
 2841           Attributes attr = man.getAttributes(path); 
 2842           String sealed = null;
 2843           if (attr != null) {
 2844               sealed = attr.getValue(Name.SEALED);
 2845           }
 2846           if (sealed == null) {
 2847               if ((attr = man.getMainAttributes()) != null) {
 2848                   sealed = attr.getValue(Name.SEALED);
 2849               }
 2850           }
 2851           return "true".equalsIgnoreCase(sealed);
 2852   
 2853       }
 2854   
 2855   
 2856       /**
 2857        * Finds the resource with the given name if it has previously been
 2858        * loaded and cached by this class loader, and return an input stream
 2859        * to the resource data.  If this resource has not been cached, return
 2860        * <code>null</code>.
 2861        *
 2862        * @param name Name of the resource to return
 2863        */
 2864       protected InputStream findLoadedResource(String name) {
 2865   
 2866           ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
 2867           if (entry != null) {
 2868               if (entry.binaryContent != null)
 2869                   return new ByteArrayInputStream(entry.binaryContent);
 2870           }
 2871           return (null);
 2872   
 2873       }
 2874   
 2875   
 2876       /**
 2877        * Finds the class with the given name if it has previously been
 2878        * loaded and cached by this class loader, and return the Class object.
 2879        * If this class has not been cached, return <code>null</code>.
 2880        *
 2881        * @param name Name of the resource to return
 2882        */
 2883       protected Class findLoadedClass0(String name) {
 2884   
 2885           ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
 2886           if (entry != null) {
 2887               return entry.loadedClass;
 2888           }
 2889           return (null);  // FIXME - findLoadedResource()
 2890   
 2891       }
 2892   
 2893   
 2894       /**
 2895        * Refresh the system policy file, to pick up eventual changes.
 2896        */
 2897       protected void refreshPolicy() {
 2898   
 2899           try {
 2900               // The policy file may have been modified to adjust 
 2901               // permissions, so we're reloading it when loading or 
 2902               // reloading a Context
 2903               Policy policy = Policy.getPolicy();
 2904               policy.refresh();
 2905           } catch (AccessControlException e) {
 2906               // Some policy files may restrict this, even for the core,
 2907               // so this exception is ignored
 2908           }
 2909   
 2910       }
 2911   
 2912   
 2913       /**
 2914        * Filter classes.
 2915        * 
 2916        * @param name class name
 2917        * @return true if the class should be filtered
 2918        */
 2919       protected boolean filter(String name) {
 2920   
 2921           if (name == null)
 2922               return false;
 2923   
 2924           // Looking up the package
 2925           String packageName = null;
 2926           int pos = name.lastIndexOf('.');
 2927           if (pos != -1)
 2928               packageName = name.substring(0, pos);
 2929           else
 2930               return false;
 2931   
 2932           for (int i = 0; i < packageTriggers.length; i++) {
 2933               if (packageName.startsWith(packageTriggers[i]))
 2934                   return true;
 2935           }
 2936   
 2937           return false;
 2938   
 2939       }
 2940   
 2941   
 2942       /**
 2943        * Validate a classname. As per SRV.9.7.2, we must restict loading of 
 2944        * classes from J2SE (java.*) and classes of the servlet API 
 2945        * (javax.servlet.*). That should enhance robustness and prevent a number
 2946        * of user error (where an older version of servlet.jar would be present
 2947        * in /WEB-INF/lib).
 2948        * 
 2949        * @param name class name
 2950        * @return true if the name is valid
 2951        */
 2952       protected boolean validate(String name) {
 2953   
 2954           if (name == null)
 2955               return false;
 2956           if (name.startsWith("java."))
 2957               return false;
 2958   
 2959           return true;
 2960   
 2961       }
 2962   
 2963   
 2964       /**
 2965        * Check the specified JAR file, and return <code>true</code> if it does
 2966        * not contain any of the trigger classes.
 2967        *
 2968        * @param jarfile The JAR file to be checked
 2969        *
 2970        * @exception IOException if an input/output error occurs
 2971        */
 2972       protected boolean validateJarFile(File jarfile)
 2973           throws IOException {
 2974   
 2975           if (triggers == null)
 2976               return (true);
 2977           JarFile jarFile = new JarFile(jarfile);
 2978           for (int i = 0; i < triggers.length; i++) {
 2979               Class clazz = null;
 2980               try {
 2981                   if (parent != null) {
 2982                       clazz = parent.loadClass(triggers[i]);
 2983                   } else {
 2984                       clazz = Class.forName(triggers[i]);
 2985                   }
 2986               } catch (Throwable t) {
 2987                   clazz = null;
 2988               }
 2989               if (clazz == null)
 2990                   continue;
 2991               String name = triggers[i].replace('.', '/') + ".class";
 2992               if (log.isDebugEnabled())
 2993                   log.debug(" Checking for " + name);
 2994               JarEntry jarEntry = jarFile.getJarEntry(name);
 2995               if (jarEntry != null) {
 2996                   log.info("validateJarFile(" + jarfile + 
 2997                       ") - jar not loaded. See Servlet Spec 2.3, "
 2998                       + "section 9.7.2. Offending class: " + name);
 2999                   jarFile.close();
 3000                   return (false);
 3001               }
 3002           }
 3003           jarFile.close();
 3004           return (true);
 3005   
 3006       }
 3007   
 3008   
 3009       /**
 3010        * Get URL.
 3011        */
 3012       protected URL getURL(File file, boolean encoded)
 3013           throws MalformedURLException {
 3014   
 3015           File realFile = file;
 3016           try {
 3017               realFile = realFile.getCanonicalFile();
 3018           } catch (IOException e) {
 3019               // Ignore
 3020           }
 3021           if(encoded) {
 3022               return getURI(realFile);
 3023           } else {
 3024               return realFile.toURL();
 3025           }
 3026   
 3027       }
 3028   
 3029   
 3030       /**
 3031        * Get URL.
 3032        */
 3033       protected URL getURI(File file)
 3034           throws MalformedURLException {
 3035   
 3036   
 3037           File realFile = file;
 3038           try {
 3039               realFile = realFile.getCanonicalFile();
 3040           } catch (IOException e) {
 3041               // Ignore
 3042           }
 3043           return realFile.toURI().toURL();
 3044   
 3045       }
 3046   
 3047   
 3048       /**
 3049        * Delete the specified directory, including all of its contents and
 3050        * subdirectories recursively.
 3051        *
 3052        * @param dir File object representing the directory to be deleted
 3053        */
 3054       protected static void deleteDir(File dir) {
 3055   
 3056           String files[] = dir.list();
 3057           if (files == null) {
 3058               files = new String[0];
 3059           }
 3060           for (int i = 0; i < files.length; i++) {
 3061               File file = new File(dir, files[i]);
 3062               if (file.isDirectory()) {
 3063                   deleteDir(file);
 3064               } else {
 3065                   file.delete();
 3066               }
 3067           }
 3068           dir.delete();
 3069   
 3070       }
 3071   
 3072   
 3073   }
 3074   

Save This Page
Home » apache-tomcat-6.0.26-src » org.apache » catalina » loader » [javadoc | source]