View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.lifecycle;
20  
21  import java.util.List;
22  import java.util.concurrent.CopyOnWriteArrayList;
23  import java.util.logging.Level;
24  import java.util.logging.Logger;
25  
26  import javax.faces.FacesException;
27  import javax.faces.FactoryFinder;
28  import javax.faces.application.ProjectStage;
29  import javax.faces.context.FacesContext;
30  import javax.faces.context.Flash;
31  import javax.faces.event.ExceptionQueuedEvent;
32  import javax.faces.event.ExceptionQueuedEventContext;
33  import javax.faces.event.PhaseId;
34  import javax.faces.event.PhaseListener;
35  import javax.faces.lifecycle.ClientWindow;
36  import javax.faces.lifecycle.ClientWindowFactory;
37  import javax.faces.lifecycle.Lifecycle;
38  
39  import org.apache.myfaces.config.FacesConfigurator;
40  import org.apache.myfaces.shared_impl.webapp.webxml.WebXml;
41  import org.apache.myfaces.util.DebugUtils;
42  
43  /**
44   * Implements the lifecycle as described in Spec. 1.0 PFD Chapter 2
45   * 
46   * @author Manfred Geiler (latest modification by $Author$)
47   * @author Nikolay Petrov
48   * @version $Revision$ $Date$
49   */
50  public class LifecycleImpl extends Lifecycle
51  {
52      //private static final Log log = LogFactory.getLog(LifecycleImpl.class);
53      private static final Logger log = Logger.getLogger(LifecycleImpl.class.getName());
54      
55      /**
56       * Boolean.TRUE is stored under this key in the application map if
57       * the first request has been processed.
58       */
59      public static final String FIRST_REQUEST_PROCESSED_PARAM = "org.apache.myfaces.lifecycle.first.request.processed";
60      
61      private final PhaseExecutor[] lifecycleExecutors;
62      private final PhaseExecutor renderExecutor;
63  
64      /**
65       * Initially, for ensure thread safety we used synchronization blocks and a cached 
66       * _phaseListenerArray and that works. The intention is ensure atomicity between
67       * _phaseListenerList and _phaseListenerArray, but thinking more about it use
68       * CopyOnWriteArrayList and do not use _phaseListenerArray is a lot better. 
69       * 
70       * Most times, we have few instances of PhaseListener registered, so the advantage of 
71       * use _phaseListenerArray is overcome by do not have a synchronization block on getPhaseListeners().
72       * Additionally, it is more often to perform traversals than insertions/removals and 
73       * we can expect only 2 calls for getPhaseListeners() per request (so only two copy 
74       * operations of a very small list).
75       */
76      private final List<PhaseListener> _phaseListenerList
77              = new CopyOnWriteArrayList<PhaseListener>(); // new ArrayList();
78  
79      /**
80       * This variable should be marked as volatile to ensure all threads can see it
81       * after the first request is processed. Note that LifecycleImpl instance could be
82       * shared by multiple requests at the same time, so this is relevant to prevent
83       * multiple updates to FIRST_REQUEST_PROCESSED_PARAM. Really since the value
84       * only changes from false to true, have a racy single check here does not harm, but
85       * note in this case the semantic of the variable must be preserved.
86       */
87      private volatile boolean _firstRequestProcessed = false;
88      /**
89       * Lazy cache for returning _phaseListenerList as an Array.
90       * 
91       * Replaced by _phaseListenerList CopyOnWriteArrayList
92       */
93      //private PhaseListener[] _phaseListenerArray = null;
94      
95      private ClientWindowFactory clientWindowFactory;
96      
97      public LifecycleImpl()
98      {
99          // hide from public access
100         lifecycleExecutors = new PhaseExecutor[] { new RestoreViewExecutor(), new ApplyRequestValuesExecutor(),
101                 new ProcessValidationsExecutor(), new UpdateModelValuesExecutor(), new InvokeApplicationExecutor() };
102 
103         renderExecutor = new RenderResponseExecutor();
104         clientWindowFactory = (ClientWindowFactory) FactoryFinder.getFactory(FactoryFinder.CLIENT_WINDOW_FACTORY);
105     }
106     
107     @Override
108     public void attachWindow(FacesContext facesContext)
109     {
110         ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
111         if (clientWindow == null)
112         {
113             clientWindow = getClientWindowFactory().getClientWindow(facesContext);
114         }
115         if (clientWindow != null)
116         {
117             clientWindow.decode(facesContext);
118             facesContext.getExternalContext().setClientWindow(clientWindow);
119         }
120     }
121     
122     protected ClientWindowFactory getClientWindowFactory()
123     {
124         return clientWindowFactory;
125     }
126 
127     @Override
128     public void execute(FacesContext facesContext) throws FacesException
129     {
130         //try
131         //{
132             // check for updates of web.xml and faces-config descriptors 
133             // only if project state is not production
134             if(!facesContext.isProjectStage(ProjectStage.Production))
135             {
136                 WebXml.update(facesContext.getExternalContext());
137                 new FacesConfigurator(facesContext.getExternalContext()).update();
138             }
139             
140             PhaseListenerManager phaseListenerMgr = new PhaseListenerManager(this, facesContext, getPhaseListeners());
141             for (PhaseExecutor executor : lifecycleExecutors)
142             {
143                 if (executePhase(facesContext, executor, phaseListenerMgr))
144                 {
145                     return;
146                 }
147             }
148         //}
149         //catch (Throwable ex)
150         //{
151             // handle the Throwable accordingly. Maybe generate an error page.
152             //ErrorPageWriter.handleThrowable(facesContext, ex);
153         //}
154     }
155 
156     private boolean executePhase(FacesContext context, PhaseExecutor executor, PhaseListenerManager phaseListenerMgr)
157         throws FacesException
158     {
159         boolean skipFurtherProcessing = false;
160 
161         if (log.isLoggable(Level.FINEST))
162         {
163             log.finest("entering " + executor.getPhase() + " in " + LifecycleImpl.class.getName());
164         }
165 
166         PhaseId currentPhaseId = executor.getPhase();
167         Flash flash = context.getExternalContext().getFlash();
168 
169         try
170         {
171             /* 
172              * Specification, section 2.2
173              * The default request lifecycle processing implementation must ensure that the currentPhaseId property 
174              * of the FacesContext instance for this request is set with the proper PhaseId constant for the current 
175              * phase as the first instruction at the beginning of each phase
176              */
177             context.setCurrentPhaseId(currentPhaseId);
178             
179             flash.doPrePhaseActions(context);
180             
181             // let the PhaseExecutor do some pre-phase actions
182             executor.doPrePhaseActions(context);
183 
184             phaseListenerMgr.informPhaseListenersBefore(currentPhaseId);
185 
186             if (isResponseComplete(context, currentPhaseId, true))
187             {
188                 // have to return right away
189                 return true;
190             }
191             if (shouldRenderResponse(context, currentPhaseId, true))
192             {
193                 skipFurtherProcessing = true;
194             }
195 
196             if (executor.execute(context))
197             {
198                 return true;
199             }
200         }
201         
202         catch (Throwable e)
203         {
204             // JSF 2.0: publish the executor's exception (if any).
205             
206             publishException (e, currentPhaseId, context);
207         }
208         
209         finally
210         {
211             phaseListenerMgr.informPhaseListenersAfter(currentPhaseId);
212             
213             flash.doPostPhaseActions(context);
214             
215         }
216         
217         context.getExceptionHandler().handle();
218         
219         if (isResponseComplete(context, currentPhaseId, false) || shouldRenderResponse(context, currentPhaseId, false))
220         {
221             // since this phase is completed we don't need to return right away even if the response is completed
222             skipFurtherProcessing = true;
223         }
224 
225         if (!skipFurtherProcessing && log.isLoggable(Level.FINEST))
226         {
227             log.finest("exiting " + executor.getPhase() + " in " + LifecycleImpl.class.getName());
228         }
229 
230         return skipFurtherProcessing;
231     }
232 
233     @Override
234     public void render(FacesContext facesContext) throws FacesException
235     {
236         //try
237         //{
238             // if the response is complete we should not be invoking the phase listeners
239             if (isResponseComplete(facesContext, renderExecutor.getPhase(), true))
240             {
241                 return;
242             }
243             if (log.isLoggable(Level.FINEST))
244             {
245                 log.finest("entering " + renderExecutor.getPhase() + " in " + LifecycleImpl.class.getName());
246             }
247     
248             PhaseListenerManager phaseListenerMgr = new PhaseListenerManager(this, facesContext, getPhaseListeners());
249             Flash flash = facesContext.getExternalContext().getFlash();
250             
251             try
252             {
253                 facesContext.setCurrentPhaseId(renderExecutor.getPhase());
254                 
255                 flash.doPrePhaseActions(facesContext);
256                 
257                 // let the PhaseExecutor do some pre-phase actions
258                 renderExecutor.doPrePhaseActions(facesContext);
259                 
260                 phaseListenerMgr.informPhaseListenersBefore(renderExecutor.getPhase());
261                 // also possible that one of the listeners completed the response
262                 if (isResponseComplete(facesContext, renderExecutor.getPhase(), true))
263                 {
264                     return;
265                 }
266                 
267                 renderExecutor.execute(facesContext);
268             }
269             
270             catch (Throwable e)
271             {
272                 // JSF 2.0: publish the executor's exception (if any).
273                 
274                 publishException (e, renderExecutor.getPhase(), facesContext);
275             }
276             
277             finally
278             {
279                 phaseListenerMgr.informPhaseListenersAfter(renderExecutor.getPhase());
280                 flash.doPostPhaseActions(facesContext);
281                 
282                 // publish a field in the application map to indicate
283                 // that the first request has been processed
284                 requestProcessed(facesContext);
285             }
286             
287             facesContext.getExceptionHandler().handle();
288             
289             if (log.isLoggable(Level.FINEST))
290             {
291                 // Note: DebugUtils Logger must also be in trace level
292                 DebugUtils.traceView("View after rendering");
293             }
294     
295             if (log.isLoggable(Level.FINEST))
296             {
297                 log.finest("exiting " + renderExecutor.getPhase() + " in " + LifecycleImpl.class.getName());
298             }
299         //}
300         //catch (Throwable ex)
301         //{
302             // handle the Throwable accordingly. Maybe generate an error page.
303             //ErrorPageWriter.handleThrowable(facesContext, ex);
304         //}
305     }
306 
307     private boolean isResponseComplete(FacesContext facesContext, PhaseId phase, boolean before)
308     {
309         boolean flag = false;
310         if (facesContext.getResponseComplete())
311         {
312             if (log.isLoggable(Level.FINE))
313             {
314                 log.fine("exiting from lifecycle.execute in " + phase
315                         + " because getResponseComplete is true from one of the " + (before ? "before" : "after")
316                         + " listeners");
317             }
318             flag = true;
319         }
320         return flag;
321     }
322 
323     private boolean shouldRenderResponse(FacesContext facesContext, PhaseId phase, boolean before)
324     {
325         boolean flag = false;
326         if (facesContext.getRenderResponse())
327         {
328             if (log.isLoggable(Level.FINE))
329             {
330                 log.fine("exiting from lifecycle.execute in " + phase
331                         + " because getRenderResponse is true from one of the " + (before ? "before" : "after")
332                         + " listeners");
333             }
334             flag = true;
335         }
336         return flag;
337     }
338 
339     @Override
340     public void addPhaseListener(PhaseListener phaseListener)
341     {
342         if (phaseListener == null)
343         {
344             throw new NullPointerException("PhaseListener must not be null.");
345         }
346         //synchronized (_phaseListenerList)
347         //{
348             _phaseListenerList.add(phaseListener);
349             //_phaseListenerArray = null; // reset lazy cache array
350         //}
351     }
352 
353     @Override
354     public void removePhaseListener(PhaseListener phaseListener)
355     {
356         if (phaseListener == null)
357         {
358             throw new NullPointerException("PhaseListener must not be null.");
359         }
360         //synchronized (_phaseListenerList)
361         //{
362             _phaseListenerList.remove(phaseListener);
363             //_phaseListenerArray = null; // reset lazy cache array
364         //}
365     }
366 
367     @Override
368     public PhaseListener[] getPhaseListeners()
369     {
370         //synchronized (_phaseListenerList)
371         //{
372             // (re)build lazy cache array if necessary
373             //if (_phaseListenerArray == null)
374             //{
375             //    _phaseListenerArray = _phaseListenerList.toArray(new PhaseListener[_phaseListenerList.size()]);
376             //}
377             //return _phaseListenerArray;
378         //}
379         return _phaseListenerList.toArray(new PhaseListener[_phaseListenerList.size()]);
380     }
381     
382     private void publishException (Throwable e, PhaseId phaseId, FacesContext facesContext)
383     {
384         ExceptionQueuedEventContext context = new ExceptionQueuedEventContext (facesContext, e, null, phaseId);
385         
386         facesContext.getApplication().publishEvent (facesContext, ExceptionQueuedEvent.class, context);
387     }
388     
389     /**
390      * This method places an attribute on the application map to 
391      * indicate that the first request has been processed. This
392      * attribute is used by several methods in ApplicationImpl 
393      * to determine whether or not to throw an IllegalStateException
394      * @param facesContext
395      */
396     private void requestProcessed(FacesContext facesContext)
397     {
398         if(!_firstRequestProcessed)
399         {
400             // The order here is important. First it is necessary to put
401             // the value on application map before change the value here.
402             // If multiple threads reach this point concurrently, the
403             // variable will be written on the application map at the same
404             // time but always with the same value.
405             facesContext.getExternalContext().getApplicationMap()
406                 .put(FIRST_REQUEST_PROCESSED_PARAM, Boolean.TRUE);
407             
408             _firstRequestProcessed = true;
409         }        
410     }
411 }