/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/jsf/lifecycle/LifecycleImpl.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 version 2
 * as published by the Free Software Foundation.
 *
 * 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.jsf.lifecycle;

import com.caucho.util.*;

import java.io.*;
import java.util.*;
import java.util.logging.*;
import java.lang.reflect.*;

import javax.el.*;
import javax.faces.*;
import javax.faces.application.*;
import javax.faces.component.*;
import javax.faces.context.*;
import javax.faces.event.*;
import javax.faces.lifecycle.*;
import javax.faces.render.*;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.*;
import javax.servlet.jsp.JspException;
import javax.servlet.ServletException;

import com.caucho.jsf.application.*;

/**
 * The default lifecycle implementation
 */
public class LifecycleImpl extends Lifecycle
{
  private static final L10N L = new L10N(LifecycleImpl.class);
  private static final Logger log
    = Logger.getLogger(LifecycleImpl.class.getName());
  
  private ArrayList<PhaseListener> _phaseList = new ArrayList<PhaseListener>();
  private PhaseListener []_phaseListeners = new PhaseListener[0];
  
  public LifecycleImpl()
  {
  }

  public void addPhaseListener(PhaseListener listener)
  {
    if (listener == null)
      throw new NullPointerException();
    
    synchronized (_phaseList) {
      _phaseList.add(listener);
      _phaseListeners = new PhaseListener[_phaseList.size()];
      _phaseList.toArray(_phaseListeners);
    }
  }
  
  public PhaseListener []getPhaseListeners()
  {
    return _phaseListeners;
  }
  
  public void removePhaseListener(PhaseListener listener)
  {
    if (listener == null)
      throw new NullPointerException();
    
    synchronized (_phaseList) {
      _phaseList.remove(listener);
      _phaseListeners = new PhaseListener[_phaseList.size()];
      _phaseList.toArray(_phaseListeners);
    }
  }

  public void execute(FacesContext context)
    throws FacesException
  {
    boolean isFiner = log.isLoggable(Level.FINER);
    
    if (context.getResponseComplete() || context.getRenderResponse())
      return;

    beforePhase(context, PhaseId.RESTORE_VIEW);

    try {
      if (isFiner)
        log.finer("JSF[] before restore view");
      
      restoreView(context);
    } finally {
      afterPhase(context, PhaseId.RESTORE_VIEW);
    }

    if (context.getResponseComplete() || context.getRenderResponse())
      return;

    UIViewRoot viewRoot = context.getViewRoot();

    beforePhase(context, PhaseId.APPLY_REQUEST_VALUES);

    try {
      if (isFiner)
        log.finer(context.getViewRoot() + " before process decodes");
      
      viewRoot.processDecodes(context);
    }
    catch (RuntimeException e) {
      log.log(Level.WARNING, e.toString(), e);
    }
    finally {
      afterPhase(context, PhaseId.APPLY_REQUEST_VALUES);
    }

    //
    // Process Validations (processValidators)
    //
    
    if (context.getResponseComplete() || context.getRenderResponse())
      return;
    
    beforePhase(context, PhaseId.PROCESS_VALIDATIONS);

    try {
      if (isFiner)
        log.finer(context.getViewRoot() + " before process validators");
      
      viewRoot.processValidators(context);
    } finally {
      afterPhase(context, PhaseId.PROCESS_VALIDATIONS);
    }

    //
    // Update Model Values (processUpdates)
    //
    
    if (context.getResponseComplete() || context.getRenderResponse())
      return;
    
    beforePhase(context, PhaseId.UPDATE_MODEL_VALUES);

    try {
      if (isFiner)
        log.finer(context.getViewRoot() + " before process updates");
      
      viewRoot.processUpdates(context);
    } catch (RuntimeException e) {
      if (sendError(context, "processUpdates", e))
        return;
    } finally {
      afterPhase(context, PhaseId.UPDATE_MODEL_VALUES);
    }

    //
    // Invoke Application (processApplication)
    //
    
    if (context.getResponseComplete() || context.getRenderResponse())
      return;
    
    beforePhase(context, PhaseId.INVOKE_APPLICATION);

    try {
      if (isFiner)
        log.finer(context.getViewRoot() + " before process application");
      
      viewRoot.processApplication(context);
    } finally {
      afterPhase(context, PhaseId.INVOKE_APPLICATION);
    }
  }

  private void restoreView(FacesContext context)
    throws FacesException
  {
    Application app = context.getApplication();

    if (app instanceof ApplicationImpl)
      ((ApplicationImpl) app).initRequest();
    
    ViewHandler view = app.getViewHandler();

    view.initView(context);

    UIViewRoot viewRoot = context.getViewRoot();
    
    if (viewRoot != null) {
      ExternalContext extContext = context.getExternalContext();
      
      viewRoot.setLocale(extContext.getRequestLocale());

      doSetBindings(context.getELContext(), viewRoot);

      return;
    }

    String viewId = calculateViewId(context);

    String renderKitId = view.calculateRenderKitId(context);

    RenderKitFactory renderKitFactory
      = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
    
    RenderKit renderKit = renderKitFactory.getRenderKit(context, renderKitId);

    ResponseStateManager stateManager = renderKit.getResponseStateManager();

    if (stateManager.isPostback(context)) {
      viewRoot = view.restoreView(context,  viewId);

      if (viewRoot != null) {
        doSetBindings(context.getELContext(), viewRoot);
      }
      else {
        // XXX: backward compat issues with ViewHandler and StateManager

        // throw new ViewExpiredException(L.l("{0} is an expired view", viewId));

        context.renderResponse();
      
        viewRoot = view.createView(context, viewId);

        context.setViewRoot(viewRoot);
      }
      
      context.setViewRoot(viewRoot);
    }
    else {
      context.renderResponse();
      
      viewRoot = view.createView(context, viewId);

      context.setViewRoot(viewRoot);
    }
  }

  private void doSetBindings(ELContext elContext, UIComponent component)
  {
    if (component == null)
      return;

    ValueExpression binding = component.getValueExpression("binding");

    if (binding != null)
      binding.setValue(elContext, component);

    Iterator<UIComponent> iter = component.getFacetsAndChildren();
    while (iter.hasNext())
      doSetBindings(elContext, iter.next());
  }

  private String calculateViewId(FacesContext context)
  {
     Map map = context.getExternalContext().getRequestMap();

    String viewId = (String)
      map.get(RequestDispatcher.INCLUDE_PATH_INFO);

    if (viewId == null)
      viewId = context.getExternalContext().getRequestPathInfo();

    if (viewId == null)
      viewId = (String) map.get(RequestDispatcher.INCLUDE_SERVLET_PATH);

    if (viewId == null)
      viewId = context.getExternalContext().getRequestServletPath();

    return viewId;
  }
  
  public void render(FacesContext context)
    throws FacesException
  {
    if (context.getResponseComplete())
      return;

    Application app = context.getApplication();
    ViewHandler view = app.getViewHandler();

    beforePhase(context, PhaseId.RENDER_RESPONSE);

    try {
      if (log.isLoggable(Level.FINER))
        log.finer(context.getViewRoot() + " before render view");
      
      view.renderView(context, context.getViewRoot());
    } catch (java.io.IOException e) {
      if (sendError(context, "renderView", e))
        return;
      
      throw new FacesException(e);
    } catch (RuntimeException e) {
      if (sendError(context, "renderView", e))
        return;

      throw e;
    } finally {
      afterPhase(context, PhaseId.RENDER_RESPONSE);

      logMessages(context);
    }
  }

  private void beforePhase(FacesContext context, PhaseId phase)
  {
    for (int i = 0; i < _phaseListeners.length; i++) {
      PhaseListener listener = _phaseListeners[i];
      PhaseId id = listener.getPhaseId();

      if (id == phase || id == PhaseId.ANY_PHASE) {
        PhaseEvent event = new PhaseEvent(context, phase, this);

        listener.beforePhase(event);
      }
    }
  }

  private void afterPhase(FacesContext context, PhaseId phase)
  {
    for (int i = _phaseListeners.length - 1; i >= 0; i--) {
      PhaseListener listener = _phaseListeners[i];
      PhaseId id = listener.getPhaseId();

      if (phase == id || id == PhaseId.ANY_PHASE) {
        PhaseEvent event = new PhaseEvent(context, phase, this);

        listener.afterPhase(event);
      }
    }
  }

  private void logMessages(FacesContext context)
  {
    UIViewRoot root = context.getViewRoot();
    String viewId = "";

    if (root != null)
      viewId = root.getViewId();

    Iterator<FacesMessage> iter = context.getMessages();

    while (iter != null && iter.hasNext()) {
      FacesMessage msg = iter.next();

      if (log.isLoggable(Level.FINE)) {
        if (msg.getDetail() != null)
          log.fine(viewId + " [ " + msg.getSeverity() + "] " + msg.getSummary() + " " + msg.getDetail());
        else
          log.fine(viewId + " [ " + msg.getSeverity() + "] " + msg.getSummary());
      }
    }
  }

  private boolean sendError(FacesContext context,
                            String lifecycle,
                            Exception e)
  {
    for (Throwable cause = e; cause != null; cause = cause.getCause()) {
      if (cause instanceof DisplayableException) {
        if (e instanceof RuntimeException)
          throw (RuntimeException) e;
        else
          throw new FacesException(e);
      }
      else if (cause instanceof ServletException)
        throw new FacesException(e);
      else if (cause instanceof JspException)
        throw new FacesException(e);
    }

    ExternalContext extContext = context.getExternalContext();
    Object response = extContext.getResponse();

    if (! (response instanceof HttpServletResponse)) {
      context.renderResponse();

      if (e instanceof RuntimeException)
        throw (RuntimeException) e;
      else
        throw new RuntimeException(e);
    }

    log.log(Level.WARNING, e.toString(), e);
    
    HttpServletResponse res = (HttpServletResponse) response;

    try {
      context.renderResponse();
      context.responseComplete();
      
      res.setStatus(500, "JSF Exception");
      res.setContentType("text/html");

      PrintWriter out = res.getWriter();

      out.println("<body>");

      out.println("<h3>JSF exception detected in " + lifecycle + " phase</h3>");

      String msg = e.getMessage();
      out.println("<span style='color:red;font:bold'>" + Html.escapeHtml(msg) + "</span><br/>");

      out.println("<h3>Context: " + context.getViewRoot() + "</h3>");
      out.println("<code><pre>");

      String errorId = null;
      
      if (e instanceof FacesException && msg.startsWith("id=")) {
        int p = msg.indexOf(' ');
        errorId = msg.substring(3, p);
      }

      printComponentTree(out, errorId, context, context.getViewRoot(), 0);
      
      out.println("</pre></code>");

      if (! Alarm.isTest()) {
        out.println("<h3>Stack Trace</h3>");
        out.println("<pre>");
        if (e.getCause() != null)
          e.getCause().printStackTrace(out);
        else
          e.printStackTrace(out);
        out.println("</pre>");
      }
      
      out.println("</body>");
      
      // clear, so we don't just loop
      Application app = context.getApplication();
    
      ViewHandler view = app.getViewHandler();
      
      UIViewRoot viewRoot = context.getViewRoot();
      
      viewRoot = view.createView(context, viewRoot.getViewId());

      context.setViewRoot(viewRoot);

      //view.writeState(context); // XXX: no need to output state, but review.

      return true;
    } catch (IOException e1) {
      throw new RuntimeException(e);
    }
  }

  private void printComponentTree(PrintWriter out,
                                  String errorId,
                                  FacesContext context,
                                  UIComponent comp,
                                  int depth)
  {
    for (int i = 0; i < depth; i++)
      out.print(' ');

    boolean isError = false;
    if (errorId != null && errorId.equals(comp.getClientId(context))) {
      isError = true;
      out.print("<span style='color:red'>");
    }

    out.print("&lt;" + comp.getClass().getSimpleName());
    if (comp.getId() != null)
      out.print(" id=\"" + comp.getId() + "\"");

    for (Method method : comp.getClass().getMethods()) {
      if (! method.getName().startsWith("get")
          && ! method.getName().startsWith("is"))
        continue;
      else if (method.getParameterTypes().length != 0)
        continue;

      String name;

      if (method.getName().startsWith("get"))
        name = method.getName().substring(3);
      else if (method.getName().startsWith("is"))
        name = method.getName().substring(2);
      else
        continue;

      // XXX: getURL
      name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
      
      ValueExpression expr = comp.getValueExpression(name);

      Class type = method.getReturnType();

      if (expr != null) {
        out.print(" " + name + "=\"" + expr.getExpressionString() + "\"");
      }
      else if (method.getDeclaringClass().equals(UIComponent.class)
               || method.getDeclaringClass().equals(UIComponentBase.class)) {
      }
      else if (name.equals("family")) {
      }
      else if (String.class.equals(type)) {
        try {
          Object value = method.invoke(comp);

          if (value != null)
            out.print(" " + name + "=\"" + value + "\"");
        } catch (Exception e) {
        }
      }
    }

    int facetCount = comp.getFacetCount();
    int childCount = comp.getChildCount();

    if (facetCount == 0 && childCount == 0) {
      out.print("/>");

      if (isError)
        out.print("</span>");

      out.println();
      return;
    }
    out.println(">");

    if (isError)
      out.print("</span>");

    for (int i = 0; i < childCount; i++) {
      printComponentTree(out, errorId, context,
                         comp.getChildren().get(i), depth + 1);
    }
    
    for (int i = 0; i < depth; i++)
      out.print(' ');

    if (isError)
      out.print("<span style='color:red'>");
    
    out.println("&lt;/" + comp.getClass().getSimpleName() + ">");

    if (isError)
      out.print("</span>");
  }

  public String toString()
  {
    return "DefaultLifecycleImpl[]";
  }
}
