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