You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2001/06/19 04:12:50 UTC

cvs commit: jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/loader WebappLoader.java

remm        01/06/18 19:12:50

  Added:       catalina/src/share/org/apache/catalina/loader
                        WebappLoader.java
  Log:
  - New classloader implementation, used for web applications only.
  - Extends URLClassLoader (to preserve maximum compatibility).
  - Does not require dynamic bindings to work.
  - Supports reloading of classes in /WEB-INF/classes.
  - Supports reloading of JARs. This is untested, and is only a convinience
    feature (for example, when you forgot a JAR required by a webapp). In many
    cases, a stop / start of the context (which destroys it and recreates it from
    scratch) will be needed to avoid getting JVM linkage errors and class casts.
  - Improved efficiency a lot : no more double tracking of the classes and
    resources, unified loaded resources repository (classes + resources).
  - Fixed lookup path : first classes repository, then JARs. It was impossible
    to control the order of the repositories by directly using URLClassLoader.
  - To the outside world (if getURLs() is called), it appears as a file-based
    URLClassLoader.
  - All the JARs in /WEB-INF/lib are extracted and copied to the work directory,
    even if the resources are filesystem based. Otherwise, it is not possible to
    actually manipulate the JARs which are in the webapp while they're open, at
    least under Windows. This operation is relatively fast anyway.
  - Tested with the examples webapp, Slide and Jasper.
  - Note : Jasper reloading of the classes may still be broken.
  
  Revision  Changes    Path
  1.1                  jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/loader/WebappLoader.java
  
  Index: WebappLoader.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/loader/WebappLoader.java,v 1.1 2001/06/19 02:12:49 remm Exp $
   * $Revision: 1.1 $
   * $Date: 2001/06/19 02:12:49 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  
  package org.apache.catalina.loader;
  
  
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.FileOutputStream;
  import java.io.OutputStream;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.net.URLStreamHandlerFactory;
  import java.util.jar.JarFile;
  import javax.servlet.ServletContext;
  import javax.naming.NamingException;
  import javax.naming.Binding;
  import javax.naming.NameClassPair;
  import javax.naming.NamingEnumeration;
  import javax.naming.directory.DirContext;
  import org.apache.naming.resources.Resource;
  import org.apache.naming.resources.DirContextURLStreamHandler;
  import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
  import org.apache.catalina.Container;
  import org.apache.catalina.Context;
  import org.apache.catalina.Globals;
  import org.apache.catalina.Lifecycle;
  import org.apache.catalina.LifecycleEvent;
  import org.apache.catalina.LifecycleException;
  import org.apache.catalina.LifecycleListener;
  import org.apache.catalina.Loader;
  import org.apache.catalina.Logger;
  // FIXME : Define a new "servlet context" interface to void directly referncing
  // the ApplicationContext
  import org.apache.catalina.core.ApplicationContext;
  // End FIXME
  import org.apache.catalina.util.LifecycleSupport;
  import org.apache.catalina.util.StringManager;
  
  
  /**
   * Classloader implementation which is specialized for handling web 
   * applications in the most efficient way, while being Catalina aware (all
   * accesses to resources are made through the DirContext interface).
   * This class loader supports detection of modified
   * Java classes, which can be used to implement auto-reload support.
   * <p>
   * This class loader is configured by adding the pathnames of directories,
   * JAR files, and ZIP files with the <code>addRepository()</code> method,
   * prior to calling <code>start()</code>.  When a new class is required,
   * these repositories will be consulted first to locate the class.  If it
   * is not present, the system class loader will be used instead.
   *
   * @author Craig R. McClanahan
   * @author Remy Maucherat
   * @version $Revision: 1.1 $ $Date: 2001/06/19 02:12:49 $
   */
  
  public class WebappLoader
      implements Lifecycle, Loader, PropertyChangeListener, Runnable {
  
  
      // ----------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new StandardLoader with no defined parent class loader
       * (so that the actual parent will be the system class loader).
       */
      public WebappLoader() {
  
          this(null);
  
      }
  
  
      /**
       * Construct a new StandardLoader with the specified class loader
       * to be defined as the parent of the ClassLoader we ultimately create.
       *
       * @param parent The parent class loader
       */
      public WebappLoader(ClassLoader parent) {
  
  
          super();
  	this.parentClassLoader = parent;
  
      }
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The number of seconds between checks for modified classes, if
       * automatic reloading is enabled.
       */
      private int checkInterval = 15;
  
  
      /**
       * The class loader being managed by this Loader component.
       */
      private WebappClassLoader classLoader = null;
  
  
      /**
       * The Container with which this Loader has been associated.
       */
      private Container container = null;
  
  
      /**
       * The debugging detail level for this component.
       */
      private int debug = 0;
  
  
      /**
       * The "follow standard delegation model" flag that will be used to
       * configure our ClassLoader.
       */
      private boolean delegate = false;
  
  
      /**
       * The descriptive information about this Loader implementation.
       */
      private static final String info =
  	"org.apache.catalina.loader.WebappLoader/1.0";
  
  
      /**
       * The lifecycle event support for this component.
       */
      protected LifecycleSupport lifecycle = new LifecycleSupport(this);
  
  
      /**
       * The Java class name of the ClassLoader implementation to be used.
       * To be useful, this ClassLoader should also implement the
       * <code>Reloader</code> interface.
       */
      private String loaderClass =
  	"org.apache.catalina.loader.WebappClassLoader";
  
  
      /**
       * The parent class loader of the class loader we will create.
       */
      private ClassLoader parentClassLoader = null;
  
  
      /**
       * The reloadable flag for this Loader.
       */
      private boolean reloadable = false;
  
  
      /**
       * The set of repositories associated with this class loader.
       */
      private String repositories[] = new String[0];
  
  
      /**
       * The string manager for this package.
       */
      protected static final StringManager sm =
  	StringManager.getManager(Constants.Package);
  
  
      /**
       * Has this component been started?
       */
      private boolean started = false;
  
  
      /**
       * The property change support for this component.
       */
      protected PropertyChangeSupport support = new PropertyChangeSupport(this);
  
  
      /**
       * The background thread.
       */
      private Thread thread = null;
  
  
      /**
       * The background thread completion semaphore.
       */
      private boolean threadDone = false;
  
  
      /**
       * Name to register for the background thread.
       */
      private String threadName = "StandardLoader";
  
  
      /**
       * Classpath (which can be used by any webapp which would need to compile
       * classes, like a JSP engine.
       */
      protected StringBuffer classpath = new StringBuffer();
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Return the check interval for this Loader.
       */
      public int getCheckInterval() {
  
  	return (this.checkInterval);
  
      }
  
  
      /**
       * Set the check interval for this Loader.
       *
       * @param checkInterval The new check interval
       */
      public void setCheckInterval(int checkInterval) {
  
          int oldCheckInterval = this.checkInterval;
  	this.checkInterval = checkInterval;
  	support.firePropertyChange("checkInterval",
  				   new Integer(oldCheckInterval),
  				   new Integer(this.checkInterval));
  
      }
  
  
      /**
       * Return the Java class loader to be used by this Container.
       */
      public ClassLoader getClassLoader() {
  
  	return ((ClassLoader) classLoader);
  
      }
  
  
      /**
       * Return the Container with which this Logger has been associated.
       */
      public Container getContainer() {
  
  	return (container);
  
      }
  
  
      /**
       * Set the Container with which this Logger has been associated.
       *
       * @param container The associated Container
       */
      public void setContainer(Container container) {
  
  	// Deregister from the old Container (if any)
  	if ((this.container != null) && (this.container instanceof Context))
  	    ((Context) this.container).removePropertyChangeListener(this);
  
  	// Process this property change
  	Container oldContainer = this.container;
  	this.container = container;
  	support.firePropertyChange("container", oldContainer, this.container);
  
  	// Register with the new Container (if any)
  	if ((this.container != null) && (this.container instanceof Context)) {
  	    setReloadable( ((Context) this.container).getReloadable() );
  	    ((Context) this.container).addPropertyChangeListener(this);
  	}
  
      }
  
  
      /**
       * Return the debugging detail level for this component.
       */
      public int getDebug() {
  
  	return (this.debug);
  
      }
  
  
      /**
       * Set the debugging detail level for this component.
       *
       * @param debug The new debugging detail level
       */
      public void setDebug(int debug) {
  
          int oldDebug = this.debug;
  	this.debug = debug;
  	support.firePropertyChange("debug", new Integer(oldDebug),
  				   new Integer(this.debug));
  
      }
  
  
      /**
       * Return the "follow standard delegation model" flag used to configure
       * our ClassLoader.
       */
      public boolean getDelegate() {
  
          return (this.delegate);
  
      }
  
  
      /**
       * Set the "follow standard delegation model" flag used to configure
       * our ClassLoader.
       *
       * @param delegate The new flag
       */
      public void setDelegate(boolean delegate) {
  
          boolean oldDelegate = this.delegate;
          this.delegate = delegate;
          support.firePropertyChange("delegate", new Boolean(oldDelegate),
                                     new Boolean(this.delegate));
  
      }
  
  
      /**
       * Return descriptive information about this Loader implementation and
       * the corresponding version number, in the format
       * <code>&lt;description&gt;/&lt;version&gt;</code>.
       */
      public String getInfo() {
  
  	return (info);
  
      }
  
  
      /**
       * Return the ClassLoader class name.
       */
      public String getLoaderClass() {
  
  	return (this.loaderClass);
  
      }
  
  
      /**
       * Set the ClassLoader class name.
       *
       * @param loaderClass The new ClassLoader class name
       */
      public void setLoaderClass() {
  
  	this.loaderClass = loaderClass;
  
      }
  
  
      /**
       * Return the reloadable flag for this Loader.
       */
      public boolean getReloadable() {
  
  	return (this.reloadable);
  
      }
  
  
      /**
       * Set the reloadable flag for this Loader.
       *
       * @param reloadable The new reloadable flag
       */
      public void setReloadable(boolean reloadable) {
  
  	// Process this property change
  	boolean oldReloadable = this.reloadable;
  	this.reloadable = reloadable;
  	support.firePropertyChange("reloadable",
  				   new Boolean(oldReloadable),
  				   new Boolean(this.reloadable));
  
  	// Start or stop our background thread if required
  	if (!started)
  	    return;
  	if (!oldReloadable && this.reloadable)
  	    threadStart();
  	else if (oldReloadable && !this.reloadable)
  	    threadStop();
  
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Add a property change listener to this component.
       *
       * @param listener The listener to add
       */
      public void addPropertyChangeListener(PropertyChangeListener listener) {
  
  	support.addPropertyChangeListener(listener);
  
      }
  
  
      /**
       * Add a new repository to the set of repositories for this class loader.
       *
       * @param repository Repository to be added
       */
      public void addRepository(String repository) {
  
          if (debug >= 1)
  	    log(sm.getString("standardLoader.addRepository", repository));
          for (int i = 0; i < repositories.length; i++) {
              if (repository.equals(repositories[i]))
                  return;
          }
  	String results[] = new String[repositories.length + 1];
  	for (int i = 0; i < repositories.length; i++)
  	    results[i] = repositories[i];
  	results[repositories.length] = repository;
  	repositories = results;
  
      }
  
  
      /**
       * Return the set of repositories defined for this class loader.
       * If none are defined, a zero-length array is returned.
       */
      public String[] findRepositories() {
  
  	return (repositories);
  
      }
  
  
      /**
       * Has the internal repository associated with this Loader been modified,
       * such that the loaded classes should be reloaded?
       */
      public boolean modified() {
  
  	return (classLoader.modified());
  
      }
  
  
      /**
       * Remove a property change listener from this component.
       *
       * @param listener The listener to remove
       */
      public void removePropertyChangeListener(PropertyChangeListener listener) {
  
  	support.removePropertyChangeListener(listener);
  
      }
  
  
      /**
       * Return a String representation of this component.
       */
      public String toString() {
  
  	StringBuffer sb = new StringBuffer("WebappLoader[");
  	if (container != null)
  	    sb.append(container.getName());
  	sb.append("]");
  	return (sb.toString());
  
      }
  
  
      // ------------------------------------------------------ Lifecycle Methods
  
  
      /**
       * Add a lifecycle event listener to this component.
       *
       * @param listener The listener to add
       */
      public void addLifecycleListener(LifecycleListener listener) {
  
  	lifecycle.addLifecycleListener(listener);
  
      }
  
  
      /**
       * Remove a lifecycle event listener from this component.
       *
       * @param listener The listener to remove
       */
      public void removeLifecycleListener(LifecycleListener listener) {
  
  	lifecycle.removeLifecycleListener(listener);
  
      }
  
  
      /**
       * Start this component, initializing our associated class loader.
       *
       * @exception LifecycleException if a lifecycle error occurs
       */
      public void start() throws LifecycleException {
  
  	// Validate and update our current component state
  	if (started)
  	    throw new LifecycleException
  		(sm.getString("standardLoader.alreadyStarted"));
  	if (debug >= 1)
  	    log(sm.getString("standardLoader.starting"));
  	lifecycle.fireLifecycleEvent(START_EVENT, null);
  	started = true;
  
          // Register a stream handler factory for the JNDI protocol
          URLStreamHandlerFactory streamHandlerFactory = 
              new DirContextURLStreamHandlerFactory();
          try {
              URL.setURLStreamHandlerFactory(streamHandlerFactory);
          } catch (Throwable t) {
              // Ignore the error here.
          }
  
  	// Construct a class loader based on our current repositories list
  	try {
  	    if (parentClassLoader == null)
  	        classLoader = new WebappClassLoader(container.getResources());
  	    else
  	        classLoader = new WebappClassLoader
                      (parentClassLoader, container.getResources());
  
              classLoader.setDebug(this.debug);
              classLoader.setDelegate(this.delegate);
  
              // Configure our repositories
              setClassPath();
              setRepositories();
  
  	    if (container instanceof Context) {
  		// Tell the class loader the root of the context
  		ServletContext servletContext = 
                      ((Context) container).getServletContext();
  		try {
  		    URL contextURL = servletContext.getResource("/");
  		    if( contextURL != null ) {
  			((WebappClassLoader)classLoader).setPermissions
                              (contextURL);
                          String jarUrl = "jar:" + contextURL.toString() 
                              + "WEB-INF/lib/";
                          ((WebappClassLoader)classLoader).setPermissions
                              (jarUrl);                  
                      }
                      String contextRoot = servletContext.getRealPath("/");
                      if (contextRoot != null) {
                          ((WebappClassLoader)classLoader).setPermissions
                              (contextRoot);
                          String rootUrl = "file:" + contextRoot;
                          ((WebappClassLoader)classLoader).setPermissions
                              (rootUrl);
  		    }
  		} catch (MalformedURLException e) {
  		}
  	    }
  	    if (classLoader instanceof Lifecycle)
  		((Lifecycle) classLoader).start();
              // Binding the Webapp class loader to the directory context
              DirContextURLStreamHandler.bind
                  ((ClassLoader) classLoader, this.container.getResources());
  	} catch (Throwable t) {
  	    throw new LifecycleException("start: ", t);
  	}
  
          // Validate that all required packages are actually available
          validatePackages();
  
  	// Start our background thread if we are reloadable
  	if (reloadable) {
  	    log(sm.getString("standardLoader.reloading"));
  	    try {
  		threadStart();
  	    } catch (IllegalStateException e) {
  		throw new LifecycleException(e);
  	    }
  	}
  
      }
  
  
      /**
       * Stop this component, finalizing our associated class loader.
       *
       * @exception LifecycleException if a lifecycle error occurs
       */
      public void stop() throws LifecycleException {
  
  	// Validate and update our current component state
  	if (!started)
  	    throw new LifecycleException
  		(sm.getString("standardLoader.notStarted"));
  	if (debug >= 1)
  	    log(sm.getString("standardLoader.stopping"));
  	lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  	started = false;
  
  	// Stop our background thread if we are reloadable
  	if (reloadable)
  	    threadStop();
  
  	// Remove context attributes as appropriate
  	if (container instanceof Context) {
  	    ServletContext servletContext =
  		((Context) container).getServletContext();
  	    servletContext.removeAttribute(Globals.CLASS_PATH_ATTR);
  	}
  
  	// Throw away our current class loader
  	if (classLoader instanceof Lifecycle)
  	    ((Lifecycle) classLoader).stop();
          DirContextURLStreamHandler.unbind((ClassLoader) classLoader);
          classLoader = null;
  
      }
  
  
      // ----------------------------------------- PropertyChangeListener Methods
  
  
      /**
       * Process property change events from our associated Context.
       *
       * @param event The property change event that has occurred
       */
      public void propertyChange(PropertyChangeEvent event) {
  
  	// Validate the source of this event
  	if (!(event.getSource() instanceof Context))
  	    return;
  	Context context = (Context) event.getSource();
  
  	// Process a relevant property change
  	if (event.getPropertyName().equals("reloadable")) {
  	    try {
  		setReloadable
  		    ( ((Boolean) event.getNewValue()).booleanValue() );
  	    } catch (NumberFormatException e) {
  		log(sm.getString("standardLoader.reloadable",
  				 event.getNewValue().toString()));
  	    }
  	}
  
      }
  
  
      // ------------------------------------------------------- Private Methods
  
  
      /**
       * Log a message on the Logger associated with our Container (if any)
       *
       * @param message Message to be logged
       */
      private void log(String message) {
  
  	Logger logger = null;
  	if (container != null)
  	    logger = container.getLogger();
  	if (logger != null)
  	    logger.log("StandardLoader[" + container.getName() + "]: "
  		       + message);
  	else {
  	    String containerName = null;
  	    if (container != null)
  		containerName = container.getName();
  	    System.out.println("StandardLoader[" + containerName
  			       + "]: " + message);
  	}
  
      }
  
  
      /**
       * Log a message on the Logger associated with our Container (if any)
       *
       * @param message Message to be logged
       * @param throwable Associated exception
       */
      private void log(String message, Throwable throwable) {
  
  	Logger logger = null;
  	if (container != null)
  	    logger = container.getLogger();
  	if (logger != null) {
  	    logger.log("StandardLoader[" + container.getName() + "] "
  		       + message, throwable);
  	} else {
  	    String containerName = null;
  	    if (container != null)
  		containerName = container.getName();
  	    System.out.println("StandardLoader[" + containerName
  			       + "]: " + message);
  	    System.out.println("" + throwable);
  	    throwable.printStackTrace(System.out);
  	}
  
      }
  
  
      /**
       * Notify our Context that a reload is appropriate.
       */
      private void notifyContext() {
  
  	ContextNotifier notifier = new ContextNotifier((Context) container);
  	(new Thread(notifier)).start();
  
      }
  
  
      /**
       * Configure the repositories for our class loader, based on the
       * associated Context.
       */
      private void setRepositories() {
  
          if (!(container instanceof Context))
              return;
  	ServletContext servletContext =
  	    ((Context) container).getServletContext();
  	if (servletContext == null)
  	    return;
  
          // Loading the work directory
          File workDir = 
              (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR);
          if (workDir == null)
              return;
  
          // Reset repositories
          repositories = new String[0];
  
          DirContext resources = container.getResources();
  
          // Setting up the class repository (/WEB-INF/classes), if it exists
  
          String classesPath = "/WEB-INF/classes";
          DirContext classes = null;
  
          try {
              Object object = resources.lookup(classesPath);
              if (object instanceof DirContext) {
                  classes = (DirContext) object;
              }
          } catch(NamingException e) {
              // Silent catch: it's valid that no /WEB-INF/classes collection 
              // exists
          }
  
          if (classes != null) {
  
              File classRepository = null;
  
              String absoluteClassesPath = 
                  servletContext.getRealPath(classesPath);
  
              if (absoluteClassesPath != null) {
  
                  if (classpath.length() != 0)
                      classpath.append(File.pathSeparator);
                  classpath.append(absoluteClassesPath);
                  classRepository = new File(absoluteClassesPath);
  
              } else {
                  
                  if (classpath.length() != 0)
                      classpath.append(File.pathSeparator);
                  classRepository = new File(workDir, classesPath);
                  classRepository.mkdirs();
                  classpath.append(classRepository.getAbsolutePath());
                  
                  copyDir(classes, classRepository);
                  
              }
  
              // Adding the repository to the class loader
              classLoader.addRepository(classesPath + "/", classRepository);
  
              // Add to the local repository list
              addRepository(classesPath + "/");
  
          }
  
          // Setting up the JAR repository (/WEB-INF/lib), if it exists
  
          String libPath = "/WEB-INF/lib";
  
          classLoader.setJarPath(libPath);
  
          DirContext libDir = null;
          // Looking up directory /WEB-INF/lib in the context
          try {
              Object object = resources.lookup(libPath);
              if (object instanceof DirContext)
                  libDir = (DirContext) object;
          } catch(NamingException e) {
              // Silent catch: it's valid that no /WEB-INF/lib collection
              // exists
          }
          
          if (libDir != null) {
              
              File destDir = new File(workDir, libPath);
              destDir.mkdirs();
              
              // Looking up directory /WEB-INF/lib in the context
              try {
                  NamingEnumeration enum = resources.listBindings(libPath);
                  while (enum.hasMoreElements()) {
                      
                      Binding binding = (Binding) enum.nextElement();
                      String filename = libPath + "/" + binding.getName();
                      if (!filename.endsWith(".jar"))
                          continue;
                      
                      // Copy JAR in the work directory, always (the JAR file 
                      // would get locked otherwise, which would make it 
                      // impossible to update it or remove it at runtime)
                      File destFile = new File(destDir, binding.getName());
                      Resource jarResource = (Resource) binding.getObject();
                      if (copy(jarResource.streamContent(), 
                               new FileOutputStream(destFile))) {
                          if (classpath.length() != 0)
                              classpath.append(File.pathSeparator);
                          classpath.append(destFile.getAbsolutePath());
                      }
                      
                      JarFile jarFile = new JarFile(destFile);
                      
                      classLoader.addJar(filename, jarFile, destFile);
                      addRepository(filename);
                      
                  }
              } catch (NamingException e) {
                  // Silent catch: it's valid that no /WEB-INF/lib directory 
                  //exists
              } catch (IOException e) {
              }
              
          }
  
          // Store the assembled class path as a servlet context attribute
          servletContext.setAttribute(Globals.CLASS_PATH_ATTR,
                                      classpath.toString());
  
      }
  
  
      /**
       * Set the appropriate context attribute for our class path.  This
       * is required only because Jasper depends on it.
       */
      private void setClassPath() {
  
          // Validate our current state information
  	if (!(container instanceof Context))
  	    return;
  	ServletContext servletContext =
  	    ((Context) container).getServletContext();
  	if (servletContext == null)
  	    return;
  
          // Assemble the class path information from our class loader chain
          ClassLoader loader = getClassLoader();
          int layers = 0;
          int n = 0;
          while ((layers < 3) && (loader != null)) {
              if (!(loader instanceof URLClassLoader))
                  break;
              URL repositories[] =
                  ((URLClassLoader) loader).getURLs();
              for (int i = 0; i < repositories.length; i++) {
                  String repository = repositories[i].toString();
                  if (repository.startsWith("file://"))
                      repository = repository.substring(7);
                  else if (repository.startsWith("file:"))
                      repository = repository.substring(5);
                  else if (repository.startsWith("jndi:"))
                      repository = 
                          servletContext.getRealPath(repository.substring(5));
                  else
                      continue;
                  if ((repository == null) || (repository.endsWith("/")))
                      continue;
                  if (n > 0)
                      classpath.append(File.pathSeparator);
                  classpath.append(repository);
                  n++;
              }
              loader = loader.getParent();
              layers++;
          }
  
      }
  
  
      /**
       * Copy directory.
       */
      private boolean copyDir(DirContext srcDir, File destDir) {
  
          try {
  
              NamingEnumeration enum = srcDir.list("");
              while (enum.hasMoreElements()) {
                  NameClassPair ncPair = 
                      (NameClassPair) enum.nextElement();
                  String name = ncPair.getName();
                  Object object = srcDir.lookup(name);
                  File currentFile = new File(destDir, name);
                  if (object instanceof Resource) {
                      InputStream is = ((Resource) object).streamContent();
                      OutputStream os = new FileOutputStream(currentFile);
                      if (!copy(is, os))
                          return false;
                  } else if (object instanceof InputStream) {
                      OutputStream os = new FileOutputStream(currentFile);
                      if (!copy((InputStream) object, os))
                          return false;
                  } else if (object instanceof DirContext) {
                      currentFile.mkdir();
                      copyDir((DirContext) object, currentFile);
                  }
              }
  
          } catch (NamingException e) {
              return false;
          } catch (IOException e) {
              return false;
          }
  
          return true;
  
      }
  
  
      /**
       * Copy a file to the specified temp directory. This is required only 
       * because Jasper depends on it.
       */
      private boolean copy(InputStream is, OutputStream os) {
  
          try {
              byte[] buf = new byte[4096];
              while (true) {
                  int len = is.read(buf);
                  if (len < 0)
                      break;
                  os.write(buf, 0, len);
              }
              is.close();
              os.close();
          } catch (IOException e) {
              return false;
          }
  
          return true;
  
      }
  
  
      /**
       * Sleep for the duration specified by the <code>checkInterval</code>
       * property.
       */
      private void threadSleep() {
  
  	try {
  	    Thread.sleep(checkInterval * 1000L);
  	} catch (InterruptedException e) {
  	    ;
  	}
  
      }
  
  
      /**
       * Start the background thread that will periodically check for
       * session timeouts.
       *
       * @exception IllegalStateException if we should not be starting
       *  a background thread now
       */
      private void threadStart() {
  
  	// Has the background thread already been started?
  	if (thread != null)
  	    return;
  
  	// Validate our current state
  	if (!reloadable)
  	    throw new IllegalStateException
  		(sm.getString("standardLoader.notReloadable"));
  	if (!(container instanceof Context))
  	    throw new IllegalStateException
  		(sm.getString("standardLoader.notContext"));
  
  	// Start the background thread
  	if (debug >= 1)
  	    log(" Starting background thread");
  	threadDone = false;
  	threadName = "StandardLoader[" + container.getName() + "]";
  	thread = new Thread(this, threadName);
  	thread.setDaemon(true);
  	thread.start();
  
      }
  
  
      /**
       * Stop the background thread that is periodically checking for
       * modified classes.
       */
      private void threadStop() {
  
  	if (thread == null)
  	    return;
  
  	if (debug >= 1)
  	    log(" Stopping background thread");
  	threadDone = true;
  	thread.interrupt();
  	try {
  	    thread.join();
  	} catch (InterruptedException e) {
  	    ;
  	}
  
  	thread = null;
  
      }
  
  
      /**
       * Validate that the required optional packages for this application
       * are actually present.
       *
       * @exception LifecycleException if a required package is not available
       */
      private void validatePackages() throws LifecycleException {
  
          ClassLoader classLoader = getClassLoader();
          if (classLoader instanceof WebappClassLoader) {
  
              Extension available[] =
                  ((WebappClassLoader) classLoader).findAvailable();
              Extension required[] =
                  ((WebappClassLoader) classLoader).findRequired();
              if (debug >= 1)
                  log("Optional Packages:  available=" +
                      available.length + ", required=" +
                      required.length);
  
              for (int i = 0; i < required.length; i++) {
                  if (debug >= 1)
                      log("Checking for required package " + required[i]);
                  boolean found = false;
                  for (int j = 0; j < available.length; j++) {
                      if (available[j].isCompatibleWith(required[i])) {
                          found = true;
                          break;
                      }
                  }
                  if (!found)
                      throw new LifecycleException
                          ("Missing optional package " + required[i]);
              }
  
          }
  
      }
  
  
      // ------------------------------------------------------ Background Thread
  
  
      /**
       * The background thread that checks for session timeouts and shutdown.
       */
      public void run() {
  
  	if (debug >= 1)
  	    log("BACKGROUND THREAD Starting");
  
  	// Loop until the termination semaphore is set
  	while (!threadDone) {
  
  	    // Wait for our check interval
  	    threadSleep();
  
  	    // Perform our modification check
  	    if (!classLoader.modified())
  		continue;
  
  	    // Handle a need for reloading
  	    notifyContext();
  	    break;
  
  	}
  
  	if (debug >= 1)
  	    log("BACKGROUND THREAD Stopping");
  
      }
  
  
  }
  
  
  // ------------------------------------------------------------ Private Classes
  
  
  /**
   * Private thread class to notify our associated Context that we have
   * recognized the need for a reload.
   */
  
  final class WebappContextNotifier implements Runnable {
  
  
      /**
       * The Context we will notify.
       */
      private Context context = null;
  
  
      /**
       * Construct a new instance of this class.
       *
       * @param context The Context to be notified
       */
      public WebappContextNotifier(Context context) {
  
  	super();
  	this.context = context;
  
      }
  
  
      /**
       * Perform the requested notification.
       */
      public void run() {
  
  	context.reload();
  
      }
  
  
  }