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.view.facelets.tag.jsf.core;
20  
21  import java.io.IOException;
22  import java.io.Serializable;
23  import java.util.Collection;
24  import java.util.Iterator;
25  
26  import javax.el.ELContext;
27  import javax.el.ELException;
28  import javax.el.MethodExpression;
29  import javax.el.MethodNotFoundException;
30  import javax.faces.FacesException;
31  import javax.faces.component.NamingContainer;
32  import javax.faces.component.PartialStateHolder;
33  import javax.faces.component.UIComponent;
34  import javax.faces.component.UINamingContainer;
35  import javax.faces.component.UIViewRoot;
36  import javax.faces.context.FacesContext;
37  import javax.faces.event.ComponentSystemEvent;
38  import javax.faces.event.ComponentSystemEventListener;
39  import javax.faces.event.PostAddToViewEvent;
40  import javax.faces.event.PreRenderViewEvent;
41  import javax.faces.view.facelets.ComponentHandler;
42  import javax.faces.view.facelets.FaceletContext;
43  import javax.faces.view.facelets.FaceletException;
44  import javax.faces.view.facelets.TagAttribute;
45  import javax.faces.view.facelets.TagAttributeException;
46  import javax.faces.view.facelets.TagConfig;
47  import javax.faces.view.facelets.TagHandler;
48  
49  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletAttribute;
50  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletTag;
51  import org.apache.myfaces.config.RuntimeConfig;
52  import org.apache.myfaces.view.facelets.FaceletCompositionContext;
53  import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
54  import org.apache.myfaces.view.facelets.el.CompositeComponentELUtils;
55  import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport;
56  import org.apache.myfaces.view.facelets.util.ReflectionUtil;
57  
58  /**
59   * Registers a listener for a given system event class on the UIComponent associated with this tag.
60   */
61  @JSFFaceletTag(
62          name = "f:event",
63          bodyContent = "empty")
64  public final class EventHandler extends TagHandler {
65      
66      private static final Class<?>[] COMPONENT_SYSTEM_EVENT_PARAMETER = new Class<?>[] { ComponentSystemEvent.class };
67      private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
68      
69      @JSFFaceletAttribute(name="listener",
70              className="javax.el.MethodExpression",
71              deferredMethodSignature=
72              "public void listener(javax.faces.event.ComponentSystemEvent evt) throws javax.faces.event.AbortProcessingException")
73      private TagAttribute listener;
74      
75      @JSFFaceletAttribute(name="type",
76              className="javax.el.ValueExpression",
77              deferredValueType="java.lang.String")
78      private TagAttribute type;
79      
80      private boolean listenerIsCompositeComponentME;
81      
82      public EventHandler (TagConfig tagConfig)
83      {
84          super (tagConfig);
85          
86          listener = getRequiredAttribute("listener");
87          if (!listener.isLiteral())
88          {
89              listenerIsCompositeComponentME = CompositeComponentELUtils.isCompositeComponentExpression(listener.getValue());
90          }
91          else
92          {
93              listenerIsCompositeComponentME = false;
94          }
95          type = getRequiredAttribute("type");
96      }
97      
98      public void apply (FaceletContext ctx, UIComponent parent) throws ELException, FacesException, FaceletException, IOException
99      {
100         //Apply only if we are creating a new component
101         if (!ComponentHandler.isNew(parent))
102         {
103             return;
104         }
105         if (parent instanceof UIViewRoot)
106         {
107             if (FaceletCompositionContext.getCurrentInstance(ctx).isRefreshingTransientBuild())
108             {
109                 return;
110             }
111             else if (!FaceletViewDeclarationLanguage.isBuildingViewMetadata(ctx.getFacesContext()) &&
112                     UIViewRoot.METADATA_FACET_NAME.equals((String) parent.getAttributes().get(FacetHandler.KEY)))
113             {
114                 // Already processed when the view metadata was created
115                 return;
116             }
117         }
118         
119         Class<? extends ComponentSystemEvent> eventClass = getEventClass(ctx);
120         
121         // Note: The listener attribute can now also take a zero-argument MethodExpression,
122         // thus we need two different MethodExpressions (see MYFACES-2503 for details).
123         MethodExpression methodExpOneArg 
124                 = listener.getMethodExpression(ctx, void.class, COMPONENT_SYSTEM_EVENT_PARAMETER);
125         MethodExpression methodExpZeroArg
126                 = listener.getMethodExpression(ctx, void.class, EMPTY_CLASS_ARRAY);
127         
128         if (eventClass == PreRenderViewEvent.class)
129         {
130             // ensure ViewRoot for PreRenderViewEvent
131             UIViewRoot viewRoot = ComponentSupport.getViewRoot(ctx, parent);
132             if (listenerIsCompositeComponentME)
133             {
134                 // Subscribe after the view is built, so we can calculate a findComponent valid expression, and then use it to
135                 // put the expression in context.
136                 UIComponent parentCompositeComponent = FaceletCompositionContext.getCurrentInstance(ctx).getCompositeComponentFromStack();
137                 parentCompositeComponent.subscribeToEvent(PostAddToViewEvent.class, 
138                         new SubscribeEventListener(eventClass, methodExpOneArg, methodExpZeroArg, (eventClass == PreRenderViewEvent.class) ? null : parent));
139             }
140             else
141             {
142                 viewRoot.subscribeToEvent(eventClass, new Listener(methodExpOneArg, methodExpZeroArg));
143             }
144         }
145         else
146         {
147             // Simply register the event on the component.
148             parent.subscribeToEvent(eventClass, new Listener(methodExpOneArg, methodExpZeroArg));
149         }
150     }
151     
152     /**
153      * Gets the event class defined by the tag (either in the "name" or "type" attribute).
154      * 
155      * @param context the Facelet context
156      * @return a Class containing the event class defined by the tag.
157      */
158     
159     @SuppressWarnings("unchecked")
160     private Class<? extends ComponentSystemEvent> getEventClass (FaceletContext context)
161     {
162         Class<?> eventClass = null;
163         String value = null;
164         if (type.isLiteral())
165         {
166             value = type.getValue();
167         }
168         else
169         {
170             value = (String) type.getValueExpression (context, String.class).
171                 getValue (context.getFacesContext().getELContext());
172         }
173         
174         Collection<Class<? extends ComponentSystemEvent>> events;
175         
176         // We can look up the event class by name in the NamedEventManager.
177         
178         events = RuntimeConfig.getCurrentInstance(
179                 context.getFacesContext().getExternalContext()).
180                     getNamedEventManager().getNamedEvent(value);
181         
182         if (events == null)
183         {
184             try
185             {
186                 eventClass = ReflectionUtil.forName (value);
187             }
188             catch (Throwable e)
189             {
190                 throw new TagAttributeException (type, "Couldn't create event class", e);
191             }
192         }
193         else if (events.size() > 1)
194         {
195             StringBuilder classNames = new StringBuilder ("[");
196             Iterator<Class<? extends ComponentSystemEvent>> eventIterator = events.iterator();
197             
198             // TODO: The spec is somewhat vague, but I think we're supposed to throw an exception
199             // here.  The @NamedEvent javadocs say that if a short name is registered to more than one
200             // event class that we must throw an exception listing the short name and the classes in
201             // the list _when the application makes reference to it_.  I believe processing this tag
202             // qualifies as the application "making reference" to the short name.  Why the exception
203             // isn't thrown when processing the @NamedEvent annotation, I don't know.  Perhaps follow
204             // up with the EG to see if this is correct.
205             
206             while (eventIterator.hasNext())
207             {
208                 classNames.append (eventIterator.next().getName());
209                 
210                 if (eventIterator.hasNext())
211                 {
212                     classNames.append (", ");
213                 }
214                 else
215                 {
216                     classNames.append ("]");
217                 }
218             }
219             
220             throw new FacesException ("The event name '" + value + "' is mapped to more than one " +
221                 " event class: " + classNames.toString());
222         }
223         else
224         {
225             eventClass = events.iterator().next();
226         }
227         
228         if (!ComponentSystemEvent.class.isAssignableFrom (eventClass))
229         {
230             throw new TagAttributeException (type, "Event class " + eventClass.getName() +
231                 " is not of type javax.faces.event.ComponentSystemEvent");
232         }
233         
234         return (Class<? extends ComponentSystemEvent>) eventClass;
235     }
236     
237     public static class Listener implements ComponentSystemEventListener, Serializable 
238     {
239 
240         private static final long serialVersionUID = 7318240026355007052L;
241         
242         private MethodExpression methodExpOneArg;
243         private MethodExpression methodExpZeroArg;
244         
245         public Listener()
246         {
247             super();
248         }
249 
250         /**
251          * Note: The listener attribute can now also take a zero-argument MethodExpression,
252          * thus we need two different MethodExpressions (see MYFACES-2503 for details).
253          * @param methodExpOneArg
254          * @param methodExpZeroArg
255          */
256         private Listener(MethodExpression methodExpOneArg, MethodExpression methodExpZeroArg)
257         {
258             this.methodExpOneArg = methodExpOneArg;
259             this.methodExpZeroArg = methodExpZeroArg;
260         }
261         
262         public void processEvent(ComponentSystemEvent event)
263         {
264             ELContext elContext = FacesContext.getCurrentInstance().getELContext();
265             try
266             {
267                 // first try to invoke the MethodExpression with one argument
268                 this.methodExpOneArg.invoke(elContext, new Object[] { event });
269             }
270             catch (MethodNotFoundException mnfeOneArg)
271             {
272                 try
273                 {
274                     // if that fails try to invoke the MethodExpression with zero arguments
275                     this.methodExpZeroArg.invoke(elContext, new Object[0]);
276                 }
277                 catch (MethodNotFoundException mnfeZeroArg)
278                 {
279                     // if that fails too rethrow the original MethodNotFoundException
280                     throw mnfeOneArg;
281                 }
282             }
283         }
284     }
285     
286     public static class CompositeComponentRelativeListener  implements ComponentSystemEventListener, Serializable 
287     {
288         /**
289          * 
290          */
291         private static final long serialVersionUID = 3822330995358746099L;
292         
293         private String _compositeComponentExpression;
294         private MethodExpression methodExpOneArg;
295         private MethodExpression methodExpZeroArg;
296         
297         public CompositeComponentRelativeListener()
298         {
299             super();
300         }
301         
302         public CompositeComponentRelativeListener(MethodExpression methodExpOneArg, 
303                                 MethodExpression methodExpZeroArg, 
304                                 String compositeComponentExpression)
305         {
306             this.methodExpOneArg = methodExpOneArg;
307             this.methodExpZeroArg = methodExpZeroArg;
308             this._compositeComponentExpression = compositeComponentExpression;
309         }
310         
311         public void processEvent(ComponentSystemEvent event)
312         {
313             FacesContext facesContext = FacesContext.getCurrentInstance();
314             UIComponent cc = facesContext.getViewRoot().findComponent(_compositeComponentExpression);
315             
316             if (cc != null)
317             {
318                 pushAllComponentsIntoStack(facesContext, cc);
319                 cc.pushComponentToEL(facesContext, cc);
320                 try
321                 {
322                     ELContext elContext = facesContext.getELContext();
323                     try
324                     {
325                         // first try to invoke the MethodExpression with one argument
326                         this.methodExpOneArg.invoke(elContext, new Object[] { event });
327                     }
328                     catch (MethodNotFoundException mnfeOneArg)
329                     {
330                         try
331                         {
332                             // if that fails try to invoke the MethodExpression with zero arguments
333                             this.methodExpZeroArg.invoke(elContext, new Object[0]);
334                         }
335                         catch (MethodNotFoundException mnfeZeroArg)
336                         {
337                             // if that fails too rethrow the original MethodNotFoundException
338                             throw mnfeOneArg;
339                         }
340                     }
341                 }
342                 finally
343                 {
344                     popAllComponentsIntoStack(facesContext, cc);
345                 }
346             }
347             else
348             {
349                 throw new NullPointerException("Composite Component associated with expression cannot be found");
350             }
351         }
352         
353         private void pushAllComponentsIntoStack(FacesContext facesContext, UIComponent component)
354         {
355             UIComponent parent = component.getParent();
356             if (parent != null)
357             {
358                 pushAllComponentsIntoStack(facesContext, parent);
359             }
360             component.pushComponentToEL(facesContext, component);
361         }
362         
363         private void popAllComponentsIntoStack(FacesContext facesContext, UIComponent component)
364         {
365             UIComponent parent = component.getParent();
366             component.popComponentFromEL(facesContext);
367             if (parent != null)
368             {
369                 popAllComponentsIntoStack(facesContext, parent);
370             }
371         }
372     }
373     
374     public static final class SubscribeEventListener implements ComponentSystemEventListener, PartialStateHolder
375     {
376         private MethodExpression methodExpOneArg;
377         private MethodExpression methodExpZeroArg;
378         private Class<? extends ComponentSystemEvent> eventClass;
379         private UIComponent _targetComponent;
380         private String _targetFindComponentExpression;
381     
382         private boolean markInitialState;
383 
384         public SubscribeEventListener()
385         {
386         }
387         
388         public SubscribeEventListener(
389                 Class<? extends ComponentSystemEvent> eventClass,
390                 MethodExpression methodExpOneArg, 
391                 MethodExpression methodExpZeroArg,
392                 UIComponent targetComponent)
393         {
394             //_listener = listener;
395             this.eventClass = eventClass;
396             this.methodExpOneArg = methodExpOneArg;
397             this.methodExpZeroArg = methodExpZeroArg;
398             this._targetComponent = targetComponent;
399         }
400         
401         public void processEvent(ComponentSystemEvent event)
402         {
403             UIComponent parentCompositeComponent = event.getComponent();
404             FacesContext facesContext = FacesContext.getCurrentInstance();
405             //Calculate a findComponent expression to locate the right instance so PreRenderViewEvent could be called
406             String findComponentExpression = ComponentSupport.getFindComponentExpression(facesContext, parentCompositeComponent);
407             
408             //Note in practice this is only used for PreRenderViewEvent, but in the future it could be more events that
409             //require this hack.
410             if (eventClass == PreRenderViewEvent.class)
411             {
412                 // ensure ViewRoot for PreRenderViewEvent
413                 UIViewRoot viewRoot = facesContext.getViewRoot();
414                 viewRoot.subscribeToEvent(eventClass, new CompositeComponentRelativeListener(methodExpOneArg, methodExpZeroArg, findComponentExpression));
415             }
416             else
417             {
418                 if (_targetComponent == null)
419                 {
420                     if (_targetFindComponentExpression.startsWith(findComponentExpression) )
421                     {
422                         _targetComponent = ComponentSupport.findComponentChildOrFacetFrom(
423                                 facesContext, parentCompositeComponent, 
424                                 _targetFindComponentExpression.substring(findComponentExpression.length()));
425                     }
426                     else
427                     {
428                         _targetComponent = facesContext.getViewRoot().findComponent(_targetFindComponentExpression);
429                     }
430                 }
431                 
432                 _targetComponent.subscribeToEvent(eventClass, new CompositeComponentRelativeListener(methodExpOneArg, methodExpZeroArg, findComponentExpression));
433             }
434         }
435         
436         public Object saveState(FacesContext context)
437         {
438             if (!initialStateMarked())
439             {
440                 Object[] values = new Object[4];
441                 values[0] = (String) ( (_targetComponent != null && _targetFindComponentExpression == null) ? 
442                                             ComponentSupport.getFindComponentExpression(context, _targetComponent) : 
443                                             _targetFindComponentExpression );
444                 values[1] = eventClass;
445                 values[2] = methodExpZeroArg;
446                 values[3] = methodExpOneArg;
447                 return values;
448             }
449             // If the listener was marked, no need to save anything, because 
450             // this object is immutable after that.
451             return null;
452         }
453 
454         public void restoreState(FacesContext context, Object state)
455         {
456             if (state == null)
457             {
458                 return;
459             }
460             Object[] values = (Object[])state;
461             _targetFindComponentExpression = (String) values[0];
462             eventClass = (Class) values[1];
463             methodExpZeroArg = (MethodExpression) values[2];
464             methodExpOneArg = (MethodExpression) values[3];
465         }
466 
467         public boolean isTransient()
468         {
469             return false;
470         }
471 
472         public void setTransient(boolean newTransientValue)
473         {
474             // no-op as listener is transient
475         }
476         
477         public void clearInitialState()
478         {
479             markInitialState = false;
480         }
481 
482         public boolean initialStateMarked()
483         {
484             return markInitialState;
485         }
486 
487         public void markInitialState()
488         {
489             markInitialState = true;
490         }
491     }
492 }