You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by cr...@locus.apache.org on 2000/07/26 22:04:59 UTC

cvs commit: jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources DirectoryBean.java FileResources.java JarResources.java ResourceBean.java ResourcesBase.java package.html LocalStrings.properties StandardResources.java

craigmcc    00/07/26 13:04:57

  Modified:    proposals/catalina/src/share/org/apache/tomcat/resources
                        LocalStrings.properties StandardResources.java
  Added:       proposals/catalina/src/share/org/apache/tomcat/resources
                        DirectoryBean.java FileResources.java
                        JarResources.java ResourceBean.java
                        ResourcesBase.java package.html
  Log:
  Refactor the code in StandardResources into a base class with common
  functionality and subclasses for each individual media.  The base class,
  among other things, supports caching of resources that are retrieved via
  getResourceAsStream() so that things besides the default servlet can
  benefit from caching.
  
  Add a 'package.html' header file for the Javadocs that goes into at least
  a little more detail than the Javadoc comments.
  
  NOTE:  The new code is not yet in use; it has not been debugged yet!
  
  Revision  Changes    Path
  1.4       +14 -0     jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/LocalStrings.properties
  
  Index: LocalStrings.properties
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/LocalStrings.properties,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- LocalStrings.properties	2000/05/21 02:39:56	1.3
  +++ LocalStrings.properties	2000/07/26 20:04:52	1.4
  @@ -1,3 +1,17 @@
  +directory.filename=Filename
  +directory.lastModified=Last Modified
  +directory.parent=Up To {0}
  +directory.size=Size
  +directory.title=Directory Listing For {0}
  +directory.version=Tomcat Catalina version 4.0
  +fileResources.base=Document base {0} does not exist or is not a readable directory
  +jarResources.syntax=Document base {0} must start with 'jar:' and end with '!/'
  +resources.alreadyStarted=Resources has already been started
  +resources.connect=Cannot connect to document base {0}
  +resources.input=Cannot create input stream for resource {0}
  +resources.notStarted=Resources has not yet been started
  +resources.null=Document base cannot be null
  +resources.path=Context relative path {0} must start with '/'
   standardResources.alreadyStarted=Resources has already been started
   standardResources.directory=File base {0} is not a directory
   standardResources.exists=File base {0} does not exist
  
  
  
  1.11      +35 -4     jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/StandardResources.java
  
  Index: StandardResources.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/StandardResources.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- StandardResources.java	2000/06/19 21:46:50	1.10
  +++ StandardResources.java	2000/07/26 20:04:53	1.11
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/StandardResources.java,v 1.10 2000/06/19 21:46:50 craigmcc Exp $
  - * $Revision: 1.10 $
  - * $Date: 2000/06/19 21:46:50 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/StandardResources.java,v 1.11 2000/07/26 20:04:53 craigmcc Exp $
  + * $Revision: 1.11 $
  + * $Date: 2000/07/26 20:04:53 $
    *
    * ====================================================================
    *
  @@ -101,7 +101,7 @@
    * </ul>
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.10 $ $Date: 2000/06/19 21:46:50 $
  + * @version $Revision: 1.11 $ $Date: 2000/07/26 20:04:53 $
    */
   
   public final class StandardResources
  @@ -143,6 +143,12 @@
   
   
       /**
  +     * Should "directory" entries be expanded?
  +     */
  +    protected boolean expand = true;
  +
  +
  +    /**
        * The document root for this component, expressed as an absolute or
        * relative pathname, or <code>null</code> if the document root is
        * not filesystem-based.
  @@ -308,6 +314,31 @@
   	    	    (sm.getString("standardResources.exists", fileBase));
   	}
   
  +
  +    }
  +
  +
  +    /**
  +     * Return the "expand directories" flag.
  +     */
  +    public boolean getExpand() {
  +
  +	return (this.expand);
  +
  +    }
  +
  +
  +    /**
  +     * Set the "expand directories" flag.
  +     *
  +     * @param expand The new "expand directories" flag
  +     */
  +    public void setExpand(boolean expand) {
  +
  +	boolean oldExpand = this.expand;
  +	this.expand = expand;
  +	support.firePropertyChange("expand", new Boolean(oldExpand),
  +				   new Boolean(this.expand));
   
       }
   
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/DirectoryBean.java
  
  Index: DirectoryBean.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/DirectoryBean.java,v 1.1 2000/07/26 20:04:52 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/07/26 20:04:52 $
   *
   * ====================================================================
   *
   * 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.tomcat.resources;
  
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.InputStream;
  import java.io.PrintWriter;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.Locale;
  import java.util.TreeMap;
  import java.util.jar.JarEntry;
  import org.apache.tomcat.util.StringManager;
  
  
  /**
   * Abstraction bean that represents the properties of a "resource" that is
   * actually a "directory", in a fashion that independent of the actual
   * underlying medium used to represent those entries.  Convenient constructors
   * are provided to populate our properties from common sources, but it is
   * feasible to do everything with property setters if necessary.
   * <p>
   * <strong>IMPLEMENTATION NOTE</strong>:  It is assumed that access to the
   * set of resources associated with this directory are done in a thread safe
   * manner.  No internal synchronization is performed.
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/07/26 20:04:52 $
   */
  
  public final class DirectoryBean extends ResourceBean {
  
  
      // ----------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new directory bean for the named resource, with default
       * properties.
       *
       * @param name Normalized context-relative name of this resource
       */
      public DirectoryBean(String name) {
  
  	super(name);
  
      }
  
  
      /**
       * Construct a new directory bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param file File representing this resource entry
       */
      public DirectoryBean(String name, File file) {
  
  	super(name, file);
  
      }
  
  
      /**
       * Construct a new directory bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param entry JAR entry representing this resource entry
       */
      public DirectoryBean(String name, JarEntry entry) {
  
  	super(name, entry);
  
      }
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The system default Locale.
       */
      private static Locale defaultLocale = Locale.getDefault();
  
  
      /**
       * The date format pattern for rendering last modified date and time.
       */
      private static String pattern = "EEE, dd MMM yyyy HH:mm z";
  
  
      /**
       * The collection of resources for this directory, sorted by name.
       */
      private TreeMap resources = new TreeMap();
  
  
      /**
       * The string manager for this package.
       */
      protected static final StringManager sm =
  	StringManager.getManager(Constants.Package);
  
  
      /**
       * The date format for rendering last modified date and time.
       */
      private static DateFormat timestamp =
  	new SimpleDateFormat(pattern, defaultLocale);
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Add a new resource to this directory.
       *
       * @param entry ResourceBean for the resource to be added
       */
      public void addResource(ResourceBean resource) {
  
  	resources.put(resource.getName(), resource);
  
      }
  
  
      /**
       * Return the set of resources that belong to this directory,
       * in alphabetical order based on their names.
       */
      public ResourceBean[] findResources() {
  
  	ResourceBean results[] = new ResourceBean[resources.size()];
  	return ((ResourceBean[]) resources.values().toArray(results));
  
      }
  
  
      /**
       * Remove an existing resource from this directory.
       *
       * @param entry ResourceBean for the resource to be removed
       */
      public void removeResource(ResourceBean resource) {
  
  	resources.remove(resource.getName());
  
      }
  
  
      /**
       * Return an InputStream to an HTML representation of the contents
       * of this directory.
       *
       * @param contextPath Context path to which our internal paths are
       *  relative
       */
      public InputStream render(String contextPath) {
  
  	// Prepare a writer to a buffered area
  	ByteArrayOutputStream stream = new ByteArrayOutputStream();
  	PrintWriter writer = new PrintWriter(stream);
  
  	// FIXME - Currently pays no attention to the user's Locale
  
  	// Render the page header
  	writer.print("<html>\r\n");
  	writer.print("<head>\r\n");
  	writer.print("<title>");
  	writer.print(sm.getString("directory.title", name));
  	writer.print("</title>\r\n</head>\r\n");
  	writer.print("<body bgcolor=\"white\">\r\n");
  	writer.print("<table width=\"90%\" cellspacing=\"0\"" +
  		     " cellpadding=\"5\" align=\"center\">\r\n");
  
  	// Render the in-page title
  	writer.print("<tr><td colspan=\"3\"><font size=\"+2\">\r\n<strong>");
  	writer.print(sm.getString("directory.title", name));
  	writer.print("</strong>\r\n</font></td></tr>\r\n");
  
  	// Render the link to our parent (if required)
  	int slash = name.lastIndexOf("/");
  	if (slash > 0) {
  	    String parent = name.substring(0, slash);
  	    writer.print("<tr><td colspan=\"3\" bgcolor=\"#ffffff\">\r\n");
  	    writer.print("<a href=\"");
  	    writer.print(contextPath);
  	    writer.print(parent);
  	    writer.print("\">");
  	    writer.print(sm.getString("directory.parent", parent));
  	    writer.print("</a>\r\n");
  	    writer.print("</td></tr>\r\n");
  	}
  
  	// Render the column headings
  	writer.print("<tr>\r\n");
  	writer.print("<td align=\"left\" bgcolor=\"#cccccc\">");
  	writer.print("<font size=\"+1\"><strong>");
  	writer.print(sm.getString("directory.filename"));
  	writer.print("</strong></font></td>\r\n");
  	writer.print("<td align=\"center\"><font size=\"+1\">");
  	writer.print(sm.getString("directory.size"));
  	writer.print("</td>\r\n");
  	writer.print("<td align=\"right\"><font size=\"+1\">");
  	writer.print(sm.getString("directory.lastModified"));
  	writer.print("</td>\r\n");
  	writer.print("</tr>\r\n");
  
  	// Render the directory entries within this directory
  	ResourceBean resources[] = findResources();
  	boolean shade = false;
  	for (int i = 0;i < resources.length; i++) {
  
  	    writer.print("<tr");
  	    if (shade)
  		writer.print(" bgcolor=\"eeeeee\"");
  	    writer.print(">\r\n");
  	    shade = !shade;
  
  	    writer.print("<td align=\"left\">&nbsp;&nbsp;&nbsp;&nbsp;\r\n");
  	    writer.print("<a href=\"");
  	    writer.print(contextPath);
  	    writer.print(resources[i].getName());
  	    writer.print("\"><tt>");
  	    writer.print(resources[i].getName());
  	    if (resources[i] instanceof DirectoryBean)
  		writer.print("/");
  	    writer.print("</tt></a></td>\r\n");
  
  	    writer.print("<td align=\"right\"><tt>");
  	    if (resources[i] instanceof DirectoryBean)
  		writer.print("&nbsp;");
  	    else
  		writer.print(renderSize(resources[i].getSize()));
  	    writer.print("</tt></td>\r\n");
  
  	    writer.print("<td align=\"right\"><tt>");
  	    writer.print(renderLastModified(resources[i].getLastModified()));
  	    writer.print("</tt></td>\r\n");
  
  	    writer.print("</tr>\r\n");
  	}
  
  	// Render the page footer
  	writer.print("<tr><td colspan=\"3\">&nbsp;</td></tr>\r\n");
  	writer.print("<tr><td colspan=\"3\" bgcolor=\"#cccccc\">");
  	writer.print("<font size=\"-1\">");
  	writer.print(sm.getString("directory.version"));
  	writer.print("</font></td></tr>\r\n");
  	writer.print("</table>\r\n");
  	writer.print("</body>\r\n");
  	writer.print("</html>\r\n");
  
  	// Return an input stream to the underlying bytes
  	writer.flush();
  	return (new ByteArrayInputStream(stream.toByteArray()));
  
      }
  
  
      /**
       * Render the last modified date and time for the specified timestamp.
       *
       * @param lastModified Last modified date and time, in milliseconds since
       *  the epoch
       */
      private String renderLastModified(long lastModified) {
  
  	return (timestamp.format(new Date(lastModified)));
  
      }
  
  
      /**
       * Render the specified file size (in bytes).
       *
       * @param size File size (in bytes)
       */
      private String renderSize(long size) {
  
  	long leftSide = size / 1024;
  	long rightSide = (size % 1024) / 103;	// Makes 1 digit
  	if ((leftSide == 0) && (rightSide == 0) && (size > 0))
  	    rightSide = 1;
  
  	return ("" + leftSide + "." + rightSide + " kb");
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/FileResources.java
  
  Index: FileResources.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/FileResources.java,v 1.1 2000/07/26 20:04:52 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/07/26 20:04:52 $
   *
   * ====================================================================
   *
   * 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.tomcat.resources;
  
  
  import java.io.ByteArrayInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.InputStream;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.Enumeration;
  import org.apache.tomcat.Context;
  
  
  /**
   * Implementation of the <b>Resources</b> that operates off a document
   * base that is a directory in the local filesystem.  If the specified
   * document base is relative, it is resolved against the application base
   * directory for our surrounding virtual host (if any), or against the
   * value of the "catalina.home" system property.
   * <p>
   * <strong>IMPLEMENTATION NOTE</strong>:  It is assumed that new files may
   * be added, and existing files modified, while this web application is
   * running.  Therefore, only resources (not directories) are cached, and
   * the background thread must remove cached entries that have been modified
   * since they were cached.
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/07/26 20:04:52 $
   */
  
  public final class FileResources extends ResourcesBase {
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The document base directory for this component.
       */
      protected File base = null;
  
  
      /**
       * The descriptive information string for this implementation.
       */
      protected static final String info =
  	"org.apache.tomcat.resources.FileResources/1.0";
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Set the document root for this component.
       *
       * @param docBase The new document root
       *
       * @exception IllegalArgumentException if the specified value is not
       *  supported by this implementation
       * @exception IllegalArgumentException if this would create a
       *  malformed URL
       */
      public void setDocBase(String docBase) {
  
  	// Validate the format of the proposed document root
  	if (docBase == null)
  	    throw new IllegalArgumentException
  		(sm.getString("resources.null"));
  
  	// Calculate a File object referencing this document base directory
  	File base = new File(docBase);
  	if (!base.isAbsolute())
  	    base = new File(hostBase(), docBase);
  
  	// Validate that the document base is an existing directory
  	if (!base.exists() || !base.isDirectory() || !base.canRead())
  	    throw new IllegalArgumentException
  		(sm.getString("fileResources.base", docBase));
  	this.base = base;
  
  	// Perform the standard superclass processing
  	super.setDocBase(docBase);
  
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Return the real path for a given virtual path, or <code>null</code>
       * if no such path can be identified.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public String getRealPath(String path) {
  
  	validate(path);
  
  	// Return a real path if such a file actually exists
  	File file = file(normalize(path));
  	if (file != null)
  	    return (file.getAbsolutePath());
  	else
  	    return (null);
  
      }
  
  
      /**
       * Return a URL to the resource specified by the given virtual path,
       * or <code>null</code> if no such URL can be identified.
       * <p>
       * <b>IMPLEMENTATION NOTE</b>:  Use of this method bypasses any caching
       * performed by this component.  To take advantage of local caching,
       * use <code>getResourceAsStream()</code> instead.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       * @exception MalformedURLException if the resulting URL does not
       *  have legal syntax
       */
      public URL getResource(String path) throws MalformedURLException {
  
  	// Acquire an absolute pathname for the requested resource
  	String pathname = getRealPath(path);
  	if (pathname == null)
  	    return (null);
  
  	// Construct a URL that refers to this file
  	return (new URL("file://" + pathname));
  
      }
  
  
      /**
       * Return an InputStream to the contents of the resource specified
       * by the given virtual path, or <code>null</code> if no resource
       * exists at the specified path.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public InputStream getResourceAsStream(String path) {
  
  	validate(path);
  
  	// Look up the cached resource entry (if it exists) for this path
  	String normalized = normalize(path);
  	if (normalized == null)
  	    return (null);
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource != null)
  	    return (new ByteArrayInputStream(resource.getData()));
  
  	// Create a File object referencing the requested resource
  	File file = file(normalized);
  	if ((file == null) || !file.exists() || !file.canRead())
  	    return (null);
  
  	// Special handling for directories
  	if (file.isDirectory()) {
  	    String contextPath =
  		((Context) container).getPath();
  	    if (contextPath == null)
  		contextPath = "";
  	    DirectoryBean directory = new DirectoryBean(normalized, file);
  	    ;	// FIXME - Populate it with ResourceBean entries
  	    return (directory.render(contextPath));
  	}
  
  	// Cache the data for this resource (if appropriate and not yet done)
  	if (cacheable(normalized, file.length())) {
  	    resource = new ResourceBean(normalized, file);
  	    try {
  		resource.cache(inputStream(resource.getName()));
  	    } catch (IOException e) {
  		log(sm.getString("resources.input", resource.getName()), e);
  		return (null);
  	    }
  	    synchronized (resourcesCache) {
  		resourcesCache.put(resource.getName(), resource);
  		resourcesCount++;
  	    }
  	    return (new ByteArrayInputStream(resource.getData()));
  	}
  
  	// Serve the contents directly from the filesystem
  	try {
  	    return (new FileInputStream(file));
  	} catch (IOException e) {
  	    log(sm.getString("resoruces.input", resource.getName()), e);
  	    return (null);
  	}
  
      }
  
  
      /**
       * Return the last modified time for the resource specified by
       * the given virtual path, or -1 if no such resource exists (or
       * the last modified time cannot be determined).
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: We are assuming that
       * files may be modified while the application is running, so we
       * bypass the cache and check the filesystem directly.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public long getResourceModified(String path) {
  
  	validate(path);
  
  	File file = new File(normalize(path));
  	if (file != null)
  	    return (file.lastModified());
  	else
  	    return (-1L);
  
      }
  
  
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * Return a File object representing the specified normalized
       * context-relative path if it exists and is readable.  Otherwise,
       * return <code>null</code>.
       *
       * @param name Normalized context-relative path (with leading '/')
       */
      private File file(String name) {
  
  	if (name == null)
  	    return (null);
  	File file = new File(base, name.substring(1));
  	if (file.exists() && file.canRead())
  	    return (file);
  	else
  	    return (null);
  
      }
  
  
      /**
       * Return an input stream to the data content of the underlying file
       * that corresponds to the specified normalized context-relative path.
       *
       * @param name Normalized context-relative path (with leading '/')
       *
       * @exception IOException if an input/output error occurs
       */
      private InputStream inputStream(String name) throws IOException {
  
  	File file = file(name);
  	if ((file == null) || file.isDirectory())
  	    return (null);
  	else
  	    return (new FileInputStream(file));
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/JarResources.java
  
  Index: JarResources.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/JarResources.java,v 1.1 2000/07/26 20:04:52 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/07/26 20:04:52 $
   *
   * ====================================================================
   *
   * 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.tomcat.resources;
  
  
  import java.io.ByteArrayInputStream;
  import java.io.InputStream;
  import java.io.IOException;
  import java.net.JarURLConnection;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.Enumeration;
  import java.util.jar.JarEntry;
  import java.util.jar.JarFile;
  import org.apache.tomcat.Context;
  import org.apache.tomcat.LifecycleException;
  
  
  /**
   * Implementation of the <b>Resources</b> that decompresses and renders
   * entries from a JAR file that is located either locally or remotely.
   * Valid syntax for the <code>docBase</code> property corresponds to the
   * syntax supported by the <code>java.net.JarURLConnection</code> class,
   * and is illustrated by the following examples:
   * <ul>
   * <li><b>jar:file:/path/to/filename.jar!/</b> - Uses the JAR file
   *     <code>/path/to/filename.jar</code> as the source of resources.
   * <li><b>jar:http://www.foo.com/bar/baz.jar!/</b> - Uses the JAR file
   *     retrieved by doing an HTTP GET operation on the URL
   *     <code>http://www.foo.com/bar/baz.jar</code> (which makes this
   *     instance of Tomcat serve as a proxy for the specified application).
   * </ul>
   * In all cases, the <code>docBase</code> you specify must begin with
   * <code>jar:</code>.  If your <code>docBase</code> value does not end with
   * "!/", this will be added for you.
   * <p>
   * <strong>IMPLEMENTATION NOTE</strong>:  It is assumed that the underlying
   * JAR file itself will not be modified without restarting this web application
   * (or at least this <code>Resources</code> implementation).  Therefore, the
   * set of directory and resource entries is pre-loaded into our resource cache.
   * The actual data associated with these resources is not cached until it is
   * requested the first time (and passes the "cacheable" test).
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/07/26 20:04:52 $
   */
  
  public final class JarResources extends ResourcesBase {
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The URLConnection to our JAR file.
       */
      protected JarURLConnection conn = null;
  
  
      /**
       * The descriptive information string for this implementation.
       */
      protected static final String info =
  	"org.apache.tomcat.resources.JarResources/1.0";
  
  
      /**
       * The JarFile object associated with our document base.
       */
      protected JarFile jarFile = null;
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Set the document root for this component.
       *
       * @param docBase The new document root
       *
       * @exception IllegalArgumentException if the specified value is not
       *  supported by this implementation
       * @exception IllegalArgumentException if this would create a
       *  malformed URL
       */
      public void setDocBase(String docBase) {
  
  	// Validate the format of the proposed document root
  	if (docBase == null)
  	    throw new IllegalArgumentException
  		(sm.getString("resources.null"));
  	if (!docBase.startsWith("jar:"))
  	    throw new IllegalArgumentException
  		(sm.getString("jarResources.syntax", docBase));
  	if (!docBase.endsWith("!/"))
  	    docBase += "!/";
  
  	// Close any previous JAR that we have opened
  	if (jarFile != null) {
  	    try {
  		jarFile.close();
  	    } catch (IOException e) {
  		log("Closing JAR file", e);
  	    }
  	    jarFile = null;
  	    conn = null;
  	}
  
  	// Open a URLConnection to the specified JAR file
  	try {
  	    URL url = new URL(docBase);
  	    conn = (JarURLConnection) url.openConnection();
  	    conn.setAllowUserInteraction(false);
  	    conn.setDoInput(true);
  	    conn.setDoOutput(false);
  	    conn.connect();
  	    jarFile = conn.getJarFile();
  	} catch (Exception e) {
  	    log("Establishing connection", e);
  	    throw new IllegalArgumentException
  		(sm.getString("resources.connect", docBase));
  	}
  
  	// Populate our cache of directory and resource entries
  	populate();
  
  	// Perform the standard superclass processing
  	super.setDocBase(docBase);
  
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Return the real path for a given virtual path, or <code>null</code>
       * if no such path can be identified.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public String getRealPath(String path) {
  
  	validate(path);
  	return (null);	// JAR entries do not have a real path
  
      }
  
  
      /**
       * Return a URL to the resource specified by the given virtual path,
       * or <code>null</code> if no such URL can be identified.
       * <p>
       * <b>IMPLEMENTATION NOTE</b>:  Use of this method bypasses any caching
       * performed by this component.  To take advantage of local caching,
       * use <code>getResourceAsStream()</code> instead.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       * @exception MalformedURLException if the resulting URL does not
       *  have legal syntax
       */
      public URL getResource(String path) throws MalformedURLException {
  
  	validate(path);
  
  	// Construct a URL from the normalized version of the specified path
  	String normalized = normalize(path);
  	if (normalized != null)
  	    return (new URL(docBase + normalized.substring(1)));
  	else
  	    return (null);
  
      }
  
  
      /**
       * Return an InputStream to the contents of the resource specified
       * by the given virtual path, or <code>null</code> if no resource
       * exists at the specified path.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public InputStream getResourceAsStream(String path) {
  
  	validate(path);
  
  	// Look up the cached resource entry (if it exists) for this path
  	String normalized = normalize(path);
  	if (normalized == null)
  	    return (null);
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource == null)
  	    return (null);
  
  	// Special handling for directories
  	if (resource instanceof DirectoryBean) {
  	    if (expand) {
  		if (!(container instanceof Context))
  		    return (null);	// No way to find the context path
  		String contextPath =
  		    ((Context) container).getPath();
  		if (contextPath == null)
  		    contextPath = "";
  		return (((DirectoryBean) resource).render(contextPath));
  	    } else {
  		return (null);
  	    }
  	}
  
  	// Cache the data for this resource (if appropriate and not yet done)
  	if (resource.getData() == null) {
  	    try {
  		resource.cache(inputStream(resource.getName()));
  	    } catch (IOException e) {
  		log(sm.getString("resources.input", resource.getName()), e);
  		return (null);
  	    }
  	    if (resource.getData() != null)
  		resourcesCount++;
  	}
  
  	// Return an input stream to the cached or uncached data
  	if (resource.getData() != null)
  	    return (new ByteArrayInputStream(resource.getData()));
  	else {
  	    try {
  		return (inputStream(normalized));
  	    } catch (IOException e) {
  		log(sm.getString("resources.input", resource.getName()), e);
  		return (null);
  	    }
  	}
  
      }
  
  
      /**
       * Return the last modified time for the resource specified by
       * the given virtual path, or -1 if no such resource exists (or
       * the last modified time cannot be determined).
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: We are assuming that the
       * underlying JAR file will not be modified without restarting our
       * associated Context, so it is sufficient to return the last modified
       * timestamp from our cached resource bean.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public long getResourceModified(String path) {
  
  	validate(path);
  
  	// Look up and return the last modified time for this resource
  	String normalized = normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource != null)
  	    return (resource.getLastModified());
  	else
  	    return (-1L);
  
      }
  
  
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * Return an input stream to the data content of the underlying JAR entry
       * that corresponds to the specified normalized context-relative path.
       *
       * @param name Normalized context-relative path (with leading '/')
       *
       * @exception IOException if an input/output error occurs
       */
      private InputStream inputStream(String name) throws IOException {
  
  	if (name == null)
  	    return (null);
  	JarEntry entry = jarFile.getJarEntry(name.substring(1));
  	if (entry == null)
  	    return (null);
  	return (jarFile.getInputStream(entry));
  
      }
  
  
      /**
       * Populate our resources cache based on all of the entries in the
       * underlying JAR file.
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: This method assumes that the
       * "name" of an entry within the JAR file is exactly the same as the
       * result of performing a <code>normalize()</code> call on that name,
       * with the exception of the leading slash that is added.
       */
      private void populate() {
  
  	synchronized (resourcesCache) {
  
  	    // Erase the existing cache
  	    resourcesCache.clear();
  	    resourcesCount = 0;
  
  	    // Construct a pseudo-directory for the entire JAR file
  	    DirectoryBean top = new DirectoryBean("/");
  	    resourcesCache.put(top.getName(), top);
  
  	    // Process the entries in this JAR file
  	    Enumeration entries = jarFile.entries();
  	    while (entries.hasMoreElements()) {
  
  		JarEntry entry = (JarEntry) entries.nextElement();
  
  		// Create and cache the resource for this entry
  		String name = "/" + entry.getName();
  		ResourceBean resource = null;
  		if (entry.isDirectory())
  		    resource = new DirectoryBean(name, entry);
  		else
  		    resource = new ResourceBean(name, entry);
  
  		// Connect to our parent entry (if any)
  		int last = name.lastIndexOf("/");
  		String parentName = name.substring(0, last);
  		if (parentName.length() < 1)
  		    parentName = "/";
  		ResourceBean parent =
  		    (ResourceBean) resourcesCache.get(parentName);
  		if ((parent != null) && (parent instanceof DirectoryBean))
  		    resource.setParent((DirectoryBean) parent);
  
  	    }
  
  	}
  
      }
  
  
      // ------------------------------------------------------ Lifecycle Methods
  
  
      /**
       * Shut down this component.
       *
       * @exception LifecycleException if a major problem occurs
       */
      public void stop() throws LifecycleException {
  
  	// Shut down our current connection (if any)
  	if (jarFile != null) {
  	    try {
  		jarFile.close();
  	    } catch (IOException e) {
  		log("Closing JAR file", e);
  	    }
  	    jarFile = null;
  	}
  	conn = null;
  
  	// Perform standard superclass processing
  	super.stop();
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/ResourceBean.java
  
  Index: ResourceBean.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/ResourceBean.java,v 1.1 2000/07/26 20:04:53 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/07/26 20:04:53 $
   *
   * ====================================================================
   *
   * 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.tomcat.resources;
  
  
  import java.io.BufferedInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.InputStream;
  import java.io.IOException;
  import java.util.jar.JarEntry;
  
  
  /**
   * Abstraction bean that represents the properties of a "resource" that
   * may or may not be a "directory", in a fashion that is independent
   * of the actual underlying medium used to represent those entries.
   * Convenient constructors are provided to populate our properties from
   * common sources, but it is feasible to do everything with property
   * setters if necessary.
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/07/26 20:04:53 $
   */
  
  public class ResourceBean {
  
  
      // ----------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new resource bean for the named resource, with default
       * properties.
       *
       * @param name Normalized context-relative name of this resource
       */
      public ResourceBean(String name) {
  
  	super();
  	setName(name);
  
      }
  
  
      /**
       * Construct a new resource bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param file File representing this resource entry
       */
      public ResourceBean(String name, File file) {
  
  	this(name);
  	populate(file);
  
      }
  
  
      /**
       * Construct a new resource bean for the named resource, with properties
       * populated from the specified object.  Note that the data content of
       * this resource is <strong>not</strong> initialized unless and until
       * <code>setData()</code> is called.
       *
       * @param name Normalized context-relative name of this resource
       * @param entry JAR entry representing this resource entry
       */
      public ResourceBean(String name, JarEntry entry) {
  
  	this(name);
  	populate(entry);
  
      }
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * The data content of this resource.  This property is
       * <strong>only</strong> initialized when the corresponding property
       * setter method is called.
       */
      protected byte[] data = null;
  
      public byte[] getData() {
  	return (this.data);
      }
  
      public void setData(byte[] data) {
  	this.data = data;
      }
  
  
      /**
       * The last modified date/time for this resource, in milliseconds since
       * the epoch.
       */
      protected long lastModified = 0L;
  
      public long getLastModified() {
  	return (this.lastModified);
      }
  
      public void setLastModified(long lastModified) {
  	this.lastModified = lastModified;
      }
  
  
      /**
       * The normalized context-relative name of this resource.
       */
      protected String name = null;
  
      public String getName() {
  	return (this.name);
      }
  
      public void setName(String name) {
  	this.name = name;
      }
  
  
  
      /**
       * The parent resource (normally a directory entry) of this resource.
       * Note that this property is <strong>not</strong> set from an underlying
       * File or JarEntry argument to our constructor -- you must call
       * <code>setParent()</code> explicitly if you wish to maintain this
       * relationship.
       */
      protected DirectoryBean parent = null;
  
      public DirectoryBean getParent() {
  	return (this.parent);
      }
  
      public void setParent(DirectoryBean parent) {
  	if (this.parent != null)
  	    this.parent.removeResource(this);
  	this.parent = parent;
  	if (this.parent != null)
  	    this.parent.addResource(this);
      }
  
  
      /**
       * The size of this resource, in bytes.
       */
      protected long size = 0L;
  
      public long getSize() {
  	return (this.size);
      }
  
      public void setSize(long size) {
  	this.size = size;
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Cache the data for this resource from the specified input stream.
       *
       * @param input InputStream from which to read the data for this resource
       *
       * @exception IOException if an input/output error occurs
       */
      public void cache(InputStream input) throws IOException {
  
  	BufferedInputStream in = new BufferedInputStream(input);
  	ByteArrayOutputStream out = new ByteArrayOutputStream();
  	while (true) {
  	    int ch = in.read();
  	    if (ch < 0)
  		break;
  	    out.write(ch);
  	}
  	in.close();
  	data = out.toByteArray();
  
      }
  
  
      // ------------------------------------------------------ Protected Methods
  
  
      /**
       * Populate our properties from the specified File object.
       *
       * @param file File representing this entry
       */
      protected void populate(File file) {
  
  	this.lastModified = file.lastModified();
  	this.size = file.length();
  
      }
  
  
      /**
       * Populate our properties from the specified JarEntry object.
       *
       * @param entry JarEntry representing this entry
       */
      protected void populate(JarEntry entry) {
  
  	this.lastModified = entry.getTime();
  	this.size = entry.getSize();
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/ResourcesBase.java
  
  Index: ResourcesBase.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/ResourcesBase.java,v 1.1 2000/07/26 20:04:53 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/07/26 20:04:53 $
   *
   * ====================================================================
   *
   * 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.tomcat.resources;
  
  
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.io.File;
  import java.io.InputStream;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.HashMap;
  import org.apache.tomcat.Container;
  import org.apache.tomcat.Context;
  import org.apache.tomcat.Host;
  import org.apache.tomcat.Lifecycle;
  import org.apache.tomcat.LifecycleEvent;
  import org.apache.tomcat.LifecycleException;
  import org.apache.tomcat.LifecycleListener;
  import org.apache.tomcat.Logger;
  import org.apache.tomcat.Resources;
  import org.apache.tomcat.util.LifecycleSupport;
  import org.apache.tomcat.util.StringManager;
  
  
  /**
   * Convenience base class for implementations of the <b>Resources</b>
   * interface.  It is expected that subclasses of this class will be
   * created for each flavor of document root to be supported.
   * <p>
   * Included in the basic support provided by this class is provisions
   * for caching of resources according to configurable policy properties.
   * This will be especially useful for web applications with relatively
   * small amounts of static content (such as a 100% dynamic JSP based
   * application with just a few images), as well as environments where
   * accessing the underlying resources is relatively time consuming
   * (such as a local or remote JAR file).
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/07/26 20:04:53 $
   */
  
  public abstract class ResourcesBase
      implements Resources, Lifecycle, PropertyChangeListener, Runnable {
  
  
      // ----------------------------------------------------------- Constructors
  
  
      /**
       * Construct a new instance of this class with default values.
       */
      public ResourcesBase() {
  
  	super();
  
      }
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The interval (in seconds) at which our background task should check
       * for out-of-date cached resources, or zero for no checks.
       */
      protected int checkInterval = 0;
  
  
      /**
       * The Container this component is associated with (normally a Context).
       */
      protected Container container = null;
  
  
      /**
       * The debugging detail level for this component.
       */
      protected int debug = 0;
  
  
      /**
       * The document root for this component.
       */
      protected String docBase = null;
  
  
      /**
       * Should "directory" entries be expanded?
       */
      protected boolean expand = true;
  
  
      /**
       * The descriptive information string for this implementation.
       */
      protected static final String info =
  	"org.apache.tomcat.resources.ResourcesBase/1.0";
  
  
      /**
       * The lifecycle event support for this component.
       */
      protected LifecycleSupport lifecycle = new LifecycleSupport(this);
  
  
      /**
       * The maximum number of resources to cache.
       */
      protected int maxCount = 0;
  
  
      /**
       * The maximum size of resources to be cached.
       */
      protected long maxSize = 0L;
  
  
      /**
       * The minimum size of resources to be cached.
       */
      protected long minSize = 0L;
  
  
      /**
       * The prefix to the log messages we will be creating.
       */
      protected static final String prefix = "ResourcesBase";
  
  
      /**
       * The set of ResourceBean entries for this component,
       * keyed by the normalized context-relative resource URL.
       */
      protected HashMap resourcesCache = new HashMap();
  
  
      /**
       * The count of ResourceBean entries for which we have actually
       * cached data.  This can be different from the number of elements
       * in the <code>resourcesCache</code> collection.
       */
      protected int resourcesCount = 0;
  
  
      /**
       * The string manager for this package.
       */
      protected static final StringManager sm =
  	StringManager.getManager(Constants.Package);
  
  
      /**
       * Has this component been started?
       */
      protected boolean started = false;
  
  
      /**
       * The property change support for this component.
       */
      protected PropertyChangeSupport support = new PropertyChangeSupport(this);
  
  
      /**
       * The background thread.
       */
      protected Thread thread = null;
  
  
      /**
       * The background thread completion semaphore.
       */
      protected boolean threadDone = false;
  
  
      /**
       * The name to register for the background thread.
       */
      protected String threadName = "ResourcesBase";
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Return the resource cache check interval.
       */
      public int getCheckInterval() {
  
  	return (this.checkInterval);
  
      }
  
  
      /**
       * Set the resource cache check interval.
       *
       * @param checkInterval The new check interval
       */
      public void setCheckInterval(int checkInterval) {
  
  	// Perform the property update
  	int oldCheckInterval = this.checkInterval;
  	this.checkInterval = checkInterval;
  	support.firePropertyChange("checkInterval",
  				   new Integer(oldCheckInterval),
  				   new Integer(this.checkInterval));
  
  	// Start or stop the background thread (if necessary)
  	if (started) {
  	    if ((oldCheckInterval > 0) && (this.checkInterval <= 0))
  		threadStop();
  	    else if ((oldCheckInterval <= 0) && (this.checkInterval > 0))
  		threadStart();
  	}
  
      }
  
  
      /**
       * Return the Container with which this Resources has been associated.
       */
      public Container getContainer() {
  
  	return (this.container);
  
      }
  
  
      /**
       * Set the Container with which this Resources has been associated.
       *
       * @param container The associated Container
       */
      public void setContainer(Container container) {
  
  	Container oldContainer = this.container;
  	if ((oldContainer != null) && (oldContainer instanceof Context))
  	    ((Context) oldContainer).removePropertyChangeListener(this);
  
  	this.container = container;
  	// We are interested in property changes to the document base
  	if ((this.container != null) && (this.container instanceof Context)) {
  	    ((Context) this.container).addPropertyChangeListener(this);
  	    setDocBase(((Context) this.container).getDocBase());
  	}
  
  	support.firePropertyChange("container", oldContainer, this.container);
  
      }
  
  
      /**
       * 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 document root for this component.
       */
      public String getDocBase() {
  
  	return (this.docBase);
  
      }
  
  
      /**
       * Set the document root for this component.
       *
       * @param docBase The new document root
       *
       * @exception IllegalArgumentException if the specified value is not
       *  supported by this implementation
       * @exception IllegalArgumentException if this would create a
       *  malformed URL
       */
      public void setDocBase(String docBase) {
  
  	// Validate the format of the proposed document root
  	if (docBase == null)
  	    throw new IllegalArgumentException
  		(sm.getString("resources.null"));
  
  	// Change the document root property
  	String oldDocBase = this.docBase;
  	this.docBase = docBase.toString();
  	support.firePropertyChange("docBase", oldDocBase, this.docBase);
  	if (debug >= 1)
  	    log("Setting docBase to '" + this.docBase + "'");
  
      }
  
  
      /**
       * Return the "expand directories" flag.
       */
      public boolean getExpand() {
  
  	return (this.expand);
  
      }
  
  
      /**
       * Set the "expand directories" flag.
       *
       * @param expand The new "expand directories" flag
       */
      public void setExpand(boolean expand) {
  
  	boolean oldExpand = this.expand;
  	this.expand = expand;
  	support.firePropertyChange("expand", new Boolean(oldExpand),
  				   new Boolean(this.expand));
  
      }
  
  
      /**
       * Return descriptive information about this Resources implementation and
       * the corresponding version number, in the format
       * <code>&lt;description&gt;/&lt;version&gt;</code>.
       */
      public String getInfo() {
  
  	return (info);
  
      }
  
  
      /**
       * Return the maximum number of resources to cache.
       */
      public int getMaxCount() {
  
  	return (this.maxCount);
  
      }
  
  
      /**
       * Set the maximum number of resources to cache.
       *
       * @param maxCount The new maximum count
       */
      public void setMaxCount(int maxCount) {
  
  	int oldMaxCount = this.maxCount;
  	this.maxCount = maxCount;
  	support.firePropertyChange("maxCount", new Integer(oldMaxCount),
  				   new Integer(this.maxCount));
  
      }
  
  
      /**
       * Return the maximum size of resources to be cached.
       */
      public long getMaxSize() {
  
  	return (this.maxSize);
  
      }
  
  
      /**
       * Set the maximum size of resources to be cached.
       *
       * @param maxSize The new maximum size
       */
      public void setMaxSize(long maxSize) {
  
  	long oldMaxSize = this.maxSize;
  	this.maxSize = maxSize;
  	support.firePropertyChange("maxSize", new Long(oldMaxSize),
  				   new Long(this.maxSize));
  
      }
  
  
      /**
       * Return the minimum size of resources to be cached.
       */
      public long getMinSize() {
  
  	return (this.minSize);
  
      }
  
  
      /**
       * Set the minimum size of resources to be cached.
       *
       * @param minSize The new minimum size
       */
      public void setMinSize(long minSize) {
  
  	long oldMinSize = this.minSize;
  	this.minSize = minSize;
  	support.firePropertyChange("minSize", new Long(oldMinSize),
  				   new Long(this.minSize));
  
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Add a property change listener to this component.
       *
       * @param listener The listener to add
       */
      public void addPropertyChangeListener(PropertyChangeListener listener) {
  
  	support.addPropertyChangeListener(listener);
  
      }
  
  
      /**
       * Return the MIME type of the specified file, or <code>null</code> if
       * the MIME type is not known.  The MIME type is determined by the
       * configuration of the servlet container, and may be specified in a
       * web application descriptor.  Common MIME types are
       * <code>"text/html"</code> and <code>"image/gif"</code>.
       * <p>
       * The default implementation consults the MIME type mappings that have
       * been registered in our associated Context, if any.
       *
       * @param file Name of the file whose MIME type is to be determined
       */
      public String getMimeType(String file) {
  
  	if (debug >= 1)
  	    log("Calculating MIME type of '" + file + "'");
  	if (file == null)
  	    return (null);
  	if ((container == null) || (!(container instanceof Context)))
  	    return (null);
  	int period = file.lastIndexOf(".");
  	if (period < 0)
  	    return (null);
  	String extension = file.substring(period + 1);
  	if (extension.length() < 1)
  	    return (null);
  	if (debug >= 1)
  	    log(" Mime type of '" + extension + "' is '" +
  		((Context) container).findMimeMapping(extension));
  	return (((Context) container).findMimeMapping(extension));
  
      }
  
  
      /**
       * Return the real path for a given virtual path.  For example, the
       * virtual path <code>"/index.html"</code> has a real path of whatever
       * file on the server's filesystem would be served by a request for
       * <code>"/index.html"</code>.
       * <p>
       * The real path returned will be in a form appropriate to the computer
       * and operating system on which the servlet container is running,
       * including the proper path separators.  This method returns
       * <code>null</code> if the servlet container cannot translate the
       * virtual path to a real path for any reason (such as when the content
       * is being made available from a <code>.war</code> archive).
       *
       * @param path The virtual path to be translated
       */
      public abstract String getRealPath(String path);
  
  
      /**
       * Return a URL to the resource that is mapped to the specified path.
       * The path must begin with a "/" and is interpreted as relative to
       * the current context root.
       * <p>
       * This method allows the Container to make a resource available to
       * servlets from any source.  Resources can be located on a local or
       * remote file system, in a database, or in a <code>.war</code> file.
       * <p>
       * The servlet container must implement the URL handlers and
       * <code>URLConnection</code> objects that are necessary to access
       * the resource.
       * <p>
       * This method returns <code>null</code> if no resource is mapped to
       * the pathname.
       * <p>
       * Some Containers may allow writing to the URL returned by this method,
       * using the methods of the URL class.
       * <p>
       * The resource content is returned directly, so be aware that
       * requesting a <code>.jsp</code> page returns the JSP source code.
       * Use a <code>RequestDispatcher</code> instead to include results
       * of an execution.
       * <p>
       * This method has a different purpose than
       * <code>java.lang.Class.getResource()</code>, which looks up resources
       * based on a class loader.  This method does not use class loaders.
       *
       * @param path The path to the desired resource
       *
       * @exception MalformedURLException if the pathname is not given
       *  in the correct form
       */
      public abstract URL getResource(String path) throws MalformedURLException;
  
  
      /**
       * Return the resource located at the named path as an
       * <code>InputStream</code> object.
       * <p>
       * The data in the <code>InputStream</code> can be of any type or length.
       * The path must be specified according to the rules given in
       * <code>getResource()</code>.  This method returns <code>null</code>
       * if no resource exists at the specified path.
       * <p>
       * Meta-information such as content length and content type that is
       * available via the <code>getResource()</code> method is lost when
       * using this method.
       * <p>
       * The servlet container must implement the URL handlers and
       * <code>URLConnection</code> objects that are necessary to access
       * the resource.
       * <p>
       * This method is different from
       * <code>java.lang.Class.getResourceAsStream()</code>, which uses a
       * class loader.  This method allows servlet containers to make a
       * resource available to a servlet from any location, without using
       * a class loader.
       *
       * @param path The path to the desired resource
       */
      public abstract InputStream getResourceAsStream(String path);
  
  
      /**
       * Return the last modified date/time of the resource at the specified
       * path, where <code>path</code> would be suitable for passing as an
       * argument to <code>getResource()</code> or
       * <code>getResourceAsStream()</code>.  If there is no resource at the
       * specified location, return -1.
       * <p>
       * <b>IMPLEMENTATION NOTE</b>:  This method should bypass any cached
       * resources and reference the underlying resource directly, because it
       * will be used by the background thread that is checking for resources
       * that have been modified.
       *
       * @param path The path to the desired resource
       */
      public abstract long getResourceModified(String path);
  
  
      /**
       * Remove a property change listener from this component.
       *
       * @param listener The listener to remove
       */
      public void removePropertyChangeListener(PropertyChangeListener listener) {
  
  	support.removePropertyChangeListener(listener);
  
      }
  
  
      // ----------------------------------------- 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("docBase"))
  	    setDocBase((String) event.getNewValue());
  
      }
  
  
      // ------------------------------------------------------ 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);
  
      }
  
  
      /**
       * Prepare for the beginning of active use of the public methods of this
       * component.  This method should be called after <code>configure()</code>,
       * and before any of the public methods of the component are utilized.
       *
       * @exception IllegalStateException if this component has already been
       *  started
       * @exception LifecycleException if this component detects a fatal error
       *  that prevents this component from being used
       */
      public void start() throws LifecycleException {
  
  	// Validate and update our current component state
  	if (started)
  	    throw new LifecycleException
  		(sm.getString("resources.alreadyStarted"));
  	lifecycle.fireLifecycleEvent(START_EVENT, null);
  	started = true;
  
  	// Start the background expiration checking thread (if necessary)
  	if (checkInterval > 0)
  	    threadStart();
  
      }
  
  
      /**
       * Gracefully terminate the active use of the public methods of this
       * component.  This method should be the last one called on a given
       * instance of this component.
       *
       * @exception IllegalStateException if this component has not been started
       * @exception LifecycleException if this component detects a fatal error
       *  that needs to be reported
       */
      public void stop() throws LifecycleException {
  
  	// Validate and update our current state
  	if (!started)
  	    throw new LifecycleException
  		(sm.getString("resources.notStarted"));
  
  	lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  	started = false;
  
  	// Stop the background expiration checking thread (if necessary)
  	threadStop();
  
      }
  
  
      // ------------------------------------------------------ Protected Methods
  
  
      /**
       * Should the resource specified by our parameters be cached?
       *
       * @param name Name of the proposed resource
       * @param size Size (in bytes) of the proposed resource
       */
      protected boolean cacheable(String name, long size) {
  
  	if ((size < minSize) || (size > maxSize))
  	    return (false);
  	else if (resourcesCount >= maxCount)
  	    return (false);
  	else
  	    return (true);
  
      }
  
  
      /**
       * Return a File object representing the base directory for the
       * entire servlet container (i.e. the Engine container if present).
       */
      protected File engineBase() {
  
  	return (new File(System.getProperty("catalina.home")));
  
      }
  
  
      /**
       * Return a File object representing the base directory for the
       * current virtual host (i.e. the Host container if present).
       */
      protected File hostBase() {
  
  	// Locate our immediately surrounding Host (if any)
  	Container container = this.container;
  	while (container != null) {
  	    if (container instanceof Host)
  		break;
  	    container = container.getParent();
  	}
  	if (container == null)
  	    return (engineBase());
  
  	// Use the "appBase" property of this container
  	String appBase = ((Host) container).getAppBase();
  	File file = new File(appBase);
  	if (!file.isAbsolute())
  	    file = new File(engineBase(), appBase);
  	return (file);
  
      }
  
  
  
      /**
       * Log a message on the Logger associated with our Container (if any)
       *
       * @param message Message to be logged
       */
      protected void log(String message) {
  
  	Logger logger = null;
  	if (container != null)
  	    logger = container.getLogger();
  	if (logger != null)
  	    logger.log(prefix + "[" + container.getName() + "]: "
  		       + message);
  	else {
  	    String containerName = null;
  	    if (container != null)
  		containerName = container.getName();
  	    System.out.println(prefix + "[" + containerName
  			       + "]: " + message);
  	}
  
      }
  
  
      /**
       * Log a message on the Logger associated with our Container (if any)
       *
       * @param message Message to be logged
       * @param throwable Associated exception
       */
      protected void log(String message, Throwable throwable) {
  
  	Logger logger = null;
  	if (container != null)
  	    logger = container.getLogger();
  	if (logger != null)
  	    logger.log(prefix + "[" + container.getName() + "] "
  		       + message, throwable);
  	else {
  	    String containerName = null;
  	    if (container != null)
  		containerName = container.getName();
  	    System.out.println(prefix + "[" + containerName
  			       + "]: " + message);
  	    System.out.println("" + throwable);
  	    throwable.printStackTrace(System.out);
  	}
  
      }
  
  
      /**
       * Return a context-relative path, beginning with a "/", that represents
       * the canonical version of the specified path after ".." and "." elements
       * are resolved out.  If the specified path attempts to go outside the
       * boundaries of the current context (i.e. too many ".." path elements
       * are present), return <code>null</code> instead.
       *
       * @param path Path to be normalized
       */
      protected String normalize(String path) {
  
  	// Normalize the slashes and add leading slash if necessary
  	String normalized = null;
  	if (normalized.indexOf('\\') >= 0)
  	    normalized = normalized.replace('\\', '/');
  	if (!normalized.startsWith("/"))
  	    normalized = "/" + normalized;
  
  	// Resolve occurrences of "//" in the normalized path
  	while (true) {
  	    int index = normalized.indexOf("//");
  	    if (index < 0)
  		break;
  	    normalized = normalized.substring(0, index) +
  		normalized.substring(index + 1);
  	}
  
  	// Resolve occurrences of "/./" in the normalized path
  	while (true) {
  	    int index = normalized.indexOf("/./");
  	    if (index < 0)
  		break;
  	    normalized = normalized.substring(0, index) +
  		normalized.substring(index + 2);
  	}
  
  	// Resolve occurrences of "/../" in the normalized path
  	while (true) {
  	    int index = normalized.indexOf("/../");
  	    if (index < 0)
  		break;
  	    if (index == 0)
  		return (null);	// Trying to go outside our context
  	    int index2 = normalized.lastIndexOf('/', index - 1);
  	    normalized = normalized.substring(0, index2) +
  		normalized.substring(index + 3);
  	}
  
  	// Return the normalized path that we have completed
  	return (normalized);
  
      }
  
  
      /**
       * Scan our cached resources, looking for cases where the underlying
       * resource has been modified since we cached it.
       */
      protected void threadProcess() {
  
  	// Create a list of the cached resources we know about
  	ResourceBean entries[] = new ResourceBean[0];
  	synchronized(resourcesCache) {
  	    entries =
  		(ResourceBean[]) resourcesCache.values().toArray(entries);
  	}
  
  	// Check the last modified date on each entry
  	for (int i = 0; i < entries.length; i++) {
  	    if (entries[i].getLastModified() !=
  		getResourceModified(entries[i].getName())) {
  		synchronized (resourcesCache) {
  		    resourcesCache.remove(entries[i].getName());
  		}
  	    }
  	}
  
      }
  
  
      /**
       * Sleep for the duration specified by the <code>checkInterval</code>
       * property.
       */
      protected void threadSleep() {
  
  	try {
  	    Thread.sleep(checkInterval * 1000L);
  	} catch (InterruptedException e) {
  	    ;
  	}
  
      }
  
  
      /**
       * Start the background thread that will periodically check for
       * session timeouts.
       */
      protected void threadStart() {
  
  	if (thread != null)
  	    return;
  
  	threadDone = false;
  	threadName = "ResourcesBase[" + container.getName() + "]";
  	thread = new Thread(this, threadName);
  	thread.setDaemon(true);
  	thread.start();
  
      }
  
  
      /**
       * Stop the background thread that is periodically checking for
       * session timeouts.
       */
      protected void threadStop() {
  
  	if (thread == null)
  	    return;
  
  	threadDone = true;
  	thread.interrupt();
  	try {
  	    thread.join();
  	} catch (InterruptedException e) {
  	    ;
  	}
  
  	thread = null;
  
      }
  
  
      /**
       * Validate the format of the specified path, which should be context
       * relative and begin with a slash character.
       *
       * @param path Context-relative path to be validated
       *
       * @exception IllegalArgumentException if the specified path is null
       *  or does not have a valid format
       */
      protected void validate(String path) {
  
  	if ((path == null) || !path.startsWith("/"))
  	    throw new IllegalArgumentException
  		(sm.getString("resources.path", path));
  
  
      }
  
  
      // ------------------------------------------------------ Background Thread
  
  
      /**
       * The background thread that checks for session timeouts and shutdown.
       */
      public void run() {
  
  	// Loop until the termination semaphore is set
  	while (!threadDone) {
  	    threadSleep();
  	    threadProcess();
  	}
  
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/resources/package.html
  
  Index: package.html
  ===================================================================
  <body>
  
  <p>This package contains <code>Resources</code> implementations for the
  various supported repositories for static resource files.  They may be used
  (by a <code>Context</code> implementation) in implementing the following
  API calls:</p>
  <ul>
  <li><b>public URL getResource(String path)</b></li>
  <li><b>publid InputStream getResourceAsStream(String path)</b></li>
  </ul>
  
  <p>The implementations share a common base class that supports caching of
  resources where appropriate.  Caching (and other features) of all the
  standard <code>Resources</code> implementations are configured by setting
  the following properties (default values are in square brackets):</p>
  <ul>
  <li><b>checkInterval</b> - The interval, in seconds, between checks by our
      background thread for out-of-date cached resources, or zero for no
      checks.  [0]</li>
  <li><b>debug</b> - Debugging detail level that controls our logging, where
      zero means no debugging messages and higher numbers mean more deatail.
      [0]</li>
  <li><b>docBase</b> - The document base for this <code>Resources</code>
      implementation, which must follow the syntax specified for each
      individual implementation.  This property is transparently duplicated
      from the <code>docBase</code> property of the <code>Context</code>
      we are associated with.  [REQUIRED - NO DEFAULT]
  <li><b>expand</b> - Should getResource() calls for a "directory" return a
      directory listing (if "true") or nothing (if "false")?
      [true]</li>
  <li><b>maxCount</b> - The maximum number of resources to cache, or zero
      for no limit.  [0]
  <li><b>maxSize</b> - The maximum size (in bytes) of resources to cache, or
      zero for no limit.  [0]
  <li><b>minSize</b> - The minimum size (in bytes) of resources to cache, or
      zero for no limit.  [0]
  </ul>
  
  <p>The standard <code>Resources</code> implementations that are currently
  available include the following (with additional configuration properties
  as specified):</p>
  <ul>
  <li><b>FileResources</b> - Implementation of <code>Resources</code> that
      operates off a document base that is a directory in the server's
      local filesystem.  The document base must specify a relative or absolute
      pathname.  If it is relative, it is resolved against the application
      base for our surrounding <code>Host</code> (if any), or against the
      value of the "catalina.home" system property.  Files can be added, removed,
      or modified within this directory while the application runs -- a
      background thread periodically checks the last modified time of all
      cached resources and un-caches entries that have been modified, so that
      the updated version will be processed the next time it is requested.</li>
  <li><b>JarResources</b> - Implementation of <code>Resources</code> that
      operates off a web application archive (WAR) file directly.  In this
      environment, there is no concept of "file paths" to the various entries,
      so <code>getRealPath()</code> will always return <code>null</code>.  In
      addition, this implementation assumes that the individual entries in the
      WAR file, or the WAR file itself, will not be modified while the
      application is running -- therefore, no checking for out-of-date cache
      entries is required.  Caching is still performed, subject to the policies
      enforced by the <code>cacheable()</code> method, because the JAR file
      itself might be on a remote server.</li>
  </ul>
  
  </body>