Original, unmodfied copy of Tomcat's WebappClassLoader.

git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4510 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java b/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java
new file mode 100644
index 0000000..465fbb0
--- /dev/null
+++ b/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java
@@ -0,0 +1,2165 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.catalina.loader;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes.Name;
+
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.util.StringManager;
+import org.apache.naming.JndiPermission;
+import org.apache.naming.resources.Resource;
+import org.apache.naming.resources.ResourceAttributes;
+import org.apache.tomcat.util.compat.JdkCompat;
+
+/**
+ * Specialized web application class loader.
+ * <p>
+ * This class loader is a full reimplementation of the 
+ * <code>URLClassLoader</code> from the JDK. It is desinged to be fully
+ * compatible with a normal <code>URLClassLoader</code>, although its internal
+ * behavior may be completely different.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows 
+ * the delegation model recommended in the specification. The system class 
+ * loader will be queried first, then the local repositories, and only then 
+ * delegation to the parent class loader will occur. This allows the web 
+ * application to override any shared class except the classes from J2SE.
+ * Special handling is provided from the JAXP XML parser interfaces, the JNDI
+ * interfaces, and the classes from the servlet API, which are never loaded 
+ * from the webapp repository.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper 
+ * compilation technology, any repository which contains classes from 
+ * the servlet API will be ignored by the class loader.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
+ * URLs which include the full JAR URL when a class is loaded from a JAR file,
+ * which allows setting security permission at the class level, even when a
+ * class is contained inside a JAR.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
+ * the order they are added via the initial constructor and/or any subsequent
+ * calls to <code>addRepository()</code> or <code>addJar()</code>.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
+ * security is made unless a security manager is present.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Revision: 1.34 $ $Date: 2004/05/26 15:47:40 $
+ */
+public class WebappClassLoader
+    extends URLClassLoader
+    implements Reloader, Lifecycle
+ {
+
+    private static org.apache.commons.logging.Log log=
+        org.apache.commons.logging.LogFactory.getLog( WebappClassLoader.class );
+
+    protected class PrivilegedFindResource
+        implements PrivilegedAction {
+
+        private File file;
+        private String path;
+
+        PrivilegedFindResource(File file, String path) {
+            this.file = file;
+            this.path = path;
+        }
+
+        public Object run() {
+            return findResourceInternal(file, path);
+        }
+
+    }
+
+
+    // ------------------------------------------------------- Static Variables
+
+
+    /**
+     * The set of trigger classes that will cause a proposed repository not
+     * to be added if this class is visible to the class loader that loaded
+     * this factory class.  Typically, trigger classes will be listed for
+     * components that have been integrated into the JDK for later versions,
+     * but where the corresponding JAR files are required to run on
+     * earlier versions.
+     */
+    private static final String[] triggers = {
+        "javax.servlet.Servlet"                     // Servlet API
+    };
+
+    /** 
+     * Jdk Compatibility Support.
+     */
+    private static JdkCompat jdkCompat = JdkCompat.getJdkCompat();
+
+    /**
+     * Set of package names which are not allowed to be loaded from a webapp
+     * class loader without delegating first.
+     */
+    private static final String[] packageTriggers = {
+        "javax",                                     // Java extensions
+        "org.xml.sax",                               // SAX 1 & 2
+        "org.w3c.dom",                               // DOM 1 & 2
+        "org.apache.xerces",                         // Xerces 1 & 2
+        "org.apache.xalan"                           // Xalan
+    };
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and no
+     * parent ClassLoader.
+     */
+    public WebappClassLoader() {
+
+        super(new URL[0]);
+        this.parent = getParent();
+        system = getSystemClassLoader();
+        securityManager = System.getSecurityManager();
+
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+
+    }
+
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and no
+     * parent ClassLoader.
+     */
+    public WebappClassLoader(ClassLoader parent) {
+
+        super(new URL[0], parent);
+                
+        this.parent = getParent();
+        
+        system = getSystemClassLoader();
+        securityManager = System.getSecurityManager();
+
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated directory context giving access to the resources in this
+     * webapp.
+     */
+    protected DirContext resources = null;
+
+
+    /**
+     * The cache of ResourceEntry for classes and resources we have loaded,
+     * keyed by resource name.
+     */
+    protected HashMap resourceEntries = new HashMap();
+
+
+    /**
+     * The list of not found resources.
+     */
+    protected HashMap notFoundResources = new HashMap();
+
+
+    /**
+     * The debugging detail level of this component.
+     */
+    protected int debug = 0;
+
+
+    /**
+     * Should this class loader delegate to the parent class loader
+     * <strong>before</strong> searching its own repositories (i.e. the
+     * usual Java2 delegation model)?  If set to <code>false</code>,
+     * this class loader will search its own repositories first, and
+     * delegate to the parent only if the class or resource is not
+     * found locally.
+     */
+    protected boolean delegate = false;
+
+
+    /**
+     * Last time a JAR was accessed.
+     */
+    protected long lastJarAccessed = 0L;
+
+
+    /**
+     * The list of local repositories, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected String[] repositories = new String[0];
+
+
+     /**
+      * Repositories URLs, used to cache the result of getURLs.
+      */
+     protected URL[] repositoryURLs = null;
+
+
+    /**
+     * Repositories translated as path in the work directory (for Jasper
+     * originally), but which is used to generate fake URLs should getURLs be
+     * called.
+     */
+    protected File[] files = new File[0];
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected JarFile[] jarFiles = new JarFile[0];
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected File[] jarRealFiles = new File[0];
+
+
+    /**
+     * The path which will be monitored for added Jar files.
+     */
+    protected String jarPath = null;
+
+
+    /**
+     * The list of JARs, in the order they should be searched
+     * for locally loaded classes or resources.
+     */
+    protected String[] jarNames = new String[0];
+
+
+    /**
+     * The list of JARs last modified dates, in the order they should be
+     * searched for locally loaded classes or resources.
+     */
+    protected long[] lastModifiedDates = new long[0];
+
+
+    /**
+     * The list of resources which should be checked when checking for
+     * modifications.
+     */
+    protected String[] paths = new String[0];
+
+
+    /**
+     * A list of read File and Jndi Permission's required if this loader
+     * is for a web application context.
+     */
+    private ArrayList permissionList = new ArrayList();
+
+
+    /**
+     * Path where resources loaded from JARs will be extracted.
+     */
+    private File loaderDir = null;
+
+
+    /**
+     * The PermissionCollection for each CodeSource for a web
+     * application context.
+     */
+    private HashMap loaderPC = new HashMap();
+
+
+    /**
+     * Instance of the SecurityManager installed.
+     */
+    private SecurityManager securityManager = null;
+
+
+    /**
+     * The parent class loader.
+     */
+    private ClassLoader parent = null;
+
+
+    /**
+     * The system class loader.
+     */
+    private ClassLoader system = null;
+
+
+    /**
+     * Has this component been started?
+     */
+    protected boolean started = false;
+
+
+    /**
+     * Has external repositories.
+     */
+    protected boolean hasExternalRepositories = false;
+
+
+    /**
+     * All permission.
+     */
+    private Permission allPermission = new java.security.AllPermission();
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Get associated resources.
+     */
+    public DirContext getResources() {
+
+        return this.resources;
+
+    }
+
+
+    /**
+     * Set associated resources.
+     */
+    public void setResources(DirContext resources) {
+
+        this.resources = resources;
+
+    }
+
+
+    /**
+     * Return the debugging detail level for this component.
+     */
+    public int getDebug() {
+
+        return (this.debug);
+
+    }
+
+
+    /**
+     * Set the debugging detail level for this component.
+     *
+     * @param debug The new debugging detail level
+     */
+    public void setDebug(int debug) {
+
+        this.debug = debug;
+
+    }
+
+
+    /**
+     * Return the "delegate first" flag for this class loader.
+     */
+    public boolean getDelegate() {
+
+        return (this.delegate);
+
+    }
+
+
+    /**
+     * Set the "delegate first" flag for this class loader.
+     *
+     * @param delegate The new "delegate first" flag
+     */
+    public void setDelegate(boolean delegate) {
+
+        this.delegate = delegate;
+
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a read FilePermission
+     * or JndiPermission for the file directory path.
+     *
+     * @param path file directory path
+     */
+    public void addPermission(String path) {
+        if (path == null) {
+            return;
+        }
+
+        if (securityManager != null) {
+            Permission permission = null;
+            if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) {
+                if (!path.endsWith("/")) {
+                    path = path + "/";
+                }
+                permission = new JndiPermission(path + "*");
+                addPermission(permission);
+            } else {
+                if (!path.endsWith(File.separator)) {
+                    permission = new FilePermission(path, "read");
+                    addPermission(permission);
+                    path = path + File.separator;
+                }
+                permission = new FilePermission(path + "-", "read");
+                addPermission(permission);
+            }
+        }
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a read FilePermission
+     * or JndiPermission for URL.
+     *
+     * @param url URL for a file or directory on local system
+     */
+    public void addPermission(URL url) {
+        if (url != null) {
+            addPermission(url.toString());
+        }
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a Permission.
+     *
+     * @param url URL for a file or directory on local system
+     */
+    public void addPermission(Permission permission) {
+        if ((securityManager != null) && (permission != null)) {
+            permissionList.add(permission);
+        }
+    }
+
+
+    /**
+     * Return the JAR path.
+     */
+    public String getJarPath() {
+
+        return this.jarPath;
+
+    }
+
+
+    /**
+     * Change the Jar path.
+     */
+    public void setJarPath(String jarPath) {
+
+        this.jarPath = jarPath;
+
+    }
+
+
+    /**
+     * Change the work directory.
+     */
+    public void setWorkDir(File workDir) {
+        this.loaderDir = new File(workDir, "loader");
+    }
+
+
+    // ------------------------------------------------------- Reloader Methods
+
+
+    /**
+     * Add a new repository to the set of places this ClassLoader can look for
+     * classes to be loaded.
+     *
+     * @param repository Name of a source of classes to be loaded, such as a
+     *  directory pathname, a JAR file pathname, or a ZIP file pathname
+     *
+     * @exception IllegalArgumentException if the specified repository is
+     *  invalid or does not exist
+     */
+    public void addRepository(String repository) {
+
+        // Ignore any of the standard repositories, as they are set up using
+        // either addJar or addRepository
+        if (repository.startsWith("/WEB-INF/lib")
+            || repository.startsWith("/WEB-INF/classes"))
+            return;
+
+        // Add this repository to our underlying class loader
+        try {
+            URL url = new URL(repository);
+            super.addURL(url);
+            hasExternalRepositories = true;
+            repositoryURLs = null;
+        } catch (MalformedURLException e) {
+            IllegalArgumentException iae = new IllegalArgumentException
+                ("Invalid repository: " + repository); 
+            jdkCompat.chainException(iae, e);
+            throw iae;
+        }
+
+    }
+
+
+    /**
+     * Add a new repository to the set of places this ClassLoader can look for
+     * classes to be loaded.
+     *
+     * @param repository Name of a source of classes to be loaded, such as a
+     *  directory pathname, a JAR file pathname, or a ZIP file pathname
+     *
+     * @exception IllegalArgumentException if the specified repository is
+     *  invalid or does not exist
+     */
+    synchronized void addRepository(String repository, File file) {
+
+        // Note : There should be only one (of course), but I think we should
+        // keep this a bit generic
+
+        if (repository == null)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug("addRepository(" + repository + ")");
+
+        int i;
+
+        // Add this repository to our internal list
+        String[] result = new String[repositories.length + 1];
+        for (i = 0; i < repositories.length; i++) {
+            result[i] = repositories[i];
+        }
+        result[repositories.length] = repository;
+        repositories = result;
+
+        // Add the file to the list
+        File[] result2 = new File[files.length + 1];
+        for (i = 0; i < files.length; i++) {
+            result2[i] = files[i];
+        }
+        result2[files.length] = file;
+        files = result2;
+
+    }
+
+
+    synchronized void addJar(String jar, JarFile jarFile, File file)
+        throws IOException {
+
+        if (jar == null)
+            return;
+        if (jarFile == null)
+            return;
+        if (file == null)
+            return;
+
+        if (log.isDebugEnabled())
+            log.debug("addJar(" + jar + ")");
+
+        int i;
+
+        if ((jarPath != null) && (jar.startsWith(jarPath))) {
+
+            String jarName = jar.substring(jarPath.length());
+            while (jarName.startsWith("/"))
+                jarName = jarName.substring(1);
+
+            String[] result = new String[jarNames.length + 1];
+            for (i = 0; i < jarNames.length; i++) {
+                result[i] = jarNames[i];
+            }
+            result[jarNames.length] = jarName;
+            jarNames = result;
+
+        }
+
+        try {
+
+            // Register the JAR for tracking
+
+            long lastModified =
+                ((ResourceAttributes) resources.getAttributes(jar))
+                .getLastModified();
+
+            String[] result = new String[paths.length + 1];
+            for (i = 0; i < paths.length; i++) {
+                result[i] = paths[i];
+            }
+            result[paths.length] = jar;
+            paths = result;
+
+            long[] result3 = new long[lastModifiedDates.length + 1];
+            for (i = 0; i < lastModifiedDates.length; i++) {
+                result3[i] = lastModifiedDates[i];
+            }
+            result3[lastModifiedDates.length] = lastModified;
+            lastModifiedDates = result3;
+
+        } catch (NamingException e) {
+            // Ignore
+        }
+
+        // If the JAR currently contains invalid classes, don't actually use it
+        // for classloading
+        if (!validateJarFile(file))
+            return;
+
+        JarFile[] result2 = new JarFile[jarFiles.length + 1];
+        for (i = 0; i < jarFiles.length; i++) {
+            result2[i] = jarFiles[i];
+        }
+        result2[jarFiles.length] = jarFile;
+        jarFiles = result2;
+
+        // Add the file to the list
+        File[] result4 = new File[jarRealFiles.length + 1];
+        for (i = 0; i < jarRealFiles.length; i++) {
+            result4[i] = jarRealFiles[i];
+        }
+        result4[jarRealFiles.length] = file;
+        jarRealFiles = result4;
+    }
+
+
+    /**
+     * Return a String array of the current repositories for this class
+     * loader.  If there are no repositories, a zero-length array is
+     * returned.For security reason, returns a clone of the Array (since 
+     * String are immutable).
+     */
+    public String[] findRepositories() {
+
+        return ((String[])repositories.clone());
+
+    }
+
+
+    /**
+     * Have one or more classes or resources been modified so that a reload
+     * is appropriate?
+     */
+    public boolean modified() {
+
+        if (log.isDebugEnabled())
+            log.debug("modified()");
+
+        // Checking for modified loaded resources
+        int length = paths.length;
+
+        // A rare race condition can occur in the updates of the two arrays
+        // It's totally ok if the latest class added is not checked (it will
+        // be checked the next time
+        int length2 = lastModifiedDates.length;
+        if (length > length2)
+            length = length2;
+
+        for (int i = 0; i < length; i++) {
+            try {
+                long lastModified =
+                    ((ResourceAttributes) resources.getAttributes(paths[i]))
+                    .getLastModified();
+                if (lastModified != lastModifiedDates[i]) {
+                    if( log.isDebugEnabled() ) 
+                        log.debug("  Resource '" + paths[i]
+                                  + "' was modified; Date is now: "
+                                  + new java.util.Date(lastModified) + " Was: "
+                                  + new java.util.Date(lastModifiedDates[i]));
+                    return (true);
+                }
+            } catch (NamingException e) {
+                log.error("    Resource '" + paths[i] + "' is missing");
+                return (true);
+            }
+        }
+
+        length = jarNames.length;
+
+        // Check if JARs have been added or removed
+        if (getJarPath() != null) {
+
+            try {
+                NamingEnumeration enum = resources.listBindings(getJarPath());
+                int i = 0;
+                while (enum.hasMoreElements() && (i < length)) {
+                    NameClassPair ncPair = (NameClassPair) enum.nextElement();
+                    String name = ncPair.getName();
+                    // Ignore non JARs present in the lib folder
+                    if (!name.endsWith(".jar"))
+                        continue;
+                    if (!name.equals(jarNames[i])) {
+                        // Missing JAR
+                        log.info("    Additional JARs have been added : '" 
+                                 + name + "'");
+                        return (true);
+                    }
+                    i++;
+                }
+                if (enum.hasMoreElements()) {
+                    while (enum.hasMoreElements()) {
+                        NameClassPair ncPair = 
+                            (NameClassPair) enum.nextElement();
+                        String name = ncPair.getName();
+                        // Additional non-JAR files are allowed
+                        if (name.endsWith(".jar")) {
+                            // There was more JARs
+                            log.info("    Additional JARs have been added");
+                            return (true);
+                        }
+                    }
+                } else if (i < jarNames.length) {
+                    // There was less JARs
+                    log.info("    Additional JARs have been added");
+                    return (true);
+                }
+            } catch (NamingException e) {
+                if (log.isDebugEnabled())
+                    log.debug("    Failed tracking modifications of '"
+                        + getJarPath() + "'");
+            } catch (ClassCastException e) {
+                log.error("    Failed tracking modifications of '"
+                          + getJarPath() + "' : " + e.getMessage());
+            }
+
+        }
+
+        // No classes have been modified
+        return (false);
+
+    }
+
+
+    /**
+     * Render a String representation of this object.
+     */
+    public String toString() {
+
+        StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
+        sb.append("  delegate: ");
+        sb.append(delegate);
+        sb.append("\r\n");
+        sb.append("  repositories:\r\n");
+        if (repositories != null) {
+            for (int i = 0; i < repositories.length; i++) {
+                sb.append("    ");
+                sb.append(repositories[i]);
+                sb.append("\r\n");
+            }
+        }
+        if (this.parent != null) {
+            sb.append("----------> Parent Classloader:\r\n");
+            sb.append(this.parent.toString());
+            sb.append("\r\n");
+        }
+        return (sb.toString());
+
+    }
+
+
+    // ---------------------------------------------------- ClassLoader Methods
+
+
+     /**
+      * Add the specified URL to the classloader.
+      */
+     protected void addURL(URL url) {
+         super.addURL(url);
+         hasExternalRepositories = true;
+         repositoryURLs = null;
+     }
+
+
+    /**
+     * Find the specified class in our local repositories, if possible.  If
+     * not found, throw <code>ClassNotFoundException</code>.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    public Class findClass(String name) throws ClassNotFoundException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findClass(" + name + ")");
+
+        // (1) Permission to define this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    if (log.isTraceEnabled())
+                        log.trace("      securityManager.checkPackageDefinition");
+                    securityManager.checkPackageDefinition(name.substring(0,i));
+                } catch (Exception se) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->Exception-->ClassNotFoundException", se);
+                    throw new ClassNotFoundException(name, se);
+                }
+            }
+        }
+
+        // Ask our superclass to locate this class, if possible
+        // (throws ClassNotFoundException if it is not found)
+        Class clazz = null;
+        try {
+            if (log.isTraceEnabled())
+                log.trace("      findClassInternal(" + name + ")");
+            try {
+                clazz = findClassInternal(name);
+            } catch(ClassNotFoundException cnfe) {
+                if (!hasExternalRepositories) {
+                    throw cnfe;
+                }
+            } catch(AccessControlException ace) {
+                throw new ClassNotFoundException(name, ace);
+            } catch (RuntimeException e) {
+                if (log.isTraceEnabled())
+                    log.trace("      -->RuntimeException Rethrown", e);
+                throw e;
+            }
+            if ((clazz == null) && hasExternalRepositories) {
+                try {
+                    clazz = super.findClass(name);
+                } catch(AccessControlException ace) {
+                    throw new ClassNotFoundException(name, ace);
+                } catch (RuntimeException e) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->RuntimeException Rethrown", e);
+                    throw e;
+                }
+            }
+            if (clazz == null) {
+                if (log.isDebugEnabled())
+                    log.debug("    --> Returning ClassNotFoundException");
+                throw new ClassNotFoundException(name);
+            }
+        } catch (ClassNotFoundException e) {
+            if (log.isTraceEnabled())
+                log.trace("    --> Passing on ClassNotFoundException");
+            throw e;
+        }
+
+        // Return the class we have located
+        if (log.isTraceEnabled())
+            log.debug("      Returning class " + clazz);
+        if ((log.isTraceEnabled()) && (clazz != null))
+            log.debug("      Loaded by " + clazz.getClassLoader());
+        return (clazz);
+
+    }
+
+
+    /**
+     * Find the specified resource in our local repository, and return a
+     * <code>URL</code> refering to it, or <code>null</code> if this resource
+     * cannot be found.
+     *
+     * @param name Name of the resource to be found
+     */
+    public URL findResource(final String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResource(" + name + ")");
+
+        URL url = null;
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry == null) {
+            entry = findResourceInternal(name, name);
+        }
+        if (entry != null) {
+            url = entry.source;
+        }
+
+        if ((url == null) && hasExternalRepositories)
+            url = super.findResource(name);
+
+        if (log.isDebugEnabled()) {
+            if (url != null)
+                log.debug("    --> Returning '" + url.toString() + "'");
+            else
+                log.debug("    --> Resource not found, returning null");
+        }
+        return (url);
+
+    }
+
+
+    /**
+     * Return an enumeration of <code>URLs</code> representing all of the
+     * resources with the given name.  If no resources with this name are
+     * found, return an empty enumeration.
+     *
+     * @param name Name of the resources to be found
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    public Enumeration findResources(String name) throws IOException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResources(" + name + ")");
+
+        Vector result = new Vector();
+
+        int jarFilesLength = jarFiles.length;
+        int repositoriesLength = repositories.length;
+
+        int i;
+
+        // Looking at the repositories
+        for (i = 0; i < repositoriesLength; i++) {
+            try {
+                String fullPath = repositories[i] + name;
+                resources.lookup(fullPath);
+                // Note : Not getting an exception here means the resource was
+                // found
+                try {
+                    result.addElement(getURI(new File(files[i], name)));
+                } catch (MalformedURLException e) {
+                    // Ignore
+                }
+            } catch (NamingException e) {
+            }
+        }
+
+        // Looking at the JAR files
+        synchronized (jarFiles) {
+            openJARs();
+            for (i = 0; i < jarFilesLength; i++) {
+                JarEntry jarEntry = jarFiles[i].getJarEntry(name);
+                if (jarEntry != null) {
+                    try {
+                        String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+                        jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
+                        result.addElement(new URL(jarFakeUrl));
+                    } catch (MalformedURLException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        // Adding the results of a call to the superclass
+        if (hasExternalRepositories) {
+
+            Enumeration otherResourcePaths = super.findResources(name);
+
+            while (otherResourcePaths.hasMoreElements()) {
+                result.addElement(otherResourcePaths.nextElement());
+            }
+
+        }
+
+        return result.elements();
+
+    }
+
+
+    /**
+     * Find the resource with the given name.  A resource is some data
+     * (images, audio, text, etc.) that can be accessed by class code in a
+     * way that is independent of the location of the code.  The name of a
+     * resource is a "/"-separated path name that identifies the resource.
+     * If the resource cannot be found, return <code>null</code>.
+     * <p>
+     * This method searches according to the following algorithm, returning
+     * as soon as it finds the appropriate URL.  If the resource cannot be
+     * found, returns <code>null</code>.
+     * <ul>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findResource()</code> to find this resource in our
+     *     locally defined repositories.</li>
+     * <li>Call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * </ul>
+     *
+     * @param name Name of the resource to return a URL for
+     */
+    public URL getResource(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResource(" + name + ")");
+        URL url = null;
+
+        // (1) Delegate to parent if requested
+        if (delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            url = loader.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return (url);
+            }
+        }
+
+        // (2) Search local repositories
+        url = findResource(name);
+        if (url != null) {
+            // Locating the repository for special handling in the case 
+            // of a JAR
+            ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+            try {
+                String repository = entry.codeBase.toString();
+                if ((repository.endsWith(".jar")) 
+                    && (!(name.endsWith(".class")))) {
+                    // Copy binary content to the work directory if not present
+                    File resourceFile = new File(loaderDir, name);
+                    url = resourceFile.toURL();
+                }
+            } catch (Exception e) {
+                // Ignore
+            }
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning '" + url.toString() + "'");
+            return (url);
+        }
+
+        // (3) Delegate to parent unconditionally if not already attempted
+        if( !delegate ) {
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            url = loader.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return (url);
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return (null);
+
+    }
+
+
+    /**
+     * Find the resource with the given name, and return an input stream
+     * that can be used for reading it.  The search order is as described
+     * for <code>getResource()</code>, after checking to see if the resource
+     * data has been previously cached.  If the resource cannot be found,
+     * return <code>null</code>.
+     *
+     * @param name Name of the resource to return an input stream for
+     */
+    public InputStream getResourceAsStream(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResourceAsStream(" + name + ")");
+        InputStream stream = null;
+
+        // (0) Check for a cached copy of this resource
+        stream = findLoadedResource(name);
+        if (stream != null) {
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning stream from cache");
+            return (stream);
+        }
+
+        // (1) Delegate to parent if requested
+        if (delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            stream = loader.getResourceAsStream(name);
+            if (stream != null) {
+                // FIXME - cache???
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return (stream);
+            }
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        URL url = findResource(name);
+        if (url != null) {
+            // FIXME - cache???
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning stream from local");
+            stream = findLoadedResource(name);
+            try {
+                if (hasExternalRepositories && (stream == null))
+                    stream = url.openStream();
+            } catch (IOException e) {
+                ; // Ignore
+            }
+            if (stream != null)
+                return (stream);
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegate) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader unconditionally " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            stream = loader.getResourceAsStream(name);
+            if (stream != null) {
+                // FIXME - cache???
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return (stream);
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return (null);
+
+    }
+
+
+    /**
+     * Load the class with the specified name.  This method searches for
+     * classes in the same manner as <code>loadClass(String, boolean)</code>
+     * with <code>false</code> as the second argument.
+     *
+     * @param name Name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    public Class loadClass(String name) throws ClassNotFoundException {
+
+        return (loadClass(name, false));
+
+    }
+
+
+    /**
+     * Load the class with the specified name, searching using the following
+     * algorithm until it finds and returns the class.  If the class cannot
+     * be found, returns <code>ClassNotFoundException</code>.
+     * <ul>
+     * <li>Call <code>findLoadedClass(String)</code> to check if the
+     *     class has already been loaded.  If it has, the same
+     *     <code>Class</code> object is returned.</li>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>loadClass()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findClass()</code> to find this class in our locally
+     *     defined repositories.</li>
+     * <li>Call the <code>loadClass()</code> method of our parent
+     *     class loader, if any.</li>
+     * </ul>
+     * If the class was found using the above steps, and the
+     * <code>resolve</code> flag is <code>true</code>, this method will then
+     * call <code>resolveClass(Class)</code> on the resulting Class object.
+     *
+     * @param name Name of the class to be loaded
+     * @param resolve If <code>true</code> then resolve the class
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    public Class loadClass(String name, boolean resolve)
+        throws ClassNotFoundException {
+
+        if (log.isDebugEnabled())
+            log.debug("loadClass(" + name + ", " + resolve + ")");
+        Class clazz = null;
+
+        // Don't load classes if class loader is stopped
+        if (!started) {
+            log.info(sm.getString("webappClassLoader.stopped"));
+            throw new ThreadDeath();
+        }
+
+        // (0) Check our previously loaded local class cache
+        clazz = findLoadedClass0(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.1) Check our previously loaded class cache
+        clazz = findLoadedClass(name);
+        if (clazz != null) {
+            if (log.isDebugEnabled())
+                log.debug("  Returning class from cache");
+            if (resolve)
+                resolveClass(clazz);
+            return (clazz);
+        }
+
+        // (0.2) Try loading the class with the system class loader, to prevent
+        //       the webapp from overriding J2SE classes
+        try {
+            clazz = system.loadClass(name);
+            if (clazz != null) {
+                if (resolve)
+                    resolveClass(clazz);
+                return (clazz);
+            }
+        } catch (ClassNotFoundException e) {
+            // Ignore
+        }
+
+        // (0.5) Permission to access this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    securityManager.checkPackageAccess(name.substring(0,i));
+                } catch (SecurityException se) {
+                    String error = "Security Violation, attempt to use " +
+                        "Restricted Class: " + name;
+                    log.info(error, se);
+                    throw new ClassNotFoundException(error, se);
+                }
+            }
+        }
+
+        boolean delegateLoad = delegate || filter(name);
+
+        // (1) Delegate to our parent if requested
+        if (delegateLoad) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader1 " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            try {
+                clazz = loader.loadClass(name);
+                if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from parent");
+                    if (resolve)
+                        resolveClass(clazz);
+                    return (clazz);
+                }
+            } catch (ClassNotFoundException e) {
+                ;
+            }
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        try {
+            clazz = findClass(name);
+            if (clazz != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  Loading class from local repository");
+                if (resolve)
+                    resolveClass(clazz);
+                return (clazz);
+            }
+        } catch (ClassNotFoundException e) {
+            ;
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegateLoad) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader at end: " + parent);
+            ClassLoader loader = parent;
+            if (loader == null)
+                loader = system;
+            try {
+                clazz = loader.loadClass(name);
+                if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from parent");
+                    if (resolve)
+                        resolveClass(clazz);
+                    return (clazz);
+                }
+            } catch (ClassNotFoundException e) {
+                ;
+            }
+        }
+
+        throw new ClassNotFoundException(name);
+    }
+
+
+    /**
+     * Get the Permissions for a CodeSource.  If this instance
+     * of WebappClassLoader is for a web application context,
+     * add read FilePermission or JndiPermissions for the base
+     * directory (if unpacked),
+     * the context URL, and jar file resources.
+     *
+     * @param codeSource where the code was loaded from
+     * @return PermissionCollection for CodeSource
+     */
+    protected PermissionCollection getPermissions(CodeSource codeSource) {
+
+        String codeUrl = codeSource.getLocation().toString();
+        PermissionCollection pc;
+        if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) {
+            pc = super.getPermissions(codeSource);
+            if (pc != null) {
+                Iterator perms = permissionList.iterator();
+                while (perms.hasNext()) {
+                    Permission p = (Permission)perms.next();
+                    pc.add(p);
+                }
+                loaderPC.put(codeUrl,pc);
+            }
+        }
+        return (pc);
+
+    }
+
+
+    /**
+     * Returns the search path of URLs for loading classes and resources.
+     * This includes the original list of URLs specified to the constructor,
+     * along with any URLs subsequently appended by the addURL() method.
+     * @return the search path of URLs for loading classes and resources.
+     */
+    public URL[] getURLs() {
+
+        if (repositoryURLs != null) {
+            return repositoryURLs;
+        }
+
+        URL[] external = super.getURLs();
+
+        int filesLength = files.length;
+        int jarFilesLength = jarRealFiles.length;
+        int length = filesLength + jarFilesLength + external.length;
+        int i;
+
+        try {
+
+            URL[] urls = new URL[length];
+            for (i = 0; i < length; i++) {
+                if (i < filesLength) {
+                    urls[i] = getURL(files[i]);
+                } else if (i < filesLength + jarFilesLength) {
+                    urls[i] = getURL(jarRealFiles[i - filesLength]);
+                } else {
+                    urls[i] = external[i - filesLength - jarFilesLength];
+                }
+            }
+
+            repositoryURLs = urls;
+
+        } catch (MalformedURLException e) {
+            repositoryURLs = new URL[0];
+        }
+
+        return repositoryURLs;
+
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Add a lifecycle event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    public void addLifecycleListener(LifecycleListener listener) {
+    }
+
+
+    /**
+     * Get the lifecycle listeners associated with this lifecycle. If this 
+     * Lifecycle has no listeners registered, a zero-length array is returned.
+     */
+    public LifecycleListener[] findLifecycleListeners() {
+        return new LifecycleListener[0];
+    }
+
+
+    /**
+     * Remove a lifecycle event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    public void removeLifecycleListener(LifecycleListener listener) {
+    }
+
+
+    /**
+     * Start the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    public void start() throws LifecycleException {
+
+        started = true;
+
+    }
+
+
+    /**
+     * Stop the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    public void stop() throws LifecycleException {
+
+        started = false;
+
+        int length = files.length;
+        for (int i = 0; i < length; i++) {
+            files[i] = null;
+        }
+
+        length = jarFiles.length;
+        for (int i = 0; i < length; i++) {
+            try {
+                if (jarFiles[i] != null) {
+                    jarFiles[i].close();
+                }
+            } catch (IOException e) {
+                // Ignore
+            }
+            jarFiles[i] = null;
+        }
+
+        notFoundResources.clear();
+        resourceEntries.clear();
+        resources = null;
+        repositories = null;
+        repositoryURLs = null;
+        files = null;
+        jarFiles = null;
+        jarRealFiles = null;
+        jarPath = null;
+        jarNames = null;
+        lastModifiedDates = null;
+        paths = null;
+        hasExternalRepositories = false;
+        parent = null;
+
+        permissionList.clear();
+        loaderPC.clear();
+
+        if (loaderDir != null) {
+            deleteDir(loaderDir);
+        }
+
+        org.apache.commons.logging.LogFactory.release(this);
+
+    }
+
+
+    /**
+     * Used to periodically signal to the classloader to release 
+     * JAR resources.
+     */
+    public void closeJARs(boolean force) {
+        if (jarFiles.length > 0) {
+            try {
+                synchronized (jarFiles) {
+                    if (force || (System.currentTimeMillis() 
+                                  > (lastJarAccessed + 90000))) {
+                        for (int i = 0; i < jarFiles.length; i++) {
+                            if (jarFiles[i] != null) {
+                                jarFiles[i].close();
+                                jarFiles[i] = null;
+                            }
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                log("Failed to close JAR", e);
+            }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Used to periodically signal to the classloader to release JAR resources.
+     */
+    protected void openJARs() {
+        if (started && (jarFiles.length > 0)) {
+            lastJarAccessed = System.currentTimeMillis();
+            if (jarFiles[0] == null) {
+                try {
+                    for (int i = 0; i < jarFiles.length; i++) {
+                        jarFiles[i] = new JarFile(jarRealFiles[i]);
+                    }
+                } catch (IOException e) {
+                    log("Failed to open JAR", e);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Find specified class in local repositories.
+     *
+     * @return the loaded class, or null if the class isn't found
+     */
+    protected Class findClassInternal(String name)
+        throws ClassNotFoundException {
+
+        if (!validate(name))
+            throw new ClassNotFoundException(name);
+
+        String tempPath = name.replace('.', '/');
+        String classPath = tempPath + ".class";
+
+        ResourceEntry entry = null;
+
+        entry = findResourceInternal(name, classPath);
+
+        if ((entry == null) || (entry.binaryContent == null))
+            throw new ClassNotFoundException(name);
+
+        Class clazz = entry.loadedClass;
+        if (clazz != null)
+            return clazz;
+
+        // Looking up the package
+        String packageName = null;
+        int pos = name.lastIndexOf('.');
+        if (pos != -1)
+            packageName = name.substring(0, pos);
+
+        Package pkg = null;
+
+        if (packageName != null) {
+
+            pkg = getPackage(packageName);
+
+            // Define the package (if null)
+            if (pkg == null) {
+                if (entry.manifest == null) {
+                    definePackage(packageName, null, null, null, null, null,
+                                  null, null);
+                } else {
+                    definePackage(packageName, entry.manifest, entry.codeBase);
+                }
+            }
+
+        }
+
+        // Create the code source object
+        CodeSource codeSource =
+            new CodeSource(entry.codeBase, entry.certificates);
+
+        if (securityManager != null) {
+
+            // Checking sealing
+            if (pkg != null) {
+                boolean sealCheck = true;
+                if (pkg.isSealed()) {
+                    sealCheck = pkg.isSealed(entry.codeBase);
+                } else {
+                    sealCheck = (entry.manifest == null)
+                        || !isPackageSealed(packageName, entry.manifest);
+                }
+                if (!sealCheck)
+                    throw new SecurityException
+                        ("Sealing violation loading " + name + " : Package "
+                         + packageName + " is sealed.");
+            }
+
+        }
+
+        if (entry.loadedClass == null) {
+            synchronized (this) {
+                if (entry.loadedClass == null) {
+                    clazz = defineClass(name, entry.binaryContent, 0,
+                                        entry.binaryContent.length, 
+                                        codeSource);
+                    entry.loadedClass = clazz;
+                    entry.binaryContent = null;
+                    entry.source = null;
+                    entry.codeBase = null;
+                    entry.manifest = null;
+                    entry.certificates = null;
+                } else {
+                    clazz = entry.loadedClass;
+                }
+            }
+        } else {
+            clazz = entry.loadedClass;
+        }
+
+        return clazz;
+
+    }
+
+    /**
+     * Find specified resource in local repositories. This block
+     * will execute under an AccessControl.doPrivilege block.
+     *
+     * @return the loaded resource, or null if the resource isn't found
+     */
+    private ResourceEntry findResourceInternal(File file, String path){
+        ResourceEntry entry = new ResourceEntry();
+        try {
+            entry.source = getURI(new File(file, path));
+            entry.codeBase = getURL(new File(file, path));
+        } catch (MalformedURLException e) {
+            return null;
+        }   
+        return entry;
+    }
+    
+
+    /**
+     * Find specified resource in local repositories.
+     *
+     * @return the loaded resource, or null if the resource isn't found
+     */
+    protected ResourceEntry findResourceInternal(String name, String path) {
+
+        if (!started) {
+            log.info(sm.getString("webappClassLoader.stopped"));
+            return null;
+        }
+
+        if ((name == null) || (path == null))
+            return null;
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry != null)
+            return entry;
+
+        int contentLength = -1;
+        InputStream binaryStream = null;
+
+        int jarFilesLength = jarFiles.length;
+        int repositoriesLength = repositories.length;
+
+        int i;
+
+        Resource resource = null;
+
+        for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
+            try {
+
+                String fullPath = repositories[i] + path;
+
+                Object lookupResult = resources.lookup(fullPath);
+                if (lookupResult instanceof Resource) {
+                    resource = (Resource) lookupResult;
+                }
+
+                // Note : Not getting an exception here means the resource was
+                // found
+                 if (securityManager != null) {
+                    PrivilegedAction dp =
+                        new PrivilegedFindResource(files[i], path);
+                    entry = (ResourceEntry)AccessController.doPrivileged(dp);
+                 } else {
+                    entry = findResourceInternal(files[i], path);
+                 }
+
+                ResourceAttributes attributes =
+                    (ResourceAttributes) resources.getAttributes(fullPath);
+                contentLength = (int) attributes.getContentLength();
+                entry.lastModified = attributes.getLastModified();
+
+                if (resource != null) {
+
+                    try {
+                        binaryStream = resource.streamContent();
+                    } catch (IOException e) {
+                        return null;
+                    }
+
+                    // Register the full path for modification checking
+                    // Note: Only syncing on a 'constant' object is needed
+                    synchronized (allPermission) {
+
+                        int j;
+
+                        long[] result2 = 
+                            new long[lastModifiedDates.length + 1];
+                        for (j = 0; j < lastModifiedDates.length; j++) {
+                            result2[j] = lastModifiedDates[j];
+                        }
+                        result2[lastModifiedDates.length] = entry.lastModified;
+                        lastModifiedDates = result2;
+
+                        String[] result = new String[paths.length + 1];
+                        for (j = 0; j < paths.length; j++) {
+                            result[j] = paths[j];
+                        }
+                        result[paths.length] = fullPath;
+                        paths = result;
+
+                    }
+
+                }
+
+            } catch (NamingException e) {
+            }
+        }
+
+        if ((entry == null) && (notFoundResources.containsKey(name)))
+            return null;
+
+        JarEntry jarEntry = null;
+
+        synchronized (jarFiles) {
+
+            openJARs();
+            for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
+
+                jarEntry = jarFiles[i].getJarEntry(path);
+
+                if (jarEntry != null) {
+
+                    entry = new ResourceEntry();
+                    try {
+                        entry.codeBase = getURL(jarRealFiles[i]);
+                        String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+                        jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
+                        entry.source = new URL(jarFakeUrl);
+                        entry.lastModified = jarRealFiles[i].lastModified();
+                    } catch (MalformedURLException e) {
+                        return null;
+                    }
+                    contentLength = (int) jarEntry.getSize();
+                    try {
+                        entry.manifest = jarFiles[i].getManifest();
+                        binaryStream = jarFiles[i].getInputStream(jarEntry);
+                    } catch (IOException e) {
+                        return null;
+                    }
+
+                    // Extract resources contained in JAR to the workdir
+                    if (!(path.endsWith(".class"))) {
+                        byte[] buf = new byte[1024];
+                        File resourceFile = new File
+                            (loaderDir, jarEntry.getName());
+                        if (!resourceFile.exists()) {
+                            Enumeration entries = jarFiles[i].entries();
+                            while (entries.hasMoreElements()) {
+                                JarEntry jarEntry2 = 
+                                    (JarEntry) entries.nextElement();
+                                if (!(jarEntry2.isDirectory()) 
+                                    && (!jarEntry2.getName().endsWith
+                                        (".class"))) {
+                                    resourceFile = new File
+                                        (loaderDir, jarEntry2.getName());
+                                    resourceFile.getParentFile().mkdirs();
+                                    FileOutputStream os = null;
+                                    InputStream is = null;
+                                    try {
+                                        is = jarFiles[i].getInputStream
+                                            (jarEntry2);
+                                        os = new FileOutputStream
+                                            (resourceFile);
+                                        while (true) {
+                                            int n = is.read(buf);
+                                            if (n <= 0) {
+                                                break;
+                                            }
+                                            os.write(buf, 0, n);
+                                        }
+                                    } catch (IOException e) {
+                                        // Ignore
+                                    } finally {
+                                        try {
+                                            if (is != null) {
+                                                is.close();
+                                            }
+                                        } catch (IOException e) {
+                                        }
+                                        try {
+                                            if (os != null) {
+                                                os.close();
+                                            }
+                                        } catch (IOException e) {
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                }
+
+            }
+
+            if (entry == null) {
+                synchronized (notFoundResources) {
+                    notFoundResources.put(name, name);
+                }
+                return null;
+            }
+
+            if (binaryStream != null) {
+
+                byte[] binaryContent = new byte[contentLength];
+
+                try {
+                    int pos = 0;
+
+                    while (true) {
+                        int n = binaryStream.read(binaryContent, pos,
+                                                  binaryContent.length - pos);
+                        if (n <= 0)
+                            break;
+                        pos += n;
+                    }
+                    binaryStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    return null;
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    return null;
+                }
+
+                entry.binaryContent = binaryContent;
+
+                // The certificates are only available after the JarEntry 
+                // associated input stream has been fully read
+                if (jarEntry != null) {
+                    entry.certificates = jarEntry.getCertificates();
+                }
+
+            }
+
+        }
+
+        // Add the entry in the local resource repository
+        synchronized (resourceEntries) {
+            // Ensures that all the threads which may be in a race to load
+            // a particular class all end up with the same ResourceEntry
+            // instance
+            ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
+            if (entry2 == null) {
+                resourceEntries.put(name, entry);
+            } else {
+                entry = entry2;
+            }
+        }
+
+        return entry;
+
+    }
+
+
+    /**
+     * Returns true if the specified package name is sealed according to the
+     * given manifest.
+     */
+    protected boolean isPackageSealed(String name, Manifest man) {
+
+        String path = name + "/";
+        Attributes attr = man.getAttributes(path);
+        String sealed = null;
+        if (attr != null) {
+            sealed = attr.getValue(Name.SEALED);
+        }
+        if (sealed == null) {
+            if ((attr = man.getMainAttributes()) != null) {
+                sealed = attr.getValue(Name.SEALED);
+            }
+        }
+        return "true".equalsIgnoreCase(sealed);
+
+    }
+
+
+    /**
+     * Finds the resource with the given name if it has previously been
+     * loaded and cached by this class loader, and return an input stream
+     * to the resource data.  If this resource has not been cached, return
+     * <code>null</code>.
+     *
+     * @param name Name of the resource to return
+     */
+    protected InputStream findLoadedResource(String name) {
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry != null) {
+            if (entry.binaryContent != null)
+                return new ByteArrayInputStream(entry.binaryContent);
+        }
+        return (null);
+
+    }
+
+
+    /**
+     * Finds the class with the given name if it has previously been
+     * loaded and cached by this class loader, and return the Class object.
+     * If this class has not been cached, return <code>null</code>.
+     *
+     * @param name Name of the resource to return
+     */
+    protected Class findLoadedClass0(String name) {
+
+        ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+        if (entry != null) {
+            return entry.loadedClass;
+        }
+        return (null);  // FIXME - findLoadedResource()
+
+    }
+
+
+    /**
+     * Refresh the system policy file, to pick up eventual changes.
+     */
+    protected void refreshPolicy() {
+
+        try {
+            // The policy file may have been modified to adjust 
+            // permissions, so we're reloading it when loading or 
+            // reloading a Context
+            Policy policy = Policy.getPolicy();
+            policy.refresh();
+        } catch (AccessControlException e) {
+            // Some policy files may restrict this, even for the core,
+            // so this exception is ignored
+        }
+
+    }
+
+
+    /**
+     * Filter classes.
+     * 
+     * @param name class name
+     * @return true if the class should be filtered
+     */
+    protected boolean filter(String name) {
+
+        if (name == null)
+            return false;
+
+        // Looking up the package
+        String packageName = null;
+        int pos = name.lastIndexOf('.');
+        if (pos != -1)
+            packageName = name.substring(0, pos);
+        else
+            return false;
+
+        for (int i = 0; i < packageTriggers.length; i++) {
+            if (packageName.startsWith(packageTriggers[i]))
+                return true;
+        }
+
+        return false;
+
+    }
+
+
+    /**
+     * Validate a classname. As per SRV.9.7.2, we must restict loading of 
+     * classes from J2SE (java.*) and classes of the servlet API 
+     * (javax.servlet.*). That should enhance robustness and prevent a number
+     * of user error (where an older version of servlet.jar would be present
+     * in /WEB-INF/lib).
+     * 
+     * @param name class name
+     * @return true if the name is valid
+     */
+    protected boolean validate(String name) {
+
+        if (name == null)
+            return false;
+        if (name.startsWith("java."))
+            return false;
+
+        return true;
+
+    }
+
+
+    /**
+     * Check the specified JAR file, and return <code>true</code> if it does
+     * not contain any of the trigger classes.
+     *
+     * @param jarfile The JAR file to be checked
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private boolean validateJarFile(File jarfile)
+        throws IOException {
+
+        if (triggers == null)
+            return (true);
+        JarFile jarFile = new JarFile(jarfile);
+        for (int i = 0; i < triggers.length; i++) {
+            Class clazz = null;
+            try {
+                if (parent != null) {
+                    clazz = parent.loadClass(triggers[i]);
+                } else {
+                    clazz = Class.forName(triggers[i]);
+                }
+            } catch (Throwable t) {
+                clazz = null;
+            }
+            if (clazz == null)
+                continue;
+            String name = triggers[i].replace('.', '/') + ".class";
+            if (log.isDebugEnabled())
+                log.debug(" Checking for " + name);
+            JarEntry jarEntry = jarFile.getJarEntry(name);
+            if (jarEntry != null) {
+                log.info("validateJarFile(" + jarfile + 
+                    ") - jar not loaded. See Servlet Spec 2.3, "
+                    + "section 9.7.2. Offending class: " + name);
+                jarFile.close();
+                return (false);
+            }
+        }
+        jarFile.close();
+        return (true);
+
+    }
+
+
+    /**
+     * Get URL.
+     */
+    protected URL getURL(File file)
+        throws MalformedURLException {
+
+        File realFile = file;
+        try {
+            realFile = realFile.getCanonicalFile();
+        } catch (IOException e) {
+            // Ignore
+        }
+        return realFile.toURL();
+
+    }
+
+
+    /**
+     * Get URL.
+     */
+    protected URL getURI(File file)
+        throws MalformedURLException {
+
+        return jdkCompat.getURI(file);
+
+    }
+
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * subdirectories recursively.
+     *
+     * @param dir File object representing the directory to be deleted
+     */
+    protected static void deleteDir(File dir) {
+
+        String files[] = dir.list();
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; i < files.length; i++) {
+            File file = new File(dir, files[i]);
+            if (file.isDirectory()) {
+                deleteDir(file);
+            } else {
+                file.delete();
+            }
+        }
+        dir.delete();
+
+    }
+
+
+    /**
+     * Log a debugging output message.
+     *
+     * @param message Message to be logged
+     */
+    private void log(String message) {
+
+        System.out.println("WebappClassLoader: " + message);
+
+    }
+
+
+    /**
+     * Log a debugging output message with an exception.
+     *
+     * @param message Message to be logged
+     * @param throwable Exception to be logged
+     */
+    private void log(String message, Throwable throwable) {
+
+        System.out.println("WebappClassLoader: " + message);
+        throwable.printStackTrace(System.out);
+
+    }
+
+}
+