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  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.fulcrum.parser.ParameterParser;
30  import org.apache.fulcrum.parser.ParserService;
31  import org.apache.turbine.Turbine;
32  import org.apache.turbine.TurbineConstants;
33  import org.apache.turbine.pipeline.PipelineData;
34  import org.apache.turbine.util.RunData;
35  
36  /**
37   * <p>
38   *
39   * This is an alternative to the Action class that allows you to do
40   * event based actions. Essentially, you label all your submit buttons
41   * with the prefix of "eventSubmit_" and the suffix of "methodName".
42   * For example, "eventSubmit_doDelete". Then any class that subclasses
43   * this class will get its "doDelete(RunData data)" method executed.
44   * If for any reason, it was not able to execute the method, it will
45   * fall back to executing the doPeform() method which is required to
46   * be implemented.
47   *
48   * <p>
49   *
50   * Limitations:
51   *
52   * <p>
53   *
54   * Because ParameterParser makes all the key values lowercase, we have
55   * to do some work to format the string into a method name. For
56   * example, a button name eventSubmit_doDelete gets converted into
57   * eventsubmit_dodelete. Thus, we need to form some sort of naming
58   * convention so that dodelete can be turned into doDelete.
59   *
60   * <p>
61   *
62   * Thus, the convention is this:
63   *
64   * <ul>
65   * <li>The variable name MUST have the prefix "eventSubmit_".</li>
66   * <li>The variable name after the prefix MUST begin with the letters
67   * "do".</li>
68   * <li>The first letter after the "do" will be capitalized and the
69   * rest will be lowercase</li>
70   * </ul>
71   *
72   * If you follow these conventions, then you should be ok with your
73   * method naming in your Action class.
74   *
75   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens </a>
76   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
77   * @author <a href="quintonm@bellsouth.net">Quinton McCombs</a>
78   * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
79   * @version $Id: ActionEvent.java 1078552 2011-03-06 19:58:46Z 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  	 * @deprecated use PipelineData version instead.
93  	 * @param data Turbine information.
94  	 * @exception Exception a generic exception.
95  	 */
96  	@Deprecated
97      @Override
98      public abstract void doPerform(RunData data)
99  			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 }