View Javadoc

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