View Javadoc

1   package org.apache.turbine.modules;
2   
3   
4   /*
5    * Copyright 2001-2004 The Apache Software Foundation.
6    *
7    * Licensed under the Apache License, Version 2.0 (the "License")
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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 		// Name of the button.
215 		String theButton = null;
216 		// Parameter parser.
217 		ParameterParser pp = data.getParameters();
218 
219 		String button = pp.convert(BUTTON);
220 		String key = null;
221 
222 		// Loop through and find the button.
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 		// Name of the button.
279 		String theButton = null;
280 		// Parameter parser.
281 		ParameterParser pp = data.getParameters();
282 
283 		String button = pp.convert(BUTTON);
284 		String key = null;
285 
286 		// Loop through and find the button.
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 			// Chop off suffixes (for image type)
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 			// If the action.eventsubmit.needsvalue key is true, 
384 			// events with a "0" or empty value are ignored.
385 			// This can be used if you have multiple eventSubmit_do<xxx>
386 			// fields in your form which are selected by client side code, 
387 			// e.g. JavaScript.
388 			//
389 			// If this key is unset or missing, nothing changes for the
390 			// current behaviour.
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 				// Not a number. So it might be a
411 				// normal Key like "continue" or "exit". Accept
412 				// it.
413 				log.debug("Not a number, accepting " + key);
414 				return true;
415 			}
416 		}
417 		log.debug("Rejecting " + key);
418 		return false;
419 	}
420 }