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    }