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 javax.faces.component;
20  
21  import javax.el.MethodExpression;
22  import javax.faces.context.FacesContext;
23  import javax.faces.context.FacesContextWrapper;
24  import javax.faces.el.MethodBinding;
25  import javax.faces.event.AbortProcessingException;
26  import javax.faces.event.ActionEvent;
27  import javax.faces.event.ActionListener;
28  import javax.faces.event.FacesEvent;
29  import javax.faces.event.PhaseId;
30  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
31  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFListener;
32  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
33  
34  /**
35   * @since 2.2
36   */
37  @JSFComponent(name = "f:viewAction")
38  public class UIViewAction extends UIComponentBase implements ActionSource2
39  {
40      //private static final Logger log = Logger.getLogger(UIViewAction.class.getName());
41      public static final String COMPONENT_FAMILY = "javax.faces.ViewAction";
42      public static final String COMPONENT_TYPE = "javax.faces.ViewAction";
43      
44      /**
45       * Key in facesContext attribute map to check if a viewAction broadcast is 
46       * being processed. This is used to check when a JSF lifecycle restart is required
47       * by the NavigationHandler implementation.
48       */
49      private static final String BROADCAST_PROCESSING_KEY = "oam.viewAction.broadcast";
50      
51      /**
52       * Key in facesContext attribute map to count the number of viewAction events that 
53       * remains to be processed.
54       */
55      private static final String EVENT_COUNT_KEY = "oam.viewAction.eventCount";
56  
57      public UIViewAction()
58      {
59          setRendererType(null);
60      }
61  
62      @Override
63      public void broadcast(FacesEvent event) throws AbortProcessingException
64      {
65          super.broadcast(event);
66          
67          FacesContext context = getFacesContext();
68          
69          if (context.getResponseComplete())
70          {
71              return;
72          }
73          
74          UIComponent c = event.getComponent();
75          UIViewRoot sourceViewRoot = null;
76          do
77          {
78              if (c instanceof UIViewRoot)
79              {
80                  sourceViewRoot = (UIViewRoot) c;
81                  break;
82              }
83              else
84              {
85                  c = c.getParent();
86              }
87          } while (c != null);
88          
89          if (!context.getViewRoot().equals(sourceViewRoot))
90          {
91              return;
92          }
93          
94          if (event instanceof ActionEvent)
95          {
96              ActionListener defaultActionListener = context.getApplication().getActionListener();
97              if (defaultActionListener != null)
98              {
99                  String  viewIdBeforeAction = context.getViewRoot().getViewId();
100                 Boolean oldBroadcastProcessing = (Boolean) context.getAttributes().
101                     get(BROADCAST_PROCESSING_KEY);
102                 try
103                 {
104                     context.getAttributes().put(BROADCAST_PROCESSING_KEY, Boolean.TRUE);
105 
106                     ViewActionFacesContextWrapper wrappedFacesContext = new ViewActionFacesContextWrapper(context);
107 
108                     try
109                     {
110                         wrappedFacesContext.setWrapperAsCurrentFacesContext();
111 
112                         MethodBinding mb = getActionListener();
113                         if (mb != null)
114                         {
115                             mb.invoke(context, new Object[]
116                             { event });
117                         }
118 
119                         if (defaultActionListener != null)
120                         {
121                             defaultActionListener.processAction((ActionEvent) event);
122                         }
123                         
124                         // Decrement count
125                         Integer count = (Integer) context.getAttributes().get(EVENT_COUNT_KEY);
126                         count = (count == null) ? 0 : count - 1;
127                         context.getAttributes().put(EVENT_COUNT_KEY, count);
128                     }
129                     finally
130                     {
131                         wrappedFacesContext.restoreCurrentFacesContext();
132                     }
133                 }
134                 finally
135                 {
136                     context.getAttributes().put(BROADCAST_PROCESSING_KEY, 
137                         oldBroadcastProcessing == null ? Boolean.FALSE : oldBroadcastProcessing);
138                 }
139 
140                 if (context.getResponseComplete())
141                 {
142                     return;
143                 }
144                 else
145                 {
146                     Integer count = (Integer) context.getAttributes().get(EVENT_COUNT_KEY);
147                     count = (count == null) ? 0 : count;
148                     String viewIdAfterAction = context.getViewRoot().getViewId();
149 
150                     if (viewIdBeforeAction.equals(viewIdAfterAction) && count == 0)
151                     {
152                         context.renderResponse();
153                     }
154                     // "... Otherwise, execute the lifecycle on the new UIViewRoot ..."
155                     // Note these words are implemented in the NavigationHandler, but 
156                     // the original proposal from seam s:viewAction did a trick here 
157                     // to restart the JSF lifecycle.
158                 }
159             }
160         }
161     }
162 
163     @Override
164     public void decode(FacesContext context)
165     {
166         super.decode(context);
167         
168         if (context.isPostback() && !isOnPostback())
169         {
170             return;
171         }
172         
173         if (!isRendered())
174         {
175             return;
176         }
177         
178         ActionEvent evt = new ActionEvent(this);
179         String phase = getPhase();
180         PhaseId phaseId = (phase != null) ? PhaseId.phaseIdValueOf(phase) :
181             isImmediate() ? PhaseId.APPLY_REQUEST_VALUES : PhaseId.INVOKE_APPLICATION;
182         evt.setPhaseId(phaseId);
183         this.queueEvent(evt);
184         
185         // "... Keep track of the number of events that are queued in this way 
186         // on this run through the lifecycle. ...". The are two options:
187         // 1. Use an attribute over FacesContext attribute map
188         // 2. Use an attribute over the component
189         // If the view is recreated again with the same viewId, the component 
190         // state get lost, so the option 1 is preferred.
191         Integer count = (Integer) context.getAttributes().get(EVENT_COUNT_KEY);
192         count = (count == null) ? 1 : count + 1;
193         context.getAttributes().put(EVENT_COUNT_KEY, count);
194     }
195 
196     @Deprecated
197     public MethodBinding getAction()
198     {
199         MethodExpression actionExpression = getActionExpression();
200         if (actionExpression instanceof _MethodBindingToMethodExpression)
201         {
202             return ((_MethodBindingToMethodExpression) actionExpression)
203                     .getMethodBinding();
204         }
205         if (actionExpression != null)
206         {
207             return new _MethodExpressionToMethodBinding(actionExpression);
208         }
209         return null;
210     }
211 
212     /**
213      * @deprecated Use setActionExpression instead.
214      */
215     @Deprecated
216     public void setAction(MethodBinding action)
217     {
218         if (action != null)
219         {
220             setActionExpression(new _MethodBindingToMethodExpression(action));
221         }
222         else
223         {
224             setActionExpression(null);
225         }
226     }
227 
228     @JSFProperty
229     public boolean isImmediate()
230     {
231         return (Boolean) getStateHelper().eval(PropertyKeys.immediate, Boolean.FALSE);
232     }
233 
234     public void setImmediate(boolean immediate)
235     {
236         getStateHelper().put(PropertyKeys.immediate, immediate );
237     }
238 
239     @JSFProperty(stateHolder=true, returnSignature = "java.lang.Object", jspName = "action", clientEvent="action")
240     public MethodExpression getActionExpression()
241     {
242         return (MethodExpression) getStateHelper().eval(PropertyKeys.actionExpression);
243     }
244 
245     public void setActionExpression(MethodExpression actionExpression)
246     {
247         getStateHelper().put(PropertyKeys.actionExpression, actionExpression);
248     }
249 
250     @Deprecated
251     @JSFProperty(stateHolder=true, returnSignature = "void", methodSignature = "javax.faces.event.ActionEvent")
252     public MethodBinding getActionListener()
253     {
254         return (MethodBinding) getStateHelper().eval(PropertyKeys.actionListener);
255     }
256 
257     /**
258      * @deprecated
259      */
260     @Deprecated
261     @JSFProperty(returnSignature="void",methodSignature="javax.faces.event.ActionEvent")
262     public void setActionListener(MethodBinding actionListener)
263     {
264         getStateHelper().put(PropertyKeys.actionListener, actionListener);
265         // Note f:viewAction does not have actionListener property defined.
266         //throw new UnsupportedOperationException();
267     }
268 
269     public void addActionListener(ActionListener listener)
270     {
271         addFacesListener(listener);
272     }
273 
274     public void removeActionListener(ActionListener listener)
275     {
276         removeFacesListener(listener);
277     }
278 
279     @JSFListener(event="javax.faces.event.ActionEvent",
280             phases="Invoke Application, Apply Request Values")
281     public ActionListener[] getActionListeners()
282     {
283         return (ActionListener[]) getFacesListeners(ActionListener.class);
284     }
285     
286     @JSFProperty
287     public String getPhase()
288     {
289         return (String) getStateHelper().get(PropertyKeys.phase);
290     }
291     
292     public void setPhase(String phase)
293     {
294         getStateHelper().put(PropertyKeys.phase, phase);
295     }
296     
297     @JSFProperty
298     public boolean isOnPostback()
299     {
300         return (Boolean) getStateHelper().eval(PropertyKeys.onPostback, Boolean.FALSE);
301     }
302 
303     public void setOnPostback(boolean onPostback)
304     {
305         getStateHelper().put(PropertyKeys.onPostback, onPostback );
306     }    
307     
308     public static boolean isProcessingBroadcast(FacesContext context)
309     {
310         return Boolean.TRUE.equals(context.getAttributes().get(BROADCAST_PROCESSING_KEY));
311     }
312 
313     enum PropertyKeys
314     {
315          immediate
316         , value
317         , actionExpression
318         , actionListener
319         , phase
320         , onPostback
321     }
322 
323     @Override
324     public String getFamily()
325     {
326         return COMPONENT_FAMILY;
327     }
328     
329     private static class ViewActionFacesContextWrapper extends FacesContextWrapper
330     {
331         private FacesContext delegate;
332         private boolean renderResponseCalled;
333 
334         public ViewActionFacesContextWrapper(FacesContext delegate)
335         {
336             this.delegate = delegate;
337             this.renderResponseCalled = false;
338         }
339 
340         @Override
341         public void renderResponse()
342         {
343             //Take no action
344             renderResponseCalled = true;
345         }
346         
347         public boolean isRenderResponseCalled()
348         {
349             return renderResponseCalled;
350         }
351         
352         @Override
353         public FacesContext getWrapped()
354         {
355             return delegate;
356         }
357         
358         void setWrapperAsCurrentFacesContext()
359         {
360             setCurrentInstance(this);
361         }
362         
363         void restoreCurrentFacesContext()
364         {
365             setCurrentInstance(delegate);
366         }
367     }
368 }