0001: /*
0002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/loader/WebappLoader.java,v 1.28 2002/06/09 01:16:11 remm Exp $
0003: * $Revision: 1.28 $
0004: * $Date: 2002/06/09 01:16:11 $
0005: *
0006: * ====================================================================
0007: *
0008: * The Apache Software License, Version 1.1
0009: *
0010: * Copyright (c) 1999 The Apache Software Foundation. All rights
0011: * reserved.
0012: *
0013: * Redistribution and use in source and binary forms, with or without
0014: * modification, are permitted provided that the following conditions
0015: * are met:
0016: *
0017: * 1. Redistributions of source code must retain the above copyright
0018: * notice, this list of conditions and the following disclaimer.
0019: *
0020: * 2. Redistributions in binary form must reproduce the above copyright
0021: * notice, this list of conditions and the following disclaimer in
0022: * the documentation and/or other materials provided with the
0023: * distribution.
0024: *
0025: * 3. The end-user documentation included with the redistribution, if
0026: * any, must include the following acknowlegement:
0027: * "This product includes software developed by the
0028: * Apache Software Foundation (http://www.apache.org/)."
0029: * Alternately, this acknowlegement may appear in the software itself,
0030: * if and wherever such third-party acknowlegements normally appear.
0031: *
0032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
0033: * Foundation" must not be used to endorse or promote products derived
0034: * from this software without prior written permission. For written
0035: * permission, please contact apache@apache.org.
0036: *
0037: * 5. Products derived from this software may not be called "Apache"
0038: * nor may "Apache" appear in their names without prior written
0039: * permission of the Apache Group.
0040: *
0041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0052: * SUCH DAMAGE.
0053: * ====================================================================
0054: *
0055: * This software consists of voluntary contributions made by many
0056: * individuals on behalf of the Apache Software Foundation. For more
0057: * information on the Apache Software Foundation, please see
0058: * <http://www.apache.org/>.
0059: *
0060: * [Additional notices, if required by prior licensing conditions]
0061: *
0062: */
0063:
0064: package org.apache.catalina.loader;
0065:
0066: import java.beans.PropertyChangeEvent;
0067: import java.beans.PropertyChangeListener;
0068: import java.beans.PropertyChangeSupport;
0069: import java.io.File;
0070: import java.io.IOException;
0071: import java.io.InputStream;
0072: import java.io.FileOutputStream;
0073: import java.io.FilePermission;
0074: import java.io.OutputStream;
0075: import java.lang.reflect.Constructor;
0076: import java.net.MalformedURLException;
0077: import java.net.URL;
0078: import java.net.URLClassLoader;
0079: import java.net.URLStreamHandlerFactory;
0080: import java.security.Permission;
0081: import java.util.jar.JarFile;
0082: import javax.servlet.ServletContext;
0083: import javax.naming.NamingException;
0084: import javax.naming.Binding;
0085: import javax.naming.NameClassPair;
0086: import javax.naming.NamingEnumeration;
0087: import javax.naming.directory.DirContext;
0088: import org.apache.naming.resources.Resource;
0089: import org.apache.naming.resources.DirContextURLStreamHandler;
0090: import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
0091: import org.apache.catalina.Container;
0092: import org.apache.catalina.Context;
0093: import org.apache.catalina.Globals;
0094: import org.apache.catalina.Lifecycle;
0095: import org.apache.catalina.LifecycleEvent;
0096: import org.apache.catalina.LifecycleException;
0097: import org.apache.catalina.LifecycleListener;
0098: import org.apache.catalina.Loader;
0099: import org.apache.catalina.Logger;
0100: import org.apache.catalina.util.LifecycleSupport;
0101: import org.apache.catalina.util.StringManager;
0102:
0103: /**
0104: * Classloader implementation which is specialized for handling web
0105: * applications in the most efficient way, while being Catalina aware (all
0106: * accesses to resources are made through the DirContext interface).
0107: * This class loader supports detection of modified
0108: * Java classes, which can be used to implement auto-reload support.
0109: * <p>
0110: * This class loader is configured by adding the pathnames of directories,
0111: * JAR files, and ZIP files with the <code>addRepository()</code> method,
0112: * prior to calling <code>start()</code>. When a new class is required,
0113: * these repositories will be consulted first to locate the class. If it
0114: * is not present, the system class loader will be used instead.
0115: *
0116: * @author Craig R. McClanahan
0117: * @author Remy Maucherat
0118: * @version $Revision: 1.28 $ $Date: 2002/06/09 01:16:11 $
0119: */
0120:
0121: public class WebappLoader implements Lifecycle, Loader,
0122: PropertyChangeListener, Runnable {
0123:
0124: // ----------------------------------------------------------- Constructors
0125:
0126: /**
0127: * Construct a new WebappLoader with no defined parent class loader
0128: * (so that the actual parent will be the system class loader).
0129: */
0130: public WebappLoader() {
0131:
0132: this (null);
0133:
0134: }
0135:
0136: /**
0137: * Construct a new WebappLoader with the specified class loader
0138: * to be defined as the parent of the ClassLoader we ultimately create.
0139: *
0140: * @param parent The parent class loader
0141: */
0142: public WebappLoader(ClassLoader parent) {
0143:
0144: super ();
0145: this .parentClassLoader = parent;
0146:
0147: }
0148:
0149: // ----------------------------------------------------- Instance Variables
0150:
0151: /**
0152: * The number of seconds between checks for modified classes, if
0153: * automatic reloading is enabled.
0154: */
0155: private int checkInterval = 15;
0156:
0157: /**
0158: * The class loader being managed by this Loader component.
0159: */
0160: private WebappClassLoader classLoader = null;
0161:
0162: /**
0163: * The Container with which this Loader has been associated.
0164: */
0165: private Container container = null;
0166:
0167: /**
0168: * The debugging detail level for this component.
0169: */
0170: private int debug = 0;
0171:
0172: /**
0173: * The "follow standard delegation model" flag that will be used to
0174: * configure our ClassLoader.
0175: */
0176: private boolean delegate = false;
0177:
0178: /**
0179: * The descriptive information about this Loader implementation.
0180: */
0181: private static final String info = "org.apache.catalina.loader.WebappLoader/1.0";
0182:
0183: /**
0184: * The lifecycle event support for this component.
0185: */
0186: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
0187:
0188: /**
0189: * The Java class name of the ClassLoader implementation to be used.
0190: * This class should extend WebappClassLoader, otherwise, a different
0191: * loader implementation must be used.
0192: */
0193: private String loaderClass = "org.apache.catalina.loader.WebappClassLoader";
0194:
0195: /**
0196: * The parent class loader of the class loader we will create.
0197: */
0198: private ClassLoader parentClassLoader = null;
0199:
0200: /**
0201: * The reloadable flag for this Loader.
0202: */
0203: private boolean reloadable = false;
0204:
0205: /**
0206: * The set of repositories associated with this class loader.
0207: */
0208: private String repositories[] = new String[0];
0209:
0210: /**
0211: * The string manager for this package.
0212: */
0213: protected static final StringManager sm = StringManager
0214: .getManager(Constants.Package);
0215:
0216: /**
0217: * Has this component been started?
0218: */
0219: private boolean started = false;
0220:
0221: /**
0222: * The property change support for this component.
0223: */
0224: protected PropertyChangeSupport support = new PropertyChangeSupport(
0225: this );
0226:
0227: /**
0228: * The background thread.
0229: */
0230: private Thread thread = null;
0231:
0232: /**
0233: * The background thread completion semaphore.
0234: */
0235: private boolean threadDone = false;
0236:
0237: /**
0238: * Name to register for the background thread.
0239: */
0240: private String threadName = "WebappLoader";
0241:
0242: // ------------------------------------------------------------- Properties
0243:
0244: /**
0245: * Return the check interval for this Loader.
0246: */
0247: public int getCheckInterval() {
0248:
0249: return (this .checkInterval);
0250:
0251: }
0252:
0253: /**
0254: * Set the check interval for this Loader.
0255: *
0256: * @param checkInterval The new check interval
0257: */
0258: public void setCheckInterval(int checkInterval) {
0259:
0260: int oldCheckInterval = this .checkInterval;
0261: this .checkInterval = checkInterval;
0262: support.firePropertyChange("checkInterval", new Integer(
0263: oldCheckInterval), new Integer(this .checkInterval));
0264:
0265: }
0266:
0267: /**
0268: * Return the Java class loader to be used by this Container.
0269: */
0270: public ClassLoader getClassLoader() {
0271:
0272: return ((ClassLoader) classLoader);
0273:
0274: }
0275:
0276: /**
0277: * Return the Container with which this Logger has been associated.
0278: */
0279: public Container getContainer() {
0280:
0281: return (container);
0282:
0283: }
0284:
0285: /**
0286: * Set the Container with which this Logger has been associated.
0287: *
0288: * @param container The associated Container
0289: */
0290: public void setContainer(Container container) {
0291:
0292: // Deregister from the old Container (if any)
0293: if ((this .container != null)
0294: && (this .container instanceof Context))
0295: ((Context) this .container)
0296: .removePropertyChangeListener(this );
0297:
0298: // Process this property change
0299: Container oldContainer = this .container;
0300: this .container = container;
0301: support.firePropertyChange("container", oldContainer,
0302: this .container);
0303:
0304: // Register with the new Container (if any)
0305: if ((this .container != null)
0306: && (this .container instanceof Context)) {
0307: setReloadable(((Context) this .container).getReloadable());
0308: ((Context) this .container).addPropertyChangeListener(this );
0309: }
0310:
0311: }
0312:
0313: /**
0314: * Return the debugging detail level for this component.
0315: */
0316: public int getDebug() {
0317:
0318: return (this .debug);
0319:
0320: }
0321:
0322: /**
0323: * Set the debugging detail level for this component.
0324: *
0325: * @param debug The new debugging detail level
0326: */
0327: public void setDebug(int debug) {
0328:
0329: int oldDebug = this .debug;
0330: this .debug = debug;
0331: support.firePropertyChange("debug", new Integer(oldDebug),
0332: new Integer(this .debug));
0333:
0334: }
0335:
0336: /**
0337: * Return the "follow standard delegation model" flag used to configure
0338: * our ClassLoader.
0339: */
0340: public boolean getDelegate() {
0341:
0342: return (this .delegate);
0343:
0344: }
0345:
0346: /**
0347: * Set the "follow standard delegation model" flag used to configure
0348: * our ClassLoader.
0349: *
0350: * @param delegate The new flag
0351: */
0352: public void setDelegate(boolean delegate) {
0353:
0354: boolean oldDelegate = this .delegate;
0355: this .delegate = delegate;
0356: support.firePropertyChange("delegate",
0357: new Boolean(oldDelegate), new Boolean(this .delegate));
0358:
0359: }
0360:
0361: /**
0362: * Return descriptive information about this Loader implementation and
0363: * the corresponding version number, in the format
0364: * <code><description>/<version></code>.
0365: */
0366: public String getInfo() {
0367:
0368: return (info);
0369:
0370: }
0371:
0372: /**
0373: * Return the ClassLoader class name.
0374: */
0375: public String getLoaderClass() {
0376:
0377: return (this .loaderClass);
0378:
0379: }
0380:
0381: /**
0382: * Set the ClassLoader class name.
0383: *
0384: * @param loaderClass The new ClassLoader class name
0385: */
0386: public void setLoaderClass(String loaderClass) {
0387:
0388: this .loaderClass = loaderClass;
0389:
0390: }
0391:
0392: /**
0393: * Return the reloadable flag for this Loader.
0394: */
0395: public boolean getReloadable() {
0396:
0397: return (this .reloadable);
0398:
0399: }
0400:
0401: /**
0402: * Set the reloadable flag for this Loader.
0403: *
0404: * @param reloadable The new reloadable flag
0405: */
0406: public void setReloadable(boolean reloadable) {
0407:
0408: // Process this property change
0409: boolean oldReloadable = this .reloadable;
0410: this .reloadable = reloadable;
0411: support.firePropertyChange("reloadable", new Boolean(
0412: oldReloadable), new Boolean(this .reloadable));
0413:
0414: // Start or stop our background thread if required
0415: if (!started)
0416: return;
0417: if (!oldReloadable && this .reloadable)
0418: threadStart();
0419: else if (oldReloadable && !this .reloadable)
0420: threadStop();
0421:
0422: }
0423:
0424: // --------------------------------------------------------- Public Methods
0425:
0426: /**
0427: * Add a property change listener to this component.
0428: *
0429: * @param listener The listener to add
0430: */
0431: public void addPropertyChangeListener(
0432: PropertyChangeListener listener) {
0433:
0434: support.addPropertyChangeListener(listener);
0435:
0436: }
0437:
0438: /**
0439: * Add a new repository to the set of repositories for this class loader.
0440: *
0441: * @param repository Repository to be added
0442: */
0443: public void addRepository(String repository) {
0444:
0445: if (debug >= 1)
0446: log(sm.getString("webappLoader.addRepository", repository));
0447:
0448: for (int i = 0; i < repositories.length; i++) {
0449: if (repository.equals(repositories[i]))
0450: return;
0451: }
0452: String results[] = new String[repositories.length + 1];
0453: for (int i = 0; i < repositories.length; i++)
0454: results[i] = repositories[i];
0455: results[repositories.length] = repository;
0456: repositories = results;
0457:
0458: if (started && (classLoader != null)) {
0459: classLoader.addRepository(repository);
0460: setClassPath();
0461: }
0462:
0463: }
0464:
0465: /**
0466: * Return the set of repositories defined for this class loader.
0467: * If none are defined, a zero-length array is returned.
0468: */
0469: public String[] findRepositories() {
0470:
0471: return (repositories);
0472:
0473: }
0474:
0475: /**
0476: * Has the internal repository associated with this Loader been modified,
0477: * such that the loaded classes should be reloaded?
0478: */
0479: public boolean modified() {
0480:
0481: return (classLoader.modified());
0482:
0483: }
0484:
0485: /**
0486: * Remove a property change listener from this component.
0487: *
0488: * @param listener The listener to remove
0489: */
0490: public void removePropertyChangeListener(
0491: PropertyChangeListener listener) {
0492:
0493: support.removePropertyChangeListener(listener);
0494:
0495: }
0496:
0497: /**
0498: * Return a String representation of this component.
0499: */
0500: public String toString() {
0501:
0502: StringBuffer sb = new StringBuffer("WebappLoader[");
0503: if (container != null)
0504: sb.append(container.getName());
0505: sb.append("]");
0506: return (sb.toString());
0507:
0508: }
0509:
0510: // ------------------------------------------------------ Lifecycle Methods
0511:
0512: /**
0513: * Add a lifecycle event listener to this component.
0514: *
0515: * @param listener The listener to add
0516: */
0517: public void addLifecycleListener(LifecycleListener listener) {
0518:
0519: lifecycle.addLifecycleListener(listener);
0520:
0521: }
0522:
0523: /**
0524: * Get the lifecycle listeners associated with this lifecycle. If this
0525: * Lifecycle has no listeners registered, a zero-length array is returned.
0526: */
0527: public LifecycleListener[] findLifecycleListeners() {
0528:
0529: return lifecycle.findLifecycleListeners();
0530:
0531: }
0532:
0533: /**
0534: * Remove a lifecycle event listener from this component.
0535: *
0536: * @param listener The listener to remove
0537: */
0538: public void removeLifecycleListener(LifecycleListener listener) {
0539:
0540: lifecycle.removeLifecycleListener(listener);
0541:
0542: }
0543:
0544: /**
0545: * Start this component, initializing our associated class loader.
0546: *
0547: * @exception LifecycleException if a lifecycle error occurs
0548: */
0549: public void start() throws LifecycleException {
0550:
0551: // Validate and update our current component state
0552: if (started)
0553: throw new LifecycleException(sm
0554: .getString("webappLoader.alreadyStarted"));
0555: if (debug >= 1)
0556: log(sm.getString("webappLoader.starting"));
0557: lifecycle.fireLifecycleEvent(START_EVENT, null);
0558: started = true;
0559:
0560: if (container.getResources() == null)
0561: return;
0562:
0563: // Register a stream handler factory for the JNDI protocol
0564: URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory();
0565: try {
0566: URL.setURLStreamHandlerFactory(streamHandlerFactory);
0567: } catch (Throwable t) {
0568: // Ignore the error here.
0569: }
0570:
0571: // Construct a class loader based on our current repositories list
0572: try {
0573:
0574: classLoader = createClassLoader();
0575: classLoader.setResources(container.getResources());
0576: classLoader.setDebug(this .debug);
0577: classLoader.setDelegate(this .delegate);
0578:
0579: for (int i = 0; i < repositories.length; i++) {
0580: classLoader.addRepository(repositories[i]);
0581: }
0582:
0583: // Configure our repositories
0584: setRepositories();
0585: setClassPath();
0586:
0587: setPermissions();
0588:
0589: if (classLoader instanceof Lifecycle)
0590: ((Lifecycle) classLoader).start();
0591:
0592: // Binding the Webapp class loader to the directory context
0593: DirContextURLStreamHandler.bind((ClassLoader) classLoader,
0594: this .container.getResources());
0595:
0596: } catch (Throwable t) {
0597: throw new LifecycleException("start: ", t);
0598: }
0599:
0600: // Validate that all required packages are actually available
0601: validatePackages();
0602:
0603: // Start our background thread if we are reloadable
0604: if (reloadable) {
0605: log(sm.getString("webappLoader.reloading"));
0606: try {
0607: threadStart();
0608: } catch (IllegalStateException e) {
0609: throw new LifecycleException(e);
0610: }
0611: }
0612:
0613: }
0614:
0615: /**
0616: * Stop this component, finalizing our associated class loader.
0617: *
0618: * @exception LifecycleException if a lifecycle error occurs
0619: */
0620: public void stop() throws LifecycleException {
0621:
0622: // Validate and update our current component state
0623: if (!started)
0624: throw new LifecycleException(sm
0625: .getString("webappLoader.notStarted"));
0626: if (debug >= 1)
0627: log(sm.getString("webappLoader.stopping"));
0628: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
0629: started = false;
0630:
0631: // Stop our background thread if we are reloadable
0632: if (reloadable)
0633: threadStop();
0634:
0635: // Remove context attributes as appropriate
0636: if (container instanceof Context) {
0637: ServletContext servletContext = ((Context) container)
0638: .getServletContext();
0639: servletContext.removeAttribute(Globals.CLASS_PATH_ATTR);
0640: }
0641:
0642: // Throw away our current class loader
0643: if (classLoader instanceof Lifecycle)
0644: ((Lifecycle) classLoader).stop();
0645: DirContextURLStreamHandler.unbind((ClassLoader) classLoader);
0646: classLoader = null;
0647:
0648: }
0649:
0650: // ----------------------------------------- PropertyChangeListener Methods
0651:
0652: /**
0653: * Process property change events from our associated Context.
0654: *
0655: * @param event The property change event that has occurred
0656: */
0657: public void propertyChange(PropertyChangeEvent event) {
0658:
0659: // Validate the source of this event
0660: if (!(event.getSource() instanceof Context))
0661: return;
0662: Context context = (Context) event.getSource();
0663:
0664: // Process a relevant property change
0665: if (event.getPropertyName().equals("reloadable")) {
0666: try {
0667: setReloadable(((Boolean) event.getNewValue())
0668: .booleanValue());
0669: } catch (NumberFormatException e) {
0670: log(sm.getString("webappLoader.reloadable", event
0671: .getNewValue().toString()));
0672: }
0673: }
0674:
0675: }
0676:
0677: // ------------------------------------------------------- Private Methods
0678:
0679: /**
0680: * Create associated classLoader.
0681: */
0682: private WebappClassLoader createClassLoader() throws Exception {
0683:
0684: Class clazz = Class.forName(loaderClass);
0685: WebappClassLoader classLoader = null;
0686:
0687: if (parentClassLoader == null) {
0688: // Will cause a ClassCast is the class does not extend WCL, but
0689: // this is on purpose (the exception will be caught and rethrown)
0690: classLoader = (WebappClassLoader) clazz.newInstance();
0691: } else {
0692: Class[] argTypes = { ClassLoader.class };
0693: Object[] args = { parentClassLoader };
0694: Constructor constr = clazz.getConstructor(argTypes);
0695: classLoader = (WebappClassLoader) constr.newInstance(args);
0696: }
0697:
0698: return classLoader;
0699:
0700: }
0701:
0702: /**
0703: * Log a message on the Logger associated with our Container (if any)
0704: *
0705: * @param message Message to be logged
0706: */
0707: private void log(String message) {
0708:
0709: Logger logger = null;
0710: if (container != null)
0711: logger = container.getLogger();
0712: if (logger != null)
0713: logger.log("WebappLoader[" + container.getName() + "]: "
0714: + message);
0715: else {
0716: String containerName = null;
0717: if (container != null)
0718: containerName = container.getName();
0719: System.out.println("WebappLoader[" + containerName + "]: "
0720: + message);
0721: }
0722:
0723: }
0724:
0725: /**
0726: * Log a message on the Logger associated with our Container (if any)
0727: *
0728: * @param message Message to be logged
0729: * @param throwable Associated exception
0730: */
0731: private void log(String message, Throwable throwable) {
0732:
0733: Logger logger = null;
0734: if (container != null)
0735: logger = container.getLogger();
0736: if (logger != null) {
0737: logger.log("WebappLoader[" + container.getName() + "] "
0738: + message, throwable);
0739: } else {
0740: String containerName = null;
0741: if (container != null)
0742: containerName = container.getName();
0743: System.out.println("WebappLoader[" + containerName + "]: "
0744: + message);
0745: System.out.println("" + throwable);
0746: throwable.printStackTrace(System.out);
0747: }
0748:
0749: }
0750:
0751: /**
0752: * Notify our Context that a reload is appropriate.
0753: */
0754: private void notifyContext() {
0755:
0756: WebappContextNotifier notifier = new WebappContextNotifier();
0757: (new Thread(notifier)).start();
0758:
0759: }
0760:
0761: /**
0762: * Configure associated class loader permissions.
0763: */
0764: private void setPermissions() {
0765:
0766: if (System.getSecurityManager() == null)
0767: return;
0768: if (!(container instanceof Context))
0769: return;
0770:
0771: // Tell the class loader the root of the context
0772: ServletContext servletContext = ((Context) container)
0773: .getServletContext();
0774:
0775: // Assigning permissions for the work directory
0776: File workDir = (File) servletContext
0777: .getAttribute(Globals.WORK_DIR_ATTR);
0778: if (workDir != null) {
0779: try {
0780: String workDirPath = workDir.getCanonicalPath();
0781: classLoader.addPermission(new FilePermission(
0782: workDirPath, "read,write"));
0783: classLoader.addPermission(new FilePermission(
0784: workDirPath + File.separator + "-",
0785: "read,write,delete"));
0786: } catch (IOException e) {
0787: // Ignore
0788: }
0789: }
0790:
0791: try {
0792:
0793: URL rootURL = servletContext.getResource("/");
0794: classLoader.addPermission(rootURL);
0795:
0796: String contextRoot = servletContext.getRealPath("/");
0797: if (contextRoot != null) {
0798: try {
0799: contextRoot = (new File(contextRoot))
0800: .getCanonicalPath()
0801: + File.separator;
0802: classLoader.addPermission(contextRoot);
0803: } catch (IOException e) {
0804: // Ignore
0805: }
0806: }
0807:
0808: URL classesURL = servletContext
0809: .getResource("/WEB-INF/classes/");
0810: if (classesURL != null)
0811: classLoader.addPermission(classesURL);
0812:
0813: URL libURL = servletContext.getResource("/WEB-INF/lib/");
0814: if (libURL != null) {
0815: classLoader.addPermission(libURL);
0816: }
0817:
0818: if (contextRoot != null) {
0819:
0820: if (libURL != null) {
0821: File rootDir = new File(contextRoot);
0822: File libDir = new File(rootDir, "WEB-INF/lib/");
0823: String path = null;
0824: try {
0825: path = libDir.getCanonicalPath()
0826: + File.separator;
0827: } catch (IOException e) {
0828: }
0829: if (path != null)
0830: classLoader.addPermission(path);
0831: }
0832:
0833: } else {
0834:
0835: if (workDir != null) {
0836: if (libURL != null) {
0837: File libDir = new File(workDir, "WEB-INF/lib/");
0838: String path = null;
0839: try {
0840: path = libDir.getCanonicalPath()
0841: + File.separator;
0842: } catch (IOException e) {
0843: }
0844: classLoader.addPermission(path);
0845: }
0846: if (classesURL != null) {
0847: File classesDir = new File(workDir,
0848: "WEB-INF/classes/");
0849: String path = null;
0850: try {
0851: path = classesDir.getCanonicalPath()
0852: + File.separator;
0853: } catch (IOException e) {
0854: }
0855: classLoader.addPermission(path);
0856: }
0857: }
0858:
0859: }
0860:
0861: } catch (MalformedURLException e) {
0862: }
0863:
0864: }
0865:
0866: /**
0867: * Configure the repositories for our class loader, based on the
0868: * associated Context.
0869: */
0870: private void setRepositories() {
0871:
0872: if (!(container instanceof Context))
0873: return;
0874: ServletContext servletContext =
0875: ((Context) container).getServletContext();
0876: if (servletContext == null)
0877: return;
0878:
0879: // Loading the work directory
0880: File workDir =
0881: (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);
0882: if (workDir == null)
0883: return;
0884:
0885: log(sm.getString("webappLoader.deploy", workDir.getAbsolutePath()));
0886:
0887: DirContext resources = container.getResources();
0888:
0889: // Setting up the class repository (/WEB-INF/classes), if it exists
0890:
0891: String classesPath = "/WEB-INF/classes";
0892: DirContext classes = null;
0893:
0894: try {
0895: Object object = resources.lookup(classesPath);
0896: if (object instanceof DirContext) {
0897: classes = (DirContext) object;
0898: }
0899: } catch(NamingException e) {
0900: // Silent catch: it's valid that no /WEB-INF/classes collection
0901: // exists
0902: }
0903:
0904: if (classes != null) {
0905:
0906: File classRepository = null;
0907:
0908: String absoluteClassesPath =
0909: servletContext.getRealPath(classesPath);
0910:
0911: if (absoluteClassesPath != null) {
0912:
0913: classRepository = new File(absoluteClassesPath);
0914:
0915: } else {
0916:
0917: classRepository = new File(workDir, classesPath);
0918: classRepository.mkdirs();
0919: copyDir(classes, classRepository);
0920:
0921: }
0922:
0923: log(sm.getString("webappLoader.classDeploy", classesPath,
0924: classRepository.getAbsolutePath()));
0925:
0926:
0927: // Adding the repository to the class loader
0928: classLoader.addRepository(classesPath + "/", classRepository);
0929:
0930: }
0931:
0932: // Setting up the JAR repository (/WEB-INF/lib), if it exists
0933:
0934: String libPath = "/WEB-INF/lib";
0935:
0936: classLoader.setJarPath(libPath);
0937:
0938: DirContext libDir = null;
0939: // Looking up directory /WEB-INF/lib in the context
0940: try {
0941: Object object = resources.lookup(libPath);
0942: if (object instanceof DirContext)
0943: libDir = (DirContext) object;
0944: } catch(NamingException e) {
0945: // Silent catch: it's valid that no /WEB-INF/lib collection
0946: // exists
0947: }
0948:
0949: if (libDir != null) {
0950:
0951: boolean copyJars = false;
0952: String absoluteLibPath = servletContext.getRealPath(libPath);
0953:
0954: File destDir = null;
0955:
0956: if (absoluteLibPath != null) {
0957: destDir = new File(absoluteLibPath);
0958: } else {
0959: copyJars = true;
0960: destDir = new File(workDir, libPath);
0961: destDir.mkdirs();
0962: }
0963:
0964: // Looking up directory /WEB-INF/lib in the context
0965: try {
0966: NamingEnumeration enum = resources.listBindings(libPath);
0967: while (enum.hasMoreElements()) {
0968:
0969: Binding binding = (Binding) enum.nextElement();
0970: String filename = libPath + "/" + binding.getName();
0971: if (!filename.endsWith(".jar"))
0972: continue;
0973:
0974: // Copy JAR in the work directory, always (the JAR file
0975: // would get locked otherwise, which would make it
0976: // impossible to update it or remove it at runtime)
0977: File destFile = new File(destDir, binding.getName());
0978:
0979: log(sm.getString("webappLoader.jarDeploy", filename,
0980: destFile.getAbsolutePath()));
0981:
0982: Resource jarResource = (Resource) binding.getObject();
0983: if (copyJars) {
0984: if (!copy(jarResource.streamContent(),
0985: new FileOutputStream(destFile)))
0986: continue;
0987: }
0988:
0989: JarFile jarFile = new JarFile(destFile);
0990: classLoader.addJar(filename, jarFile, destFile);
0991:
0992: }
0993: } catch (NamingException e) {
0994: // Silent catch: it's valid that no /WEB-INF/lib directory
0995: // exists
0996: } catch (IOException e) {
0997: e.printStackTrace();
0998: }
0999:
1000: }
1001:
1002: }
1003:
1004: /**
1005: * Set the appropriate context attribute for our class path. This
1006: * is required only because Jasper depends on it.
1007: */
1008: private void setClassPath() {
1009:
1010: // Validate our current state information
1011: if (!(container instanceof Context))
1012: return;
1013: ServletContext servletContext = ((Context) container)
1014: .getServletContext();
1015: if (servletContext == null)
1016: return;
1017:
1018: StringBuffer classpath = new StringBuffer();
1019:
1020: // Assemble the class path information from our class loader chain
1021: ClassLoader loader = getClassLoader();
1022: int layers = 0;
1023: int n = 0;
1024: while ((layers < 3) && (loader != null)) {
1025: if (!(loader instanceof URLClassLoader))
1026: break;
1027: URL repositories[] = ((URLClassLoader) loader).getURLs();
1028: for (int i = 0; i < repositories.length; i++) {
1029: String repository = repositories[i].toString();
1030: if (repository.startsWith("file://"))
1031: repository = repository.substring(7);
1032: else if (repository.startsWith("file:"))
1033: repository = repository.substring(5);
1034: else if (repository.startsWith("jndi:"))
1035: repository = servletContext.getRealPath(repository
1036: .substring(5));
1037: else
1038: continue;
1039: if (repository == null)
1040: continue;
1041: if (n > 0)
1042: classpath.append(File.pathSeparator);
1043: classpath.append(repository);
1044: n++;
1045: }
1046: loader = loader.getParent();
1047: layers++;
1048: }
1049:
1050: // Store the assembled class path as a servlet context attribute
1051: servletContext.setAttribute(Globals.CLASS_PATH_ATTR, classpath
1052: .toString());
1053:
1054: }
1055:
1056: /**
1057: * Copy directory.
1058: */
1059: private boolean copyDir(DirContext srcDir, File destDir) {
1060:
1061: try {
1062:
1063: NamingEnumeration enum = srcDir.list("");
1064: while (enum.hasMoreElements()) {
1065: NameClassPair ncPair =
1066: (NameClassPair) enum.nextElement();
1067: String name = ncPair.getName();
1068: Object object = srcDir.lookup(name);
1069: File currentFile = new File(destDir, name);
1070: if (object instanceof Resource) {
1071: InputStream is = ((Resource) object).streamContent();
1072: OutputStream os = new FileOutputStream(currentFile);
1073: if (!copy(is, os))
1074: return false;
1075: } else if (object instanceof InputStream) {
1076: OutputStream os = new FileOutputStream(currentFile);
1077: if (!copy((InputStream) object, os))
1078: return false;
1079: } else if (object instanceof DirContext) {
1080: currentFile.mkdir();
1081: copyDir((DirContext) object, currentFile);
1082: }
1083: }
1084:
1085: } catch (NamingException e) {
1086: return false;
1087: } catch (IOException e) {
1088: return false;
1089: }
1090:
1091: return true;
1092:
1093: }
1094:
1095: /**
1096: * Copy a file to the specified temp directory. This is required only
1097: * because Jasper depends on it.
1098: */
1099: private boolean copy(InputStream is, OutputStream os) {
1100:
1101: try {
1102: byte[] buf = new byte[4096];
1103: while (true) {
1104: int len = is.read(buf);
1105: if (len < 0)
1106: break;
1107: os.write(buf, 0, len);
1108: }
1109: is.close();
1110: os.close();
1111: } catch (IOException e) {
1112: return false;
1113: }
1114:
1115: return true;
1116:
1117: }
1118:
1119: /**
1120: * Sleep for the duration specified by the <code>checkInterval</code>
1121: * property.
1122: */
1123: private void threadSleep() {
1124:
1125: try {
1126: Thread.sleep(checkInterval * 1000L);
1127: } catch (InterruptedException e) {
1128: ;
1129: }
1130:
1131: }
1132:
1133: /**
1134: * Start the background thread that will periodically check for
1135: * session timeouts.
1136: *
1137: * @exception IllegalStateException if we should not be starting
1138: * a background thread now
1139: */
1140: private void threadStart() {
1141:
1142: // Has the background thread already been started?
1143: if (thread != null)
1144: return;
1145:
1146: // Validate our current state
1147: if (!reloadable)
1148: throw new IllegalStateException(sm
1149: .getString("webappLoader.notReloadable"));
1150: if (!(container instanceof Context))
1151: throw new IllegalStateException(sm
1152: .getString("webappLoader.notContext"));
1153:
1154: // Start the background thread
1155: if (debug >= 1)
1156: log(" Starting background thread");
1157: threadDone = false;
1158: threadName = "WebappLoader[" + container.getName() + "]";
1159: thread = new Thread(this , threadName);
1160: thread.setDaemon(true);
1161: thread.start();
1162:
1163: }
1164:
1165: /**
1166: * Stop the background thread that is periodically checking for
1167: * modified classes.
1168: */
1169: private void threadStop() {
1170:
1171: if (thread == null)
1172: return;
1173:
1174: if (debug >= 1)
1175: log(" Stopping background thread");
1176: threadDone = true;
1177: thread.interrupt();
1178: try {
1179: thread.join();
1180: } catch (InterruptedException e) {
1181: ;
1182: }
1183:
1184: thread = null;
1185:
1186: }
1187:
1188: /**
1189: * Validate that the required optional packages for this application
1190: * are actually present.
1191: *
1192: * @exception LifecycleException if a required package is not available
1193: */
1194: private void validatePackages() throws LifecycleException {
1195:
1196: ClassLoader classLoader = getClassLoader();
1197: if (classLoader instanceof WebappClassLoader) {
1198:
1199: Extension available[] = ((WebappClassLoader) classLoader)
1200: .findAvailable();
1201: Extension required[] = ((WebappClassLoader) classLoader)
1202: .findRequired();
1203: if (debug >= 1)
1204: log("Optional Packages: available=" + available.length
1205: + ", required=" + required.length);
1206:
1207: for (int i = 0; i < required.length; i++) {
1208: if (debug >= 1)
1209: log("Checking for required package " + required[i]);
1210: boolean found = false;
1211: for (int j = 0; j < available.length; j++) {
1212: if (available[j].isCompatibleWith(required[i])) {
1213: found = true;
1214: break;
1215: }
1216: }
1217: if (!found)
1218: throw new LifecycleException(
1219: "Missing optional package " + required[i]);
1220: }
1221:
1222: }
1223:
1224: }
1225:
1226: // ------------------------------------------------------ Background Thread
1227:
1228: /**
1229: * The background thread that checks for session timeouts and shutdown.
1230: */
1231: public void run() {
1232:
1233: if (debug >= 1)
1234: log("BACKGROUND THREAD Starting");
1235:
1236: // Loop until the termination semaphore is set
1237: while (!threadDone) {
1238:
1239: // Wait for our check interval
1240: threadSleep();
1241:
1242: if (!started)
1243: break;
1244:
1245: try {
1246: // Perform our modification check
1247: if (!classLoader.modified())
1248: continue;
1249: } catch (Exception e) {
1250: log(sm.getString("webappLoader.failModifiedCheck"), e);
1251: continue;
1252: }
1253:
1254: // Handle a need for reloading
1255: notifyContext();
1256: break;
1257:
1258: }
1259:
1260: if (debug >= 1)
1261: log("BACKGROUND THREAD Stopping");
1262:
1263: }
1264:
1265: // -------------------------------------- WebappContextNotifier Inner Class
1266:
1267: /**
1268: * Private thread class to notify our associated Context that we have
1269: * recognized the need for a reload.
1270: */
1271: protected class WebappContextNotifier implements Runnable {
1272:
1273: /**
1274: * Perform the requested notification.
1275: */
1276: public void run() {
1277:
1278: ((Context) container).reload();
1279:
1280: }
1281:
1282: }
1283:
1284: }
|