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 }