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/08/12 00:46:01 UTC

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

craigmcc    00/08/11 15:46:01

  Added:       catalina/src/share/org/apache/catalina/resources
                        Constants.java DirectoryBean.java
                        FileResources.java JarResources.java
                        LocalStrings.properties ResourceBean.java
                        ResourcesBase.java package.html
  Log:
  Migrate to Tomcat 4.0.
  
  Revision  Changes    Path
  1.1                  jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/Constants.java
  
  Index: Constants.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/Constants.java,v 1.1 2000/08/11 22:45:57 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/08/11 22:45:57 $
   *
   * ====================================================================
   *
   * 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.resources;
  
  
  public class Constants {
  
      public static final String Package = "org.apache.catalina.resources";
  
  }
  
  
  
  
  1.1                  jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/DirectoryBean.java
  
  Index: DirectoryBean.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/DirectoryBean.java,v 1.1 2000/08/11 22:45:57 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/08/11 22:45:57 $
   *
   * ====================================================================
   *
   * 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.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.catalina.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/08/11 22:45:57 $
   */
  
  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) {
  
  	// Number of characters to trim from the beginnings of filenames
  	int trim = name.length() + 1;
  	if (name.equals("/"))
  	    trim = 1;
  
  	// 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)
          String parentDirectory = name;
          if (parentDirectory.endsWith("/")) {
              parentDirectory = 
                  parentDirectory.substring(0, parentDirectory.length() - 1);
          }
  	int slash = parentDirectory.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(rewriteUrl(contextPath));
              if (parent.equals(""))
                  parent = "/";
  	    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 bgcolor=\"#cccccc\">\r\n");
  	writer.print("<td align=\"left\"><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\"><strong>");
  	writer.print(sm.getString("directory.size"));
  	writer.print("</strong></font></td>\r\n");
  	writer.print("<td align=\"right\"><font size=\"+1\"><strong>");
  	writer.print(sm.getString("directory.lastModified"));
  	writer.print("</strong></font></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++) {
  
  	    String trimmed = resources[i].getName().substring(trim);
  	    if (trimmed.equalsIgnoreCase("WEB-INF") ||
  		trimmed.equalsIgnoreCase("META-INF"))
  		continue;
  
  	    writer.print("<tr");
  	    if (shade)
  		writer.print(" bgcolor=\"eeeeee\"");
  	    writer.print(">\r\n");
  	    shade = !shade;
  
  	    writer.print("<td align=\"left\">&nbsp;&nbsp;\r\n");
  	    writer.print("<a href=\"");
  	    writer.print(rewriteUrl(contextPath));
  	    writer.print(rewriteUrl(resources[i].getName()));
  	    writer.print("\"><tt>");
  	    writer.print(trimmed);
  	    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");
  
      }
  
  
      /**
       * URL rewriter.
       * 
       * @param path Path which has to be rewiten
       */
      private String rewriteUrl(String path) {
          
          String normalized = path;
          
  	// Replace " " with "%20"
  	while (true) {
  	    int index = normalized.indexOf(" ");
  	    if (index < 0)
  		break;
  	    normalized = normalized.substring(0, index) + "%20"
  		+ normalized.substring(index + 1);
  	}
  
          return normalized;
          
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/FileResources.java
  
  Index: FileResources.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/FileResources.java,v 1.1 2000/08/11 22:45:57 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/08/11 22:45:57 $
   *
   * ====================================================================
   *
   * 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.resources;
  
  
  import java.io.ByteArrayInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.InputStream;
  import java.io.FileOutputStream;
  import java.io.FileNotFoundException;
  import java.io.OutputStream;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import org.apache.catalina.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
   * @author Remy Maucherat
   * @version $Revision: 1.1 $ $Date: 2000/08/11 22:45:57 $
   */
  
  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.catalina.resources.FileResources/1.0";
  
  
      /**
       * The descriptive information string for this implementation.
       */
      protected static final int BUFFER_SIZE = 2048;
  
  
      // ------------------------------------------------------------- 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) {
  
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (null);
  	validate(normalized);
  
  	// Return a real path to where this file does, or would, exist
  	File file = new File(base, normalized.substring(1));
  	return (file.getAbsolutePath());
  
      }
  
  
      /**
       * 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) {
  
  	String normalized = normalize(path);
  	if (normalized == null)
  	    return (null);
  	validate(normalized);
  
  	// Look up the cached resource entry (if it exists) for this path
  	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);
              File[] fileList = file.listFiles();
              for (int i=0; i<fileList.length; i++) {
                  File currentFile = fileList[i];
                  ResourceBean newEntry = null;
                  if (currentFile.isDirectory()) {
                      newEntry = 
                          new DirectoryBean(normalize(normalized + "/" 
                                                      + currentFile.getName()), 
                                                      currentFile);
                  } else {
                      newEntry = 
                          new ResourceBean(normalize(normalized + "/" 
                                                     + currentFile.getName()), 
                                                     currentFile);
                  }
                  directory.addResource(newEntry);
              }
  	    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);
  	}
  
      }
  
  
      /**
       * Returns true if a resource exists 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 false.
       *
       * @param path The path to the desired resource
       */
      public boolean exists(String path) {
          
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (false);
  	validate(normalized);
          
  	File file = new File(base, normalized.substring(1));
          if (file != null)
              return (file.exists());
          else
              return (false);
          
      }
  
  
      /**
       * 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) {
  
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	validate(normalized);
  
          String contextPath = ((Context) container).getPath();
  	File file = file(normalized);
          if (file != null)
  	    return (file.lastModified());
  	else
  	    return (-1L);
  
      }
  
  
      /**
       * Return the creation 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. If this time is unknown, the 
       * implementation should return getResourceModified(path).
       * <p>
       * <strong>IMPLEMENTATION NOTE</strong>: The creation date of a file 
       * shouldn't change except if the file is deleted and the recreated, so
       * this method uses the cache.
       *
       * @param path Context-relative path starting with '/'
       *
       * @exception IllegalArgumentException if the path argument is null
       *  or does not start with a '/'
       */
      public long getResourceCreated(String path) {
  
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	validate(normalized);
  
  	File file = file(normalized);
  	if (file != null)
  	    return (file.lastModified());
  	else
  	    return (-1L);
  
      }
  
  
      /**
       * Return the content length 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 the content length
       * of the resource can't be determined, return -1. If no content is 
       * available (when for exemple, the resource is a collection), return 0.
       *
       * @param path The path to the desired resource
       */
      public long getResourceLength(String path) {
  
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (-1L);
  	validate(normalized);
  
  	// Look up the cached resource entry (if it exists) for this path
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
          if (resource != null) {
              return (resource.getSize());
          }
          
          // No entry was found in the cache
  	File file = file(normalized);
  	if (file != null)
  	    return (file.length());
  	else
  	    return (-1L);
          
      }
  
  
      /**
       * Return true if the resource at the specified path is a collection. A
       * collection is a special type of resource which has no content but 
       * contains child resources.
       *
       * @param path The path to the desired resource
       */
      public boolean isCollection(String path) {
  
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (false);
  	validate(normalized);
  
  	File file = file(normalized);
  	if (file != null)
  	    return (file.isDirectory());
  	else
  	    return (false);
          
      }
  
  
      /**
       * Return the children of the resource at the specified path, if any. This
       * will return null if the resource is not a collection, or if it is a 
       * collection but has no children.
       * 
       * @param path The path to the desired resource
       */
      public String[] getCollectionMembers(String path) {
  
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (null);
  	validate(normalized);
  
  	File file = file(normalized);
  	if (file != null) {
  	    String[] dirList = file.list();
              for (int i=0; i<dirList.length; i++) {
                  dirList[i] = normalize(normalized + "/" + dirList[i]);
              }
              return dirList;
          } else {
  	    return (null);
          }
  
      }
  
  
      /**
       * Set the content of the resource at the specified path. If the resource
       * already exists, its previous content is overwritten. If the resource
       * doesn't exist, its immediate parent collection (according to the path 
       * given) exists, then its created, and the given content is associated
       * with it. Return false if either the resource is a collection, or
       * no parent collection exist.
       * 
       * @param path The path to the desired resource
       * @param content InputStream to the content to be set
       */
      public boolean setResource(String path, InputStream content) {
          
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (false);
  	validate(normalized);
  
  	File file = new File(base, normalized.substring(1));
          //if ((file.exists()) && (file.isDirectory()))
          //return (false);
          
          OutputStream os = null;
          
          try {
              os = new FileOutputStream(file);
          } catch (FileNotFoundException e) {
              return (false);
  	} catch (IOException e) {
  	  return (false);
          }
          
          try {
              byte[] buffer = new byte[BUFFER_SIZE];
              while (true) {
                  int nb = content.read(buffer);
                  if (nb == -1)
                      break;
                  os.write(buffer, 0, nb);
              }
          } catch (IOException e) {
              return (false);
          }
          
          try {
              os.close();
          } catch (IOException e) {
              return (false);
          }
          
          try {
              content.close();
          } catch (IOException e) {
              return (false);
          }
          
          return (true);
          
      }
  
  
      /**
       * Create a collection at the specified path. A parent collection for this
       * collection must exist. Return false if a resource already exist at the
       * path specified, or if the parent collection doesn't exist.
       * 
       * @param path The path to the desired resource
       */
      public boolean createCollection(String path) {
  
          String normalized = normalize(path);
  	if (normalized == null)
  	    return (false);
  	validate(normalized);
  
  	File file = new File(base, normalized.substring(1));
  	if (file != null)
  	    return (file.mkdir());
  	else
  	    return (false);
  
      }
  
  
      /**
       * Delete the specified resource. Non-empty collections cannot be deleted
       * before deleting all their member resources. Return false is deletion
       * fails because either the resource specified doesn't exist, or the 
       * resource is a non-empty collection.
       * 
       * @param path The path to the desired resource
       */
      public boolean deleteResource(String path) {
  
          String normalized = normalize(path);
  	if (normalized == null) {
              return (false);
          }
  	validate(normalized);
  
  	File file = file(normalized);
  	if (file != null) {
              return (file.delete());
  	} else {
  	    return (false);
          }
  
      }
  
  
      // -------------------------------------------------------- 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-4.0/catalina/src/share/org/apache/catalina/resources/JarResources.java
  
  Index: JarResources.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/JarResources.java,v 1.1 2000/08/11 22:45:57 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/08/11 22:45:57 $
   *
   * ====================================================================
   *
   * 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.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.catalina.Context;
  import org.apache.catalina.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 Catalina 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/08/11 22:45:57 $
   */
  
  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.catalina.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);
  	    }
  	}
  
      }
  
  
      /**
       * Returns true if a resource exists 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 false.
       *
       * @param path The path to the desired resource
       */
      public boolean exists(String path) {
          
  	// Look up and return the last modified time for this resource
  	String normalized = normalize(path);
  	if (normalized == null)
  	    return (false);
  	validate(normalized);
          
  	ResourceBean resource = null;
  	synchronized (resourcesCache) {
  	    resource = (ResourceBean) resourcesCache.get(normalized);
  	}
  	if (resource != null)
  	    return (true);
  	else
  	    return (false);
          
      }
  
  
      /**
       * 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);
  
      }
  
  
      /**
       * Return the creation 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. If this time is unknown, the 
       * implementation should return getResourceModified(path).
       *
       * @param path The path to the desired resource
       */
      public long getResourceCreated(String path) {
          return 0;
      }
  
  
      /**
       * Return the content length 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 the content length
       * of the resource can't be determined, return -1. If no content is 
       * available (when for exemple, the resource is a collection), return 0.
       *
       * @param path The path to the desired resource
       */
      public long getResourceLength(String path) {
          return -1;
      }
  
  
      /**
       * Return true if the resource at the specified path is a collection. A
       * collection is a special type of resource which has no content but 
       * contains child resources.
       *
       * @param path The path to the desired resource
       */
      public boolean isCollection(String path) {
          return false;
      }
  
  
      /**
       * Return the children of the resource at the specified path, if any. This
       * will return null if the resource is not a collection, or if it is a 
       * collection but has no children.
       * 
       * @param path The path to the desired resource
       */
      public String[] getCollectionMembers(String path) {
          return null;
      }
  
  
      /**
       * Set the content of the resource at the specified path. If the resource
       * already exists, its previous content is overwritten. If the resource
       * doesn't exist, its immediate parent collection (according to the path 
       * given) exists, then its created, and the given content is associated
       * with it. Return false if either the resource is a collection, or
       * no parent collection exist.
       * 
       * @param path The path to the desired resource
       * @param content InputStream to the content to be set
       */
      public boolean setResource(String path, InputStream content) {
          return false;
      }
  
  
      /**
       * Create a collection at the specified path. A parent collection for this
       * collection must exist. Return false if a resource already exist at the
       * path specified, or if the parent collection doesn't exist.
       * 
       * @param path The path to the desired resource
       */
      public boolean createCollection(String path) {
          return false;
      }
  
  
      /**
       * Delete the specified resource. Non-empty collections cannot be deleted
       * before deleting all their member resources. Return false is deletion
       * fails because either the resource specified doesn't exist, or the 
       * resource is a non-empty collection.
       * 
       * @param path The path to the desired resource
       */
      public boolean deleteResource(String path) {
          return false;
      }
  
  
      // -------------------------------------------------------- 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-4.0/catalina/src/share/org/apache/catalina/resources/LocalStrings.properties
  
  Index: LocalStrings.properties
  ===================================================================
  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
  standardResources.notStarted=Resources has not yet been started
  standardResources.null=Document base cannot be null
  standardResources.slash=Document base {0} must not end with a slash
  
  
  
  1.1                  jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/ResourceBean.java
  
  Index: ResourceBean.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/ResourceBean.java,v 1.1 2000/08/11 22:45:58 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/08/11 22:45:58 $
   *
   * ====================================================================
   *
   * 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.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/08/11 22:45:58 $
   */
  
  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-4.0/catalina/src/share/org/apache/catalina/resources/ResourcesBase.java
  
  Index: ResourcesBase.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/resources/ResourcesBase.java,v 1.1 2000/08/11 22:45:58 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 2000/08/11 22:45:58 $
   *
   * ====================================================================
   *
   * 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.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.catalina.Container;
  import org.apache.catalina.Context;
  import org.apache.catalina.Host;
  import org.apache.catalina.Lifecycle;
  import org.apache.catalina.LifecycleEvent;
  import org.apache.catalina.LifecycleException;
  import org.apache.catalina.LifecycleListener;
  import org.apache.catalina.Logger;
  import org.apache.catalina.Resources;
  import org.apache.catalina.util.LifecycleSupport;
  import org.apache.catalina.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/08/11 22:45:58 $
   */
  
  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.catalina.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);
  
  
      /**
       * Returns true if a resource exists 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 false.
       *
       * @param path The path to the desired resource
       */
      public abstract boolean exists(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);
  
  
      /**
       * Return the creation 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. If this time is unknown, the 
       * implementation should return getResourceModified(path).
       *
       * @param path The path to the desired resource
       */
      public abstract long getResourceCreated(String path);
  
  
      /**
       * Return the content length 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 the content length
       * of the resource can't be determined, return -1. If no content is 
       * available (when for exemple, the resource is a collection), return 0.
       *
       * @param path The path to the desired resource
       */
      public abstract long getResourceLength(String path);
  
  
      /**
       * Return true if the resource at the specified path is a collection. A
       * collection is a special type of resource which has no content but 
       * contains child resources.
       *
       * @param path The path to the desired resource
       */
      public abstract boolean isCollection(String path);
  
  
      /**
       * Return the children of the resource at the specified path, if any. This
       * will return null if the resource is not a collection, or if it is a 
       * collection but has no children.
       * 
       * @param path The path to the desired resource
       */
      public abstract String[] getCollectionMembers(String path);
  
  
      /**
       * Set the content of the resource at the specified path. If the resource
       * already exists, its previous content is overwritten. If the resource
       * doesn't exist, its immediate parent collection (according to the path 
       * given) exists, then its created, and the given content is associated
       * with it. Return false if either the resource is a collection, or
       * no parent collection exist.
       * 
       * @param path The path to the desired resource
       * @param content InputStream to the content to be set
       */
      public abstract boolean setResource(String path, InputStream content);
  
  
      /**
       * Create a collection at the specified path. A parent collection for this
       * collection must exist. Return false if a resource already exist at the
       * path specified, or if the parent collection doesn't exist.
       * 
       * @param path The path to the desired resource
       */
      public abstract boolean createCollection(String path);
  
  
      /**
       * Delete the specified resource. Non-empty collections cannot be deleted
       * before deleting all their member resources. Return false is deletion
       * fails because either the resource specified doesn't exist, or the 
       * resource is a non-empty collection.
       * 
       * @param path The path to the desired resource
       */
      public abstract boolean deleteResource(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 = path;
  	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 "%20" in the normalized path
  	while (true) {
  	    int index = normalized.indexOf("%20");
  	    if (index < 0)
  		break;
  	    normalized = normalized.substring(0, index) + " " +
  		normalized.substring(index + 3);
          }
  
  	// 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-4.0/catalina/src/share/org/apache/catalina/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>