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