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