Coverage Report - org.apache.myfaces.view.facelets.tag.jsf.core.EventHandler
 
Classes in this File Line Coverage Branch Coverage Complexity
EventHandler
0%
0/57
0%
0/36
3.143
EventHandler$1
N/A
N/A
3.143
EventHandler$CompositeComponentRelativeListener
0%
0/34
0%
0/6
3.143
EventHandler$Listener
0%
0/16
N/A
3.143
EventHandler$SubscribeEventListener
0%
0/44
0%
0/14
3.143
 
 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  0
     private static final Class<?>[] COMPONENT_SYSTEM_EVENT_PARAMETER = new Class<?>[] { ComponentSystemEvent.class };
 66  0
     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  0
         super (tagConfig);
 87  
         
 88  0
         listener = getRequiredAttribute("listener");
 89  0
         if (!listener.isLiteral())
 90  
         {
 91  0
             listenerIsCompositeComponentME
 92  
                     = CompositeComponentELUtils.isCompositeComponentExpression(listener.getValue());
 93  
         }
 94  
         else
 95  
         {
 96  0
             listenerIsCompositeComponentME = false;
 97  
         }
 98  0
         type = getRequiredAttribute("type");
 99  0
     }
 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  0
         if (!ComponentHandler.isNew(parent))
 106  
         {
 107  0
             return;
 108  
         }
 109  0
         if (parent instanceof UIViewRoot)
 110  
         {
 111  0
             if (FaceletCompositionContext.getCurrentInstance(ctx).isRefreshingTransientBuild())
 112  
             {
 113  0
                 return;
 114  
             }
 115  0
             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  0
                 return;
 120  
             }
 121  
         }
 122  
         
 123  0
         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  0
         MethodExpression methodExpOneArg 
 128  
                 = listener.getMethodExpression(ctx, void.class, COMPONENT_SYSTEM_EVENT_PARAMETER);
 129  0
         MethodExpression methodExpZeroArg
 130  
                 = listener.getMethodExpression(ctx, void.class, EMPTY_CLASS_ARRAY);
 131  
         
 132  0
         if (eventClass == PreRenderViewEvent.class)
 133  
         {
 134  
             // ensure ViewRoot for PreRenderViewEvent
 135  0
             UIViewRoot viewRoot = ComponentSupport.getViewRoot(ctx, parent);
 136  0
             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  0
                 UIComponent parentCompositeComponent
 142  
                         = FaceletCompositionContext.getCurrentInstance(ctx).getCompositeComponentFromStack();
 143  0
                 parentCompositeComponent.subscribeToEvent(PostAddToViewEvent.class, 
 144  
                         new SubscribeEventListener(eventClass, methodExpOneArg, methodExpZeroArg,
 145  
                                                    (eventClass == PreRenderViewEvent.class) ? null : parent));
 146  0
             }
 147  
             else
 148  
             {
 149  0
                 viewRoot.subscribeToEvent(eventClass, new Listener(methodExpOneArg, methodExpZeroArg));
 150  
             }
 151  0
         }
 152  
         else
 153  
         {
 154  
             // Simply register the event on the component.
 155  0
             parent.subscribeToEvent(eventClass, new Listener(methodExpOneArg, methodExpZeroArg));
 156  
         }
 157  0
     }
 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  0
         Class<?> eventClass = null;
 170  0
         String value = null;
 171  
         
 172  0
         if (type.isLiteral() && eventClassLiteral != null)
 173  
         {
 174  
             // if type is literal it does not change, avoid Reflection and use cached value
 175  0
             return (Class<? extends ComponentSystemEvent>) eventClassLiteral;
 176  
         }
 177  
         
 178  0
         if (type.isLiteral())
 179  
         {
 180  0
             value = type.getValue();
 181  
         }
 182  
         else
 183  
         {
 184  0
             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  0
         events = RuntimeConfig.getCurrentInstance(
 193  
                 context.getFacesContext().getExternalContext()).
 194  
                     getNamedEventManager().getNamedEvent(value);
 195  
         
 196  0
         if (events == null)
 197  
         {
 198  
             try
 199  
             {
 200  0
                 eventClass = ReflectionUtil.forName (value);
 201  0
                 if (type.isLiteral())
 202  
                 {
 203  0
                     eventClassLiteral = eventClass;
 204  
                 }
 205  
             }
 206  0
             catch (Throwable e)
 207  
             {
 208  0
                 throw new TagAttributeException (type, "Couldn't create event class", e);
 209  0
             }
 210  
         }
 211  0
         else if (events.size() > 1)
 212  
         {
 213  0
             StringBuilder classNames = new StringBuilder ("[");
 214  0
             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  0
             while (eventIterator.hasNext())
 225  
             {
 226  0
                 classNames.append (eventIterator.next().getName());
 227  
                 
 228  0
                 if (eventIterator.hasNext())
 229  
                 {
 230  0
                     classNames.append (", ");
 231  
                 }
 232  
                 else
 233  
                 {
 234  0
                     classNames.append ("]");
 235  
                 }
 236  
             }
 237  
             
 238  0
             throw new FacesException ("The event name '" + value + "' is mapped to more than one " +
 239  
                 " event class: " + classNames.toString());
 240  
         }
 241  
         else
 242  
         {
 243  0
             eventClass = events.iterator().next();
 244  
         }
 245  
         
 246  0
         if (!ComponentSystemEvent.class.isAssignableFrom (eventClass))
 247  
         {
 248  0
             throw new TagAttributeException (type, "Event class " + eventClass.getName() +
 249  
                 " is not of type javax.faces.event.ComponentSystemEvent");
 250  
         }
 251  
         
 252  0
         return (Class<? extends ComponentSystemEvent>) eventClass;
 253  
     }
 254  
     
 255  0
     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  0
             super();
 266  0
         }
 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  0
         {
 276  0
             this.methodExpOneArg = methodExpOneArg;
 277  0
             this.methodExpZeroArg = methodExpZeroArg;
 278  0
         }
 279  
         
 280  
         public void processEvent(ComponentSystemEvent event)
 281  
         {
 282  0
             ELContext elContext = FacesContext.getCurrentInstance().getELContext();
 283  
             try
 284  
             {
 285  
                 // first try to invoke the MethodExpression with one argument
 286  0
                 this.methodExpOneArg.invoke(elContext, new Object[] { event });
 287  
             }
 288  0
             catch (MethodNotFoundException mnfeOneArg)
 289  
             {
 290  
                 try
 291  
                 {
 292  
                     // if that fails try to invoke the MethodExpression with zero arguments
 293  0
                     this.methodExpZeroArg.invoke(elContext, new Object[0]);
 294  
                 }
 295  0
                 catch (MethodNotFoundException mnfeZeroArg)
 296  
                 {
 297  
                     // if that fails too rethrow the original MethodNotFoundException
 298  0
                     throw mnfeOneArg;
 299  0
                 }
 300  0
             }
 301  0
         }
 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  0
             super();
 318  0
         }
 319  
         
 320  
         public CompositeComponentRelativeListener(MethodExpression methodExpOneArg, 
 321  
                                 MethodExpression methodExpZeroArg, 
 322  
                                 String compositeComponentExpression)
 323  0
         {
 324  0
             this.methodExpOneArg = methodExpOneArg;
 325  0
             this.methodExpZeroArg = methodExpZeroArg;
 326  0
             this._compositeComponentExpression = compositeComponentExpression;
 327  0
         }
 328  
         
 329  
         public void processEvent(ComponentSystemEvent event)
 330  
         {
 331  0
             FacesContext facesContext = FacesContext.getCurrentInstance();
 332  0
             UIComponent cc = facesContext.getViewRoot().findComponent(_compositeComponentExpression);
 333  
             
 334  0
             if (cc != null)
 335  
             {
 336  0
                 pushAllComponentsIntoStack(facesContext, cc);
 337  0
                 cc.pushComponentToEL(facesContext, cc);
 338  
                 try
 339  
                 {
 340  0
                     ELContext elContext = facesContext.getELContext();
 341  
                     try
 342  
                     {
 343  
                         // first try to invoke the MethodExpression with one argument
 344  0
                         this.methodExpOneArg.invoke(elContext, new Object[] { event });
 345  
                     }
 346  0
                     catch (MethodNotFoundException mnfeOneArg)
 347  
                     {
 348  
                         try
 349  
                         {
 350  
                             // if that fails try to invoke the MethodExpression with zero arguments
 351  0
                             this.methodExpZeroArg.invoke(elContext, new Object[0]);
 352  
                         }
 353  0
                         catch (MethodNotFoundException mnfeZeroArg)
 354  
                         {
 355  
                             // if that fails too rethrow the original MethodNotFoundException
 356  0
                             throw mnfeOneArg;
 357  0
                         }
 358  0
                     }
 359  
                 }
 360  
                 finally
 361  
                 {
 362  0
                     popAllComponentsIntoStack(facesContext, cc);
 363  0
                 }
 364  
             }
 365  
             else
 366  
             {
 367  0
                 throw new NullPointerException("Composite Component associated with expression cannot be found");
 368  
             }
 369  0
         }
 370  
         
 371  
         private void pushAllComponentsIntoStack(FacesContext facesContext, UIComponent component)
 372  
         {
 373  0
             UIComponent parent = component.getParent();
 374  0
             if (parent != null)
 375  
             {
 376  0
                 pushAllComponentsIntoStack(facesContext, parent);
 377  
             }
 378  0
             component.pushComponentToEL(facesContext, component);
 379  0
         }
 380  
         
 381  
         private void popAllComponentsIntoStack(FacesContext facesContext, UIComponent component)
 382  
         {
 383  0
             UIComponent parent = component.getParent();
 384  0
             component.popComponentFromEL(facesContext);
 385  0
             if (parent != null)
 386  
             {
 387  0
                 popAllComponentsIntoStack(facesContext, parent);
 388  
             }
 389  0
         }
 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  0
         {
 404  0
         }
 405  
         
 406  
         public SubscribeEventListener(
 407  
                 Class<? extends ComponentSystemEvent> eventClass,
 408  
                 MethodExpression methodExpOneArg, 
 409  
                 MethodExpression methodExpZeroArg,
 410  
                 UIComponent targetComponent)
 411  0
         {
 412  
             //_listener = listener;
 413  0
             this.eventClass = eventClass;
 414  0
             this.methodExpOneArg = methodExpOneArg;
 415  0
             this.methodExpZeroArg = methodExpZeroArg;
 416  0
             this._targetComponent = targetComponent;
 417  0
         }
 418  
         
 419  
         public void processEvent(ComponentSystemEvent event)
 420  
         {
 421  0
             UIComponent parentCompositeComponent = event.getComponent();
 422  0
             FacesContext facesContext = FacesContext.getCurrentInstance();
 423  
             //Calculate a findComponent expression to locate the right instance so PreRenderViewEvent could be called
 424  0
             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  0
             if (eventClass == PreRenderViewEvent.class)
 430  
             {
 431  
                 // ensure ViewRoot for PreRenderViewEvent
 432  0
                 UIViewRoot viewRoot = facesContext.getViewRoot();
 433  0
                 viewRoot.subscribeToEvent(eventClass, new CompositeComponentRelativeListener(methodExpOneArg,
 434  
                                                                         methodExpZeroArg, findComponentExpression));
 435  0
             }
 436  
             else
 437  
             {
 438  0
                 if (_targetComponent == null)
 439  
                 {
 440  0
                     if (_targetFindComponentExpression.startsWith(findComponentExpression) )
 441  
                     {
 442  0
                         _targetComponent = ComponentSupport.findComponentChildOrFacetFrom(
 443  
                                 facesContext, parentCompositeComponent, 
 444  
                                 _targetFindComponentExpression.substring(findComponentExpression.length()));
 445  
                     }
 446  
                     else
 447  
                     {
 448  0
                         _targetComponent = facesContext.getViewRoot().findComponent(_targetFindComponentExpression);
 449  
                     }
 450  
                 }
 451  
                 
 452  0
                 _targetComponent.subscribeToEvent(eventClass,
 453  
                         new CompositeComponentRelativeListener(methodExpOneArg, methodExpZeroArg,
 454  
                                                                findComponentExpression));
 455  
             }
 456  0
         }
 457  
         
 458  
         public Object saveState(FacesContext context)
 459  
         {
 460  0
             if (!initialStateMarked())
 461  
             {
 462  0
                 Object[] values = new Object[4];
 463  0
                 values[0] = (String) ( (_targetComponent != null && _targetFindComponentExpression == null) ? 
 464  
                                             ComponentSupport.getFindComponentExpression(context, _targetComponent) : 
 465  
                                             _targetFindComponentExpression );
 466  0
                 values[1] = eventClass;
 467  0
                 values[2] = methodExpZeroArg;
 468  0
                 values[3] = methodExpOneArg;
 469  0
                 return values;
 470  
             }
 471  
             // If the listener was marked, no need to save anything, because 
 472  
             // this object is immutable after that.
 473  0
             return null;
 474  
         }
 475  
 
 476  
         public void restoreState(FacesContext context, Object state)
 477  
         {
 478  0
             if (state == null)
 479  
             {
 480  0
                 return;
 481  
             }
 482  0
             Object[] values = (Object[])state;
 483  0
             _targetFindComponentExpression = (String) values[0];
 484  0
             eventClass = (Class) values[1];
 485  0
             methodExpZeroArg = (MethodExpression) values[2];
 486  0
             methodExpOneArg = (MethodExpression) values[3];
 487  0
         }
 488  
 
 489  
         public boolean isTransient()
 490  
         {
 491  0
             return false;
 492  
         }
 493  
 
 494  
         public void setTransient(boolean newTransientValue)
 495  
         {
 496  
             // no-op as listener is transient
 497  0
         }
 498  
         
 499  
         public void clearInitialState()
 500  
         {
 501  0
             markInitialState = false;
 502  0
         }
 503  
 
 504  
         public boolean initialStateMarked()
 505  
         {
 506  0
             return markInitialState;
 507  
         }
 508  
 
 509  
         public void markInitialState()
 510  
         {
 511  0
             markInitialState = true;
 512  0
         }
 513  
     }
 514  
 }