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);
+
+ }
+
+}
+