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 }