/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed 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.myfaces.lifecycle;
import org.apache.myfaces.util.DebugUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.lifecycle.Lifecycle;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.portlet.PortletRequest;
import org.apache.myfaces.portlet.MyFacesGenericPortlet;
import org.apache.myfaces.portlet.PortletUtil;
/**
* Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2
* @author Manfred Geiler (latest modification by $Author: bdudney $)
* @version $Revision: 294841 $ $Date: 2005-10-04 13:13:34 -0400 (Tue, 04 Oct 2005) $
*/
public class LifecycleImpl
extends Lifecycle
{
private static final Log log = LogFactory.getLog(LifecycleImpl.class);
private List _phaseListenerList = new ArrayList();
private PhaseListener[] _phaseListenerArray = null;
public LifecycleImpl()
{
// hide from public access
}
public void execute(FacesContext facesContext)
throws FacesException
{
if (restoreView(facesContext))
{
return;
}
if (applyRequestValues(facesContext))
{
return;
}
if (processValidations(facesContext))
{
return;
}
if (updateModelValues(facesContext))
{
return;
}
if (invokeApplication(facesContext))
{
return;
}
}
// Phases
/**
* Restore View (JSF.2.2.1)
* @return true, if immediate rendering should occur
*/
private boolean restoreView(FacesContext facesContext)
throws FacesException
{
boolean skipFurtherProcessing = false;
if (log.isTraceEnabled()) log.trace("entering restoreView in " + LifecycleImpl.class.getName());
informPhaseListenersBefore(facesContext, PhaseId.RESTORE_VIEW);
if(isResponseComplete(facesContext, "restoreView", true))
{
// have to skips this phase
return true;
}
if (shouldRenderResponse(facesContext, "restoreView", true))
{
skipFurtherProcessing = true;
}
// Derive view identifier
String viewId = deriveViewId(facesContext);
Application application = facesContext.getApplication();
ViewHandler viewHandler = application.getViewHandler();
//boolean viewCreated = false;
UIViewRoot viewRoot = viewHandler.restoreView(facesContext, viewId);
if (viewRoot == null)
{
viewRoot = viewHandler.createView(facesContext, viewId);
viewRoot.setViewId(viewId);
facesContext.renderResponse();
//viewCreated = true;
}
facesContext.setViewRoot(viewRoot);
/* This section has been disabled because it causes some bug.
* Be careful if you need to re-enable it.
* Furthermore, for an unknown reason, it seems that by default
* it is executed (i.e. log.isTraceEnabled() is true).
* Bug example :
* This traceView causes DebugUtil.printComponent to print all the attributes
* of the view components.
* And if you have a data table within an aliasBean, this causes the data table
* to initialize it's value attribute while the alias isn't set.
* So, the value initializes with an UIData.EMPTY_DATA_MODEL, and not with the aliased one.
* But as it's initialized, it will not try to get the value from the ValueBinding next
* time it needs to.
* I expect this to cause more similar bugs.
* TODO : Completely remove or be SURE by default it's not executed, and it has no more side-effects.
if (log.isTraceEnabled())
{
//Note: DebugUtils Logger must also be in trace level
DebugUtils.traceView(viewCreated ? "Newly created view" : "Restored view");
}*/
if (facesContext.getExternalContext().getRequestParameterMap().isEmpty())
{
//no POST or query parameters --> set render response flag
facesContext.renderResponse();
}
recursivelyHandleComponentReferencesAndSetValid(facesContext, viewRoot);
informPhaseListenersAfter(facesContext, PhaseId.RESTORE_VIEW);
if (isResponseComplete(facesContext, "restoreView", false)
|| shouldRenderResponse(facesContext, "restoreView", false))
{
// since this phase is completed we don't need to return right away even if the response is completed
skipFurtherProcessing = true;
}
if (!skipFurtherProcessing && log.isTraceEnabled()) log.trace("exiting restoreView in " + LifecycleImpl.class.getName());
return skipFurtherProcessing;
}
/**
* Apply Request Values (JSF.2.2.2)
* @return true, if response is complete
*/
private boolean applyRequestValues(FacesContext facesContext)
throws FacesException
{
boolean skipFurtherProcessing = false;
if (log.isTraceEnabled()) log.trace("entering applyRequestValues in " + LifecycleImpl.class.getName());
informPhaseListenersBefore(facesContext, PhaseId.APPLY_REQUEST_VALUES);
if(isResponseComplete(facesContext, "applyRequestValues", true))
{
// have to return right away
return true;
}
if(shouldRenderResponse(facesContext, "applyRequestValues", true))
{
skipFurtherProcessing = true;
}
facesContext.getViewRoot().processDecodes(facesContext);
informPhaseListenersAfter(facesContext, PhaseId.APPLY_REQUEST_VALUES);
if (isResponseComplete(facesContext, "applyRequestValues", false)
|| shouldRenderResponse(facesContext, "applyRequestValues", false))
{
// since this phase is completed we don't need to return right away even if the response is completed
skipFurtherProcessing = true;
}
if (!skipFurtherProcessing && log.isTraceEnabled())
log.trace("exiting applyRequestValues in "
+ LifecycleImpl.class.getName());
return skipFurtherProcessing;
}
/**
* Process Validations (JSF.2.2.3)
* @return true, if response is complete
*/
private boolean processValidations(FacesContext facesContext) throws FacesException
{
boolean skipFurtherProcessing = false;
if (log.isTraceEnabled()) log.trace("entering processValidations in " + LifecycleImpl.class.getName());
informPhaseListenersBefore(facesContext, PhaseId.PROCESS_VALIDATIONS);
if(isResponseComplete(facesContext, "processValidations", true))
{
// have to return right away
return true;
}
if(shouldRenderResponse(facesContext, "processValidations", true))
{
skipFurtherProcessing = true;
}
facesContext.getViewRoot().processValidators(facesContext);
informPhaseListenersAfter(facesContext, PhaseId.PROCESS_VALIDATIONS);
if (isResponseComplete(facesContext, "processValidations", false)
|| shouldRenderResponse(facesContext, "processValidations", false))
{
// since this phase is completed we don't need to return right away even if the response is completed
skipFurtherProcessing = true;
}
if (!skipFurtherProcessing && log.isTraceEnabled()) log.trace("exiting processValidations in " + LifecycleImpl.class.getName());
return skipFurtherProcessing;
}
/**
* Update Model Values (JSF.2.2.4)
* @return true, if response is complete
*/
private boolean updateModelValues(FacesContext facesContext) throws FacesException
{
boolean skipFurtherProcessing = false;
if (log.isTraceEnabled()) log.trace("entering updateModelValues in " + LifecycleImpl.class.getName());
informPhaseListenersBefore(facesContext, PhaseId.UPDATE_MODEL_VALUES);
if(isResponseComplete(facesContext, "updateModelValues", true))
{
// have to return right away
return true;
}
if(shouldRenderResponse(facesContext, "updateModelValues", true))
{
skipFurtherProcessing = true;
}
facesContext.getViewRoot().processUpdates(facesContext);
informPhaseListenersAfter(facesContext, PhaseId.UPDATE_MODEL_VALUES);
if (isResponseComplete(facesContext, "updateModelValues", false)
|| shouldRenderResponse(facesContext, "updateModelValues", false))
{
// since this phase is completed we don't need to return right away even if the response is completed
skipFurtherProcessing = true;
}
if (!skipFurtherProcessing && log.isTraceEnabled()) log.trace("exiting updateModelValues in " + LifecycleImpl.class.getName());
return skipFurtherProcessing;
}
/**
* Invoke Application (JSF.2.2.5)
* @return true, if response is complete
*/
private boolean invokeApplication(FacesContext facesContext)
throws FacesException
{
boolean skipFurtherProcessing = false;
if (log.isTraceEnabled()) log.trace("entering invokeApplication in " + LifecycleImpl.class.getName());
informPhaseListenersBefore(facesContext, PhaseId.INVOKE_APPLICATION);
if(isResponseComplete(facesContext, "invokeApplication", true))
{
// have to return right away
return true;
}
if(shouldRenderResponse(facesContext, "invokeApplication", true))
{
skipFurtherProcessing = true;
}
facesContext.getViewRoot().processApplication(facesContext);
informPhaseListenersAfter(facesContext, PhaseId.INVOKE_APPLICATION);
if (isResponseComplete(facesContext, "invokeApplication", false)
|| shouldRenderResponse(facesContext, "invokeApplication", false))
{
// since this phase is completed we don't need to return right away even if the response is completed
skipFurtherProcessing = true;
}
if (!skipFurtherProcessing && log.isTraceEnabled()) log.trace("exiting invokeApplication in " + LifecycleImpl.class.getName());
return skipFurtherProcessing;
}
public void render(FacesContext facesContext) throws FacesException
{
// if the response is complete we should not be invoking the phase listeners
if(isResponseComplete(facesContext, "render", true))
{
return;
}
if (log.isTraceEnabled()) log.trace("entering renderResponse in " + LifecycleImpl.class.getName());
informPhaseListenersBefore(facesContext, PhaseId.RENDER_RESPONSE);
// also possible that one of the listeners completed the response
if(isResponseComplete(facesContext, "render", true))
{
return;
}
Application application = facesContext.getApplication();
ViewHandler viewHandler = application.getViewHandler();
try
{
viewHandler.renderView(facesContext, facesContext.getViewRoot());
}
catch (IOException e)
{
throw new FacesException(e.getMessage(), e);
}
informPhaseListenersAfter(facesContext, PhaseId.RENDER_RESPONSE);
if (log.isTraceEnabled())
{
//Note: DebugUtils Logger must also be in trace level
DebugUtils.traceView("View after rendering");
}
if (log.isTraceEnabled()) log.trace("exiting renderResponse in " + LifecycleImpl.class.getName());
}
private boolean isResponseComplete(FacesContext facesContext, String phase, boolean before) {
boolean flag = false;
if (facesContext.getResponseComplete())
{
if (log.isDebugEnabled())
log.debug("exiting from lifecycle.execute in " + phase
+ " because getResponseComplete is true from one of the " +
(before ? "before" : "after") + " listeners");
flag = true;
}
return flag;
}
private boolean shouldRenderResponse(FacesContext facesContext, String phase, boolean before) {
boolean flag = false;
if (facesContext.getRenderResponse())
{
if (log.isDebugEnabled())
log.debug("exiting from lifecycle.execute in " + phase
+ " because getRenderResponse is true from one of the " +
(before ? "before" : "after") + " listeners");
flag = true;
}
return flag;
}
private static String deriveViewId(FacesContext facesContext)
{
ExternalContext externalContext = facesContext.getExternalContext();
if (PortletUtil.isPortletRequest(facesContext))
{
PortletRequest request = (PortletRequest)externalContext.getRequest();
return request.getParameter(MyFacesGenericPortlet.VIEW_ID);
}
String viewId = externalContext.getRequestPathInfo(); //getPathInfo
if (viewId == null)
{
//No extra path info found, so it is propably extension mapping
viewId = externalContext.getRequestServletPath(); //getServletPath
DebugUtils.assertError(viewId != null,
log, "RequestServletPath is null, cannot determine viewId of current page.");
//TODO: JSF Spec 2.2.1 - what do they mean by "if the default ViewHandler implementation is used..." ?
String defaultSuffix = externalContext.getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
String suffix = defaultSuffix != null ? defaultSuffix : ViewHandler.DEFAULT_SUFFIX;
DebugUtils.assertError(suffix.charAt(0) == '.',
log, "Default suffix must start with a dot!");
int dot = viewId.lastIndexOf('.');
if (dot == -1)
{
log.error("Assumed extension mapping, but there is no extension in " + viewId);
}
else
{
viewId = viewId.substring(0, dot) + suffix;
}
}
return viewId;
}
private static void recursivelyHandleComponentReferencesAndSetValid(FacesContext facesContext,
UIComponent root)
{
for (Iterator it = root.getFacetsAndChildren(); it.hasNext(); )
{
UIComponent component = (UIComponent)it.next();
ValueBinding binding = component.getValueBinding("binding"); //TODO: constant
if (binding != null && !binding.isReadOnly(facesContext))
{
binding.setValue(facesContext, component);
}
if (component instanceof UIInput)
{
((UIInput)component).setValid(true);
}
recursivelyHandleComponentReferencesAndSetValid(facesContext, component);
}
}
public void addPhaseListener(PhaseListener phaseListener)
{
if(phaseListener == null)
{
throw new NullPointerException("PhaseListener must not be null.");
}
if (_phaseListenerList == null)
{
_phaseListenerList = new ArrayList();
if (_phaseListenerArray != null)
{
_phaseListenerList.addAll(Arrays.asList(_phaseListenerArray));
_phaseListenerArray = null;
}
}
_phaseListenerList.add(phaseListener);
}
public void removePhaseListener(PhaseListener phaseListener)
{
if(phaseListener == null)
{
throw new NullPointerException("PhaseListener must not be null.");
}
if (_phaseListenerList == null)
{
_phaseListenerList = new ArrayList();
if (_phaseListenerArray != null)
{
_phaseListenerList.addAll(Arrays.asList(_phaseListenerArray));
_phaseListenerArray = null;
}
}
_phaseListenerList.remove(phaseListener);
}
public PhaseListener[] getPhaseListeners()
{
if (_phaseListenerArray == null)
{
if (_phaseListenerList == null)
{
_phaseListenerArray = new PhaseListener[0];
}
else
{
_phaseListenerArray = (PhaseListener[])_phaseListenerList.toArray(new PhaseListener[_phaseListenerList.size()]);
_phaseListenerList = null;
}
}
return _phaseListenerArray;
}
private void informPhaseListenersBefore(FacesContext facesContext, PhaseId phaseId)
{
PhaseListener[] phaseListeners = getPhaseListeners();
for (int i = 0; i < phaseListeners.length; i++)
{
PhaseListener phaseListener = phaseListeners[i];
int listenerPhaseId = phaseListener.getPhaseId().getOrdinal();
if (listenerPhaseId == PhaseId.ANY_PHASE.getOrdinal() ||
listenerPhaseId == phaseId.getOrdinal())
{
phaseListener.beforePhase(new PhaseEvent(facesContext, phaseId, this));
}
}
}
private void informPhaseListenersAfter(FacesContext facesContext, PhaseId phaseId)
{
PhaseListener[] phaseListeners = getPhaseListeners();
for (int i = 0; i < phaseListeners.length; i++)
{
PhaseListener phaseListener = phaseListeners[i];
int listenerPhaseId = phaseListener.getPhaseId().getOrdinal();
if (listenerPhaseId == PhaseId.ANY_PHASE.getOrdinal() ||
listenerPhaseId == phaseId.getOrdinal())
{
phaseListener.afterPhase(new PhaseEvent(facesContext, phaseId, this));
}
}
}
}