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.event;
20  
21  import javax.el.ELContext;
22  import javax.el.ELException;
23  import javax.el.ExpressionFactory;
24  import javax.el.MethodExpression;
25  import javax.el.MethodNotFoundException;
26  import javax.faces.component.StateHolder;
27  import javax.faces.context.FacesContext;
28  
29  /**
30   * See Javadoc of <a href="https://javaserverfaces.dev.java.net/nonav/docs/2.0/javadocs/javax/faces/event/
31   * MethodExpressionValueChangeListener.html">JSF Specification</a>
32   * 
33   * @author Stan Silvert
34   * @author Jakob Korherr
35   */
36  public class MethodExpressionValueChangeListener implements ValueChangeListener, StateHolder
37  {
38  
39      private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
40      private static final Object[] EMPTY_PARAMS = new Object[0];
41      
42      private MethodExpression methodExpressionOneArg;
43      private MethodExpression methodExpressionZeroArg;
44      private boolean isTransient = false;
45  
46      /** Creates a new instance of MethodExpressionValueChangeListener */
47      public MethodExpressionValueChangeListener()
48      {
49          // constructor for state-saving 
50      }
51  
52      public MethodExpressionValueChangeListener(MethodExpression methodExpressionOneArg)
53      {
54          this.methodExpressionOneArg = methodExpressionOneArg;
55          
56          _createZeroArgsMethodExpression(methodExpressionOneArg); 
57      }
58  
59      public MethodExpressionValueChangeListener(MethodExpression methodExpressionOneArg,
60                                                 MethodExpression methodExpressionZeroArg)
61      {
62          this.methodExpressionOneArg = methodExpressionOneArg;
63          if (methodExpressionZeroArg != null) 
64          {
65              this.methodExpressionZeroArg = methodExpressionZeroArg;
66          }
67          else
68          {
69              _createZeroArgsMethodExpression(methodExpressionOneArg);
70          }
71      }
72  
73      public void processValueChange(ValueChangeEvent event) throws AbortProcessingException
74      {
75          try
76          {
77              try
78              {
79                  // call to the one argument MethodExpression
80                  Object[] params = new Object[] { event };
81                  methodExpressionOneArg.invoke(getElContext(), params);
82              }
83              catch (MethodNotFoundException mnfe)
84              {
85                  // call to the zero argument MethodExpression
86                  methodExpressionZeroArg.invoke(getElContext(), EMPTY_PARAMS);
87              }
88          }
89          catch (ELException e)
90          {
91              // "... If that fails for any reason, throw an AbortProcessingException,
92              // including the cause of the failure ..."
93              // -= Leonardo Uribe =- after discussing this topic on MYFACES-3199, the conclusion is the part is an advice
94              // for the developer implementing a listener in a method expressions that could be wrapped by this class.
95              // The spec wording is poor but, to keep this coherently with ExceptionHandler API,
96              // the spec and code on UIViewRoot we need:
97              // 2a) "exception is instance of APE or any of the causes of the exception are an APE, 
98              // DON'T publish ExceptionQueuedEvent and terminate processing for current event".
99              // 2b) for any other exception publish ExceptionQueuedEvent and continue broadcast processing.
100             Throwable cause = e.getCause();
101             AbortProcessingException ape = null;
102             if (cause != null)
103             {
104                 do
105                 {
106                     if (cause != null && cause instanceof AbortProcessingException)
107                     {
108                         ape = (AbortProcessingException) cause;
109                         break;
110                     }
111                     cause = cause.getCause();
112                 }
113                 while (cause != null);
114             }
115             
116             if (ape != null)
117             {
118                 // 2a) "exception is instance of APE or any of the causes of the exception are an APE, 
119                 // DON'T publish ExceptionQueuedEvent and terminate processing for current event".
120                 // To do this throw an AbortProcessingException here, later on UIViewRoot.broadcastAll,
121                 // this exception will be received and stored to handle later.
122                 throw ape;
123             }
124             //for any other exception publish ExceptionQueuedEvent and continue broadcast processing.
125             throw e;
126             //Throwable cause = e.getCause();
127             //if (cause == null)
128             //{
129             //    cause = e;
130             //}
131             //if (cause instanceof AbortProcessingException)
132             //{
133             //    throw (AbortProcessingException) cause;
134             //}
135             //else
136             //{
137             //    throw new AbortProcessingException(cause);
138             //}
139         }
140     }
141 
142     public void restoreState(FacesContext context, Object state)
143     {
144         methodExpressionOneArg = (MethodExpression) ((Object[]) state)[0];
145         methodExpressionZeroArg = (MethodExpression) ((Object[]) state)[1];
146     }
147 
148     public Object saveState(FacesContext context)
149     {
150         return new Object[] {methodExpressionOneArg, methodExpressionZeroArg};
151     }
152 
153     public void setTransient(boolean newTransientValue)
154     {
155         isTransient = newTransientValue;
156     }
157 
158     public boolean isTransient()
159     {
160         return isTransient;
161     }
162     
163     private ELContext getElContext()
164     {
165         return getFacesContext().getELContext();
166     }
167     
168     private FacesContext getFacesContext()
169     {
170         return FacesContext.getCurrentInstance();
171     }
172     
173     /**
174      * Creates a {@link MethodExpression} with no params and with the same Expression as 
175      * param <code>methodExpression</code>
176      * <b>WARNING!</b> This method creates new {@link MethodExpression} with expressionFactory.createMethodExpression.
177      * That means is not decorating MethodExpression passed as parameter -
178      * support for EL VariableMapper will not be available!
179      * This is a problem when using facelets and <ui:decorate/> with EL params (see MYFACES-2541 for details).
180      */
181     private void _createZeroArgsMethodExpression(MethodExpression methodExpression)
182     {
183         ExpressionFactory expressionFactory = getFacesContext().getApplication().getExpressionFactory();
184 
185         this.methodExpressionZeroArg = expressionFactory.createMethodExpression(getElContext(), 
186                   methodExpression.getExpressionString(), Void.class, EMPTY_CLASS_ARRAY);
187     }
188 
189 }