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  
20  package org.apache.myfaces.orchestra.viewController.jsf;
21  
22  import java.util.Set;
23  import java.util.TreeSet;
24  
25  import javax.faces.component.UIViewRoot;
26  import javax.faces.context.FacesContext;
27  import javax.faces.event.PhaseEvent;
28  import javax.faces.event.PhaseId;
29  import javax.faces.event.PhaseListener;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.myfaces.orchestra.viewController.ViewControllerManager;
34  import org.apache.myfaces.orchestra.viewController.ViewControllerManagerFactory;
35  
36  /**
37   * Causes lifecycle methods to be invoked on backing beans that are associated with
38   * the current view.
39   * <p>
40   * For details about when initView callbacks occur, see the documentation for
41   * method initView on class ViewController.
42   * <p>
43   * See the javadoc for class ViewControllerManager on how to configure the ViewController
44   * behaviour.
45   * <p>
46   * Note that at the moment this does not support a ViewController bean for subviews
47   * (ie f:subView tags), which the Shale ViewController framework does provide. ViewController
48   * beans can only be associated with the main viewId, ie the "top level" view template file.
49   * <p>
50   * Note that some callbacks might not be invoked if exceptions are thrown by actionlisteners,
51   * etc (which again Shale guarantees). This is particularly important for an "endView"
52   * callback, where resources allocated on initView (such as database connections) might
53   * be released. Hopefully this will be implemented in some later Orchestra release.
54   */
55  public class ViewControllerPhaseListener implements PhaseListener
56  {
57      private static final long serialVersionUID = -3975277433747722402L;
58      private final Log log = LogFactory.getLog(ViewControllerPhaseListener.class);
59  
60      /**
61       * @since 1.1
62       */
63      public static class ViewControllerPhaseListenerState
64      {
65          private Set initedViews = new TreeSet();
66  
67          protected ViewControllerPhaseListenerState()
68          {
69          }
70      }
71  
72      public void beforePhase(PhaseEvent event)
73      {
74          if (PhaseId.RESTORE_VIEW.equals(event.getPhaseId()) ||
75              PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
76          {
77              assertConversationState(event.getFacesContext());
78              if (event.getFacesContext().getResponseComplete())
79              {
80                  // we have a redirect ... stop now
81                  return;
82              }
83          }
84  
85          // Try to init the view in every phase, just so we are sure to never miss it.
86          // This skips the actual call if init has already happened for the current
87          // view root instance.
88          executeInitView(event.getFacesContext());
89  
90          if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
91          {
92              preRenderResponse(event.getFacesContext());
93          }
94  
95          if (PhaseId.INVOKE_APPLICATION.equals(event.getPhaseId()))
96          {
97              preInvokeApplication(event.getFacesContext());
98          }
99      }
100 
101     public void afterPhase(PhaseEvent event)
102     {
103         if (PhaseId.RESTORE_VIEW.equals(event.getPhaseId()))
104         {
105             assertConversationState(event.getFacesContext());
106             if (event.getFacesContext().getResponseComplete())
107             {
108                 // we have a redirect ... stop now
109                 return;
110             }
111         }
112 
113         executeInitView(event.getFacesContext());
114     }
115 
116     public PhaseId getPhaseId()
117     {
118         return PhaseId.ANY_PHASE;
119     }
120 
121     protected String getViewId(FacesContext facesContext)
122     {
123         UIViewRoot viewRoot = facesContext.getViewRoot();
124         if (viewRoot == null)
125         {
126             return null;
127         }
128         return viewRoot.getViewId();
129     }
130 
131     /**
132      * invoked multiple times during the lifecycle to ensure the conversation(s)
133      * to the associated viewController are running.
134      *
135      * @param facesContext
136      */
137     protected void assertConversationState(FacesContext facesContext)
138     {
139         ViewControllerManager manager = ViewControllerManagerFactory.getInstance();
140         if (manager == null)
141         {
142             return;
143         }
144 
145         String viewId = getViewId(facesContext);
146         if (viewId == null)
147         {
148             return;
149         }
150 
151         manager.assertConversationState(viewId);
152     }
153 
154     /**
155      * invokes the preRenderView method on your view controller
156      */
157     protected void preRenderResponse(FacesContext facesContext)
158     {
159         ViewControllerManager manager = ViewControllerManagerFactory.getInstance();
160         if (manager == null)
161         {
162             return;
163         }
164 
165         String viewId = getViewId(facesContext);
166         if (viewId == null)
167         {
168             return;
169         }
170 
171         manager.executePreRenderView(viewId);
172     }
173 
174     /**
175      * invokes the initView method on your view controller
176      * @since 1.1
177      */
178     protected void executeInitView(FacesContext facesContext)
179     {
180         postRestoreView(facesContext);
181     }
182 
183     /**
184      * @deprecated overload/use {@link #executeInitView(javax.faces.context.FacesContext)} instead
185      */
186     protected void postRestoreView(FacesContext facesContext)
187     {
188         ViewControllerManager manager = ViewControllerManagerFactory.getInstance();
189         if (manager == null)
190         {
191             return;
192         }
193 
194         UIViewRoot viewRoot = facesContext.getViewRoot();
195         if (viewRoot == null)
196         {
197             return;
198         }
199 
200         String viewId = viewRoot.getViewId();
201         if (viewId == null)
202         {
203             return;
204         }
205 
206         // Here we keep track of the ViewRoot instances that we have already called initView for,
207         // and if it changes then we call initView again.
208         //
209         // An alternative would be to keep track of the ViewController instance, and call initView
210         // if that instance changes. But this is tricky as this object is often a proxy for the
211         // real object, and may not change even when the target is invalidated and recreated.
212 
213         String viewKey = String.valueOf(System.identityHashCode(viewRoot));
214         ViewControllerPhaseListenerState state = getState(facesContext);
215         if (state.initedViews.contains(viewKey))
216         {
217             // this view instance is already initialized
218             if (log.isDebugEnabled())
219             {
220                 log.debug("Skipping already-initialized viewcontroller bean " + viewKey + " for view " + viewId);
221             }
222             return;
223         }
224 
225         if (log.isDebugEnabled())
226         {
227             log.debug("Initializing viewcontroller bean " + viewKey + " for view " + viewId);
228         }
229 
230         state.initedViews.add(viewKey);
231         manager.executeInitView(viewId);
232     }
233 
234     protected ViewControllerPhaseListenerState getState(FacesContext facesContext)
235     {
236         ViewControllerPhaseListenerState state = (ViewControllerPhaseListenerState)
237             facesContext.getExternalContext().getRequestMap()
238                 .get(ViewControllerPhaseListenerState.class.getName());
239         if (state == null)
240         {
241             state = new ViewControllerPhaseListenerState();
242             facesContext.getExternalContext().getRequestMap().put(
243                 ViewControllerPhaseListenerState.class.getName(), state);
244         }
245         return state;
246     }
247 
248     /**
249      * invokes the preProcess method on your view controller
250      */
251     protected void preInvokeApplication(FacesContext facesContext)
252     {
253         ViewControllerManager manager = ViewControllerManagerFactory.getInstance();
254         if (manager == null)
255         {
256             return;
257         }
258 
259         String viewId = getViewId(facesContext);
260         if (viewId == null)
261         {
262             return;
263         }
264 
265         manager.executePreProcess(viewId);
266     }
267 }