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 }