/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/server/webapp/RequestDispatcherImpl.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.server.webapp;

import com.caucho.server.dispatch.Invocation;
import com.caucho.server.http.AbstractHttpResponse;
import com.caucho.server.http.AbstractResponseStream;
import com.caucho.server.http.CauchoRequest;
import com.caucho.server.http.CauchoResponse;
import com.caucho.server.http.HttpServletRequestImpl;
import com.caucho.server.http.HttpServletResponseImpl;
import com.caucho.util.L10N;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.logging.Logger;

public class RequestDispatcherImpl implements RequestDispatcher {
  private final static Logger log
    = Logger.getLogger(RequestDispatcherImpl.class.getName());

  private static final L10N L = new L10N(RequestDispatcherImpl.class);

  static final int MAX_DEPTH = 64;

  // WebApp the request dispatcher was called from
  private WebApp _webApp;
  private Invocation _includeInvocation;
  private Invocation _forwardInvocation;
  private Invocation _errorInvocation;
  private Invocation _dispatchInvocation;
  private Invocation _requestInvocation;
  private Invocation _asyncInvocation;
  private boolean _isLogin;

  RequestDispatcherImpl(Invocation includeInvocation,
                        Invocation forwardInvocation,
                        Invocation errorInvocation,
                        Invocation dispatchInvocation,
                        Invocation requestInvocation,
                        WebApp webApp)
  {
    _includeInvocation = includeInvocation;
    _forwardInvocation = forwardInvocation;
    _errorInvocation = errorInvocation;
    _dispatchInvocation = dispatchInvocation;
    _requestInvocation = requestInvocation;

    _webApp = webApp;
  }

  public void setLogin(boolean isLogin)
  {
    _isLogin = isLogin;
  }

  public boolean isModified()
  {
    return _includeInvocation.isModified();
  }
  
  public Invocation getAsyncInvocation()
  {
    if (_asyncInvocation == null) {
      Invocation invocation = new Invocation();
      invocation.copyFrom(_dispatchInvocation);
      
      FilterChain chain = invocation.getFilterChain();
      chain = new ResumeFilterChain(chain, invocation.getWebApp());
      
      invocation.setFilterChain(chain);
      
      _asyncInvocation = invocation;
    }
    
    return _asyncInvocation;
  }

  @Override
  public void forward(ServletRequest request, ServletResponse response)
    throws ServletException, IOException
  {
    forward(request, response,
            null, _forwardInvocation, DispatcherType.FORWARD);
  }

  public void dispatchResume(ServletRequest request, ServletResponse response)
    throws ServletException, IOException
  {
    // server/1lb1
    dispatchResume((HttpServletRequest) request, (HttpServletResponse) response,
                   getAsyncInvocation());
  }

  public void error(ServletRequest request, ServletResponse response)
    throws ServletException, IOException
  {
    forward(request, response, "error", _errorInvocation, DispatcherType.ERROR);
  }

  public void dispatch(ServletRequest request, ServletResponse response)
    throws ServletException, IOException
  {
    if (request.getServletContext() instanceof UnknownWebApp) {
      _requestInvocation.getFilterChain().doFilter(request, response);
    }
    else {
      forward(request, response,
              "error", _dispatchInvocation, DispatcherType.REQUEST);
    }
  }

  /**
   * Forwards the request to the servlet named by the request dispatcher.
   *
   * @param topRequest the servlet request.
   * @param topResponse the servlet response.
   * @param method special to tell if from error.
   */
  public void forward(ServletRequest topRequest, ServletResponse topResponse,
                      String method, Invocation invocation,
                      DispatcherType type)
    throws ServletException, IOException
  {
    CauchoResponse cauchoRes = null;

    boolean allowForward = false;
    
    if (_webApp != null)
      allowForward = _webApp.isAllowForwardAfterFlush();

    if (topResponse instanceof CauchoResponse) {
      cauchoRes = (CauchoResponse) topResponse;

      cauchoRes.setForwardEnclosed(! allowForward);
    }

    // jsp/15m8
    if (topResponse.isCommitted() && method == null && ! allowForward) {
      IllegalStateException exn;
      exn = new IllegalStateException(L.l("forward() not allowed after buffer has committed."));

      if (cauchoRes == null || ! cauchoRes.hasError()) {
        if (cauchoRes != null)
          cauchoRes.setHasError(true);
        throw exn;
      }

      _webApp.log(exn.getMessage(), exn);

      return;
    } else if ("error".equals(method) || (method == null)) {
      // server/10yg
      
      // } else if ("error".equals(method) || (method == null && ! allowForward)) {
      
      topResponse.resetBuffer();

      if (cauchoRes != null) {
        // server/10yh
        // ServletResponse resp = cauchoRes.getResponse();
        ServletResponse resp = cauchoRes;

        while (resp != null) {
          if (allowForward && resp instanceof IncludeResponse) {
            // server/10yh
            break;
          }
          else if (resp instanceof CauchoResponse) {
            CauchoResponse cr = (CauchoResponse) resp;
            cr.resetBuffer();
            resp = cr.getResponse();
          } else {
            resp.resetBuffer();

            resp = null;
          }
        }
      }
    }

    HttpServletRequest parentReq;
    ServletRequestWrapper reqWrapper = null;
    if (topRequest instanceof ServletRequestWrapper) {
      // reqWrapper = (ServletRequestWrapper) req;

      ServletRequest request = topRequest; // reqWrapper.getRequest();

      while (request instanceof ServletRequestWrapper) {
        reqWrapper = (ServletRequestWrapper) request;
      
        request = ((ServletRequestWrapper) request).getRequest();
      }

      parentReq = (HttpServletRequest) request;
    } else if (topRequest instanceof HttpServletRequest) {
      parentReq = (HttpServletRequest) topRequest;
    } else {
      throw new IllegalStateException(L.l(
        "expected instance of ServletRequest at `{0}'", topRequest));
    }

    HttpServletResponse parentRes;
    ServletResponseWrapper resWrapper = null;

    if (topResponse instanceof ServletResponseWrapper) {
      ServletResponse response = topResponse;

      while (response instanceof ServletResponseWrapper) {
        resWrapper = (ServletResponseWrapper) response;

        response = ((ServletResponseWrapper) response).getResponse();
      }

      parentRes = (HttpServletResponse) response;
    } else if (topResponse instanceof HttpServletResponse) {
      parentRes = (HttpServletResponse) topResponse;
    } else {
      throw new IllegalStateException(L.l(
        "expected instance of ServletResponse at `{0}'", topResponse));
    }

    ForwardRequest subRequest;

    if (_isLogin)
      subRequest = new LoginRequest(parentReq, parentRes, invocation);
    else if (type == DispatcherType.ERROR)
      subRequest = new ErrorRequest(parentReq, parentRes, invocation);
    else if (type == DispatcherType.REQUEST)
      subRequest = new DispatchRequest(parentReq, parentRes, invocation);
    else
      subRequest = new ForwardRequest(parentReq, parentRes, invocation);

    // server/10ye
    if (subRequest.getRequestDepth(0) > MAX_DEPTH)
      throw new ServletException(L.l("too many servlet forwards `{0}'", parentReq.getServletPath()));

    ForwardResponse subResponse = subRequest.getResponse();

    if (reqWrapper != null) {
      reqWrapper.setRequest(subRequest);
    }
    else {
      topRequest = subRequest;
    }

    if (resWrapper != null) {
      resWrapper.setResponse(subResponse);
    }
    else {
      topResponse = subResponse;
    }
    
    boolean isValid = false;

    subRequest.startRequest();
    Thread thread = Thread.currentThread();
    ClassLoader loader = thread.getContextClassLoader();

    try {
      // server/1s30
      if (_webApp != null)
        thread.setContextClassLoader(_webApp.getClassLoader());
      
      invocation.service(topRequest, topResponse);

      isValid = true;
    } finally {
      if (reqWrapper != null)
        reqWrapper.setRequest(parentReq);
      
      if (resWrapper != null)
        resWrapper.setResponse(parentRes);

      subRequest.finishRequest(isValid);
      
      // server/106r, ioc/0310
      if (isValid) {
        finishResponse(topResponse);
      }
      
      thread.setContextClassLoader(loader);
    }
  }

  private void finishResponse(ServletResponse res)
    throws ServletException, IOException
  {
    if (_webApp.isAllowForwardAfterFlush()) {
      //
    } else {
      if (res instanceof CauchoResponse) {
        CauchoResponse cauchoResponse = (CauchoResponse) res;
        cauchoResponse.close();

        ServletResponse resp = cauchoResponse.getResponse();

        while(resp != null) {
          if (resp instanceof CauchoResponse) {
            CauchoResponse cr = (CauchoResponse)resp;
            cr.close();
            resp = cr.getResponse();
          } else {
            resp = null;
          }
        }
      } else {
        try {
          OutputStream os = res.getOutputStream();
          os.close();
        } catch (Exception e) {
        }

        try {
          PrintWriter out = res.getWriter();
          out.close();
        } catch (Exception e) {
        }
      }
    }
  }
  
  public void include(ServletRequest request, ServletResponse response)
    throws ServletException, IOException
  {
    include(request, response, null);
  }

  /**
   * Include a request into the current page.
   */
  public void include(ServletRequest topRequest, ServletResponse topResponse,
                      String method)
    throws ServletException, IOException
  {
    Invocation invocation = _includeInvocation;

    HttpServletRequest parentReq;
    ServletRequestWrapper reqWrapper = null;

    if (topRequest instanceof ServletRequestWrapper) {
      ServletRequest request = topRequest;
      
      while (request instanceof ServletRequestWrapper) {
        reqWrapper = (ServletRequestWrapper) request;
        
        request = ((ServletRequestWrapper) request).getRequest();
      }

      parentReq = (HttpServletRequest) request;
    } else if (topRequest instanceof HttpServletRequest) {
      parentReq = (HttpServletRequest) topRequest;
    } else {
      throw new IllegalStateException(L.l(
        "expected instance of ServletRequestWrapper at `{0}'", topResponse));
    }

    HttpServletResponse parentRes;
    ServletResponseWrapper resWrapper = null;

    if (topResponse instanceof ServletResponseWrapper) {
      ServletResponse response = topResponse;
      
      while (response instanceof ServletResponseWrapper) {
        resWrapper = (ServletResponseWrapper) response;
        
        response = ((ServletResponseWrapper) response).getResponse();
      }

      parentRes = (HttpServletResponse) response;
    } else if (topResponse instanceof HttpServletResponse) {
      parentRes = (HttpServletResponse) topResponse;
    } else {
      throw new IllegalStateException(L.l(
        "expected instance of ServletResponse at '{0}'", topResponse));
    }

    IncludeRequest subRequest
      = new IncludeRequest(parentReq, parentRes, invocation);
    
    // server/10yf, jsp/15di
    if (subRequest.getRequestDepth(0) > MAX_DEPTH)
      throw new ServletException(L.l("too many servlet includes '{0}'", parentReq.getServletPath()));

    IncludeResponse subResponse = subRequest.getResponse();

    if (reqWrapper != null) {
      reqWrapper.setRequest(subRequest);
    }
    else {
      topRequest = subRequest;
    }

    if (resWrapper != null) {
      resWrapper.setResponse(subResponse);
    }
    else {
      topResponse = subResponse;
    }
    
    // jsp/15lf, jsp/17eg - XXX: integrated with ResponseStream?
    // res.flushBuffer();

    subRequest.startRequest();

    try {
      invocation.service(topRequest, topResponse);
    } finally {
      if (reqWrapper != null)
        reqWrapper.setRequest(parentReq);
      
      if (resWrapper != null)
        resWrapper.setResponse(parentRes);
      
      subRequest.finishRequest();
    }
  }

  /**
   * Dispatch the async resume request to the servlet
   * named by the request dispatcher.
   *
   * @param request the servlet request.
   * @param response the servlet response.
   * @param invocation current invocation
   */
  public void dispatchResume(HttpServletRequest request,
                             HttpServletResponse response,
                             Invocation invocation)
    throws ServletException, IOException
  {
    HttpServletRequestWrapper parentRequest = null;
    HttpServletRequestImpl bottomRequest = null;
    HttpServletResponseImpl bottomResponse = null;
    
    HttpServletRequest req = request;
    while (req != null && req instanceof HttpServletRequestWrapper) {
      parentRequest = (HttpServletRequestWrapper) req;
      
      req = (HttpServletRequest) parentRequest.getRequest();
    }
    
    if (! (req instanceof HttpServletRequestImpl)) {
      throw new IllegalStateException(L.l("Wrapped async requests must use HttpServletRequestWrapper around the original request"));
    }
    
    bottomRequest = (HttpServletRequestImpl) req;
    
    HttpServletResponse res = response;
    while (res != null && res instanceof HttpServletResponseWrapper) {
      HttpServletResponseWrapper parentResponse
        = (HttpServletResponseWrapper) res;
      
      res = (HttpServletResponse) parentResponse.getResponse();
    }
    
    if (! (res instanceof HttpServletResponseImpl)) {
      throw new IllegalStateException(L.l("Wrapped async requests must use HttpServletRequestWrapper around the original request"));
    }
    
    bottomResponse = (HttpServletResponseImpl) res;
    
    AsyncRequest asyncRequest
      = new AsyncRequest(bottomRequest, bottomResponse, invocation);
    
    if (parentRequest != null) {
      parentRequest.setRequest(asyncRequest);
    }
    else {
      request = asyncRequest;
    }

    Thread thread = Thread.currentThread();
    ClassLoader oldLoader = thread.getContextClassLoader();

    try {
      invocation.service(request, response);
    } finally {
      // subRequest.finishRequest();

      thread.setContextClassLoader(oldLoader);
    }
  }

  @Override
  public String toString()
  {
    return (getClass().getSimpleName()
            + "[" + _dispatchInvocation.getRawURI() + "]");
  }
}
