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