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