1 package org.apache.turbine.modules;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.Iterator;
24
25 import org.apache.commons.lang.StringUtils;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29
30 import org.apache.turbine.Turbine;
31 import org.apache.turbine.TurbineConstants;
32 import org.apache.turbine.pipeline.PipelineData;
33 import org.apache.turbine.util.RunData;
34 import org.apache.fulcrum.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 * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
80 * @version $Id: ActionEvent.java 349547 2005-11-28 23:02:03Z epugh $
81 */
82 public abstract class ActionEvent extends Action
83 {
84 /*** Logging */
85 protected Log log = LogFactory.getLog(this.getClass());
86
87 /*** Constant needed for Reflection */
88 private static final Class [] methodParams
89 = new Class [] { RunData.class };
90
91 /***
92 * You need to implement this in your classes that extend this class.
93 * @deprecated use PipelineData version instead.
94 * @param data Turbine information.
95 * @exception Exception a generic exception.
96 */
97 public abstract void doPerform(RunData data)
98 throws Exception;
99
100 /***
101 * You need to implement this in your classes that extend this class.
102 * This should revert to being abstract when RunData has gone.
103 * @param data Turbine information.
104 * @exception Exception a generic exception.
105 */
106 public void doPerform(PipelineData pipelineData)
107 throws Exception
108 {
109 RunData data = (RunData) getRunData(pipelineData);
110 doPerform(data);
111 }
112
113
114 /*** The name of the button to look for. */
115 protected static final String BUTTON = "eventSubmit_";
116 /*** The length of the button to look for. */
117 protected static final int BUTTON_LENGTH = BUTTON.length();
118 /*** The prefix of the method name. */
119 protected static final String METHOD_NAME_PREFIX = "do";
120 /*** The length of the method name. */
121 protected static final int METHOD_NAME_LENGTH = METHOD_NAME_PREFIX.length();
122 /*** The length of the button to look for. */
123 protected static final int LENGTH = BUTTON.length();
124
125 /***
126 * If true, the eventSubmit_do<xxx> variable must contain
127 * a not null value to be executed.
128 */
129 private boolean submitValueKey = false;
130
131 /***
132 * If true, then exceptions raised in eventSubmit_do<xxx> methods
133 * as well as in doPerform methods are bubbled up to the Turbine
134 * servlet's handleException method.
135 */
136 protected boolean bubbleUpException = true;
137 /***
138 * C'tor
139 */
140 public ActionEvent()
141 {
142 super();
143
144 submitValueKey = Turbine.getConfiguration()
145 .getBoolean(TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_KEY,
146 TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_DEFAULT);
147 bubbleUpException = Turbine.getConfiguration()
148 .getBoolean(TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP,
149 TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP_DEFAULT);
150
151 if (log.isDebugEnabled()){
152 log.debug(submitValueKey
153 ? "ActionEvent accepts only eventSubmit_do Keys with a value != 0"
154 : "ActionEvent accepts all eventSubmit_do Keys");
155 log.debug(bubbleUpException
156 ? "ActionEvent will bubble exceptions up to Turbine.handleException() method"
157 : "ActionEvent will not bubble exceptions up.");
158 }
159 }
160
161 /***
162 * This overrides the default Action.perform() to execute the
163 * doEvent() method. If that fails, then it will execute the
164 * doPerform() method instead.
165 * @deprecated Use PipelineData version instead.
166 * @param data Turbine information.
167 * @exception Exception a generic exception.
168 */
169 protected void perform(RunData data)
170 throws Exception
171 {
172 try
173 {
174 executeEvents(data);
175 }
176 catch (NoSuchMethodException e)
177 {
178 doPerform(data);
179 }
180 }
181
182 /***
183 * This overrides the default Action.perform() to execute the
184 * doEvent() method. If that fails, then it will execute the
185 * doPerform() method instead.
186 *
187 * @param data Turbine information.
188 * @exception Exception a generic exception.
189 */
190 protected void perform(PipelineData pipelineData)
191 throws Exception
192 {
193 try
194 {
195 executeEvents(pipelineData);
196 }
197 catch (NoSuchMethodException e)
198 {
199 doPerform(pipelineData);
200 }
201 }
202
203
204 /***
205 * This method should be called to execute the event based system.
206 *
207 * @deprecated Use PipelineData version instead.
208 * @param data Turbine information.
209 * @exception Exception a generic exception.
210 */
211 public void executeEvents(RunData data)
212 throws Exception
213 {
214
215 String theButton = null;
216
217 ParameterParser pp = data.getParameters();
218
219 String button = pp.convert(BUTTON);
220 String key = null;
221
222
223 for (Iterator it = pp.keySet().iterator(); it.hasNext();)
224 {
225 key = (String) it.next();
226 if (key.startsWith(button))
227 {
228 if (considerKey(key, pp))
229 {
230 theButton = formatString(key);
231 break;
232 }
233 }
234 }
235
236 if (theButton == null)
237 {
238 throw new NoSuchMethodException("ActionEvent: The button was null");
239 }
240
241 Method method = null;
242
243 try
244 {
245 method = getClass().getMethod(theButton, methodParams);
246 Object[] methodArgs = new Object[] { data };
247
248 if (log.isDebugEnabled())
249 {
250 log.debug("Invoking " + method);
251 }
252
253 method.invoke(this, methodArgs);
254 }
255 catch (InvocationTargetException ite)
256 {
257 Throwable t = ite.getTargetException();
258 log.error("Invokation of " + method , t);
259 }
260 finally
261 {
262 pp.remove(key);
263 }
264 }
265
266 /***
267 * This method should be called to execute the event based system.
268 *
269 * @param data Turbine information.
270 * @exception Exception a generic exception.
271 */
272 public void executeEvents(PipelineData pipelineData)
273 throws Exception
274 {
275
276 RunData data = (RunData) getRunData(pipelineData);
277
278
279 String theButton = null;
280
281 ParameterParser pp = data.getParameters();
282
283 String button = pp.convert(BUTTON);
284 String key = null;
285
286
287 for (Iterator it = pp.keySet().iterator(); it.hasNext();)
288 {
289 key = (String) it.next();
290 if (key.startsWith(button))
291 {
292 if (considerKey(key, pp))
293 {
294 theButton = formatString(key);
295 break;
296 }
297 }
298 }
299
300 if (theButton == null)
301 {
302 throw new NoSuchMethodException("ActionEvent: The button was null");
303 }
304
305 Method method = null;
306
307 try
308 {
309 method = getClass().getMethod(theButton, methodParams);
310 Object[] methodArgs = new Object[] { pipelineData };
311
312 if (log.isDebugEnabled())
313 {
314 log.debug("Invoking " + method);
315 }
316
317 method.invoke(this, methodArgs);
318 }
319 catch (InvocationTargetException ite)
320 {
321 Throwable t = ite.getTargetException();
322 log.error("Invokation of " + method , t);
323 }
324 finally
325 {
326 pp.remove(key);
327 }
328 }
329
330
331
332 /***
333 * This method does the conversion of the lowercase method name
334 * into the proper case.
335 *
336 * @param input The unconverted method name.
337 * @return A string with the method name in the proper case.
338 */
339 protected final String formatString(String input)
340 {
341 String tmp = input;
342
343 if (StringUtils.isNotEmpty(input))
344 {
345 tmp = input.toLowerCase();
346
347
348 input = (tmp.endsWith(".x") || tmp.endsWith(".y"))
349 ? input.substring(0, input.length() - 2)
350 : input;
351
352 if (ParserUtils.getUrlFolding()
353 != ParserUtils.URL_CASE_FOLDING_NONE)
354 {
355 tmp = input.toLowerCase().substring(BUTTON_LENGTH + METHOD_NAME_LENGTH);
356 tmp = METHOD_NAME_PREFIX + StringUtils.capitalize(tmp);
357 }
358 else
359 {
360 tmp = input.substring(BUTTON_LENGTH);
361 }
362 }
363 return tmp;
364 }
365
366 /***
367 * Checks whether the selected key really is a valid event.
368 *
369 * @param key The selected key
370 * @param pp The parameter parser to look for the key value
371 *
372 * @return true if this key is really an ActionEvent Key
373 */
374 protected boolean considerKey(String key, ParameterParser pp)
375 {
376 if (!submitValueKey)
377 {
378 log.debug("No Value required, accepting " + key);
379 return true;
380 }
381 else
382 {
383
384
385
386
387
388
389
390
391
392 String keyValue = pp.getString(key);
393 log.debug("Key Value is " + keyValue);
394 if (StringUtils.isEmpty(keyValue))
395 {
396 log.debug("Key is empty, rejecting " + key);
397 return false;
398 }
399
400 try
401 {
402 if (Integer.parseInt(keyValue) != 0)
403 {
404 log.debug("Integer != 0, accepting " + key);
405 return true;
406 }
407 }
408 catch (NumberFormatException nfe)
409 {
410
411
412
413 log.debug("Not a number, accepting " + key);
414 return true;
415 }
416 }
417 log.debug("Rejecting " + key);
418 return false;
419 }
420 }