001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 package org.apache.myfaces.tobago.lifecycle; 021 022 import org.apache.commons.logging.Log; 023 import org.apache.commons.logging.LogFactory; 024 import org.apache.myfaces.tobago.component.ComponentUtil; 025 import org.apache.myfaces.tobago.util.RequestUtils; 026 027 import javax.faces.FacesException; 028 import javax.faces.context.FacesContext; 029 import javax.faces.event.PhaseId; 030 import javax.faces.event.PhaseListener; 031 import javax.faces.lifecycle.Lifecycle; 032 import java.util.ArrayList; 033 import java.util.List; 034 035 /** 036 * Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2 037 */ 038 public class TobagoLifecycle extends Lifecycle { 039 040 private static final Log LOG = LogFactory.getLog(TobagoLifecycle.class); 041 042 public static final String VIEW_ROOT_KEY = TobagoLifecycle.class.getName() + ".VIEW_ROOT_KEY"; 043 public static final String FACES_MESSAGES_KEY = TobagoLifecycle.class.getName() + ".FACES_MESSAGES_KEY"; 044 045 private PhaseExecutor[] lifecycleExecutors; 046 private PhaseExecutor renderExecutor; 047 048 private final List<PhaseListener> phaseListenerList = new ArrayList<PhaseListener>(); 049 050 /** 051 * Lazy cache for returning phaseListenerList as an Array. 052 */ 053 private PhaseListener[] phaseListenerArray = null; 054 055 public TobagoLifecycle() { 056 // hide from public access 057 lifecycleExecutors = new PhaseExecutor[]{ 058 new RestoreViewExecutor(), 059 new ApplyRequestValuesExecutor(), 060 new ProcessValidationsExecutor(), 061 new UpdateModelValuesExecutor(), 062 new InvokeApplicationExecutor() 063 }; 064 065 renderExecutor = new RenderResponseExecutor(); 066 } 067 068 public void execute(FacesContext facesContext) throws FacesException { 069 PhaseListenerManager phaseListenerMgr 070 = new PhaseListenerManager(this, facesContext, getPhaseListeners()); 071 072 // At very first ensure the requestEncoding, this MUST done before 073 // accessing request parameters, wich can occur in custom phaseListeners. 074 RequestUtils.ensureEncoding(facesContext); 075 076 for (PhaseExecutor executor : lifecycleExecutors) { 077 if (executePhase(facesContext, executor, phaseListenerMgr)) { 078 return; 079 } 080 } 081 } 082 083 private boolean executePhase(FacesContext facesContext, PhaseExecutor executor, 084 PhaseListenerManager phaseListenerMgr) 085 throws FacesException { 086 087 boolean skipFurtherProcessing = false; 088 if (LOG.isTraceEnabled()) { 089 LOG.trace("entering " + executor.getPhase() + " in " + TobagoLifecycle.class.getName()); 090 } 091 092 try { 093 phaseListenerMgr.informPhaseListenersBefore(executor.getPhase()); 094 095 if (isResponseComplete(facesContext, executor.getPhase(), true)) { 096 // have to return right away 097 return true; 098 } 099 if (shouldRenderResponse(facesContext, executor.getPhase(), true)) { 100 skipFurtherProcessing = true; 101 } 102 103 if (executor.execute(facesContext)) { 104 return true; 105 } 106 } finally { 107 phaseListenerMgr.informPhaseListenersAfter(executor.getPhase()); 108 } 109 110 111 if (isResponseComplete(facesContext, executor.getPhase(), false) 112 || shouldRenderResponse(facesContext, executor.getPhase(), false)) { 113 // since this phase is completed we don't need to return right away even if the response is completed 114 skipFurtherProcessing = true; 115 } 116 117 if (!skipFurtherProcessing && LOG.isTraceEnabled()) { 118 LOG.trace("exiting " + executor.getPhase() + " in " + TobagoLifecycle.class.getName()); 119 } 120 121 return skipFurtherProcessing; 122 } 123 124 public void render(FacesContext facesContext) throws FacesException { 125 // if the response is complete we should not be invoking the phase listeners 126 if (isResponseComplete(facesContext, renderExecutor.getPhase(), true)) { 127 return; 128 } 129 if (LOG.isTraceEnabled()) { 130 LOG.trace("entering " + renderExecutor.getPhase() + " in " + TobagoLifecycle.class.getName()); 131 } 132 133 PhaseListenerManager phaseListenerMgr = new PhaseListenerManager(this, facesContext, getPhaseListeners()); 134 135 try { 136 phaseListenerMgr.informPhaseListenersBefore(renderExecutor.getPhase()); 137 // also possible that one of the listeners completed the response 138 if (isResponseComplete(facesContext, renderExecutor.getPhase(), true)) { 139 return; 140 } 141 142 renderExecutor.execute(facesContext); 143 } finally { 144 phaseListenerMgr.informPhaseListenersAfter(renderExecutor.getPhase()); 145 } 146 147 if (LOG.isTraceEnabled()) { 148 LOG.trace(ComponentUtil.toString(facesContext.getViewRoot(), 0)); 149 } 150 151 if (LOG.isTraceEnabled()) { 152 LOG.trace("exiting " + renderExecutor.getPhase() + " in " + TobagoLifecycle.class.getName()); 153 } 154 } 155 156 private boolean isResponseComplete(FacesContext facesContext, PhaseId phase, boolean before) { 157 boolean flag = false; 158 if (facesContext.getResponseComplete()) { 159 if (LOG.isDebugEnabled()) { 160 LOG.debug("exiting from lifecycle.execute in " + phase 161 + " because getResponseComplete is true from one of the " 162 + (before ? "before" : "after") + " listeners"); 163 } 164 flag = true; 165 } 166 return flag; 167 } 168 169 private boolean shouldRenderResponse(FacesContext facesContext, PhaseId phase, boolean before) { 170 boolean flag = false; 171 if (facesContext.getRenderResponse()) { 172 if (LOG.isDebugEnabled()) { 173 LOG.debug("exiting from lifecycle.execute in " + phase 174 + " because getRenderResponse is true from one of the " 175 + (before ? "before" : "after") + " listeners"); 176 } 177 flag = true; 178 } 179 return flag; 180 } 181 182 public void addPhaseListener(PhaseListener phaseListener) { 183 if (phaseListener == null) { 184 throw new NullPointerException("PhaseListener must not be null."); 185 } 186 synchronized (phaseListenerList) { 187 phaseListenerList.add(phaseListener); 188 phaseListenerArray = null; // reset lazy cache array 189 } 190 } 191 192 public void removePhaseListener(PhaseListener phaseListener) { 193 if (phaseListener == null) { 194 throw new NullPointerException("PhaseListener must not be null."); 195 } 196 synchronized (phaseListenerList) { 197 phaseListenerList.remove(phaseListener); 198 phaseListenerArray = null; // reset lazy cache array 199 } 200 } 201 202 public PhaseListener[] getPhaseListeners() { 203 synchronized (phaseListenerList) { 204 // (re)build lazy cache array if necessary 205 if (phaseListenerArray == null) { 206 phaseListenerArray = phaseListenerList.toArray(new PhaseListener[phaseListenerList.size()]); 207 } 208 return phaseListenerArray; 209 } 210 } 211 }