0001: /*
0002: * Copyright 1999,2004 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.apache.catalina.loader;
0018:
0019: import java.io.ByteArrayInputStream;
0020: import java.io.File;
0021: import java.io.FileOutputStream;
0022: import java.io.FilePermission;
0023: import java.io.IOException;
0024: import java.io.InputStream;
0025: import java.net.MalformedURLException;
0026: import java.net.URL;
0027: import java.net.URLClassLoader;
0028: import java.security.AccessControlException;
0029: import java.security.AccessController;
0030: import java.security.CodeSource;
0031: import java.security.Permission;
0032: import java.security.PermissionCollection;
0033: import java.security.Policy;
0034: import java.security.PrivilegedAction;
0035: import java.util.ArrayList;
0036: import java.util.Enumeration;
0037: import java.util.HashMap;
0038: import java.util.Iterator;
0039: import java.util.Vector;
0040: import java.util.jar.Attributes;
0041: import java.util.jar.JarEntry;
0042: import java.util.jar.JarFile;
0043: import java.util.jar.Manifest;
0044: import java.util.jar.Attributes.Name;
0045:
0046: import javax.naming.NameClassPair;
0047: import javax.naming.NamingEnumeration;
0048: import javax.naming.NamingException;
0049: import javax.naming.directory.DirContext;
0050:
0051: import org.apache.catalina.Lifecycle;
0052: import org.apache.catalina.LifecycleException;
0053: import org.apache.catalina.LifecycleListener;
0054: import org.apache.catalina.util.StringManager;
0055: import org.apache.naming.JndiPermission;
0056: import org.apache.naming.resources.Resource;
0057: import org.apache.naming.resources.ResourceAttributes;
0058: import org.apache.tomcat.util.compat.JdkCompat;
0059:
0060: /**
0061: * Specialized web application class loader.
0062: * <p>
0063: * This class loader is a full reimplementation of the
0064: * <code>URLClassLoader</code> from the JDK. It is desinged to be fully
0065: * compatible with a normal <code>URLClassLoader</code>, although its internal
0066: * behavior may be completely different.
0067: * <p>
0068: * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows
0069: * the delegation model recommended in the specification. The system class
0070: * loader will be queried first, then the local repositories, and only then
0071: * delegation to the parent class loader will occur. This allows the web
0072: * application to override any shared class except the classes from J2SE.
0073: * Special handling is provided from the JAXP XML parser interfaces, the JNDI
0074: * interfaces, and the classes from the servlet API, which are never loaded
0075: * from the webapp repository.
0076: * <p>
0077: * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
0078: * compilation technology, any repository which contains classes from
0079: * the servlet API will be ignored by the class loader.
0080: * <p>
0081: * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
0082: * URLs which include the full JAR URL when a class is loaded from a JAR file,
0083: * which allows setting security permission at the class level, even when a
0084: * class is contained inside a JAR.
0085: * <p>
0086: * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
0087: * the order they are added via the initial constructor and/or any subsequent
0088: * calls to <code>addRepository()</code> or <code>addJar()</code>.
0089: * <p>
0090: * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
0091: * security is made unless a security manager is present.
0092: *
0093: * @author Remy Maucherat
0094: * @author Craig R. McClanahan
0095: * @version $Revision: 1.34 $ $Date: 2004/05/26 15:47:40 $
0096: */
0097: public class WebappClassLoader extends URLClassLoader implements
0098: Reloader, Lifecycle {
0099:
0100: private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
0101: .getLog(WebappClassLoader.class);
0102:
0103: protected class PrivilegedFindResource implements PrivilegedAction {
0104:
0105: private File file;
0106: private String path;
0107:
0108: PrivilegedFindResource(File file, String path) {
0109: this .file = file;
0110: this .path = path;
0111: }
0112:
0113: public Object run() {
0114: return findResourceInternal(file, path);
0115: }
0116:
0117: }
0118:
0119: // ------------------------------------------------------- Static Variables
0120:
0121: /**
0122: * The set of trigger classes that will cause a proposed repository not
0123: * to be added if this class is visible to the class loader that loaded
0124: * this factory class. Typically, trigger classes will be listed for
0125: * components that have been integrated into the JDK for later versions,
0126: * but where the corresponding JAR files are required to run on
0127: * earlier versions.
0128: */
0129: private static final String[] triggers = { "javax.servlet.Servlet" // Servlet API
0130: };
0131:
0132: /**
0133: * Jdk Compatibility Support.
0134: */
0135: private static JdkCompat jdkCompat = JdkCompat.getJdkCompat();
0136:
0137: /**
0138: * Set of package names which are not allowed to be loaded from a webapp
0139: * class loader without delegating first.
0140: */
0141: private static final String[] packageTriggers = { "javax", // Java extensions
0142: "org.xml.sax", // SAX 1 & 2
0143: "org.w3c.dom", // DOM 1 & 2
0144: "org.apache.xerces", // Xerces 1 & 2
0145: "org.apache.xalan" // Xalan
0146: };
0147:
0148: /**
0149: * The string manager for this package.
0150: */
0151: protected static final StringManager sm = StringManager
0152: .getManager(Constants.Package);
0153:
0154: // ----------------------------------------------------------- Constructors
0155:
0156: /**
0157: * Construct a new ClassLoader with no defined repositories and no
0158: * parent ClassLoader.
0159: */
0160: public WebappClassLoader() {
0161:
0162: super (new URL[0]);
0163: this .parent = getParent();
0164: system = getSystemClassLoader();
0165: securityManager = System.getSecurityManager();
0166:
0167: if (securityManager != null) {
0168: refreshPolicy();
0169: }
0170:
0171: }
0172:
0173: /**
0174: * Construct a new ClassLoader with no defined repositories and no
0175: * parent ClassLoader.
0176: */
0177: public WebappClassLoader(ClassLoader parent) {
0178:
0179: super (new URL[0], parent);
0180:
0181: this .parent = getParent();
0182:
0183: system = getSystemClassLoader();
0184: securityManager = System.getSecurityManager();
0185:
0186: if (securityManager != null) {
0187: refreshPolicy();
0188: }
0189: }
0190:
0191: // ----------------------------------------------------- Instance Variables
0192:
0193: /**
0194: * Associated directory context giving access to the resources in this
0195: * webapp.
0196: */
0197: protected DirContext resources = null;
0198:
0199: /**
0200: * The cache of ResourceEntry for classes and resources we have loaded,
0201: * keyed by resource name.
0202: */
0203: protected HashMap resourceEntries = new HashMap();
0204:
0205: /**
0206: * The list of not found resources.
0207: */
0208: protected HashMap notFoundResources = new HashMap();
0209:
0210: /**
0211: * The debugging detail level of this component.
0212: */
0213: protected int debug = 0;
0214:
0215: /**
0216: * Should this class loader delegate to the parent class loader
0217: * <strong>before</strong> searching its own repositories (i.e. the
0218: * usual Java2 delegation model)? If set to <code>false</code>,
0219: * this class loader will search its own repositories first, and
0220: * delegate to the parent only if the class or resource is not
0221: * found locally.
0222: */
0223: protected boolean delegate = false;
0224:
0225: /**
0226: * Last time a JAR was accessed.
0227: */
0228: protected long lastJarAccessed = 0L;
0229:
0230: /**
0231: * The list of local repositories, in the order they should be searched
0232: * for locally loaded classes or resources.
0233: */
0234: protected String[] repositories = new String[0];
0235:
0236: /**
0237: * Repositories URLs, used to cache the result of getURLs.
0238: */
0239: protected URL[] repositoryURLs = null;
0240:
0241: /**
0242: * Repositories translated as path in the work directory (for Jasper
0243: * originally), but which is used to generate fake URLs should getURLs be
0244: * called.
0245: */
0246: protected File[] files = new File[0];
0247:
0248: /**
0249: * The list of JARs, in the order they should be searched
0250: * for locally loaded classes or resources.
0251: */
0252: protected JarFile[] jarFiles = new JarFile[0];
0253:
0254: /**
0255: * The list of JARs, in the order they should be searched
0256: * for locally loaded classes or resources.
0257: */
0258: protected File[] jarRealFiles = new File[0];
0259:
0260: /**
0261: * The path which will be monitored for added Jar files.
0262: */
0263: protected String jarPath = null;
0264:
0265: /**
0266: * The list of JARs, in the order they should be searched
0267: * for locally loaded classes or resources.
0268: */
0269: protected String[] jarNames = new String[0];
0270:
0271: /**
0272: * The list of JARs last modified dates, in the order they should be
0273: * searched for locally loaded classes or resources.
0274: */
0275: protected long[] lastModifiedDates = new long[0];
0276:
0277: /**
0278: * The list of resources which should be checked when checking for
0279: * modifications.
0280: */
0281: protected String[] paths = new String[0];
0282:
0283: /**
0284: * A list of read File and Jndi Permission's required if this loader
0285: * is for a web application context.
0286: */
0287: private ArrayList permissionList = new ArrayList();
0288:
0289: /**
0290: * Path where resources loaded from JARs will be extracted.
0291: */
0292: private File loaderDir = null;
0293:
0294: /**
0295: * The PermissionCollection for each CodeSource for a web
0296: * application context.
0297: */
0298: private HashMap loaderPC = new HashMap();
0299:
0300: /**
0301: * Instance of the SecurityManager installed.
0302: */
0303: private SecurityManager securityManager = null;
0304:
0305: /**
0306: * The parent class loader.
0307: */
0308: private ClassLoader parent = null;
0309:
0310: /**
0311: * The system class loader.
0312: */
0313: private ClassLoader system = null;
0314:
0315: /**
0316: * Has this component been started?
0317: */
0318: protected boolean started = false;
0319:
0320: /**
0321: * Has external repositories.
0322: */
0323: protected boolean hasExternalRepositories = false;
0324:
0325: /**
0326: * All permission.
0327: */
0328: private Permission allPermission = new java.security.AllPermission();
0329:
0330: // ------------------------------------------------------------- Properties
0331:
0332: /**
0333: * Get associated resources.
0334: */
0335: public DirContext getResources() {
0336:
0337: return this .resources;
0338:
0339: }
0340:
0341: /**
0342: * Set associated resources.
0343: */
0344: public void setResources(DirContext resources) {
0345:
0346: this .resources = resources;
0347:
0348: }
0349:
0350: /**
0351: * Return the debugging detail level for this component.
0352: */
0353: public int getDebug() {
0354:
0355: return (this .debug);
0356:
0357: }
0358:
0359: /**
0360: * Set the debugging detail level for this component.
0361: *
0362: * @param debug The new debugging detail level
0363: */
0364: public void setDebug(int debug) {
0365:
0366: this .debug = debug;
0367:
0368: }
0369:
0370: /**
0371: * Return the "delegate first" flag for this class loader.
0372: */
0373: public boolean getDelegate() {
0374:
0375: return (this .delegate);
0376:
0377: }
0378:
0379: /**
0380: * Set the "delegate first" flag for this class loader.
0381: *
0382: * @param delegate The new "delegate first" flag
0383: */
0384: public void setDelegate(boolean delegate) {
0385:
0386: this .delegate = delegate;
0387:
0388: }
0389:
0390: /**
0391: * If there is a Java SecurityManager create a read FilePermission
0392: * or JndiPermission for the file directory path.
0393: *
0394: * @param path file directory path
0395: */
0396: public void addPermission(String path) {
0397: if (path == null) {
0398: return;
0399: }
0400:
0401: if (securityManager != null) {
0402: Permission permission = null;
0403: if (path.startsWith("jndi:")
0404: || path.startsWith("jar:jndi:")) {
0405: if (!path.endsWith("/")) {
0406: path = path + "/";
0407: }
0408: permission = new JndiPermission(path + "*");
0409: addPermission(permission);
0410: } else {
0411: if (!path.endsWith(File.separator)) {
0412: permission = new FilePermission(path, "read");
0413: addPermission(permission);
0414: path = path + File.separator;
0415: }
0416: permission = new FilePermission(path + "-", "read");
0417: addPermission(permission);
0418: }
0419: }
0420: }
0421:
0422: /**
0423: * If there is a Java SecurityManager create a read FilePermission
0424: * or JndiPermission for URL.
0425: *
0426: * @param url URL for a file or directory on local system
0427: */
0428: public void addPermission(URL url) {
0429: if (url != null) {
0430: addPermission(url.toString());
0431: }
0432: }
0433:
0434: /**
0435: * If there is a Java SecurityManager create a Permission.
0436: *
0437: * @param url URL for a file or directory on local system
0438: */
0439: public void addPermission(Permission permission) {
0440: if ((securityManager != null) && (permission != null)) {
0441: permissionList.add(permission);
0442: }
0443: }
0444:
0445: /**
0446: * Return the JAR path.
0447: */
0448: public String getJarPath() {
0449:
0450: return this .jarPath;
0451:
0452: }
0453:
0454: /**
0455: * Change the Jar path.
0456: */
0457: public void setJarPath(String jarPath) {
0458:
0459: this .jarPath = jarPath;
0460:
0461: }
0462:
0463: /**
0464: * Change the work directory.
0465: */
0466: public void setWorkDir(File workDir) {
0467: this .loaderDir = new File(workDir, "loader");
0468: }
0469:
0470: // ------------------------------------------------------- Reloader Methods
0471:
0472: /**
0473: * Add a new repository to the set of places this ClassLoader can look for
0474: * classes to be loaded.
0475: *
0476: * @param repository Name of a source of classes to be loaded, such as a
0477: * directory pathname, a JAR file pathname, or a ZIP file pathname
0478: *
0479: * @exception IllegalArgumentException if the specified repository is
0480: * invalid or does not exist
0481: */
0482: public void addRepository(String repository) {
0483:
0484: // Ignore any of the standard repositories, as they are set up using
0485: // either addJar or addRepository
0486: if (repository.startsWith("/WEB-INF/lib")
0487: || repository.startsWith("/WEB-INF/classes"))
0488: return;
0489:
0490: // Add this repository to our underlying class loader
0491: try {
0492: URL url = new URL(repository);
0493: super .addURL(url);
0494: hasExternalRepositories = true;
0495: repositoryURLs = null;
0496: } catch (MalformedURLException e) {
0497: IllegalArgumentException iae = new IllegalArgumentException(
0498: "Invalid repository: " + repository);
0499: jdkCompat.chainException(iae, e);
0500: throw iae;
0501: }
0502:
0503: }
0504:
0505: /**
0506: * Add a new repository to the set of places this ClassLoader can look for
0507: * classes to be loaded.
0508: *
0509: * @param repository Name of a source of classes to be loaded, such as a
0510: * directory pathname, a JAR file pathname, or a ZIP file pathname
0511: *
0512: * @exception IllegalArgumentException if the specified repository is
0513: * invalid or does not exist
0514: */
0515: synchronized void addRepository(String repository, File file) {
0516:
0517: // Note : There should be only one (of course), but I think we should
0518: // keep this a bit generic
0519:
0520: if (repository == null)
0521: return;
0522:
0523: if (log.isDebugEnabled())
0524: log.debug("addRepository(" + repository + ")");
0525:
0526: int i;
0527:
0528: // Add this repository to our internal list
0529: String[] result = new String[repositories.length + 1];
0530: for (i = 0; i < repositories.length; i++) {
0531: result[i] = repositories[i];
0532: }
0533: result[repositories.length] = repository;
0534: repositories = result;
0535:
0536: // Add the file to the list
0537: File[] result2 = new File[files.length + 1];
0538: for (i = 0; i < files.length; i++) {
0539: result2[i] = files[i];
0540: }
0541: result2[files.length] = file;
0542: files = result2;
0543:
0544: }
0545:
0546: synchronized void addJar(String jar, JarFile jarFile, File file)
0547: throws IOException {
0548:
0549: if (jar == null)
0550: return;
0551: if (jarFile == null)
0552: return;
0553: if (file == null)
0554: return;
0555:
0556: if (log.isDebugEnabled())
0557: log.debug("addJar(" + jar + ")");
0558:
0559: int i;
0560:
0561: if ((jarPath != null) && (jar.startsWith(jarPath))) {
0562:
0563: String jarName = jar.substring(jarPath.length());
0564: while (jarName.startsWith("/"))
0565: jarName = jarName.substring(1);
0566:
0567: String[] result = new String[jarNames.length + 1];
0568: for (i = 0; i < jarNames.length; i++) {
0569: result[i] = jarNames[i];
0570: }
0571: result[jarNames.length] = jarName;
0572: jarNames = result;
0573:
0574: }
0575:
0576: try {
0577:
0578: // Register the JAR for tracking
0579:
0580: long lastModified = ((ResourceAttributes) resources
0581: .getAttributes(jar)).getLastModified();
0582:
0583: String[] result = new String[paths.length + 1];
0584: for (i = 0; i < paths.length; i++) {
0585: result[i] = paths[i];
0586: }
0587: result[paths.length] = jar;
0588: paths = result;
0589:
0590: long[] result3 = new long[lastModifiedDates.length + 1];
0591: for (i = 0; i < lastModifiedDates.length; i++) {
0592: result3[i] = lastModifiedDates[i];
0593: }
0594: result3[lastModifiedDates.length] = lastModified;
0595: lastModifiedDates = result3;
0596:
0597: } catch (NamingException e) {
0598: // Ignore
0599: }
0600:
0601: // If the JAR currently contains invalid classes, don't actually use it
0602: // for classloading
0603: if (!validateJarFile(file))
0604: return;
0605:
0606: JarFile[] result2 = new JarFile[jarFiles.length + 1];
0607: for (i = 0; i < jarFiles.length; i++) {
0608: result2[i] = jarFiles[i];
0609: }
0610: result2[jarFiles.length] = jarFile;
0611: jarFiles = result2;
0612:
0613: // Add the file to the list
0614: File[] result4 = new File[jarRealFiles.length + 1];
0615: for (i = 0; i < jarRealFiles.length; i++) {
0616: result4[i] = jarRealFiles[i];
0617: }
0618: result4[jarRealFiles.length] = file;
0619: jarRealFiles = result4;
0620: }
0621:
0622: /**
0623: * Return a String array of the current repositories for this class
0624: * loader. If there are no repositories, a zero-length array is
0625: * returned.For security reason, returns a clone of the Array (since
0626: * String are immutable).
0627: */
0628: public String[] findRepositories() {
0629:
0630: return ((String[]) repositories.clone());
0631:
0632: }
0633:
0634: /**
0635: * Have one or more classes or resources been modified so that a reload
0636: * is appropriate?
0637: */
0638: public boolean modified() {
0639:
0640: if (log.isDebugEnabled())
0641: log.debug("modified()");
0642:
0643: // Checking for modified loaded resources
0644: int length = paths.length;
0645:
0646: // A rare race condition can occur in the updates of the two arrays
0647: // It's totally ok if the latest class added is not checked (it will
0648: // be checked the next time
0649: int length2 = lastModifiedDates.length;
0650: if (length > length2)
0651: length = length2;
0652:
0653: for (int i = 0; i < length; i++) {
0654: try {
0655: long lastModified =
0656: ((ResourceAttributes) resources.getAttributes(paths[i]))
0657: .getLastModified();
0658: if (lastModified != lastModifiedDates[i]) {
0659: if( log.isDebugEnabled() )
0660: log.debug(" Resource '" + paths[i]
0661: + "' was modified; Date is now: "
0662: + new java.util.Date(lastModified) + " Was: "
0663: + new java.util.Date(lastModifiedDates[i]));
0664: return (true);
0665: }
0666: } catch (NamingException e) {
0667: log.error(" Resource '" + paths[i] + "' is missing");
0668: return (true);
0669: }
0670: }
0671:
0672: length = jarNames.length;
0673:
0674: // Check if JARs have been added or removed
0675: if (getJarPath() != null) {
0676:
0677: try {
0678: NamingEnumeration enum = resources.listBindings(getJarPath());
0679: int i = 0;
0680: while (enum.hasMoreElements() && (i < length)) {
0681: NameClassPair ncPair = (NameClassPair) enum.nextElement();
0682: String name = ncPair.getName();
0683: // Ignore non JARs present in the lib folder
0684: if (!name.endsWith(".jar"))
0685: continue;
0686: if (!name.equals(jarNames[i])) {
0687: // Missing JAR
0688: log.info(" Additional JARs have been added : '"
0689: + name + "'");
0690: return (true);
0691: }
0692: i++;
0693: }
0694: if (enum.hasMoreElements()) {
0695: while (enum.hasMoreElements()) {
0696: NameClassPair ncPair =
0697: (NameClassPair) enum.nextElement();
0698: String name = ncPair.getName();
0699: // Additional non-JAR files are allowed
0700: if (name.endsWith(".jar")) {
0701: // There was more JARs
0702: log.info(" Additional JARs have been added");
0703: return (true);
0704: }
0705: }
0706: } else if (i < jarNames.length) {
0707: // There was less JARs
0708: log.info(" Additional JARs have been added");
0709: return (true);
0710: }
0711: } catch (NamingException e) {
0712: if (log.isDebugEnabled())
0713: log.debug(" Failed tracking modifications of '"
0714: + getJarPath() + "'");
0715: } catch (ClassCastException e) {
0716: log.error(" Failed tracking modifications of '"
0717: + getJarPath() + "' : " + e.getMessage());
0718: }
0719:
0720: }
0721:
0722: // No classes have been modified
0723: return (false);
0724:
0725: }
0726:
0727: /**
0728: * Render a String representation of this object.
0729: */
0730: public String toString() {
0731:
0732: StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
0733: sb.append(" delegate: ");
0734: sb.append(delegate);
0735: sb.append("\r\n");
0736: sb.append(" repositories:\r\n");
0737: if (repositories != null) {
0738: for (int i = 0; i < repositories.length; i++) {
0739: sb.append(" ");
0740: sb.append(repositories[i]);
0741: sb.append("\r\n");
0742: }
0743: }
0744: if (this .parent != null) {
0745: sb.append("----------> Parent Classloader:\r\n");
0746: sb.append(this .parent.toString());
0747: sb.append("\r\n");
0748: }
0749: return (sb.toString());
0750:
0751: }
0752:
0753: // ---------------------------------------------------- ClassLoader Methods
0754:
0755: /**
0756: * Add the specified URL to the classloader.
0757: */
0758: protected void addURL(URL url) {
0759: super .addURL(url);
0760: hasExternalRepositories = true;
0761: repositoryURLs = null;
0762: }
0763:
0764: /**
0765: * Find the specified class in our local repositories, if possible. If
0766: * not found, throw <code>ClassNotFoundException</code>.
0767: *
0768: * @param name Name of the class to be loaded
0769: *
0770: * @exception ClassNotFoundException if the class was not found
0771: */
0772: public Class findClass(String name) throws ClassNotFoundException {
0773:
0774: if (log.isDebugEnabled())
0775: log.debug(" findClass(" + name + ")");
0776:
0777: // (1) Permission to define this class when using a SecurityManager
0778: if (securityManager != null) {
0779: int i = name.lastIndexOf('.');
0780: if (i >= 0) {
0781: try {
0782: if (log.isTraceEnabled())
0783: log
0784: .trace(" securityManager.checkPackageDefinition");
0785: securityManager.checkPackageDefinition(name
0786: .substring(0, i));
0787: } catch (Exception se) {
0788: if (log.isTraceEnabled())
0789: log
0790: .trace(
0791: " -->Exception-->ClassNotFoundException",
0792: se);
0793: throw new ClassNotFoundException(name, se);
0794: }
0795: }
0796: }
0797:
0798: // Ask our superclass to locate this class, if possible
0799: // (throws ClassNotFoundException if it is not found)
0800: Class clazz = null;
0801: try {
0802: if (log.isTraceEnabled())
0803: log.trace(" findClassInternal(" + name + ")");
0804: try {
0805: clazz = findClassInternal(name);
0806: } catch (ClassNotFoundException cnfe) {
0807: if (!hasExternalRepositories) {
0808: throw cnfe;
0809: }
0810: } catch (AccessControlException ace) {
0811: throw new ClassNotFoundException(name, ace);
0812: } catch (RuntimeException e) {
0813: if (log.isTraceEnabled())
0814: log.trace(" -->RuntimeException Rethrown", e);
0815: throw e;
0816: }
0817: if ((clazz == null) && hasExternalRepositories) {
0818: try {
0819: clazz = super .findClass(name);
0820: } catch (AccessControlException ace) {
0821: throw new ClassNotFoundException(name, ace);
0822: } catch (RuntimeException e) {
0823: if (log.isTraceEnabled())
0824: log.trace(" -->RuntimeException Rethrown",
0825: e);
0826: throw e;
0827: }
0828: }
0829: if (clazz == null) {
0830: if (log.isDebugEnabled())
0831: log
0832: .debug(" --> Returning ClassNotFoundException");
0833: throw new ClassNotFoundException(name);
0834: }
0835: } catch (ClassNotFoundException e) {
0836: if (log.isTraceEnabled())
0837: log.trace(" --> Passing on ClassNotFoundException");
0838: throw e;
0839: }
0840:
0841: // Return the class we have located
0842: if (log.isTraceEnabled())
0843: log.debug(" Returning class " + clazz);
0844: if ((log.isTraceEnabled()) && (clazz != null))
0845: log.debug(" Loaded by " + clazz.getClassLoader());
0846: return (clazz);
0847:
0848: }
0849:
0850: /**
0851: * Find the specified resource in our local repository, and return a
0852: * <code>URL</code> refering to it, or <code>null</code> if this resource
0853: * cannot be found.
0854: *
0855: * @param name Name of the resource to be found
0856: */
0857: public URL findResource(final String name) {
0858:
0859: if (log.isDebugEnabled())
0860: log.debug(" findResource(" + name + ")");
0861:
0862: URL url = null;
0863:
0864: ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
0865: if (entry == null) {
0866: entry = findResourceInternal(name, name);
0867: }
0868: if (entry != null) {
0869: url = entry.source;
0870: }
0871:
0872: if ((url == null) && hasExternalRepositories)
0873: url = super .findResource(name);
0874:
0875: if (log.isDebugEnabled()) {
0876: if (url != null)
0877: log.debug(" --> Returning '" + url.toString() + "'");
0878: else
0879: log.debug(" --> Resource not found, returning null");
0880: }
0881: return (url);
0882:
0883: }
0884:
0885: /**
0886: * Return an enumeration of <code>URLs</code> representing all of the
0887: * resources with the given name. If no resources with this name are
0888: * found, return an empty enumeration.
0889: *
0890: * @param name Name of the resources to be found
0891: *
0892: * @exception IOException if an input/output error occurs
0893: */
0894: public Enumeration findResources(String name) throws IOException {
0895:
0896: if (log.isDebugEnabled())
0897: log.debug(" findResources(" + name + ")");
0898:
0899: Vector result = new Vector();
0900:
0901: int jarFilesLength = jarFiles.length;
0902: int repositoriesLength = repositories.length;
0903:
0904: int i;
0905:
0906: // Looking at the repositories
0907: for (i = 0; i < repositoriesLength; i++) {
0908: try {
0909: String fullPath = repositories[i] + name;
0910: resources.lookup(fullPath);
0911: // Note : Not getting an exception here means the resource was
0912: // found
0913: try {
0914: result.addElement(getURI(new File(files[i], name)));
0915: } catch (MalformedURLException e) {
0916: // Ignore
0917: }
0918: } catch (NamingException e) {
0919: }
0920: }
0921:
0922: // Looking at the JAR files
0923: synchronized (jarFiles) {
0924: openJARs();
0925: for (i = 0; i < jarFilesLength; i++) {
0926: JarEntry jarEntry = jarFiles[i].getJarEntry(name);
0927: if (jarEntry != null) {
0928: try {
0929: String jarFakeUrl = getURI(jarRealFiles[i])
0930: .toString();
0931: jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
0932: result.addElement(new URL(jarFakeUrl));
0933: } catch (MalformedURLException e) {
0934: // Ignore
0935: }
0936: }
0937: }
0938: }
0939:
0940: // Adding the results of a call to the superclass
0941: if (hasExternalRepositories) {
0942:
0943: Enumeration otherResourcePaths = super .findResources(name);
0944:
0945: while (otherResourcePaths.hasMoreElements()) {
0946: result.addElement(otherResourcePaths.nextElement());
0947: }
0948:
0949: }
0950:
0951: return result.elements();
0952:
0953: }
0954:
0955: /**
0956: * Find the resource with the given name. A resource is some data
0957: * (images, audio, text, etc.) that can be accessed by class code in a
0958: * way that is independent of the location of the code. The name of a
0959: * resource is a "/"-separated path name that identifies the resource.
0960: * If the resource cannot be found, return <code>null</code>.
0961: * <p>
0962: * This method searches according to the following algorithm, returning
0963: * as soon as it finds the appropriate URL. If the resource cannot be
0964: * found, returns <code>null</code>.
0965: * <ul>
0966: * <li>If the <code>delegate</code> property is set to <code>true</code>,
0967: * call the <code>getResource()</code> method of the parent class
0968: * loader, if any.</li>
0969: * <li>Call <code>findResource()</code> to find this resource in our
0970: * locally defined repositories.</li>
0971: * <li>Call the <code>getResource()</code> method of the parent class
0972: * loader, if any.</li>
0973: * </ul>
0974: *
0975: * @param name Name of the resource to return a URL for
0976: */
0977: public URL getResource(String name) {
0978:
0979: if (log.isDebugEnabled())
0980: log.debug("getResource(" + name + ")");
0981: URL url = null;
0982:
0983: // (1) Delegate to parent if requested
0984: if (delegate) {
0985: if (log.isDebugEnabled())
0986: log.debug(" Delegating to parent classloader "
0987: + parent);
0988: ClassLoader loader = parent;
0989: if (loader == null)
0990: loader = system;
0991: url = loader.getResource(name);
0992: if (url != null) {
0993: if (log.isDebugEnabled())
0994: log.debug(" --> Returning '" + url.toString()
0995: + "'");
0996: return (url);
0997: }
0998: }
0999:
1000: // (2) Search local repositories
1001: url = findResource(name);
1002: if (url != null) {
1003: // Locating the repository for special handling in the case
1004: // of a JAR
1005: ResourceEntry entry = (ResourceEntry) resourceEntries
1006: .get(name);
1007: try {
1008: String repository = entry.codeBase.toString();
1009: if ((repository.endsWith(".jar"))
1010: && (!(name.endsWith(".class")))) {
1011: // Copy binary content to the work directory if not present
1012: File resourceFile = new File(loaderDir, name);
1013: url = resourceFile.toURL();
1014: }
1015: } catch (Exception e) {
1016: // Ignore
1017: }
1018: if (log.isDebugEnabled())
1019: log.debug(" --> Returning '" + url.toString() + "'");
1020: return (url);
1021: }
1022:
1023: // (3) Delegate to parent unconditionally if not already attempted
1024: if (!delegate) {
1025: ClassLoader loader = parent;
1026: if (loader == null)
1027: loader = system;
1028: url = loader.getResource(name);
1029: if (url != null) {
1030: if (log.isDebugEnabled())
1031: log.debug(" --> Returning '" + url.toString()
1032: + "'");
1033: return (url);
1034: }
1035: }
1036:
1037: // (4) Resource was not found
1038: if (log.isDebugEnabled())
1039: log.debug(" --> Resource not found, returning null");
1040: return (null);
1041:
1042: }
1043:
1044: /**
1045: * Find the resource with the given name, and return an input stream
1046: * that can be used for reading it. The search order is as described
1047: * for <code>getResource()</code>, after checking to see if the resource
1048: * data has been previously cached. If the resource cannot be found,
1049: * return <code>null</code>.
1050: *
1051: * @param name Name of the resource to return an input stream for
1052: */
1053: public InputStream getResourceAsStream(String name) {
1054:
1055: if (log.isDebugEnabled())
1056: log.debug("getResourceAsStream(" + name + ")");
1057: InputStream stream = null;
1058:
1059: // (0) Check for a cached copy of this resource
1060: stream = findLoadedResource(name);
1061: if (stream != null) {
1062: if (log.isDebugEnabled())
1063: log.debug(" --> Returning stream from cache");
1064: return (stream);
1065: }
1066:
1067: // (1) Delegate to parent if requested
1068: if (delegate) {
1069: if (log.isDebugEnabled())
1070: log.debug(" Delegating to parent classloader "
1071: + parent);
1072: ClassLoader loader = parent;
1073: if (loader == null)
1074: loader = system;
1075: stream = loader.getResourceAsStream(name);
1076: if (stream != null) {
1077: // FIXME - cache???
1078: if (log.isDebugEnabled())
1079: log.debug(" --> Returning stream from parent");
1080: return (stream);
1081: }
1082: }
1083:
1084: // (2) Search local repositories
1085: if (log.isDebugEnabled())
1086: log.debug(" Searching local repositories");
1087: URL url = findResource(name);
1088: if (url != null) {
1089: // FIXME - cache???
1090: if (log.isDebugEnabled())
1091: log.debug(" --> Returning stream from local");
1092: stream = findLoadedResource(name);
1093: try {
1094: if (hasExternalRepositories && (stream == null))
1095: stream = url.openStream();
1096: } catch (IOException e) {
1097: ; // Ignore
1098: }
1099: if (stream != null)
1100: return (stream);
1101: }
1102:
1103: // (3) Delegate to parent unconditionally
1104: if (!delegate) {
1105: if (log.isDebugEnabled())
1106: log
1107: .debug(" Delegating to parent classloader unconditionally "
1108: + parent);
1109: ClassLoader loader = parent;
1110: if (loader == null)
1111: loader = system;
1112: stream = loader.getResourceAsStream(name);
1113: if (stream != null) {
1114: // FIXME - cache???
1115: if (log.isDebugEnabled())
1116: log.debug(" --> Returning stream from parent");
1117: return (stream);
1118: }
1119: }
1120:
1121: // (4) Resource was not found
1122: if (log.isDebugEnabled())
1123: log.debug(" --> Resource not found, returning null");
1124: return (null);
1125:
1126: }
1127:
1128: /**
1129: * Load the class with the specified name. This method searches for
1130: * classes in the same manner as <code>loadClass(String, boolean)</code>
1131: * with <code>false</code> as the second argument.
1132: *
1133: * @param name Name of the class to be loaded
1134: *
1135: * @exception ClassNotFoundException if the class was not found
1136: */
1137: public Class loadClass(String name) throws ClassNotFoundException {
1138:
1139: return (loadClass(name, false));
1140:
1141: }
1142:
1143: /**
1144: * Load the class with the specified name, searching using the following
1145: * algorithm until it finds and returns the class. If the class cannot
1146: * be found, returns <code>ClassNotFoundException</code>.
1147: * <ul>
1148: * <li>Call <code>findLoadedClass(String)</code> to check if the
1149: * class has already been loaded. If it has, the same
1150: * <code>Class</code> object is returned.</li>
1151: * <li>If the <code>delegate</code> property is set to <code>true</code>,
1152: * call the <code>loadClass()</code> method of the parent class
1153: * loader, if any.</li>
1154: * <li>Call <code>findClass()</code> to find this class in our locally
1155: * defined repositories.</li>
1156: * <li>Call the <code>loadClass()</code> method of our parent
1157: * class loader, if any.</li>
1158: * </ul>
1159: * If the class was found using the above steps, and the
1160: * <code>resolve</code> flag is <code>true</code>, this method will then
1161: * call <code>resolveClass(Class)</code> on the resulting Class object.
1162: *
1163: * @param name Name of the class to be loaded
1164: * @param resolve If <code>true</code> then resolve the class
1165: *
1166: * @exception ClassNotFoundException if the class was not found
1167: */
1168: public Class loadClass(String name, boolean resolve)
1169: throws ClassNotFoundException {
1170:
1171: if (log.isDebugEnabled())
1172: log.debug("loadClass(" + name + ", " + resolve + ")");
1173: Class clazz = null;
1174:
1175: // Don't load classes if class loader is stopped
1176: if (!started) {
1177: log.info(sm.getString("webappClassLoader.stopped"));
1178: throw new ThreadDeath();
1179: }
1180:
1181: // (0) Check our previously loaded local class cache
1182: clazz = findLoadedClass0(name);
1183: if (clazz != null) {
1184: if (log.isDebugEnabled())
1185: log.debug(" Returning class from cache");
1186: if (resolve)
1187: resolveClass(clazz);
1188: return (clazz);
1189: }
1190:
1191: // (0.1) Check our previously loaded class cache
1192: clazz = findLoadedClass(name);
1193: if (clazz != null) {
1194: if (log.isDebugEnabled())
1195: log.debug(" Returning class from cache");
1196: if (resolve)
1197: resolveClass(clazz);
1198: return (clazz);
1199: }
1200:
1201: // (0.2) Try loading the class with the system class loader, to prevent
1202: // the webapp from overriding J2SE classes
1203: try {
1204: clazz = system.loadClass(name);
1205: if (clazz != null) {
1206: if (resolve)
1207: resolveClass(clazz);
1208: return (clazz);
1209: }
1210: } catch (ClassNotFoundException e) {
1211: // Ignore
1212: }
1213:
1214: // (0.5) Permission to access this class when using a SecurityManager
1215: if (securityManager != null) {
1216: int i = name.lastIndexOf('.');
1217: if (i >= 0) {
1218: try {
1219: securityManager.checkPackageAccess(name.substring(
1220: 0, i));
1221: } catch (SecurityException se) {
1222: String error = "Security Violation, attempt to use "
1223: + "Restricted Class: " + name;
1224: log.info(error, se);
1225: throw new ClassNotFoundException(error, se);
1226: }
1227: }
1228: }
1229:
1230: boolean delegateLoad = delegate || filter(name);
1231:
1232: // (1) Delegate to our parent if requested
1233: if (delegateLoad) {
1234: if (log.isDebugEnabled())
1235: log.debug(" Delegating to parent classloader1 "
1236: + parent);
1237: ClassLoader loader = parent;
1238: if (loader == null)
1239: loader = system;
1240: try {
1241: clazz = loader.loadClass(name);
1242: if (clazz != null) {
1243: if (log.isDebugEnabled())
1244: log.debug(" Loading class from parent");
1245: if (resolve)
1246: resolveClass(clazz);
1247: return (clazz);
1248: }
1249: } catch (ClassNotFoundException e) {
1250: ;
1251: }
1252: }
1253:
1254: // (2) Search local repositories
1255: if (log.isDebugEnabled())
1256: log.debug(" Searching local repositories");
1257: try {
1258: clazz = findClass(name);
1259: if (clazz != null) {
1260: if (log.isDebugEnabled())
1261: log.debug(" Loading class from local repository");
1262: if (resolve)
1263: resolveClass(clazz);
1264: return (clazz);
1265: }
1266: } catch (ClassNotFoundException e) {
1267: ;
1268: }
1269:
1270: // (3) Delegate to parent unconditionally
1271: if (!delegateLoad) {
1272: if (log.isDebugEnabled())
1273: log.debug(" Delegating to parent classloader at end: "
1274: + parent);
1275: ClassLoader loader = parent;
1276: if (loader == null)
1277: loader = system;
1278: try {
1279: clazz = loader.loadClass(name);
1280: if (clazz != null) {
1281: if (log.isDebugEnabled())
1282: log.debug(" Loading class from parent");
1283: if (resolve)
1284: resolveClass(clazz);
1285: return (clazz);
1286: }
1287: } catch (ClassNotFoundException e) {
1288: ;
1289: }
1290: }
1291:
1292: throw new ClassNotFoundException(name);
1293: }
1294:
1295: /**
1296: * Get the Permissions for a CodeSource. If this instance
1297: * of WebappClassLoader is for a web application context,
1298: * add read FilePermission or JndiPermissions for the base
1299: * directory (if unpacked),
1300: * the context URL, and jar file resources.
1301: *
1302: * @param codeSource where the code was loaded from
1303: * @return PermissionCollection for CodeSource
1304: */
1305: protected PermissionCollection getPermissions(CodeSource codeSource) {
1306:
1307: String codeUrl = codeSource.getLocation().toString();
1308: PermissionCollection pc;
1309: if ((pc = (PermissionCollection) loaderPC.get(codeUrl)) == null) {
1310: pc = super .getPermissions(codeSource);
1311: if (pc != null) {
1312: Iterator perms = permissionList.iterator();
1313: while (perms.hasNext()) {
1314: Permission p = (Permission) perms.next();
1315: pc.add(p);
1316: }
1317: loaderPC.put(codeUrl, pc);
1318: }
1319: }
1320: return (pc);
1321:
1322: }
1323:
1324: /**
1325: * Returns the search path of URLs for loading classes and resources.
1326: * This includes the original list of URLs specified to the constructor,
1327: * along with any URLs subsequently appended by the addURL() method.
1328: * @return the search path of URLs for loading classes and resources.
1329: */
1330: public URL[] getURLs() {
1331:
1332: if (repositoryURLs != null) {
1333: return repositoryURLs;
1334: }
1335:
1336: URL[] external = super .getURLs();
1337:
1338: int filesLength = files.length;
1339: int jarFilesLength = jarRealFiles.length;
1340: int length = filesLength + jarFilesLength + external.length;
1341: int i;
1342:
1343: try {
1344:
1345: URL[] urls = new URL[length];
1346: for (i = 0; i < length; i++) {
1347: if (i < filesLength) {
1348: urls[i] = getURL(files[i]);
1349: } else if (i < filesLength + jarFilesLength) {
1350: urls[i] = getURL(jarRealFiles[i - filesLength]);
1351: } else {
1352: urls[i] = external[i - filesLength - jarFilesLength];
1353: }
1354: }
1355:
1356: repositoryURLs = urls;
1357:
1358: } catch (MalformedURLException e) {
1359: repositoryURLs = new URL[0];
1360: }
1361:
1362: return repositoryURLs;
1363:
1364: }
1365:
1366: // ------------------------------------------------------ Lifecycle Methods
1367:
1368: /**
1369: * Add a lifecycle event listener to this component.
1370: *
1371: * @param listener The listener to add
1372: */
1373: public void addLifecycleListener(LifecycleListener listener) {
1374: }
1375:
1376: /**
1377: * Get the lifecycle listeners associated with this lifecycle. If this
1378: * Lifecycle has no listeners registered, a zero-length array is returned.
1379: */
1380: public LifecycleListener[] findLifecycleListeners() {
1381: return new LifecycleListener[0];
1382: }
1383:
1384: /**
1385: * Remove a lifecycle event listener from this component.
1386: *
1387: * @param listener The listener to remove
1388: */
1389: public void removeLifecycleListener(LifecycleListener listener) {
1390: }
1391:
1392: /**
1393: * Start the class loader.
1394: *
1395: * @exception LifecycleException if a lifecycle error occurs
1396: */
1397: public void start() throws LifecycleException {
1398:
1399: started = true;
1400:
1401: }
1402:
1403: /**
1404: * Stop the class loader.
1405: *
1406: * @exception LifecycleException if a lifecycle error occurs
1407: */
1408: public void stop() throws LifecycleException {
1409:
1410: started = false;
1411:
1412: int length = files.length;
1413: for (int i = 0; i < length; i++) {
1414: files[i] = null;
1415: }
1416:
1417: length = jarFiles.length;
1418: for (int i = 0; i < length; i++) {
1419: try {
1420: if (jarFiles[i] != null) {
1421: jarFiles[i].close();
1422: }
1423: } catch (IOException e) {
1424: // Ignore
1425: }
1426: jarFiles[i] = null;
1427: }
1428:
1429: notFoundResources.clear();
1430: resourceEntries.clear();
1431: resources = null;
1432: repositories = null;
1433: repositoryURLs = null;
1434: files = null;
1435: jarFiles = null;
1436: jarRealFiles = null;
1437: jarPath = null;
1438: jarNames = null;
1439: lastModifiedDates = null;
1440: paths = null;
1441: hasExternalRepositories = false;
1442: parent = null;
1443:
1444: permissionList.clear();
1445: loaderPC.clear();
1446:
1447: if (loaderDir != null) {
1448: deleteDir(loaderDir);
1449: }
1450:
1451: org.apache.commons.logging.LogFactory.release(this );
1452:
1453: }
1454:
1455: /**
1456: * Used to periodically signal to the classloader to release
1457: * JAR resources.
1458: */
1459: public void closeJARs(boolean force) {
1460: if (jarFiles.length > 0) {
1461: try {
1462: synchronized (jarFiles) {
1463: if (force
1464: || (System.currentTimeMillis() > (lastJarAccessed + 90000))) {
1465: for (int i = 0; i < jarFiles.length; i++) {
1466: if (jarFiles[i] != null) {
1467: jarFiles[i].close();
1468: jarFiles[i] = null;
1469: }
1470: }
1471: }
1472: }
1473: } catch (IOException e) {
1474: log("Failed to close JAR", e);
1475: }
1476: }
1477: }
1478:
1479: // ------------------------------------------------------ Protected Methods
1480:
1481: /**
1482: * Used to periodically signal to the classloader to release JAR resources.
1483: */
1484: protected void openJARs() {
1485: if (started && (jarFiles.length > 0)) {
1486: lastJarAccessed = System.currentTimeMillis();
1487: if (jarFiles[0] == null) {
1488: try {
1489: for (int i = 0; i < jarFiles.length; i++) {
1490: jarFiles[i] = new JarFile(jarRealFiles[i]);
1491: }
1492: } catch (IOException e) {
1493: log("Failed to open JAR", e);
1494: }
1495: }
1496: }
1497: }
1498:
1499: /**
1500: * Find specified class in local repositories.
1501: *
1502: * @return the loaded class, or null if the class isn't found
1503: */
1504: protected Class findClassInternal(String name)
1505: throws ClassNotFoundException {
1506:
1507: if (!validate(name))
1508: throw new ClassNotFoundException(name);
1509:
1510: String tempPath = name.replace('.', '/');
1511: String classPath = tempPath + ".class";
1512:
1513: ResourceEntry entry = null;
1514:
1515: entry = findResourceInternal(name, classPath);
1516:
1517: if ((entry == null) || (entry.binaryContent == null))
1518: throw new ClassNotFoundException(name);
1519:
1520: Class clazz = entry.loadedClass;
1521: if (clazz != null)
1522: return clazz;
1523:
1524: // Looking up the package
1525: String packageName = null;
1526: int pos = name.lastIndexOf('.');
1527: if (pos != -1)
1528: packageName = name.substring(0, pos);
1529:
1530: Package pkg = null;
1531:
1532: if (packageName != null) {
1533:
1534: pkg = getPackage(packageName);
1535:
1536: // Define the package (if null)
1537: if (pkg == null) {
1538: if (entry.manifest == null) {
1539: definePackage(packageName, null, null, null, null,
1540: null, null, null);
1541: } else {
1542: definePackage(packageName, entry.manifest,
1543: entry.codeBase);
1544: }
1545: }
1546:
1547: }
1548:
1549: // Create the code source object
1550: CodeSource codeSource = new CodeSource(entry.codeBase,
1551: entry.certificates);
1552:
1553: if (securityManager != null) {
1554:
1555: // Checking sealing
1556: if (pkg != null) {
1557: boolean sealCheck = true;
1558: if (pkg.isSealed()) {
1559: sealCheck = pkg.isSealed(entry.codeBase);
1560: } else {
1561: sealCheck = (entry.manifest == null)
1562: || !isPackageSealed(packageName,
1563: entry.manifest);
1564: }
1565: if (!sealCheck)
1566: throw new SecurityException(
1567: "Sealing violation loading " + name
1568: + " : Package " + packageName
1569: + " is sealed.");
1570: }
1571:
1572: }
1573:
1574: if (entry.loadedClass == null) {
1575: synchronized (this ) {
1576: if (entry.loadedClass == null) {
1577: clazz = defineClass(name, entry.binaryContent, 0,
1578: entry.binaryContent.length, codeSource);
1579: entry.loadedClass = clazz;
1580: entry.binaryContent = null;
1581: entry.source = null;
1582: entry.codeBase = null;
1583: entry.manifest = null;
1584: entry.certificates = null;
1585: } else {
1586: clazz = entry.loadedClass;
1587: }
1588: }
1589: } else {
1590: clazz = entry.loadedClass;
1591: }
1592:
1593: return clazz;
1594:
1595: }
1596:
1597: /**
1598: * Find specified resource in local repositories. This block
1599: * will execute under an AccessControl.doPrivilege block.
1600: *
1601: * @return the loaded resource, or null if the resource isn't found
1602: */
1603: private ResourceEntry findResourceInternal(File file, String path) {
1604: ResourceEntry entry = new ResourceEntry();
1605: try {
1606: entry.source = getURI(new File(file, path));
1607: entry.codeBase = getURL(new File(file, path));
1608: } catch (MalformedURLException e) {
1609: return null;
1610: }
1611: return entry;
1612: }
1613:
1614: /**
1615: * Find specified resource in local repositories.
1616: *
1617: * @return the loaded resource, or null if the resource isn't found
1618: */
1619: protected ResourceEntry findResourceInternal(String name,
1620: String path) {
1621:
1622: if (!started) {
1623: log.info(sm.getString("webappClassLoader.stopped"));
1624: return null;
1625: }
1626:
1627: if ((name == null) || (path == null))
1628: return null;
1629:
1630: ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
1631: if (entry != null)
1632: return entry;
1633:
1634: int contentLength = -1;
1635: InputStream binaryStream = null;
1636:
1637: int jarFilesLength = jarFiles.length;
1638: int repositoriesLength = repositories.length;
1639:
1640: int i;
1641:
1642: Resource resource = null;
1643:
1644: for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
1645: try {
1646:
1647: String fullPath = repositories[i] + path;
1648:
1649: Object lookupResult = resources.lookup(fullPath);
1650: if (lookupResult instanceof Resource) {
1651: resource = (Resource) lookupResult;
1652: }
1653:
1654: // Note : Not getting an exception here means the resource was
1655: // found
1656: if (securityManager != null) {
1657: PrivilegedAction dp = new PrivilegedFindResource(
1658: files[i], path);
1659: entry = (ResourceEntry) AccessController
1660: .doPrivileged(dp);
1661: } else {
1662: entry = findResourceInternal(files[i], path);
1663: }
1664:
1665: ResourceAttributes attributes = (ResourceAttributes) resources
1666: .getAttributes(fullPath);
1667: contentLength = (int) attributes.getContentLength();
1668: entry.lastModified = attributes.getLastModified();
1669:
1670: if (resource != null) {
1671:
1672: try {
1673: binaryStream = resource.streamContent();
1674: } catch (IOException e) {
1675: return null;
1676: }
1677:
1678: // Register the full path for modification checking
1679: // Note: Only syncing on a 'constant' object is needed
1680: synchronized (allPermission) {
1681:
1682: int j;
1683:
1684: long[] result2 = new long[lastModifiedDates.length + 1];
1685: for (j = 0; j < lastModifiedDates.length; j++) {
1686: result2[j] = lastModifiedDates[j];
1687: }
1688: result2[lastModifiedDates.length] = entry.lastModified;
1689: lastModifiedDates = result2;
1690:
1691: String[] result = new String[paths.length + 1];
1692: for (j = 0; j < paths.length; j++) {
1693: result[j] = paths[j];
1694: }
1695: result[paths.length] = fullPath;
1696: paths = result;
1697:
1698: }
1699:
1700: }
1701:
1702: } catch (NamingException e) {
1703: }
1704: }
1705:
1706: if ((entry == null) && (notFoundResources.containsKey(name)))
1707: return null;
1708:
1709: JarEntry jarEntry = null;
1710:
1711: synchronized (jarFiles) {
1712:
1713: openJARs();
1714: for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
1715:
1716: jarEntry = jarFiles[i].getJarEntry(path);
1717:
1718: if (jarEntry != null) {
1719:
1720: entry = new ResourceEntry();
1721: try {
1722: entry.codeBase = getURL(jarRealFiles[i]);
1723: String jarFakeUrl = getURI(jarRealFiles[i])
1724: .toString();
1725: jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
1726: entry.source = new URL(jarFakeUrl);
1727: entry.lastModified = jarRealFiles[i]
1728: .lastModified();
1729: } catch (MalformedURLException e) {
1730: return null;
1731: }
1732: contentLength = (int) jarEntry.getSize();
1733: try {
1734: entry.manifest = jarFiles[i].getManifest();
1735: binaryStream = jarFiles[i]
1736: .getInputStream(jarEntry);
1737: } catch (IOException e) {
1738: return null;
1739: }
1740:
1741: // Extract resources contained in JAR to the workdir
1742: if (!(path.endsWith(".class"))) {
1743: byte[] buf = new byte[1024];
1744: File resourceFile = new File(loaderDir,
1745: jarEntry.getName());
1746: if (!resourceFile.exists()) {
1747: Enumeration entries = jarFiles[i].entries();
1748: while (entries.hasMoreElements()) {
1749: JarEntry jarEntry2 = (JarEntry) entries
1750: .nextElement();
1751: if (!(jarEntry2.isDirectory())
1752: && (!jarEntry2.getName()
1753: .endsWith(".class"))) {
1754: resourceFile = new File(loaderDir,
1755: jarEntry2.getName());
1756: resourceFile.getParentFile()
1757: .mkdirs();
1758: FileOutputStream os = null;
1759: InputStream is = null;
1760: try {
1761: is = jarFiles[i]
1762: .getInputStream(jarEntry2);
1763: os = new FileOutputStream(
1764: resourceFile);
1765: while (true) {
1766: int n = is.read(buf);
1767: if (n <= 0) {
1768: break;
1769: }
1770: os.write(buf, 0, n);
1771: }
1772: } catch (IOException e) {
1773: // Ignore
1774: } finally {
1775: try {
1776: if (is != null) {
1777: is.close();
1778: }
1779: } catch (IOException e) {
1780: }
1781: try {
1782: if (os != null) {
1783: os.close();
1784: }
1785: } catch (IOException e) {
1786: }
1787: }
1788: }
1789: }
1790: }
1791: }
1792:
1793: }
1794:
1795: }
1796:
1797: if (entry == null) {
1798: synchronized (notFoundResources) {
1799: notFoundResources.put(name, name);
1800: }
1801: return null;
1802: }
1803:
1804: if (binaryStream != null) {
1805:
1806: byte[] binaryContent = new byte[contentLength];
1807:
1808: try {
1809: int pos = 0;
1810:
1811: while (true) {
1812: int n = binaryStream.read(binaryContent, pos,
1813: binaryContent.length - pos);
1814: if (n <= 0)
1815: break;
1816: pos += n;
1817: }
1818: binaryStream.close();
1819: } catch (IOException e) {
1820: e.printStackTrace();
1821: return null;
1822: } catch (Exception e) {
1823: e.printStackTrace();
1824: return null;
1825: }
1826:
1827: entry.binaryContent = binaryContent;
1828:
1829: // The certificates are only available after the JarEntry
1830: // associated input stream has been fully read
1831: if (jarEntry != null) {
1832: entry.certificates = jarEntry.getCertificates();
1833: }
1834:
1835: }
1836:
1837: }
1838:
1839: // Add the entry in the local resource repository
1840: synchronized (resourceEntries) {
1841: // Ensures that all the threads which may be in a race to load
1842: // a particular class all end up with the same ResourceEntry
1843: // instance
1844: ResourceEntry entry2 = (ResourceEntry) resourceEntries
1845: .get(name);
1846: if (entry2 == null) {
1847: resourceEntries.put(name, entry);
1848: } else {
1849: entry = entry2;
1850: }
1851: }
1852:
1853: return entry;
1854:
1855: }
1856:
1857: /**
1858: * Returns true if the specified package name is sealed according to the
1859: * given manifest.
1860: */
1861: protected boolean isPackageSealed(String name, Manifest man) {
1862:
1863: String path = name + "/";
1864: Attributes attr = man.getAttributes(path);
1865: String sealed = null;
1866: if (attr != null) {
1867: sealed = attr.getValue(Name.SEALED);
1868: }
1869: if (sealed == null) {
1870: if ((attr = man.getMainAttributes()) != null) {
1871: sealed = attr.getValue(Name.SEALED);
1872: }
1873: }
1874: return "true".equalsIgnoreCase(sealed);
1875:
1876: }
1877:
1878: /**
1879: * Finds the resource with the given name if it has previously been
1880: * loaded and cached by this class loader, and return an input stream
1881: * to the resource data. If this resource has not been cached, return
1882: * <code>null</code>.
1883: *
1884: * @param name Name of the resource to return
1885: */
1886: protected InputStream findLoadedResource(String name) {
1887:
1888: ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
1889: if (entry != null) {
1890: if (entry.binaryContent != null)
1891: return new ByteArrayInputStream(entry.binaryContent);
1892: }
1893: return (null);
1894:
1895: }
1896:
1897: /**
1898: * Finds the class with the given name if it has previously been
1899: * loaded and cached by this class loader, and return the Class object.
1900: * If this class has not been cached, return <code>null</code>.
1901: *
1902: * @param name Name of the resource to return
1903: */
1904: protected Class findLoadedClass0(String name) {
1905:
1906: ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
1907: if (entry != null) {
1908: return entry.loadedClass;
1909: }
1910: return (null); // FIXME - findLoadedResource()
1911:
1912: }
1913:
1914: /**
1915: * Refresh the system policy file, to pick up eventual changes.
1916: */
1917: protected void refreshPolicy() {
1918:
1919: try {
1920: // The policy file may have been modified to adjust
1921: // permissions, so we're reloading it when loading or
1922: // reloading a Context
1923: Policy policy = Policy.getPolicy();
1924: policy.refresh();
1925: } catch (AccessControlException e) {
1926: // Some policy files may restrict this, even for the core,
1927: // so this exception is ignored
1928: }
1929:
1930: }
1931:
1932: /**
1933: * Filter classes.
1934: *
1935: * @param name class name
1936: * @return true if the class should be filtered
1937: */
1938: protected boolean filter(String name) {
1939:
1940: if (name == null)
1941: return false;
1942:
1943: // Looking up the package
1944: String packageName = null;
1945: int pos = name.lastIndexOf('.');
1946: if (pos != -1)
1947: packageName = name.substring(0, pos);
1948: else
1949: return false;
1950:
1951: for (int i = 0; i < packageTriggers.length; i++) {
1952: if (packageName.startsWith(packageTriggers[i]))
1953: return true;
1954: }
1955:
1956: return false;
1957:
1958: }
1959:
1960: /**
1961: * Validate a classname. As per SRV.9.7.2, we must restict loading of
1962: * classes from J2SE (java.*) and classes of the servlet API
1963: * (javax.servlet.*). That should enhance robustness and prevent a number
1964: * of user error (where an older version of servlet.jar would be present
1965: * in /WEB-INF/lib).
1966: *
1967: * @param name class name
1968: * @return true if the name is valid
1969: */
1970: protected boolean validate(String name) {
1971:
1972: if (name == null)
1973: return false;
1974: if (name.startsWith("java."))
1975: return false;
1976:
1977: return true;
1978:
1979: }
1980:
1981: /**
1982: * Check the specified JAR file, and return <code>true</code> if it does
1983: * not contain any of the trigger classes.
1984: *
1985: * @param jarfile The JAR file to be checked
1986: *
1987: * @exception IOException if an input/output error occurs
1988: */
1989: private boolean validateJarFile(File jarfile) throws IOException {
1990:
1991: if (triggers == null)
1992: return (true);
1993: JarFile jarFile = new JarFile(jarfile);
1994: for (int i = 0; i < triggers.length; i++) {
1995: Class clazz = null;
1996: try {
1997: if (parent != null) {
1998: clazz = parent.loadClass(triggers[i]);
1999: } else {
2000: clazz = Class.forName(triggers[i]);
2001: }
2002: } catch (Throwable t) {
2003: clazz = null;
2004: }
2005: if (clazz == null)
2006: continue;
2007: String name = triggers[i].replace('.', '/') + ".class";
2008: if (log.isDebugEnabled())
2009: log.debug(" Checking for " + name);
2010: JarEntry jarEntry = jarFile.getJarEntry(name);
2011: if (jarEntry != null) {
2012: log.info("validateJarFile(" + jarfile
2013: + ") - jar not loaded. See Servlet Spec 2.3, "
2014: + "section 9.7.2. Offending class: " + name);
2015: jarFile.close();
2016: return (false);
2017: }
2018: }
2019: jarFile.close();
2020: return (true);
2021:
2022: }
2023:
2024: /**
2025: * Get URL.
2026: */
2027: protected URL getURL(File file) throws MalformedURLException {
2028:
2029: File realFile = file;
2030: try {
2031: realFile = realFile.getCanonicalFile();
2032: } catch (IOException e) {
2033: // Ignore
2034: }
2035: return realFile.toURL();
2036:
2037: }
2038:
2039: /**
2040: * Get URL.
2041: */
2042: protected URL getURI(File file) throws MalformedURLException {
2043:
2044: return jdkCompat.getURI(file);
2045:
2046: }
2047:
2048: /**
2049: * Delete the specified directory, including all of its contents and
2050: * subdirectories recursively.
2051: *
2052: * @param dir File object representing the directory to be deleted
2053: */
2054: protected static void deleteDir(File dir) {
2055:
2056: String files[] = dir.list();
2057: if (files == null) {
2058: files = new String[0];
2059: }
2060: for (int i = 0; i < files.length; i++) {
2061: File file = new File(dir, files[i]);
2062: if (file.isDirectory()) {
2063: deleteDir(file);
2064: } else {
2065: file.delete();
2066: }
2067: }
2068: dir.delete();
2069:
2070: }
2071:
2072: /**
2073: * Log a debugging output message.
2074: *
2075: * @param message Message to be logged
2076: */
2077: private void log(String message) {
2078:
2079: System.out.println("WebappClassLoader: " + message);
2080:
2081: }
2082:
2083: /**
2084: * Log a debugging output message with an exception.
2085: *
2086: * @param message Message to be logged
2087: * @param throwable Exception to be logged
2088: */
2089: private void log(String message, Throwable throwable) {
2090:
2091: System.out.println("WebappClassLoader: " + message);
2092: throwable.printStackTrace(System.out);
2093:
2094: }
2095:
2096: }
|