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