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