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