View Javadoc

1   package org.apache.turbine.modules;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.util.Iterator;
25  
26  import org.apache.commons.lang.StringUtils;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import org.apache.turbine.Turbine;
32  import org.apache.turbine.TurbineConstants;
33  import org.apache.turbine.util.RunData;
34  import org.apache.turbine.util.parser.ParameterParser;
35  import org.apache.turbine.util.parser.ParserUtils;
36  
37  /***
38   * <p>
39   *
40   * This is an alternative to the Action class that allows you to do
41   * event based actions. Essentially, you label all your submit buttons
42   * with the prefix of "eventSubmit_" and the suffix of "methodName".
43   * For example, "eventSubmit_doDelete". Then any class that subclasses
44   * this class will get its "doDelete(RunData data)" method executed.
45   * If for any reason, it was not able to execute the method, it will
46   * fall back to executing the doPeform() method which is required to
47   * be implemented.
48   *
49   * <p>
50   *
51   * Limitations:
52   *
53   * <p>
54   *
55   * Because ParameterParser makes all the key values lowercase, we have
56   * to do some work to format the string into a method name. For
57   * example, a button name eventSubmit_doDelete gets converted into
58   * eventsubmit_dodelete. Thus, we need to form some sort of naming
59   * convention so that dodelete can be turned into doDelete.
60   *
61   * <p>
62   *
63   * Thus, the convention is this:
64   *
65   * <ul>
66   * <li>The variable name MUST have the prefix "eventSubmit_".</li>
67   * <li>The variable name after the prefix MUST begin with the letters
68   * "do".</li>
69   * <li>The first letter after the "do" will be capitalized and the
70   * rest will be lowercase</li>
71   * </ul>
72   *
73   * If you follow these conventions, then you should be ok with your
74   * method naming in your Action class.
75   *
76   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens </a>
77   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
78   * @author <a href="quintonm@bellsouth.net">Quinton McCombs</a>
79   * @version $Id: ActionEvent.java 534527 2007-05-02 16:10:59Z tv $
80   */
81  public abstract class ActionEvent extends Action
82  {
83      /*** Logging */
84      protected Log log = LogFactory.getLog(this.getClass());
85  
86      /*** Constant needed for Reflection */
87      private static final Class [] methodParams
88              = new Class [] { RunData.class };
89  
90      /***
91       * You need to implement this in your classes that extend this class.
92       *
93       * @param data Turbine information.
94       * @exception Exception a generic exception.
95       */
96      public abstract void doPerform(RunData data)
97              throws Exception;
98  
99      /*** The name of the button to look for. */
100     protected static final String BUTTON = "eventSubmit_";
101     /*** The length of the button to look for. */
102     protected static final int BUTTON_LENGTH = BUTTON.length();
103     /*** The prefix of the method name. */
104     protected static final String METHOD_NAME_PREFIX = "do";
105     /*** The length of the method name. */
106     protected static final int METHOD_NAME_LENGTH = METHOD_NAME_PREFIX.length();
107     /*** The length of the button to look for. */
108     protected static final int LENGTH = BUTTON.length();
109 
110     /***
111      * If true, the eventSubmit_do<xxx> variable must contain
112      * a not null value to be executed.
113      */
114     private boolean submitValueKey = false;
115 
116     /***
117      * C'tor
118      */
119     public ActionEvent()
120     {
121         super();
122 
123         submitValueKey = Turbine.getConfiguration()
124                 .getBoolean(TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_KEY,
125                         TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_DEFAULT);
126 
127         log.debug(submitValueKey
128                 ? "ActionEvent accepts only eventSubmit_do Keys with a value != 0"
129                 : "ActionEvent accepts all eventSubmit_do Keys");
130     }
131 
132     /***
133      * This overrides the default Action.perform() to execute the
134      * doEvent() method. If that fails, then it will execute the
135      * doPerform() method instead.
136      *
137      * @param data Turbine information.
138      * @exception Exception a generic exception.
139      */
140     protected void perform(RunData data)
141             throws Exception
142     {
143         try
144         {
145             executeEvents(data);
146         }
147         catch (NoSuchMethodException e)
148         {
149             doPerform(data);
150         }
151     }
152 
153     /***
154      * This method should be called to execute the event based system.
155      *
156      * @param data Turbine information.
157      * @exception Exception a generic exception.
158      */
159     public void executeEvents(RunData data)
160             throws Exception
161     {
162         // Name of the button.
163         String theButton = null;
164         // Parameter parser.
165         ParameterParser pp = data.getParameters();
166 
167         String button = pp.convert(BUTTON);
168         String key = null;
169 
170         // Loop through and find the button.
171         for (Iterator it = pp.keySet().iterator(); it.hasNext();)
172         {
173             key = (String) it.next();
174             if (key.startsWith(button))
175             {
176                 if (considerKey(key, pp))
177                 {
178                     theButton = formatString(key);
179                     break;
180                 }
181             }
182         }
183 
184         if (theButton == null)
185         {
186             throw new NoSuchMethodException("ActionEvent: The button was null");
187         }
188 
189         try
190         {
191             Method method = getClass().getMethod(theButton, methodParams);
192             Object[] methodArgs = new Object[] { data };
193 
194             if (log.isDebugEnabled())
195             {
196                 log.debug("Invoking " + method);
197             }
198 
199             method.invoke(this, methodArgs);
200         }
201         catch (InvocationTargetException ite)
202         {
203             Throwable t = ite.getTargetException();
204             if (t instanceof Exception)
205             {
206                 throw (Exception) t;
207             }
208             else
209             {
210                 throw ite;
211             }
212         }
213         finally
214         {
215             pp.remove(key);
216         }
217     }
218 
219     /***
220      * This method does the conversion of the lowercase method name
221      * into the proper case.
222      *
223      * @param input The unconverted method name.
224      * @return A string with the method name in the proper case.
225      */
226     protected final String formatString(String input)
227     {
228         String tmp = input;
229 
230         if (StringUtils.isNotEmpty(input))
231         {
232             tmp = input.toLowerCase();
233 
234             // Chop off suffixes (for image type)
235             input = (tmp.endsWith(".x") || tmp.endsWith(".y"))
236                     ? input.substring(0, input.length() - 2)
237                     : input;
238 
239             if (ParserUtils.getUrlFolding()
240                     != ParserUtils.URL_CASE_FOLDING_NONE)
241             {
242                 tmp = input.toLowerCase().substring(BUTTON_LENGTH + METHOD_NAME_LENGTH);
243                 tmp = METHOD_NAME_PREFIX + StringUtils.capitalize(tmp);
244             }
245             else
246             {
247                 tmp = input.substring(BUTTON_LENGTH);
248             }
249         }
250         return tmp;
251     }
252 
253     /***
254      * Checks whether the selected key really is a valid event.
255      *
256      * @param key The selected key
257      * @param pp The parameter parser to look for the key value
258      *
259      * @return true if this key is really an ActionEvent Key
260      */
261     protected boolean considerKey(String key, ParameterParser pp)
262     {
263         if (!submitValueKey)
264         {
265             log.debug("No Value required, accepting " + key);
266             return true;
267         }
268         else
269         {
270             // If the action.eventsubmit.needsvalue key is true,
271             // events with a "0" or empty value are ignored.
272             // This can be used if you have multiple eventSubmit_do<xxx>
273             // fields in your form which are selected by client side code,
274             // e.g. JavaScript.
275             //
276             // If this key is unset or missing, nothing changes for the
277             // current behaviour.
278             //
279             String keyValue = pp.getString(key);
280             log.debug("Key Value is " + keyValue);
281             if (StringUtils.isEmpty(keyValue))
282             {
283                 log.debug("Key is empty, rejecting " + key);
284                 return false;
285             }
286 
287             try
288             {
289                 if (Integer.parseInt(keyValue) != 0)
290                 {
291                     log.debug("Integer != 0, accepting " + key);
292                     return true;
293                 }
294             }
295             catch (NumberFormatException nfe)
296             {
297                 // Not a number. So it might be a
298                 // normal Key like "continue" or "exit". Accept
299                 // it.
300                 log.debug("Not a number, accepting " + key);
301                 return true;
302             }
303         }
304         log.debug("Rejecting " + key);
305         return false;
306     }
307 }