001 package org.apache.turbine.modules;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.lang.reflect.InvocationTargetException;
023 import java.lang.reflect.Method;
024 import java.util.Iterator;
025
026 import org.apache.commons.lang.StringUtils;
027 import org.apache.commons.logging.Log;
028 import org.apache.commons.logging.LogFactory;
029 import org.apache.fulcrum.parser.ParameterParser;
030 import org.apache.fulcrum.parser.ParserService;
031 import org.apache.turbine.Turbine;
032 import org.apache.turbine.TurbineConstants;
033 import org.apache.turbine.pipeline.PipelineData;
034 import org.apache.turbine.util.RunData;
035
036 /**
037 * <p>
038 *
039 * This is an alternative to the Action class that allows you to do
040 * event based actions. Essentially, you label all your submit buttons
041 * with the prefix of "eventSubmit_" and the suffix of "methodName".
042 * For example, "eventSubmit_doDelete". Then any class that subclasses
043 * this class will get its "doDelete(RunData data)" method executed.
044 * If for any reason, it was not able to execute the method, it will
045 * fall back to executing the doPeform() method which is required to
046 * be implemented.
047 *
048 * <p>
049 *
050 * Limitations:
051 *
052 * <p>
053 *
054 * Because ParameterParser makes all the key values lowercase, we have
055 * to do some work to format the string into a method name. For
056 * example, a button name eventSubmit_doDelete gets converted into
057 * eventsubmit_dodelete. Thus, we need to form some sort of naming
058 * convention so that dodelete can be turned into doDelete.
059 *
060 * <p>
061 *
062 * Thus, the convention is this:
063 *
064 * <ul>
065 * <li>The variable name MUST have the prefix "eventSubmit_".</li>
066 * <li>The variable name after the prefix MUST begin with the letters
067 * "do".</li>
068 * <li>The first letter after the "do" will be capitalized and the
069 * rest will be lowercase</li>
070 * </ul>
071 *
072 * If you follow these conventions, then you should be ok with your
073 * method naming in your Action class.
074 *
075 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens </a>
076 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
077 * @author <a href="quintonm@bellsouth.net">Quinton McCombs</a>
078 * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
079 * @version $Id: ActionEvent.java 1078552 2011-03-06 19:58:46Z tv $
080 */
081 public abstract class ActionEvent extends Action
082 {
083 /** Logging */
084 protected Log log = LogFactory.getLog(this.getClass());
085
086 /** Constant needed for Reflection */
087 private static final Class [] methodParams
088 = new Class [] { RunData.class };
089
090 /**
091 * You need to implement this in your classes that extend this class.
092 * @deprecated use PipelineData version instead.
093 * @param data Turbine information.
094 * @exception Exception a generic exception.
095 */
096 @Deprecated
097 @Override
098 public abstract void doPerform(RunData data)
099 throws Exception;
100
101 /**
102 * You need to implement this in your classes that extend this class.
103 * This should revert to being abstract when RunData has gone.
104 * @param data Turbine information.
105 * @exception Exception a generic exception.
106 */
107 @Override
108 public void doPerform(PipelineData pipelineData)
109 throws Exception
110 {
111 RunData data = getRunData(pipelineData);
112 doPerform(data);
113 }
114
115
116 /** The name of the button to look for. */
117 protected static final String BUTTON = "eventSubmit_";
118 /** The length of the button to look for. */
119 protected static final int BUTTON_LENGTH = BUTTON.length();
120 /** The prefix of the method name. */
121 protected static final String METHOD_NAME_PREFIX = "do";
122 /** The length of the method name. */
123 protected static final int METHOD_NAME_LENGTH = METHOD_NAME_PREFIX.length();
124 /** The length of the button to look for. */
125 protected static final int LENGTH = BUTTON.length();
126
127 /**
128 * If true, the eventSubmit_do<xxx> variable must contain
129 * a not null value to be executed.
130 */
131 private boolean submitValueKey = false;
132
133 /**
134 * If true, then exceptions raised in eventSubmit_do<xxx> methods
135 * as well as in doPerform methods are bubbled up to the Turbine
136 * servlet's handleException method.
137 */
138 protected boolean bubbleUpException = true;
139 /**
140 * C'tor
141 */
142 public ActionEvent()
143 {
144 super();
145
146 submitValueKey = Turbine.getConfiguration()
147 .getBoolean(TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_KEY,
148 TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_DEFAULT);
149 bubbleUpException = Turbine.getConfiguration()
150 .getBoolean(TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP,
151 TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP_DEFAULT);
152
153 if (log.isDebugEnabled()){
154 log.debug(submitValueKey
155 ? "ActionEvent accepts only eventSubmit_do Keys with a value != 0"
156 : "ActionEvent accepts all eventSubmit_do Keys");
157 log.debug(bubbleUpException
158 ? "ActionEvent will bubble exceptions up to Turbine.handleException() method"
159 : "ActionEvent will not bubble exceptions up.");
160 }
161 }
162
163 /**
164 * This overrides the default Action.perform() to execute the
165 * doEvent() method. If that fails, then it will execute the
166 * doPerform() method instead.
167 * @deprecated Use PipelineData version instead.
168 * @param data Turbine information.
169 * @exception Exception a generic exception.
170 */
171 @Deprecated
172 @Override
173 protected void perform(RunData data)
174 throws Exception
175 {
176 try
177 {
178 executeEvents(data);
179 }
180 catch (NoSuchMethodException e)
181 {
182 doPerform(data);
183 }
184 }
185
186 /**
187 * This overrides the default Action.perform() to execute the
188 * doEvent() method. If that fails, then it will execute the
189 * doPerform() method instead.
190 *
191 * @param data Turbine information.
192 * @exception Exception a generic exception.
193 */
194 @Override
195 protected void perform(PipelineData pipelineData)
196 throws Exception
197 {
198 try
199 {
200 executeEvents(pipelineData);
201 }
202 catch (NoSuchMethodException e)
203 {
204 doPerform(pipelineData);
205 }
206 }
207
208
209 /**
210 * This method should be called to execute the event based system.
211 *
212 * @deprecated Use PipelineData version instead.
213 * @param data Turbine information.
214 * @exception Exception a generic exception.
215 */
216 @Deprecated
217 public void executeEvents(RunData data)
218 throws Exception
219 {
220 // Name of the button.
221 String theButton = null;
222 // Parameter parser.
223 ParameterParser pp = data.getParameters();
224
225 String button = pp.convert(BUTTON);
226 String key = null;
227
228 // Loop through and find the button.
229 for (Iterator it = pp.keySet().iterator(); it.hasNext();)
230 {
231 key = (String) it.next();
232 if (key.startsWith(button))
233 {
234 if (considerKey(key, pp))
235 {
236 theButton = formatString(key, pp);
237 break;
238 }
239 }
240 }
241
242 if (theButton == null)
243 {
244 throw new NoSuchMethodException("ActionEvent: The button was null");
245 }
246
247 Method method = null;
248
249 try
250 {
251 method = getClass().getMethod(theButton, methodParams);
252 Object[] methodArgs = new Object[] { data };
253
254 if (log.isDebugEnabled())
255 {
256 log.debug("Invoking " + method);
257 }
258
259 method.invoke(this, methodArgs);
260 }
261 catch (InvocationTargetException ite)
262 {
263 Throwable t = ite.getTargetException();
264 log.error("Invokation of " + method , t);
265 }
266 finally
267 {
268 pp.remove(key);
269 }
270 }
271
272 /**
273 * This method should be called to execute the event based system.
274 *
275 * @param data Turbine information.
276 * @exception Exception a generic exception.
277 */
278 public void executeEvents(PipelineData pipelineData)
279 throws Exception
280 {
281
282 RunData data = getRunData(pipelineData);
283
284 // Name of the button.
285 String theButton = null;
286 // Parameter parser.
287 ParameterParser pp = data.getParameters();
288
289 String button = pp.convert(BUTTON);
290 String key = null;
291
292 // Loop through and find the button.
293 for (Iterator it = pp.keySet().iterator(); it.hasNext();)
294 {
295 key = (String) it.next();
296 if (key.startsWith(button))
297 {
298 if (considerKey(key, pp))
299 {
300 theButton = formatString(key, pp);
301 break;
302 }
303 }
304 }
305
306 if (theButton == null)
307 {
308 throw new NoSuchMethodException("ActionEvent: The button was null");
309 }
310
311 Method method = null;
312
313 try
314 {
315 method = getClass().getMethod(theButton, methodParams);
316 Object[] methodArgs = new Object[] { pipelineData };
317
318 if (log.isDebugEnabled())
319 {
320 log.debug("Invoking " + method);
321 }
322
323 method.invoke(this, methodArgs);
324 }
325 catch (InvocationTargetException ite)
326 {
327 Throwable t = ite.getTargetException();
328 log.error("Invokation of " + method , t);
329 }
330 finally
331 {
332 pp.remove(key);
333 }
334 }
335
336
337
338 /**
339 * This method does the conversion of the lowercase method name
340 * into the proper case.
341 *
342 * @param input The unconverted method name.
343 * @param pp The parameter parser (for correct folding)
344 * @return A string with the method name in the proper case.
345 */
346 protected final String formatString(String input, ParameterParser pp)
347 {
348 String tmp = input;
349
350 if (StringUtils.isNotEmpty(input))
351 {
352 tmp = input.toLowerCase();
353
354 // Chop off suffixes (for image type)
355 input = (tmp.endsWith(".x") || tmp.endsWith(".y"))
356 ? input.substring(0, input.length() - 2)
357 : input;
358
359 if (pp.getUrlFolding()
360 != ParserService.URL_CASE_FOLDING_NONE)
361 {
362 tmp = input.toLowerCase().substring(BUTTON_LENGTH + METHOD_NAME_LENGTH);
363 tmp = METHOD_NAME_PREFIX + StringUtils.capitalize(tmp);
364 }
365 else
366 {
367 tmp = input.substring(BUTTON_LENGTH);
368 }
369 }
370 return tmp;
371 }
372
373 /**
374 * Checks whether the selected key really is a valid event.
375 *
376 * @param key The selected key
377 * @param pp The parameter parser to look for the key value
378 *
379 * @return true if this key is really an ActionEvent Key
380 */
381 protected boolean considerKey(String key, ParameterParser pp)
382 {
383 if (!submitValueKey)
384 {
385 log.debug("No Value required, accepting " + key);
386 return true;
387 }
388 else
389 {
390 // If the action.eventsubmit.needsvalue key is true,
391 // events with a "0" or empty value are ignored.
392 // This can be used if you have multiple eventSubmit_do<xxx>
393 // fields in your form which are selected by client side code,
394 // e.g. JavaScript.
395 //
396 // If this key is unset or missing, nothing changes for the
397 // current behaviour.
398 //
399 String keyValue = pp.getString(key);
400 log.debug("Key Value is " + keyValue);
401 if (StringUtils.isEmpty(keyValue))
402 {
403 log.debug("Key is empty, rejecting " + key);
404 return false;
405 }
406
407 try
408 {
409 if (Integer.parseInt(keyValue) != 0)
410 {
411 log.debug("Integer != 0, accepting " + key);
412 return true;
413 }
414 }
415 catch (NumberFormatException nfe)
416 {
417 // Not a number. So it might be a
418 // normal Key like "continue" or "exit". Accept
419 // it.
420 log.debug("Not a number, accepting " + key);
421 return true;
422 }
423 }
424 log.debug("Rejecting " + key);
425 return false;
426 }
427 }