Coverage Report - org.apache.turbine.modules.ActionEvent
 
Classes in this File Line Coverage Branch Coverage Complexity
ActionEvent
76%
67/88
66%
32/48
6,5
 
 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.Arrays;
 25  
 
 26  
 import org.apache.commons.collections.map.MultiKeyMap;
 27  
 import org.apache.commons.lang.StringUtils;
 28  
 import org.apache.commons.logging.Log;
 29  
 import org.apache.commons.logging.LogFactory;
 30  
 import org.apache.fulcrum.parser.ParameterParser;
 31  
 import org.apache.fulcrum.parser.ValueParser.URLCaseFolding;
 32  
 import org.apache.turbine.Turbine;
 33  
 import org.apache.turbine.TurbineConstants;
 34  
 import org.apache.turbine.annotation.TurbineActionEvent;
 35  
 import org.apache.turbine.pipeline.PipelineData;
 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(PipelineData data)" method executed.
 45  
  * If for any reason, it was not able to execute the method, it will
 46  
  * fall back to executing the doPerform() 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 1706425 2015-10-02 14:47:51Z tv $
 81  
  */
 82  
 public abstract class ActionEvent extends Action
 83  
 {
 84  
         /** Logging */
 85  11
         protected Log log = LogFactory.getLog(this.getClass());
 86  
 
 87  
         /** The name of the button to look for. */
 88  
         protected static final String BUTTON = "eventSubmit_";
 89  
         /** The length of the button to look for. */
 90  2
         protected static final int BUTTON_LENGTH = BUTTON.length();
 91  
     /** The default method. */
 92  
     protected static final String DEFAULT_METHOD = "doPerform";
 93  
         /** The prefix of the method name. */
 94  
         protected static final String METHOD_NAME_PREFIX = "do";
 95  
         /** The length of the method name. */
 96  2
         protected static final int METHOD_NAME_LENGTH = METHOD_NAME_PREFIX.length();
 97  
         /** The length of the button to look for. */
 98  2
         protected static final int LENGTH = BUTTON.length();
 99  
 
 100  
         /**
 101  
          * If true, the eventSubmit_do<xxx> variable must contain
 102  
          * a not null value to be executed.
 103  
          */
 104  11
         private boolean submitValueKey = false;
 105  
 
 106  
         /**
 107  
          * If true, then exceptions raised in eventSubmit_do<xxx> methods
 108  
          * as well as in doPerform methods are bubbled up to the Turbine
 109  
          * servlet's handleException method.
 110  
          */
 111  11
         protected boolean bubbleUpException = true;
 112  
 
 113  
         /**
 114  
          * Cache for the methods to invoke
 115  
          */
 116  11
         private MultiKeyMap/* <String, Method> */ methodCache = new MultiKeyMap/* <String, Method> */();
 117  
 
 118  
         /**
 119  
          * C'tor
 120  
          */
 121  
         public ActionEvent()
 122  
         {
 123  11
                 super();
 124  
 
 125  11
                 submitValueKey = Turbine.getConfiguration()
 126  
                                 .getBoolean(TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_KEY,
 127  
                                                 TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_DEFAULT);
 128  11
                 bubbleUpException = Turbine.getConfiguration()
 129  
                                 .getBoolean(TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP,
 130  
                                                 TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP_DEFAULT);
 131  
 
 132  11
                 if (log.isDebugEnabled())
 133  
                 {
 134  11
                     log.debug(submitValueKey
 135  
                                     ? "ActionEvent accepts only eventSubmit_do Keys with a value != 0"
 136  
                                     : "ActionEvent accepts all eventSubmit_do Keys");
 137  11
                     log.debug(bubbleUpException
 138  
                                       ? "ActionEvent will bubble exceptions up to Turbine.handleException() method"
 139  
                                       : "ActionEvent will not bubble exceptions up.");
 140  
                 }
 141  11
         }
 142  
 
 143  
         /**
 144  
          * Retrieve a method of the given name and signature. The value is cached.
 145  
          *
 146  
          * @param name the name of the method
 147  
          * @param signature an array of classes forming the signature of the method
 148  
          * @param pp ParameterParser for correct folding
 149  
          *
 150  
          * @return the method object
 151  
          * @throws NoSuchMethodException if the method does not exist
 152  
          */
 153  
         protected Method getMethod(String name, Class<?>[] signature, ParameterParser pp) throws NoSuchMethodException
 154  
         {
 155  8
             Method method = (Method) this.methodCache.get(name, signature);
 156  
 
 157  8
             if (method == null)
 158  
             {
 159  
                 // Try annotations of public methods
 160  8
                 Method[] methods = getClass().getMethods();
 161  119
                 for (Method m : methods)
 162  
                 {
 163  112
                     if (m.isAnnotationPresent(TurbineActionEvent.class))
 164  
                     {
 165  3
                         TurbineActionEvent tae = m.getAnnotation(TurbineActionEvent.class);
 166  3
                         if (name.equals(pp.convert(tae.value()))
 167  
                         && Arrays.equals(signature, m.getParameterTypes()))
 168  
                         {
 169  1
                             method = m;
 170  1
                             break;
 171  
                         }
 172  
                     }
 173  
                 }
 174  
 
 175  
                 // Try legacy mode
 176  8
                 if (method == null)
 177  
                 {
 178  7
                 String tmp = name.toLowerCase().substring(METHOD_NAME_LENGTH);
 179  7
                     method = getClass().getMethod(METHOD_NAME_PREFIX + StringUtils.capitalize(tmp), signature);
 180  
                 }
 181  
 
 182  8
                 this.methodCache.put(name, signature, method);
 183  
             }
 184  
 
 185  8
             return method;
 186  
         }
 187  
 
 188  
         /**
 189  
          * This overrides the default Action.doPerform() to execute the
 190  
          * doEvent() method. If that fails, then it will execute the
 191  
          * doPerform() method instead.
 192  
          *
 193  
          * @param pipelineData Turbine information.
 194  
          * @exception Exception a generic exception.
 195  
          */
 196  
         @Override
 197  
     public void doPerform(PipelineData pipelineData)
 198  
                         throws Exception
 199  
         {
 200  0
             ParameterParser pp = pipelineData.get(Turbine.class, ParameterParser.class);
 201  0
                 executeEvents(pp, new Class<?>[]{ PipelineData.class }, new Object[]{ pipelineData });
 202  0
         }
 203  
 
 204  
         /**
 205  
          * This method should be called to execute the event based system.
 206  
          *
 207  
          * @param pp the parameter parser
 208  
          * @param signature the signature of the method to call
 209  
          * @param parameters the parameters for the method to call
 210  
          *
 211  
          * @exception Exception a generic exception.
 212  
          */
 213  
         protected void executeEvents(ParameterParser pp, Class<?>[] signature, Object[] parameters)
 214  
                         throws Exception
 215  
         {
 216  
                 // Name of the button.
 217  8
                 String theButton = null;
 218  
 
 219  8
                 String button = pp.convert(BUTTON);
 220  8
                 String key = null;
 221  
 
 222  
                 // Loop through and find the button.
 223  8
                 for (String k : pp)
 224  
                 {
 225  5
                         key = k;
 226  5
                         if (key.startsWith(button))
 227  
                         {
 228  3
                                 if (considerKey(key, pp))
 229  
                                 {
 230  3
                                         theButton = key;
 231  3
                                         break;
 232  
                                 }
 233  
                         }
 234  2
                 }
 235  
 
 236  8
                 if (theButton == null)
 237  
                 {
 238  5
                     theButton = BUTTON + DEFAULT_METHOD;
 239  5
                     key = null;
 240  
                 }
 241  
 
 242  8
                 theButton = formatString(theButton, pp);
 243  8
                 Method method = null;
 244  
 
 245  
         try
 246  
         {
 247  8
             method = getMethod(theButton, signature, pp);
 248  
         }
 249  0
         catch (NoSuchMethodException e)
 250  
         {
 251  0
             method = getMethod(DEFAULT_METHOD, signature, pp);
 252  
         }
 253  
         finally
 254  
         {
 255  8
             if (key != null)
 256  
             {
 257  3
                 pp.remove(key);
 258  
             }
 259  
         }
 260  
 
 261  
                 try
 262  
                 {
 263  8
                         if (log.isDebugEnabled())
 264  
                         {
 265  8
                                 log.debug("Invoking " + method);
 266  
                         }
 267  
 
 268  8
                         method.invoke(this, parameters);
 269  
                 }
 270  3
                 catch (InvocationTargetException ite)
 271  
                 {
 272  3
                         Throwable t = ite.getTargetException();
 273  3
                         if (bubbleUpException)
 274  
                         {
 275  1
                 if (t instanceof Exception)
 276  
                 {
 277  1
                     throw (Exception) t;
 278  
                 }
 279  
                 else
 280  
                 {
 281  0
                     throw ite;
 282  
                 }
 283  
                         }
 284  
                         else
 285  
                         {
 286  2
                             log.error("Invokation of " + method , t);
 287  
                         }
 288  5
                 }
 289  7
         }
 290  
 
 291  
         /**
 292  
          * This method does the conversion of the lowercase method name
 293  
          * into the proper case.
 294  
          *
 295  
          * @param input The unconverted method name.
 296  
          * @param pp The parameter parser (for correct folding)
 297  
          * @return A string with the method name in the proper case.
 298  
          */
 299  
         protected String formatString(String input, ParameterParser pp)
 300  
         {
 301  8
                 String tmp = input;
 302  
 
 303  8
                 if (StringUtils.isNotEmpty(input))
 304  
                 {
 305  8
                         tmp = input.toLowerCase();
 306  
 
 307  
                         // Chop off suffixes (for image type)
 308  8
                         String methodName = (tmp.endsWith(".x") || tmp.endsWith(".y"))
 309  
                                         ? input.substring(0, input.length() - 2)
 310  
                                         : input;
 311  
 
 312  8
                         if (pp.getUrlFolding() == URLCaseFolding.NONE)
 313  
                         {
 314  8
                 tmp = methodName.substring(BUTTON_LENGTH);
 315  
                         }
 316  
                         else
 317  
                         {
 318  0
                 tmp = methodName.toLowerCase().substring(BUTTON_LENGTH);
 319  
                         }
 320  
                 }
 321  
 
 322  8
                 return tmp;
 323  
         }
 324  
 
 325  
         /**
 326  
          * Checks whether the selected key really is a valid event.
 327  
          *
 328  
          * @param key The selected key
 329  
          * @param pp The parameter parser to look for the key value
 330  
          *
 331  
          * @return true if this key is really an ActionEvent Key
 332  
          */
 333  
         protected boolean considerKey(String key, ParameterParser pp)
 334  
         {
 335  3
                 if (!submitValueKey)
 336  
                 {
 337  3
                         log.debug("No Value required, accepting " + key);
 338  3
                         return true;
 339  
                 }
 340  
                 else
 341  
                 {
 342  
                         // If the action.eventsubmit.needsvalue key is true,
 343  
                         // events with a "0" or empty value are ignored.
 344  
                         // This can be used if you have multiple eventSubmit_do<xxx>
 345  
                         // fields in your form which are selected by client side code,
 346  
                         // e.g. JavaScript.
 347  
                         //
 348  
                         // If this key is unset or missing, nothing changes for the
 349  
                         // current behavior.
 350  
                         //
 351  0
                         String keyValue = pp.getString(key);
 352  0
                         log.debug("Key Value is " + keyValue);
 353  0
                         if (StringUtils.isEmpty(keyValue))
 354  
                         {
 355  0
                                 log.debug("Key is empty, rejecting " + key);
 356  0
                                 return false;
 357  
                         }
 358  
 
 359  
                         try
 360  
                         {
 361  0
                                 if (Integer.parseInt(keyValue) != 0)
 362  
                                 {
 363  0
                                         log.debug("Integer != 0, accepting " + key);
 364  0
                                         return true;
 365  
                                 }
 366  
                         }
 367  0
                         catch (NumberFormatException nfe)
 368  
                         {
 369  
                                 // Not a number. So it might be a
 370  
                                 // normal Key like "continue" or "exit". Accept
 371  
                                 // it.
 372  0
                                 log.debug("Not a number, accepting " + key);
 373  0
                                 return true;
 374  0
                         }
 375  
                 }
 376  0
                 log.debug("Rejecting " + key);
 377  0
                 return false;
 378  
         }
 379  
 }