Project: agile
Code Examples
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more 
 * contributor license agreements.  See the NOTICE file distributed with 
 * this work for additional information regarding copyright ownership. 
 * The ASF licenses this file to You under the Apache License, Version 2.0 
 * (the "License"); you may not use this file except in compliance with 
 * the License.  You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
 
package org.apache.catalina.servlets; 
 
 
import java.io.BufferedInputStream; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 
import java.io.PrintWriter; 
import java.io.RandomAccessFile; 
import java.io.Reader; 
import java.io.StringWriter; 
import java.util.*; 
 
import javax.naming.NameClassPair; 
import javax.naming.NamingEnumeration; 
import javax.naming.NamingException; 
import javax.naming.directory.DirContext; 
import javax.servlet.ServletException; 
import javax.servlet.ServletOutputStream; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
 
import org.headsupdev.agile.web.WebManager; 
import org.headsupdev.support.java.StringUtil; 
import org.headsupdev.agile.api.HeadsUpConfiguration; 
import org.headsupdev.agile.api.Manager; 
import org.headsupdev.agile.api.User; 
import org.headsupdev.agile.api.mime.Mime; 
import org.headsupdev.agile.api.util.FileUtil; 
import org.headsupdev.agile.web.HeadsUpPage; 
import org.headsupdev.agile.web.HeadsUpSession; 
import org.headsupdev.agile.web.components.FormattedDurationModel; 
import org.headsupdev.agile.web.components.FormattedSizeModel; 
import org.apache.catalina.Globals; 
import org.apache.catalina.util.RequestUtil; 
import org.apache.catalina.util.URLEncoder; 
import org.apache.naming.resources.*; 
 
 
/**
 * The default resource-serving servlet for most web applications, 
 * used to serve static resources such as HTML pages and images. 
 * 
 * @author Craig R. McClanahan 
 * @author Remy Maucherat 
 * @version $Id$ 
 */
 
 
public class DefaultServlet 
    extends HttpServlet { 
     
    // ----------------------------------------------------- Instance Variables 
 
    protected final String PATH_PREFIX = "/repository"
 
    /**
     * The debugging detail level for this servlet. 
     */
 
    protected int debug = 0
 
 
    /**
     * The input buffer size to use when serving resources. 
     */
 
    protected int input = 2048
 
 
    /**
     * Should we generate directory listings? 
     */
 
    protected boolean listings = true
 
 
    /**
     * The output buffer size to use when serving resources. 
     */
 
    protected int output = 2048
 
 
    /**
     * Array containing the safe characters set. 
     */
 
    protected static URLEncoder urlEncoder; 
 
 
    /**
     * Allow a readme file to be included. 
     */
 
    protected String readmeFile = null
 
 
    /**
     * Proxy directory context. 
     */
 
    protected ProxyDirContext resources = null
 
 
    /**
     * File encoding to be used when reading static files. If none is specified 
     * the platform default is used. 
     */
 
    protected String fileEncoding = null
     
     
    /**
     * Minimum size for sendfile usage in bytes. 
     */
 
    protected int sendfileSize = 48 * 1024
     
    /**
     * Should the Accept-Ranges: bytes header be send with static resources? 
     */
 
    protected boolean useAcceptRanges = true
 
    /**
     * Full range marker. 
     */
 
    protected static ArrayList FULL = new ArrayList(); 
     
     
    // ----------------------------------------------------- Static Initializer 
 
 
    /**
     * GMT timezone - all HTTP dates are on GMT 
     */
 
    static { 
        urlEncoder = new URLEncoder(); 
        urlEncoder.addSafeCharacter('-'); 
        urlEncoder.addSafeCharacter('_'); 
        urlEncoder.addSafeCharacter('.'); 
        urlEncoder.addSafeCharacter('*'); 
        urlEncoder.addSafeCharacter('/'); 
    } 
 
 
    /**
     * MIME multipart separation string 
     */
 
    protected static final String mimeSeparation = "CATALINA_MIME_BOUNDARY"
 
 
    /**
     * Size of file transfer buffer in bytes. 
     */
 
    protected static final int BUFFER_SIZE = 4096
 
 
    // --------------------------------------------------------- Public Methods 
 
 
    /**
     * Finalize this servlet. 
     */
 
    public void destroy() { 
    } 
 
 
    /**
     * Initialize this servlet. 
     */
 
    public void init() throws ServletException { 
        if (getServletConfig().getInitParameter("debug") != null
            debug = Integer.parseInt(getServletConfig().getInitParameter("debug")); 
 
        if (getServletConfig().getInitParameter("input") != null
            input = Integer.parseInt(getServletConfig().getInitParameter("input")); 
 
        if (getServletConfig().getInitParameter("output") != null
            output = Integer.parseInt(getServletConfig().getInitParameter("output")); 
 
        listings = listings || Boolean.parseBoolean(getServletConfig().getInitParameter("listings")); 
 
        if (getServletConfig().getInitParameter("sendfileSize") != null
            sendfileSize =  
                Integer.parseInt(getServletConfig().getInitParameter("sendfileSize")) * 1024
 
        fileEncoding = getServletConfig().getInitParameter("fileEncoding"); 
 
        readmeFile = getServletConfig().getInitParameter("readmeFile"); 
 
        if (getServletConfig().getInitParameter("useAcceptRanges") != null
            useAcceptRanges = Boolean.parseBoolean(getServletConfig().getInitParameter("useAcceptRanges")); 
 
        // Sanity check on the specified buffer sizes 
        if (input < 256
            input = 256
        if (output < 256
            output = 256
 
        if (debug > 0) { 
            log("DefaultServlet.init:  input buffer size=" + input + 
                ", output buffer size=" + output); 
        } 
 
        // Load the proxy dir context. 
        resources = (ProxyDirContext) getServletContext() 
            .getAttribute(Globals.RESOURCES_ATTR); 
 
        if (resources == null) { 
            setRootDirectory( new File(System.getProperty("java.io.tmpdir"))); 
        } 
    } 
 
    public void setRootDirectory( File root ) 
    { 
        FileDirContext fileDir = new FileDirContext(); 
        fileDir.setDocBase( root.getAbsolutePath() ); 
 
        if (!root.exists()) 
            root.mkdirs(); 
 
        resources = new ProxyDirContext( new Hashtable(), fileDir ); 
    } 
 
    public void setDebugboolean debug ) 
    { 
        this.debug = debug?1:0
    } 
 
    @Override 
    public void log(String msg) { 
        Manager.getLogger( "WebDavServlet" ).debug(msg); 
    } 
 
    @Override 
    public void log(String message, Throwable t) { 
        Manager.getLogger( "WebDavRepository" ).error(message, t); 
    } 
 
    public boolean isAuthenticated(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException 
    { 
        return true
    } 
 
    public boolean isAuthorized(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException 
    { 
        return true
    } 
 
    public void collectionCreated( String resource ) 
    { 
        log("[callback] col created: " + resource ); 
    } 
 
    public void collectionRemoved( String resource ) 
    { 
        log("[callback] col removed: " + resource ); 
    } 
 
    public void resourceCreated( String resource ) 
    { 
        log("[callback] res created: " + resource ); 
    } 
 
    public void resourceRemoved( String resource ) 
    { 
        log("[callback] res removed: " + resource ); 
    } 
 
    public void resourceModified( String resource ) 
    { 
        log("[callback] res modified: " + resource ); 
    } 
 
    // ------------------------------------------------------ Protected Methods 
 
 
    /**
     * Return the relative path associated with this servlet. 
     * 
     * @param request The servlet request we are processing 
     */
 
    protected String getRelativePath(HttpServletRequest request) { 
 
        // Are we being processed by a RequestDispatcher.include()? 
        if (request.getAttribute(Globals.INCLUDE_REQUEST_URI_ATTR) != null) { 
            String result = (String) request.getAttribute( 
                                            Globals.INCLUDE_PATH_INFO_ATTR); 
            if (result == null
                result = (String) request.getAttribute( 
                                            Globals.INCLUDE_SERVLET_PATH_ATTR); 
            if ((result == null) || (result.equals(""))) 
                result = "/"
            return (result); 
        } 
 
        // No, extract the desired path directly from the request 
        String result = request.getPathInfo(); 
        if (result == null) { 
            result = request.getServletPath(); 
        } 
        if ((result == null) || (result.equals(""))) { 
            result = "/"
        } 
        return (result); 
 
    } 
 
 
    /**
     * Process a GET request for the specified resource. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * 
     * @exception IOException if an input/output error occurs 
     * @exception ServletException if a servlet-specified error occurs 
     */
 
    protected void doGet(HttpServletRequest request, 
                         HttpServletResponse response) 
        throws IOException, ServletException { 
        if (!isAuthenticated(request, response) || !isAuthorized(request, response)) { 
            response.sendError(HttpServletResponse.SC_FORBIDDEN); 
            return
        } 
 
        // Serve the requested resource, including the data content 
        serveResource(request, response, true); 
 
    } 
 
 
    /**
     * Process a HEAD request for the specified resource. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * 
     * @exception IOException if an input/output error occurs 
     * @exception ServletException if a servlet-specified error occurs 
     */
 
    protected void doHead(HttpServletRequest request, 
                          HttpServletResponse response) 
        throws IOException, ServletException { 
        if (!isAuthenticated(request, response) || !isAuthorized(request, response)) { 
            response.sendError(HttpServletResponse.SC_FORBIDDEN); 
            return
        } 
 
        // Serve the requested resource, without the data content 
        serveResource(request, response, false); 
 
    } 
 
 
    /**
     * Override default implementation to ensure that TRACE is correctly 
     * handled. 
     * 
     * @param req   the {@link HttpServletRequest} object that 
     *                  contains the request the client made of 
     *                  the servlet 
     * 
     * @param resp  the {@link HttpServletResponse} object that 
     *                  contains the response the servlet returns 
     *                  to the client                                 
     * 
     * @exception IOException   if an input or output error occurs 
     *                              while the servlet is handling the 
     *                              OPTIONS request 
     * 
     * @exception ServletException  if the request for the 
     *                                  OPTIONS cannot be handled 
     */
 
    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 
        // TODO should we be restricting OPTIONS? 
        if (!isAuthenticated(req, resp) || !isAuthorized(req, resp)) { 
            resp.sendError(HttpServletResponse.SC_FORBIDDEN); 
            return
        } 
 
        StringBuffer allow = new StringBuffer(); 
        // There is a doGet method 
        allow.append("GET, HEAD"); 
        // There is a doPost 
        allow.append(", POST"); 
        // There is a doPut 
        allow.append(", PUT"); 
        // There is a doDelete 
        allow.append(", DELETE"); 
        // Always allow options 
        allow.append(", OPTIONS"); 
         
        resp.setHeader("Allow", allow.toString()); 
    } 
     
     
    /**
     * Process a POST request for the specified resource. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * 
     * @exception IOException if an input/output error occurs 
     * @exception ServletException if a servlet-specified error occurs 
     */
 
    protected void doPost(HttpServletRequest request, 
                          HttpServletResponse response) 
        throws IOException, ServletException { 
        doGet(request, response); 
    } 
 
 
    /**
     * Process a POST request for the specified resource. 
     * 
     * @param req The servlet request we are processing 
     * @param resp The servlet response we are creating 
     * 
     * @exception IOException if an input/output error occurs 
     * @exception ServletException if a servlet-specified error occurs 
     */
 
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 
 
        if (!isAuthenticated(req, resp) || !isAuthorized(req, resp)) { 
            resp.sendError(HttpServletResponse.SC_FORBIDDEN); 
            return
        } 
 
        String path = getRelativePath(req); 
 
        boolean exists = true
        try { 
            resources.lookup(path); 
        } catch (NamingException e) { 
            exists = false
        } 
 
        boolean result = true
 
        // Temp. content file used to support partial PUT 
        File contentFile = null
 
        Range range = parseContentRange(req, resp); 
 
        InputStream resourceInputStream = null
 
        // Append data specified in ranges to existing content for this 
        // resource - create a temp. file on the local filesystem to 
        // perform this operation 
        // Assume just one range is specified for now 
        if (range != null) { 
            contentFile = executePartialPut(req, range, path); 
            resourceInputStream = new FileInputStream(contentFile); 
        } else { 
            resourceInputStream = req.getInputStream(); 
        } 
 
        try { 
            Resource newResource = new Resource(resourceInputStream); 
            // FIXME: Add attributes 
            if (exists) { 
                resources.rebind(path, newResource); 
            } else { 
                resources.bind(path, newResource); 
            } 
        } catch(NamingException e) { 
            result = false
        } 
 
        if (result) { 
            if (exists) { 
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT); 
                resourceModified(path); 
            } else { 
                resp.setStatus(HttpServletResponse.SC_CREATED); 
                resourceCreated(path); 
            } 
        } else { 
            resp.sendError(HttpServletResponse.SC_CONFLICT); 
        } 
 
    } 
 
 
    /**
     * Handle a partial PUT.  New content specified in request is appended to 
     * existing content in oldRevisionContent (if present). This code does 
     * not support simultaneous partial updates to the same resource. 
     */
 
    protected File executePartialPut(HttpServletRequest req, Range range, 
                                     String path) 
        throws IOException { 
 
        // Append data specified in ranges to existing content for this 
        // resource - create a temp. file on the local filesystem to 
        // perform this operation 
        File tempDir = (File) getServletContext().getAttribute 
            ("javax.servlet.context.tempdir"); 
        // Convert all '/' characters to '.' in resourcePath 
        String convertedResourcePath = path.replace('/', '.'); 
        File contentFile = new File(tempDir, convertedResourcePath); 
        if (contentFile.createNewFile()) { 
            // Clean up contentFile when Tomcat is terminated 
            contentFile.deleteOnExit(); 
        } 
 
        RandomAccessFile randAccessContentFile = 
            new RandomAccessFile(contentFile, "rw"); 
 
        Resource oldResource = null
        try { 
            Object obj = resources.lookup(path); 
            if (obj instanceof Resource) 
                oldResource = (Resource) obj; 
        } catch (NamingException e) { 
            ; 
        } 
 
        // Copy data in oldRevisionContent to contentFile 
        if (oldResource != null) { 
            BufferedInputStream bufOldRevStream = 
                new BufferedInputStream(oldResource.streamContent(), 
                                        BUFFER_SIZE); 
 
            int numBytesRead; 
            byte[] copyBuffer = new byte[BUFFER_SIZE]; 
            while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) { 
                randAccessContentFile.write(copyBuffer, 0, numBytesRead); 
            } 
 
            bufOldRevStream.close(); 
        } 
 
        randAccessContentFile.setLength(range.length); 
 
        // Append data in request input stream to contentFile 
        randAccessContentFile.seek(range.start); 
        int numBytesRead; 
        byte[] transferBuffer = new byte[BUFFER_SIZE]; 
        BufferedInputStream requestBufInStream = 
            new BufferedInputStream(req.getInputStream(), BUFFER_SIZE); 
        while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) { 
            randAccessContentFile.write(transferBuffer, 0, numBytesRead); 
        } 
        randAccessContentFile.close(); 
        requestBufInStream.close(); 
 
        return contentFile; 
 
    } 
 
 
    /**
     * Process a POST request for the specified resource. 
     * 
     * @param req The servlet request we are processing 
     * @param resp The servlet response we are creating 
     * 
     * @exception IOException if an input/output error occurs 
     * @exception ServletException if a servlet-specified error occurs 
     */
 
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException { 
 
        if (!isAuthenticated(req, resp) || !isAuthorized(req, resp)) { 
            resp.sendError(HttpServletResponse.SC_FORBIDDEN); 
            return
        } 
 
        String path = getRelativePath(req); 
 
        boolean exists = true
        try { 
            resources.lookup(path); 
        } catch (NamingException e) { 
            exists = false
        } 
 
        if (exists) { 
            boolean result = true
            try { 
                resources.unbind(path); 
            } catch (NamingException e) { 
                result = false
            } 
            if (result) { 
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT); 
                resourceRemoved(path); 
            } else { 
                resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); 
            } 
        } else { 
            resp.sendError(HttpServletResponse.SC_NOT_FOUND); 
        } 
 
    } 
 
 
    /**
     * Check if the conditions specified in the optional If headers are 
     * satisfied. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @param resourceAttributes The resource information 
     * @return boolean true if the resource meets all the specified conditions, 
     * and false if any of the conditions is not satisfied, in which case 
     * request processing is stopped 
     */
 
    protected boolean checkIfHeaders(HttpServletRequest request, 
                                     HttpServletResponse response, 
                                     ResourceAttributes resourceAttributes) 
        throws IOException { 
 
        return checkIfMatch(request, response, resourceAttributes) 
            && checkIfModifiedSince(request, response, resourceAttributes) 
            && checkIfNoneMatch(request, response, resourceAttributes) 
            && checkIfUnmodifiedSince(request, response, resourceAttributes); 
 
    } 
 
 
    /**
     * URL rewriter. 
     * 
     * @param path Path which has to be rewiten 
     */
 
    protected String rewriteUrl(String path) { 
        return urlEncoder.encode( path ); 
    } 
 
 
    /**
     * Serve the specified resource, optionally including the data content. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @param content Should the content be included? 
     * 
     * @exception IOException if an input/output error occurs 
     * @exception ServletException if a servlet-specified error occurs 
     */
 
    protected void serveResource(HttpServletRequest request, 
                                 HttpServletResponse response, 
                                 boolean content) 
        throws IOException, ServletException { 
 
        // Identify the requested resource path 
        String path = getRelativePath(request); 
        if (debug > 0) { 
            if (content) 
                log("DefaultServlet.serveResource:  Serving resource '" + 
                    path + "' headers and data"); 
            else 
                log("DefaultServlet.serveResource:  Serving resource '" + 
                    path + "' headers only"); 
        } 
 
        CacheEntry cacheEntry = resources.lookupCache(path); 
 
        if (!cacheEntry.exists) { 
 
            // Check if we're included so we can return the appropriate  
            // missing resource name in the error 
            String requestUri = (String) request.getAttribute( 
                                            Globals.INCLUDE_REQUEST_URI_ATTR); 
            if (requestUri == null) { 
                requestUri = request.getRequestURI(); 
            } else { 
                // We're included, and the response.sendError() below is going 
                // to be ignored by the resource that is including us. 
                // Therefore, the only way we can let the including resource 
                // know is by including warning message in response 
                response.getWriter().write(RequestUtil.filter("The requested resource ("+requestUri+") is not available")); 
            } 
 
            response.sendError(HttpServletResponse.SC_NOT_FOUND, 
                               requestUri); 
            return
        } 
 
        // If the resource is not a collection, and the resource path 
        // ends with "/" or "\", return NOT FOUND 
        if (cacheEntry.context == null) { 
            if (path.endsWith("/") || (path.endsWith("\\"))) { 
                // Check if we're included so we can return the appropriate  
                // missing resource name in the error 
                String requestUri = (String) request.getAttribute( 
                                            Globals.INCLUDE_REQUEST_URI_ATTR); 
                if (requestUri == null) { 
                    requestUri = request.getRequestURI(); 
                } 
                response.sendError(HttpServletResponse.SC_NOT_FOUND, 
                                   requestUri); 
                return
            } 
        } 
 
        // Check if the conditions specified in the optional If headers are 
        // satisfied. 
        if (cacheEntry.context == null) { 
 
            // Checking If headers 
            boolean included = 
                (request.getAttribute(Globals.INCLUDE_CONTEXT_PATH_ATTR) != null); 
            if (!included 
                && !checkIfHeaders(request, response, cacheEntry.attributes)) { 
                return
            } 
 
        } 
 
        // Find content type. 
        String contentType = cacheEntry.attributes.getMimeType(); 
        if (contentType == null) { 
            contentType = getServletContext().getMimeType(cacheEntry.name); 
            cacheEntry.attributes.setMimeType(contentType); 
        } 
 
        ArrayList ranges = null
        long contentLength = -1L
 
        if (cacheEntry.context != null) { 
 
            // Skip directory listings if we have been configured to 
            // suppress them 
            if (!listings) { 
                response.sendError(HttpServletResponse.SC_NOT_FOUND, 
                                   request.getRequestURI()); 
                return
            } 
            contentType = "text/html;charset=UTF-8"
 
        } else { 
            if (useAcceptRanges) { 
                // Accept ranges header 
                response.setHeader("Accept-Ranges""bytes"); 
            } 
 
            // Parse range specifier 
            ranges = parseRange(request, response, cacheEntry.attributes); 
 
            // ETag header 
            response.setHeader("ETag", cacheEntry.attributes.getETag()); 
 
            // Last-Modified header 
            response.setHeader("Last-Modified"
                    cacheEntry.attributes.getLastModifiedHttp()); 
 
            // Get content length 
            contentLength = cacheEntry.attributes.getContentLength(); 
            // Special case for zero length files, which would cause a 
            // (silent) ISE when setting the output buffer size 
            if (contentLength == 0L) { 
                content = false
            } 
 
        } 
 
        ServletOutputStream ostream = null
        PrintWriter writer = null
 
        if (content) { 
 
            // Trying to retrieve the servlet output stream 
 
            try { 
                ostream = response.getOutputStream(); 
            } catch (IllegalStateException e) { 
                // If it fails, we try to get a Writer instead if we're 
                // trying to serve a text file 
                if ( (contentType == null
                        || (contentType.startsWith("text")) 
                        || (contentType.endsWith("xml")) ) { 
                    writer = response.getWriter(); 
                } else { 
                    throw e; 
                } 
            } 
 
        } 
 
        if ( (cacheEntry.context != null)  
                || ( ((ranges == null) || (ranges.isEmpty())) 
                        && (request.getHeader("Range") == null) ) 
                || (ranges == FULL) ) { 
 
            // Set the appropriate output headers 
            if (contentType != null) { 
                if (debug > 0
                    log("DefaultServlet.serveFile:  contentType='" + 
                        contentType + "'"); 
                response.setContentType(contentType); 
            } 
            if ((cacheEntry.resource != null) && (contentLength >= 0)) { 
                if (debug > 0
                    log("DefaultServlet.serveFile:  contentLength=" + 
                        contentLength); 
                if (contentLength < Integer.MAX_VALUE) { 
                    response.setContentLength((int) contentLength); 
                } else { 
                    // Set the content-length as String to be able to use a long 
                    response.setHeader("content-length""" + contentLength); 
                } 
            } 
 
            InputStream renderResult = null
            if (cacheEntry.context != null) { 
 
                if (content) { 
                    // Serve the directory browser 
                    renderResult = 
                        render(request.getContextPath(), cacheEntry, request); 
                } 
 
            } 
 
            // Copy the input stream to our output stream (if requested) 
            if (content) { 
                try { 
                    response.setBufferSize(output); 
                } catch (IllegalStateException e) { 
                    // Silent catch 
                } 
                if (ostream != null) { 
                    if (!checkSendfile(request, response, cacheEntry, contentLength, null)) 
                        copy(cacheEntry, renderResult, ostream); 
                } else { 
                    copy(cacheEntry, renderResult, writer); 
                } 
            } 
 
        } else { 
 
            if ((ranges == null) || (ranges.isEmpty())) 
                return
 
            // Partial content response. 
 
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); 
 
            if (ranges.size() == 1) { 
 
                Range range = (Range) ranges.get(0); 
                response.addHeader("Content-Range""bytes " 
                                   + range.start 
                                   + "-" + range.end + "/" 
                                   + range.length); 
                long length = range.end - range.start + 1
                if (length < Integer.MAX_VALUE) { 
                    response.setContentLength((int) length); 
                } else { 
                    // Set the content-length as String to be able to use a long 
                    response.setHeader("content-length""" + length); 
                } 
 
                if (contentType != null) { 
                    if (debug > 0
                        log("DefaultServlet.serveFile:  contentType='" + 
                            contentType + "'"); 
                    response.setContentType(contentType); 
                } 
 
                if (content) { 
                    try { 
                        response.setBufferSize(output); 
                    } catch (IllegalStateException e) { 
                        // Silent catch 
                    } 
                    if (ostream != null) { 
                        if (!checkSendfile(request, response, cacheEntry, range.end - range.start + 1, range)) 
                            copy(cacheEntry, ostream, range); 
                    } else { 
                        copy(cacheEntry, writer, range); 
                    } 
                } 
 
            } else { 
 
                response.setContentType("multipart/byteranges; boundary=" 
                                        + mimeSeparation); 
 
                if (content) { 
                    try { 
                        response.setBufferSize(output); 
                    } catch (IllegalStateException e) { 
                        // Silent catch 
                    } 
                    if (ostream != null) { 
                        copy(cacheEntry, ostream, ranges.iterator(), 
                             contentType); 
                    } else { 
                        copy(cacheEntry, writer, ranges.iterator(), 
                             contentType); 
                    } 
                } 
 
            } 
 
        } 
 
    } 
 
 
    /**
     * Parse the content-range header. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @return Range 
     */
 
    protected Range parseContentRange(HttpServletRequest request, 
                                      HttpServletResponse response) 
        throws IOException { 
 
        // Retrieving the content-range header (if any is specified 
        String rangeHeader = request.getHeader("Content-Range"); 
 
        if (rangeHeader == null
            return null
 
        // bytes is the only range unit supported 
        if (!rangeHeader.startsWith("bytes")) { 
            response.sendError(HttpServletResponse.SC_BAD_REQUEST); 
            return null
        } 
 
        rangeHeader = rangeHeader.substring(6).trim(); 
 
        int dashPos = rangeHeader.indexOf('-'); 
        int slashPos = rangeHeader.indexOf('/'); 
 
        if (dashPos == -1) { 
            response.sendError(HttpServletResponse.SC_BAD_REQUEST); 
            return null
        } 
 
        if (slashPos == -1) { 
            response.sendError(HttpServletResponse.SC_BAD_REQUEST); 
            return null
        } 
 
        Range range = new Range(); 
 
        try { 
            range.start = Long.parseLong(rangeHeader.substring(0, dashPos)); 
            range.end = 
                Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos)); 
            range.length = Long.parseLong 
                (rangeHeader.substring(slashPos + 1, rangeHeader.length())); 
        } catch (NumberFormatException e) { 
            response.sendError(HttpServletResponse.SC_BAD_REQUEST); 
            return null
        } 
 
        if (!range.validate()) { 
            response.sendError(HttpServletResponse.SC_BAD_REQUEST); 
            return null
        } 
 
        return range; 
 
    } 
 
 
    /**
     * Parse the range header. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @return Vector of ranges 
     */
 
    protected ArrayList parseRange(HttpServletRequest request, 
                                HttpServletResponse response, 
                                ResourceAttributes resourceAttributes) 
        throws IOException { 
 
        // Checking If-Range 
        String headerValue = request.getHeader("If-Range"); 
 
        if (headerValue != null) { 
 
            long headerValueTime = (-1L); 
            try { 
                headerValueTime = request.getDateHeader("If-Range"); 
            } catch (IllegalArgumentException e) { 
                ; 
            } 
 
            String eTag = resourceAttributes.getETag(); 
            long lastModified = resourceAttributes.getLastModified(); 
 
            if (headerValueTime == (-1L)) { 
 
                // If the ETag the client gave does not match the entity 
                // etag, then the entire entity is returned. 
                if (!eTag.equals(headerValue.trim())) 
                    return FULL; 
 
            } else { 
 
                // If the timestamp of the entity the client got is older than 
                // the last modification date of the entity, the entire entity 
                // is returned. 
                if (lastModified > (headerValueTime + 1000)) 
                    return FULL; 
 
            } 
 
        } 
 
        long fileLength = resourceAttributes.getContentLength(); 
 
        if (fileLength == 0
            return null
 
        // Retrieving the range header (if any is specified 
        String rangeHeader = request.getHeader("Range"); 
 
        if (rangeHeader == null
            return null
        // bytes is the only range unit supported (and I don't see the point 
        // of adding new ones). 
        if (!rangeHeader.startsWith("bytes")) { 
            response.addHeader("Content-Range""bytes */" + fileLength); 
            response.sendError 
                (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); 
            return null
        } 
 
        rangeHeader = rangeHeader.substring(6); 
 
        // Vector which will contain all the ranges which are successfully 
        // parsed. 
        ArrayList<Range> result = new ArrayList<Range>(); 
        StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ","); 
 
        // Parsing the range list 
        while (commaTokenizer.hasMoreTokens()) { 
            String rangeDefinition = commaTokenizer.nextToken().trim(); 
 
            Range currentRange = new Range(); 
            currentRange.length = fileLength; 
 
            int dashPos = rangeDefinition.indexOf('-'); 
 
            if (dashPos == -1) { 
                response.addHeader("Content-Range""bytes */" + fileLength); 
                response.sendError 
                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); 
                return null
            } 
 
            if (dashPos == 0) { 
 
                try { 
                    long offset = Long.parseLong(rangeDefinition); 
                    currentRange.start = fileLength + offset; 
                    currentRange.end = fileLength - 1
                } catch (NumberFormatException e) { 
                    response.addHeader("Content-Range"
                                       "bytes */" + fileLength); 
                    response.sendError 
                        (HttpServletResponse 
                         .SC_REQUESTED_RANGE_NOT_SATISFIABLE); 
                    return null
                } 
 
            } else { 
 
                try { 
                    currentRange.start = Long.parseLong 
                        (rangeDefinition.substring(0, dashPos)); 
                    if (dashPos < rangeDefinition.length() - 1
                        currentRange.end = Long.parseLong 
                            (rangeDefinition.substring 
                             (dashPos + 1, rangeDefinition.length())); 
                    else 
                        currentRange.end = fileLength - 1
                } catch (NumberFormatException e) { 
                    response.addHeader("Content-Range"
                                       "bytes */" + fileLength); 
                    response.sendError 
                        (HttpServletResponse 
                         .SC_REQUESTED_RANGE_NOT_SATISFIABLE); 
                    return null
                } 
 
            } 
 
            if (!currentRange.validate()) { 
                response.addHeader("Content-Range""bytes */" + fileLength); 
                response.sendError 
                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); 
                return null
            } 
 
            result.add(currentRange); 
        } 
 
        return result; 
    } 
 
 
    /**
     * Return an InputStream to an HTML representation of the contents 
     * of this directory. 
     * 
     * @param contextPath Context path to which our internal paths are 
     *  relative 
     */
 
    protected InputStream render(String contextPath, CacheEntry cacheEntry, HttpServletRequest req) 
        throws IOException, ServletException { 
 
        String name = cacheEntry.name; 
        if (cacheEntry.context instanceof DirContext) { 
            if (!cacheEntry.name.endsWith( "/" ) ) { 
                name = cacheEntry.name + "/"
            } 
        } 
 
        // check for index.html 
        CacheEntry indexEntry = resources.lookupCache(name + "index.html"); 
        if (indexEntry.exists) { 
            return new FileInputStream(new File(((FileDirContext) cacheEntry.context).getDocBase(), "index.html")); 
        } 
        indexEntry = resources.lookupCache(name + "index.htm"); 
        if (indexEntry.exists) { 
            return new FileInputStream(new File(((FileDirContext) cacheEntry.context).getDocBase(), "index.htm")); 
        } 
 
        // Prepare a writer to a buffered area 
        ByteArrayOutputStream stream = new ByteArrayOutputStream(); 
        OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8"); 
        PrintWriter writer = new PrintWriter(osWriter); 
 
        StringBuffer sb = new StringBuffer(); 
         
        // rewriteUrl(contextPath) is expensive. cache result for later reuse 
        String rewrittenContextPath =  rewriteUrl(contextPath); 
 
        // Render the page header 
        sb.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n"); 
        sb.append("<html xml:lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\r\n"); 
        sb.append("<head>\r\n"); 
        sb.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"/resources/org.headsupdev.agile.web.HeadsUpPage/agile.css\" />"); 
        sb.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"/resources/org.headsupdev.agile.app.files.Browse/browse.css\" />"); 
        sb.append("<title>"); 
        sb.append(Manager.getStorageInstance().getGlobalConfiguration().getProductName()); 
        sb.append(" Repository :: "); 
        sb.append(name); 
        sb.append("</title>\r\n"); 
        sb.append("</head>\r\n"); 
        sb.append("  <body>"); 
        sb.append("    <div id=\"spacer\"> </div>\r\n"); 
        sb.append("    <div id=\"outerpage\">\r\n"); 
        sb.append("      <div id=\"page\">\r\n"); 
        sb.append("        <div id=\"header1\">\r\n"); 
        sb.append("          <img src=\"/resources/org.headsupdev.agile.web.HeadsUpPage/images/agile-title.png\" />\r\n"); 
        sb.append("          <!--<h1>"); 
        sb.append(Manager.getStorageInstance().getGlobalConfiguration().getProductName()); 
        sb.append(" </h1>-->\r\n"); 
 
        sb.append("          <div id=\"userpanel\">"); 
        User user = (User) req.getAttribute( "agile-user" ); 
        if ( user == null || user.equals( HeadsUpSession.ANONYMOUS_USER ) ) { 
            sb.append("            <span class=\"username\">you are not logged in</span>"); 
        } else { 
            sb.append("            <span class=\"username\">logged in as "); 
            sb.append(user.getFullnameOrUsername()); 
            sb.append("</span>\r\n"); 
        } 
        sb.append("          </div>\r\n"); 
        sb.append("        </div>\r\n"); 
        sb.append("        <div id=\"header2\"></div>\r\n"); 
 
        sb.append("        <div id=\"mainmenu\">\r\n"); 
        sb.append("          <ul>\r\n"); 
        sb.append("            <li class=\"mainmenu-item-selected\"><a href=\""); 
        sb.append(PATH_PREFIX); 
        sb.append("/\"><span>Repository</span></a></li>\r\n"); 
        sb.append("          </ul>\r\n"); 
        sb.append("        </div>\r\n"); 
        sb.append("        <!--div id=\"submenu\">\r\n"); 
        sb.append("          <ul>\r\n"); 
        sb.append("          </ul>\r\n"); 
        sb.append("        </div-->\r\n"); 
 
        sb.append("        <div id=\"main\">"); 
        sb.append("          <div id=\"content\" style=\"margin-left: 0;\">"); 
 
        sb.append("          <h2><span>"); 
 
        int split = name.indexOf('/', 1); 
        if (name.length() < 2) { 
            sb.append("Repository"); 
        } else { 
            sb.append(StringUtil.toTitleCase(name.substring(1, split))); 
            sb.append("("); 
            sb.append(name.substring(split)); 
            sb.append(")"); 
        } 
        sb.append("</span></a>"); 
 
        sb.append("</h2>"); 
 
        sb.append("<table class=\"browse listing\" width=\"100%\">\r\n"); 
 
        // Render the column headings 
        sb.append("<tr><th class=\"icon\"> </th>\r\n"); 
        sb.append("<th class=\"title\">File</th>\r\n"); 
        sb.append("<th class=\"size\">Size</th>\r\n"); 
        sb.append("<th class=\"date\">Modified</th>\r\n"); 
        sb.append("</tr>"); 
 
        try { 
            // Render the directory entries within this directory 
            NamingEnumeration enumeration = resources.list(name); 
            boolean odd = true
 
            // 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); 
 
                sb.append("<tr class=\"odd\">\n"); 
                sb.append("          <td class=\"icon\">"); 
 
                sb.append("<a href=\""); 
                sb.append(PATH_PREFIX); 
                sb.append(rewrittenContextPath); 
                if (parent.equals("")) 
                    parent = "/"
                sb.append(rewriteUrl(parent)); 
                if (!parent.endsWith("/")) 
                    sb.append("/"); 
                sb.append("\">"); 
 
                sb.append("<img alt=\"parent\" src=\"/resources/org.headsupdev.agile.api.mime.Mime/folder_up.png\"/>"); 
 
                sb.append("</a></td>"); 
                sb.append("          <td class=\"title\">"); 
 
 
                sb.append("<a href=\""); 
                sb.append(PATH_PREFIX); 
                sb.append(rewrittenContextPath); 
                if (parent.equals("")) 
                    parent = "/"
                sb.append(rewriteUrl(parent)); 
                if (!parent.endsWith("/")) 
                    sb.append("/"); 
                sb.append("\">"); 
 
 
                sb.append("<span>..</span></a></td>\n" + 
                        "          <td colspan=\"2\"> </td>\n" + 
                        "        </tr>"); 
                odd = false
            } 
 
            // assume we should show latest until we find a file that is not suitable 
            NameClassPair latestFile = null
            boolean showLatest = enumeration.hasMoreElements(); 
            while (enumeration.hasMoreElements()) { 
                NameClassPair pair = (NameClassPair) enumeration.nextElement(); 
                showLatest = showLatest && FileUtil.isFileNumeric(pair.getName()); 
                if ( showLatest ) { 
                    // was a number, so let's create a latest item; 
                    if ( latestFile == null ) { 
                        latestFile = pair; 
                    } else { 
                        CacheEntry latestCacheEntry = 
                            resources.lookupCache(name + latestFile.getName()); 
                        CacheEntry pairCacheEntry = 
                            resources.lookupCache(name + pair.getName()); 
 
                        if (latestCacheEntry.attributes.getLastModified() < pairCacheEntry.attributes.getLastModified()) { 
                            latestFile = pair; 
                        } 
                    } 
                } 
            } 
 
            List<NameClassPair> files = new LinkedList<NameClassPair>(); 
            if (showLatest) 
            { 
                files.add(new NameClassPair(FileUtil.LATEST_ITEM_NAME, "")); 
            } 
            enumeration = resources.list(name); 
            while (enumeration.hasMoreElements()) { 
                files.add((NameClassPair) enumeration.nextElement()); 
            } 
            CacheEntry latestCacheEntry = null
            if ( latestFile != null ) { 
                latestCacheEntry = resources.lookupCache(name + latestFile.getName()); 
            } 
            final boolean latestIsFile = latestFile != null && latestCacheEntry.context == null
 
            for (NameClassPair ncPair : files) { 
                String resourceName = ncPair.getName(); 
                String childName = resourceName; 
 
                CacheEntry childCacheEntry = 
                    resources.lookupCache(name + resourceName); 
 
                String fileImage = "folder.png"
                if (resourceName.equals(FileUtil.LATEST_ITEM_NAME)) { 
                    if (latestIsFile) { 
                        fileImage = "page_white_go.png"
                    } else { 
                        fileImage = "folder_go.png"
                    } 
                    resourceName = latestFile.getName(); 
                    childName = FileUtil.LATEST_ITEM_NAME + " (" + resourceName + ")"
                    childCacheEntry = resources.lookupCache(name + resourceName); 
                    resourceName = FileUtil.LATEST_ITEM_NAME; 
                } else if (childCacheEntry.context == null
                    fileImage = Mime.get(new File(childName).getName()).getIconName(); 
 
                if (!childCacheEntry.exists) { 
                    continue
                } 
 
                sb.append("<tr class=\""); 
                if (odd) 
                    sb.append("odd"); 
                else 
                    sb.append("even"); 
                sb.append("\">\r\n"); 
                odd = !odd; 
 
                sb.append("<td class=\"icon\"><img src=\"/resources/org.headsupdev.agile.api.mime.Mime/"); 
                sb.append(fileImage); 
                sb.append("\" /></td><td class=\"title\">\r\n"); 
                sb.append("<a href=\""); 
                sb.append(PATH_PREFIX); 
                sb.append(rewrittenContextPath); 
                resourceName = rewriteUrl(name + resourceName); 
                sb.append(resourceName); 
                if (childCacheEntry.context != null
                    sb.append("/"); 
                sb.append("\"><span>"); 
                sb.append(RequestUtil.filter(childName)); 
                if (childCacheEntry.context != null
                    sb.append("/"); 
                sb.append("</span></a></td>\r\n"); 
 
                sb.append("<td class=\"size\"><span>"); 
                if (childCacheEntry.context != null
                    sb.append(" "); 
                else 
                    sb.append(FormattedSizeModel.formatSize(childCacheEntry.attributes.getContentLength())); 
                sb.append("</span></td>\r\n"); 
 
                sb.append("<td class=\"date\"><span>"); 
                sb.append(FormattedDurationModel.parseDuration(childCacheEntry.attributes.getLastModifiedDate(), 
                        new Date())); 
                sb.append(" ago</span></td>\r\n"); 
 
                sb.append("</tr>\r\n"); 
            } 
 
        } catch (NamingException e) { 
            // Something went wrong 
            throw new ServletException("Error accessing resource", e); 
        } 
 
        // Render the page footer 
        sb.append("</table>\r\n"); 
 
//        String readme = getReadme(cacheEntry.context); 
//        if (readme!=null) { 
//            sb.append(readme); 
//        } 
 
        sb.append("          </div>\r\n"); 
        sb.append("        </div>\r\n"); 
 
        TimeZone timezone = Manager.getStorageInstance().getGlobalConfiguration().getDefaultTimeZone(); 
        if (user != null) { 
            timezone = user.getTimeZone(); 
        } 
 
        sb.append("        <div id=\"footer\">\r\n"); 
        sb.append("          <div class=\"content\">\r\n"); 
        sb.append("            <span class=\"description\">"); 
        sb.append(WebManager.getInstance().getFooterDescriptionHTML(timezone)); 
        sb.append("            </span>"); 
        sb.append("            <span class=\"copyright\"><span>"); 
        sb.append(WebManager.getInstance().getFooterCopyrightHTML()); 
        sb.append("</span>\r\n"); 
 
        String note = WebManager.getInstance().getFooterNoteHTML(); 
        if (note != null) { 
            sb.append("              <span class=\"note\">"); 
            sb.append(note); 
            sb.append("</span>\n"); 
        } 
 
        sb.append("            <span class=\"update\">"); 
        // TODO we could link updates to here 
        sb.append("</span></span>\r\n"); 
        sb.append("          </div>\r\n"); 
        sb.append("        </div>\r\n"); 
        sb.append("      </div>\r\n"); 
        sb.append("    </div>\r\n"); 
 
        sb.append("  </body>\r\n"); 
        sb.append("</html>\r\n"); 
 
        // Return an input stream to the underlying bytes 
        writer.write(sb.toString()); 
        writer.flush(); 
        return (new ByteArrayInputStream(stream.toByteArray())); 
 
    } 
 
 
    /**
     * Get the readme file as a string. 
     */
 
    protected String getReadme(DirContext directory) 
        throws IOException, ServletException { 
 
        if (readmeFile != null) { 
            try { 
                Object obj = directory.lookup(readmeFile); 
                if ((obj != null) && (obj instanceof Resource)) { 
                    StringWriter buffer = new StringWriter(); 
                    InputStream is = ((Resource) obj).streamContent(); 
                    copyRange(new InputStreamReader(is), 
                            new PrintWriter(buffer)); 
                    return buffer.toString(); 
                } 
            } catch (NamingException e) { 
                if (debug > 10
                    log("readme '" + readmeFile + "' not found", e); 
 
                return null
            } 
        } 
 
        return null
    } 
 
 
    // -------------------------------------------------------- protected Methods 
 
 
    /**
     * Check if sendfile can be used. 
     */
 
    protected boolean checkSendfile(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  CacheEntry entry, 
                                  long length, Range range) { 
        if ((sendfileSize > 0
            && (entry.resource != null
            && ((length > sendfileSize) || (entry.resource.getContent() == null)) 
            && (entry.attributes.getCanonicalPath() != null
            && (Boolean.TRUE == request.getAttribute("org.apache.tomcat.sendfile.support")) 
            && (request.getClass().getName().equals("org.apache.catalina.connector.RequestFacade")) 
            && (response.getClass().getName().equals("org.apache.catalina.connector.ResponseFacade"))) { 
            request.setAttribute("org.apache.tomcat.sendfile.filename", entry.attributes.getCanonicalPath()); 
            if (range == null) { 
                request.setAttribute("org.apache.tomcat.sendfile.start"new Long(0L)); 
                request.setAttribute("org.apache.tomcat.sendfile.end"new Long(length)); 
            } else { 
                request.setAttribute("org.apache.tomcat.sendfile.start"new Long(range.start)); 
                request.setAttribute("org.apache.tomcat.sendfile.end"new Long(range.end + 1)); 
            } 
            request.setAttribute("org.apache.tomcat.sendfile.token"this); 
            return true
        } else { 
            return false
        } 
    } 
     
     
    /**
     * Check if the if-match condition is satisfied. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @param resourceInfo File object 
     * @return boolean true if the resource meets the specified condition, 
     * and false if the condition is not satisfied, in which case request 
     * processing is stopped 
     */
 
    protected boolean checkIfMatch(HttpServletRequest request, 
                                 HttpServletResponse response, 
                                 ResourceAttributes resourceAttributes) 
        throws IOException { 
 
        String eTag = resourceAttributes.getETag(); 
        String headerValue = request.getHeader("If-Match"); 
        if (headerValue != null) { 
            if (headerValue.indexOf('*') == -1) { 
 
                StringTokenizer commaTokenizer = new StringTokenizer 
                    (headerValue, ","); 
                boolean conditionSatisfied = false
 
                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) { 
                    String currentToken = commaTokenizer.nextToken(); 
                    if (currentToken.trim().equals(eTag)) 
                        conditionSatisfied = true
                } 
 
                // If none of the given ETags match, 412 Precodition failed is 
                // sent back 
                if (!conditionSatisfied) { 
                    response.sendError 
                        (HttpServletResponse.SC_PRECONDITION_FAILED); 
                    return false
                } 
 
            } 
        } 
        return true
 
    } 
 
 
    /**
     * Check if the if-modified-since condition is satisfied. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @param resourceInfo File object 
     * @return boolean true if the resource meets the specified condition, 
     * and false if the condition is not satisfied, in which case request 
     * processing is stopped 
     */
 
    protected boolean checkIfModifiedSince(HttpServletRequest request, 
                                         HttpServletResponse response, 
                                         ResourceAttributes resourceAttributes) 
        throws IOException { 
        try { 
            long headerValue = request.getDateHeader("If-Modified-Since"); 
            long lastModified = resourceAttributes.getLastModified(); 
            if (headerValue != -1) { 
 
                // If an If-None-Match header has been specified, if modified since 
                // is ignored. 
                if ((request.getHeader("If-None-Match") == null
                    && (lastModified < headerValue + 1000)) { 
                    // The entity has not been modified since the date 
                    // specified by the client. This is not an error case. 
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 
                    response.setHeader("ETag", resourceAttributes.getETag()); 
 
                    return false
                } 
            } 
        } catch (IllegalArgumentException illegalArgument) { 
            return true
        } 
        return true
 
    } 
 
 
    /**
     * Check if the if-none-match condition is satisfied. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @param resourceInfo File object 
     * @return boolean true if the resource meets the specified condition, 
     * and false if the condition is not satisfied, in which case request 
     * processing is stopped 
     */
 
    protected boolean checkIfNoneMatch(HttpServletRequest request, 
                                     HttpServletResponse response, 
                                     ResourceAttributes resourceAttributes) 
        throws IOException { 
 
        String eTag = resourceAttributes.getETag(); 
        String headerValue = request.getHeader("If-None-Match"); 
        if (headerValue != null) { 
 
            boolean conditionSatisfied = false
 
            if (!headerValue.equals("*")) { 
 
                StringTokenizer commaTokenizer = 
                    new StringTokenizer(headerValue, ","); 
 
                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) { 
                    String currentToken = commaTokenizer.nextToken(); 
                    if (currentToken.trim().equals(eTag)) 
                        conditionSatisfied = true
                } 
 
            } else { 
                conditionSatisfied = true
            } 
 
            if (conditionSatisfied) { 
 
                // For GET and HEAD, we should respond with 
                // 304 Not Modified. 
                // For every other method, 412 Precondition Failed is sent 
                // back. 
                if ( ("GET".equals(request.getMethod())) 
                     || ("HEAD".equals(request.getMethod())) ) { 
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 
                    response.setHeader("ETag", eTag); 
 
                    return false
                } else { 
                    response.sendError 
                        (HttpServletResponse.SC_PRECONDITION_FAILED); 
                    return false
                } 
            } 
        } 
        return true
 
    } 
 
 
    /**
     * Check if the if-unmodified-since condition is satisfied. 
     * 
     * @param request The servlet request we are processing 
     * @param response The servlet response we are creating 
     * @param resourceInfo File object 
     * @return boolean true if the resource meets the specified condition, 
     * and false if the condition is not satisfied, in which case request 
     * processing is stopped 
     */
 
    protected boolean checkIfUnmodifiedSince(HttpServletRequest request, 
                                           HttpServletResponse response, 
                                           ResourceAttributes resourceAttributes) 
        throws IOException { 
        try { 
            long lastModified = resourceAttributes.getLastModified(); 
            long headerValue = request.getDateHeader("If-Unmodified-Since"); 
            if (headerValue != -1) { 
                if ( lastModified >= (headerValue + 1000)) { 
                    // The entity has not been modified since the date 
                    // specified by the client. This is not an error case. 
                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); 
                    return false
                } 
            } 
        } catch(IllegalArgumentException illegalArgument) { 
            return true
        } 
        return true
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param resourceInfo The resource information 
     * @param ostream The output stream to write to 
     * 
     * @exception IOException if an input/output error occurs 
     */
 
    protected void copy(CacheEntry cacheEntry, InputStream is, 
                      ServletOutputStream ostream) 
        throws IOException { 
 
        IOException exception = null
        InputStream resourceInputStream = null
 
        // Optimization: If the binary content has already been loaded, send 
        // it directly 
        if (cacheEntry.resource != null) { 
            byte buffer[] = cacheEntry.resource.getContent(); 
            if (buffer != null) { 
                ostream.write(buffer, 0, buffer.length); 
                return
            } 
            resourceInputStream = cacheEntry.resource.streamContent(); 
        } else { 
            resourceInputStream = is; 
        } 
 
        InputStream istream = new BufferedInputStream 
            (resourceInputStream, input); 
 
        // Copy the input stream to the output stream 
        exception = copyRange(istream, ostream); 
 
        // Clean up the input stream 
        istream.close(); 
 
        // Rethrow any exception that has occurred 
        if (exception != null
            throw exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param resourceInfo The resource info 
     * @param writer The writer to write to 
     * 
     * @exception IOException if an input/output error occurs 
     */
 
    protected void copy(CacheEntry cacheEntry, InputStream is, PrintWriter writer) 
        throws IOException { 
 
        IOException exception = null
 
        InputStream resourceInputStream = null
        if (cacheEntry.resource != null) { 
            resourceInputStream = cacheEntry.resource.streamContent(); 
        } else { 
            resourceInputStream = is; 
        } 
 
        Reader reader; 
        if (fileEncoding == null) { 
            reader = new InputStreamReader(resourceInputStream); 
        } else { 
            reader = new InputStreamReader(resourceInputStream, 
                                           fileEncoding); 
        } 
 
        // Copy the input stream to the output stream 
        exception = copyRange(reader, writer); 
 
        // Clean up the reader 
        reader.close(); 
 
        // Rethrow any exception that has occurred 
        if (exception != null
            throw exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param resourceInfo The ResourceInfo object 
     * @param ostream The output stream to write to 
     * @param range Range the client wanted to retrieve 
     * @exception IOException if an input/output error occurs 
     */
 
    protected void copy(CacheEntry cacheEntry, ServletOutputStream ostream, 
                      Range range) 
        throws IOException { 
 
        IOException exception = null
 
        InputStream resourceInputStream = cacheEntry.resource.streamContent(); 
        InputStream istream = 
            new BufferedInputStream(resourceInputStream, input); 
        exception = copyRange(istream, ostream, range.start, range.end); 
 
        // Clean up the input stream 
        istream.close(); 
 
        // Rethrow any exception that has occurred 
        if (exception != null
            throw exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param resourceInfo The ResourceInfo object 
     * @param writer The writer to write to 
     * @param range Range the client wanted to retrieve 
     * @exception IOException if an input/output error occurs 
     */
 
    protected void copy(CacheEntry cacheEntry, PrintWriter writer, 
                      Range range) 
        throws IOException { 
 
        IOException exception = null
 
        InputStream resourceInputStream = cacheEntry.resource.streamContent(); 
 
        Reader reader; 
        if (fileEncoding == null) { 
            reader = new InputStreamReader(resourceInputStream); 
        } else { 
            reader = new InputStreamReader(resourceInputStream, 
                                           fileEncoding); 
        } 
 
        exception = copyRange(reader, writer, range.start, range.end); 
 
        // Clean up the input stream 
        reader.close(); 
 
        // Rethrow any exception that has occurred 
        if (exception != null
            throw exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param resourceInfo The ResourceInfo object 
     * @param ostream The output stream to write to 
     * @param ranges Enumeration of the ranges the client wanted to retrieve 
     * @param contentType Content type of the resource 
     * @exception IOException if an input/output error occurs 
     */
 
    protected void copy(CacheEntry cacheEntry, ServletOutputStream ostream, 
                      Iterator ranges, String contentType) 
        throws IOException { 
 
        IOException exception = null
 
        while ( (exception == null) && (ranges.hasNext()) ) { 
 
            InputStream resourceInputStream = cacheEntry.resource.streamContent(); 
            InputStream istream = 
                new BufferedInputStream(resourceInputStream, input); 
 
            Range currentRange = (Range) ranges.next(); 
 
            // Writing MIME header. 
            ostream.println(); 
            ostream.println("--" + mimeSeparation); 
            if (contentType != null
                ostream.println("Content-Type: " + contentType); 
            ostream.println("Content-Range: bytes " + currentRange.start 
                           + "-" + currentRange.end + "/" 
                           + currentRange.length); 
            ostream.println(); 
 
            // Printing content 
            exception = copyRange(istream, ostream, currentRange.start, 
                                  currentRange.end); 
 
            istream.close(); 
 
        } 
 
        ostream.println(); 
        ostream.print("--" + mimeSeparation + "--"); 
 
        // Rethrow any exception that has occurred 
        if (exception != null
            throw exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param resourceInfo The ResourceInfo object 
     * @param writer The writer to write to 
     * @param ranges Enumeration of the ranges the client wanted to retrieve 
     * @param contentType Content type of the resource 
     * @exception IOException if an input/output error occurs 
     */
 
    protected void copy(CacheEntry cacheEntry, PrintWriter writer, 
                      Iterator ranges, String contentType) 
        throws IOException { 
 
        IOException exception = null
 
        while ( (exception == null) && (ranges.hasNext()) ) { 
 
            InputStream resourceInputStream = cacheEntry.resource.streamContent(); 
             
            Reader reader; 
            if (fileEncoding == null) { 
                reader = new InputStreamReader(resourceInputStream); 
            } else { 
                reader = new InputStreamReader(resourceInputStream, 
                                               fileEncoding); 
            } 
 
            Range currentRange = (Range) ranges.next(); 
 
            // Writing MIME header. 
            writer.println(); 
            writer.println("--" + mimeSeparation); 
            if (contentType != null
                writer.println("Content-Type: " + contentType); 
            writer.println("Content-Range: bytes " + currentRange.start 
                           + "-" + currentRange.end + "/" 
                           + currentRange.length); 
            writer.println(); 
 
            // Printing content 
            exception = copyRange(reader, writer, currentRange.start, 
                                  currentRange.end); 
 
            reader.close(); 
 
        } 
 
        writer.println(); 
        writer.print("--" + mimeSeparation + "--"); 
 
        // Rethrow any exception that has occurred 
        if (exception != null
            throw exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param istream The input stream to read from 
     * @param ostream The output stream to write to 
     * @return Exception which occurred during processing 
     */
 
    protected IOException copyRange(InputStream istream, 
                                  ServletOutputStream ostream) { 
 
        // Copy the input stream to the output stream 
        IOException exception = null
        byte buffer[] = new byte[input]; 
        int len = buffer.length; 
        while (true) { 
            try { 
                len = istream.read(buffer); 
                if (len == -1
                    break
                ostream.write(buffer, 0, len); 
            } catch (IOException e) { 
                exception = e; 
                len = -1
                break
            } 
        } 
        return exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param reader The reader to read from 
     * @param writer The writer to write to 
     * @return Exception which occurred during processing 
     */
 
    protected IOException copyRange(Reader reader, PrintWriter writer) { 
 
        // Copy the input stream to the output stream 
        IOException exception = null
        char buffer[] = new char[input]; 
        int len = buffer.length; 
        while (true) { 
            try { 
                len = reader.read(buffer); 
                if (len == -1
                    break
                writer.write(buffer, 0, len); 
            } catch (IOException e) { 
                exception = e; 
                len = -1
                break
            } 
        } 
        return exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param istream The input stream to read from 
     * @param ostream The output stream to write to 
     * @param start Start of the range which will be copied 
     * @param end End of the range which will be copied 
     * @return Exception which occurred during processing 
     */
 
    protected IOException copyRange(InputStream istream, 
                                  ServletOutputStream ostream, 
                                  long start, long end) { 
 
        if (debug > 10
            log("Serving bytes:" + start + "-" + end); 
 
        try { 
            istream.skip(start); 
        } catch (IOException e) { 
            return e; 
        } 
 
        IOException exception = null
        long bytesToRead = end - start + 1
 
        byte buffer[] = new byte[input]; 
        int len = buffer.length; 
        while ( (bytesToRead > 0) && (len >= buffer.length)) { 
            try { 
                len = istream.read(buffer); 
                if (bytesToRead >= len) { 
                    ostream.write(buffer, 0, len); 
                    bytesToRead -= len; 
                } else { 
                    ostream.write(buffer, 0, (int) bytesToRead); 
                    bytesToRead = 0
                } 
            } catch (IOException e) { 
                exception = e; 
                len = -1
            } 
            if (len < buffer.length) 
                break
        } 
 
        return exception; 
 
    } 
 
 
    /**
     * Copy the contents of the specified input stream to the specified 
     * output stream, and ensure that both streams are closed before returning 
     * (even in the face of an exception). 
     * 
     * @param reader The reader to read from 
     * @param writer The writer to write to 
     * @param start Start of the range which will be copied 
     * @param end End of the range which will be copied 
     * @return Exception which occurred during processing 
     */
 
    protected IOException copyRange(Reader reader, PrintWriter writer, 
                                  long start, long end) { 
 
        try { 
            reader.skip(start); 
        } catch (IOException e) { 
            return e; 
        } 
 
        IOException exception = null
        long bytesToRead = end - start + 1
 
        char buffer[] = new char[input]; 
        int len = buffer.length; 
        while ( (bytesToRead > 0) && (len >= buffer.length)) { 
            try { 
                len = reader.read(buffer); 
                if (bytesToRead >= len) { 
                    writer.write(buffer, 0, len); 
                    bytesToRead -= len; 
                } else { 
                    writer.write(buffer, 0, (int) bytesToRead); 
                    bytesToRead = 0
                } 
            } catch (IOException e) { 
                exception = e; 
                len = -1
            } 
            if (len < buffer.length) 
                break
        } 
 
        return exception; 
 
    } 
 
 
 
    // ------------------------------------------------------ Range Inner Class 
 
 
    protected class Range { 
 
        public long start; 
        public long end; 
        public long length; 
 
        /**
         * Validate range. 
         */
 
        public boolean validate() { 
            if (end >= length) 
                end = length - 1
            return ( (start >= 0) && (end >= 0) && (start <= end) 
                     && (length > 0) ); 
        } 
 
        public void recycle() { 
            start = 0
            end = 0
            length = 0
        } 
 
    } 
 
 
}