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    }