FileDocCategorySizeDatePackage
ApplicationDispatcherForward.javaAPI DocGlassfish v2 API14785Fri May 04 22:31:54 BST 2007org.apache.catalina.core

ApplicationDispatcherForward.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * Portions Copyright Apache Software Foundation.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */


package org.apache.catalina.core;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.deploy.ErrorPage;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.ResponseUtil;
import org.apache.catalina.util.StringManager;
import org.apache.catalina.valves.ErrorReportValve;
import org.apache.coyote.tomcat5.CoyoteResponseFacade;

import com.sun.org.apache.commons.logging.Log;
import com.sun.org.apache.commons.logging.LogFactory;

/**
 * Class responsible for processing the result of a RD.forward() invocation
 * before committing the response.
 *
 * If sendError() was called during RD.forward(), an attempt is made to match
 * the status code against the error pages of the RD's associated context, or
 * those of the host on which the context has been deployed.
 *
 * The response contents are then committed, to comply with SRV.8.4
 * ("The Forward Method"):
 * 
 *   Before the forward method of the <code>RequestDispatcher</code>
 *   interface returns without exception, the response content must be
 *   sent and committed, and closed by the servlet container.
 *
 *   If an error occurs in the target of the
 *   <code>RequestDispatcher.forward()</code> the exception may be
 *   propogated back through all the calling filters and servlets and
 *   eventually back to the container.
 */
class ApplicationDispatcherForward {

    private static Log log = LogFactory.getLog(
        ApplicationDispatcherForward.class);

    private static final StringManager sm =
        StringManager.getManager(org.apache.catalina.valves.Constants.Package);


    static void commit(HttpServletRequest request,
                       HttpServletResponse response,
                       Context context,
                       Wrapper wrapper)
            throws IOException, ServletException {

        CoyoteResponseFacade responseFacade = getResponseFacade(response);
        int statusCode = responseFacade.getStatus();
        Object exception = request.getAttribute(Globals.EXCEPTION_ATTR);
        String errorReportValveClass = 
            ((StandardHost)(context.getParent())).getErrorReportValveClass();

        if (errorReportValveClass != null && statusCode >= 400
                && exception == null) {
            boolean matchFound = status(request, response, responseFacade,
                                        context, wrapper, statusCode);
            if (!matchFound) {
                serveDefaultErrorPage(request, response, responseFacade,
                                      statusCode);
            }
        }

        /*
         * Commit the response only if no exception
         */
        if (statusCode < 400
                || (exception == null && errorReportValveClass != null)) {
            try {
                PrintWriter writer = response.getWriter();
                writer.flush();
                writer.close();
            } catch (IllegalStateException e) {
                try {
                    ServletOutputStream stream = response.getOutputStream();
                    stream.flush();
                    stream.close();
                } catch (IllegalStateException f) {
                    ;
                } catch (IOException f) {
                    ;
                }
            } catch (IOException e) {
                ;
            }
        }
    }


    /*
     * Searches and processes a custom error page for the given status code.
     *
     * This method attempts to map the given status code to an error page,
     * using the mappings of the given context or those of the host on which
     * the given context has been deployed.
     *
     * If a match is found using the context mappings, the request is forwarded
     * to the error page. Otherwise, if a match is found using the host 
     * mappings, the contents of the error page are returned. If no match is
     * found, no action is taken.
     *
     * @return true if a matching error page was found, false otherwise
     */
    private static boolean status(HttpServletRequest request,
                                  HttpServletResponse response,
                                  CoyoteResponseFacade responseFacade,
                                  Context context,
                                  Wrapper wrapper,
                                  int statusCode) {

        /*
         * Attempt error-page mapping only if response.sendError(), as
         * opposed to response.setStatus(), was called.
         */
        if (!responseFacade.isError()) {
            return false;
        }

        boolean matchFound = false;

        ErrorPage errorPage = context.findErrorPage(statusCode);
        if (errorPage != null) {

            matchFound = true;

            // Prevent infinite loop
            String requestPath = (String) request.getAttribute(
                ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR);
            if (requestPath == null
                    || !requestPath.equals(errorPage.getLocation())) {
                String message = RequestUtil.filter(responseFacade.getMessage());
                if (message == null) {
                    message = "";
                }
                prepareRequestForDispatch(request,
                                          wrapper,
                                          errorPage.getLocation(),
                                          statusCode,
                                          message);
                custom(request, response, responseFacade, errorPage, context);
            }
        } else {
            errorPage = ((StandardHost) context.getParent()).findErrorPage(
                                            statusCode);
            if (errorPage != null) {
                matchFound = true;
                try {
                    serveErrorPage(response, errorPage, statusCode);
                } catch (Exception e) {
                    log.warn("Exception processing " + errorPage, e);
                }
            }
        }

        return matchFound;
    }


    /**
     * Handles an HTTP status code or exception by forwarding control
     * to the location included in the specified errorPage object. 
     */
    private static void custom(HttpServletRequest request,
                               HttpServletResponse response,
                               CoyoteResponseFacade responseFacade,
                               ErrorPage errorPage,
                               Context context) {
        try {
            // Forward control to the specified error page
            if (response.isCommitted()) {
                /*
                 * If the target of the RD.forward() has called
                 * response.sendError(), the response will have been committed,
                 * and any attempt to RD.forward() the response to the error
                 * page will cause an IllegalStateException.
                 * Uncommit the response.
                 */
                resetResponse(responseFacade);
            }
            ServletContext servletContext = context.getServletContext();
            RequestDispatcher rd =
                servletContext.getRequestDispatcher(errorPage.getLocation());
            rd.forward(request, response);
        } catch (IllegalStateException ise) {
            log.warn("Exception processing " + errorPage, ise);
        } catch (Throwable t) {
            log.warn("Exception processing " + errorPage, t);
        }
    }


    /**
     * Adds request attributes in preparation for RD.forward().
     */
    private static void prepareRequestForDispatch(HttpServletRequest request,
                                                  Wrapper errorServlet,
                                                  String errorPageLocation,
                                                  int errorCode,
                                                  String errorMessage) {
        request.setAttribute(
            ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
            Integer.valueOf(ApplicationFilterFactory.ERROR));

        request.setAttribute(
            Globals.EXCEPTION_PAGE_ATTR,
            request.getRequestURI());

        if (errorServlet != null) {
            // Save the logical name of the servlet in which the error occurred
            request.setAttribute(Globals.SERVLET_NAME_ATTR,
                                 errorServlet.getName());
        }

        request.setAttribute(
            ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
            errorPageLocation);

        request.setAttribute(Globals.STATUS_CODE_ATTR,
                             Integer.valueOf(errorCode));

        request.setAttribute(Globals.ERROR_MESSAGE_ATTR, errorMessage);
    }

    /**
     * Copies the contents of the given error page to the response.
     */
    private static void serveErrorPage(HttpServletResponse response,
                                       ErrorPage errorPage,
                                       int statusCode)
            throws Exception {

        ServletOutputStream ostream = null;
        PrintWriter writer = null;
        FileReader reader = null;
        BufferedInputStream istream = null;
        IOException ioe = null;

        String message = errorPage.getReason();
        if (message != null && !response.isCommitted()) {
            response.reset();
            response.setStatus(statusCode, message);
        }
         
        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
            writer = response.getWriter();
        }

        if (writer != null) {
            reader = new FileReader(errorPage.getLocation());
            ioe = ResponseUtil.copy(reader, writer);
            try {
                reader.close();
            } catch (Throwable t) {
                ;
            }
        } else {
            istream = new BufferedInputStream(
                new FileInputStream(errorPage.getLocation()));
            ioe = ResponseUtil.copy(istream, ostream);
            try {
                istream.close();
            } catch (Throwable t) {
                ;
            }
        }

        // Rethrow any exception that may have occurred
        if (ioe != null) {
            throw ioe;
        }
    }


    /**
     * Renders the default error page.
     */
    private static void serveDefaultErrorPage(HttpServletRequest request,
                                              HttpServletResponse response,
                                              CoyoteResponseFacade responseFacade,
                                              int statusCode)
            throws IOException, ServletException {

        // Do nothing on a 1xx, 2xx and 3xx status
        if (response.isCommitted() || statusCode < 400
                || responseFacade.getContentCount() > 0) {
            return;
        }

        String message = RequestUtil.filter(responseFacade.getMessage());
        if (message == null) {
            message = "";
        }

        // Do nothing if there is no report for the specified status code
        String report = null;
        try {
            report = sm.getString("http." + statusCode, message);
        } catch (Throwable t) {
            ;
        }
        if (report == null) {
            return;
        }

        String responseContents =
            ErrorReportValve.makeErrorPage(statusCode, message, null, null,
                                           report, response);
        // START SJSAS 6412710
        response.setLocale(sm.getResourceBundleLocale(response.getLocale()));
        // END SJSAS 6412710

        try {
            response.setContentType("text/html");
            response.getWriter().write(responseContents);
        } catch (Throwable t) {
            log.warn("Exception sending default error page", t);
        }
    }

    
    private static CoyoteResponseFacade getResponseFacade(
            ServletResponse response) {
   
        while (response instanceof ServletResponseWrapper) {
            response = ((ServletResponseWrapper) response).getResponse();
        }

        return ((CoyoteResponseFacade) response);
    }

    
    private static void resetResponse(CoyoteResponseFacade responseFacade) {
        responseFacade.setSuspended(false);
        responseFacade.setAppCommitted(false);
    }
}