Coverage Report - org.apache.turbine.modules.ActionEvent
 
Classes in this File Line Coverage Branch Coverage Complexity
ActionEvent
73%
67/91
67%
31/46
7,4
 
 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.annotation.Annotation;
 23  
 import java.lang.reflect.InvocationTargetException;
 24  
 import java.lang.reflect.Method;
 25  
 import java.util.Arrays;
 26  
 import java.util.concurrent.ConcurrentHashMap;
 27  
 import java.util.concurrent.ConcurrentMap;
 28  
 
 29  
 import org.apache.commons.lang3.StringUtils;
 30  
 import org.apache.fulcrum.parser.ParameterParser;
 31  
 import org.apache.fulcrum.parser.ValueParser.URLCaseFolding;
 32  
 import org.apache.logging.log4j.LogManager;
 33  
 import org.apache.logging.log4j.Logger;
 34  
 import org.apache.turbine.Turbine;
 35  
 import org.apache.turbine.TurbineConstants;
 36  
 import org.apache.turbine.annotation.AnnotationProcessor;
 37  
 import org.apache.turbine.annotation.TurbineActionEvent;
 38  
 import org.apache.turbine.annotation.TurbineConfiguration;
 39  
 import org.apache.turbine.pipeline.PipelineData;
 40  
 
 41  
 /**
 42  
  * <p>
 43  
  *
 44  
  * This is an alternative to the Action class that allows you to do
 45  
  * event based actions. Essentially, you label all your submit buttons
 46  
  * with the prefix of "eventSubmit_" and the suffix of "methodName".
 47  
  * For example, "eventSubmit_doDelete". Then any class that subclasses
 48  
  * this class will get its "doDelete(PipelineData data)" method executed.
 49  
  * If for any reason, it was not able to execute the method, it will
 50  
  * fall back to executing the doPerform() method which is required to
 51  
  * be implemented.
 52  
  *
 53  
  * <p>
 54  
  *
 55  
  * Limitations:
 56  
  *
 57  
  * <p>
 58  
  *
 59  
  * Because ParameterParser makes all the key values lowercase, we have
 60  
  * to do some work to format the string into a method name. For
 61  
  * example, a button name eventSubmit_doDelete gets converted into
 62  
  * eventsubmit_dodelete. Thus, we need to form some sort of naming
 63  
  * convention so that dodelete can be turned into doDelete.
 64  
  *
 65  
  * <p>
 66  
  *
 67  
  * Thus, the convention is this:
 68  
  *
 69  
  * <ul>
 70  
  * <li>The variable name MUST have the prefix "eventSubmit_".</li>
 71  
  * <li>The variable name after the prefix MUST begin with the letters
 72  
  * "do".</li>
 73  
  * <li>The first letter after the "do" will be capitalized and the
 74  
  * rest will be lowercase</li>
 75  
  * </ul>
 76  
  *
 77  
  * If you follow these conventions, then you should be ok with your
 78  
  * method naming in your Action class.
 79  
  *
 80  
  * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens </a>
 81  
  * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
 82  
  * @author <a href="quintonm@bellsouth.net">Quinton McCombs</a>
 83  
  * @author <a href="mailto:peter@courcoux.biz">Peter Courcoux</a>
 84  
  * @version $Id: ActionEvent.java 1854786 2019-03-04 18:29:18Z tv $
 85  
  */
 86  33
 public abstract class ActionEvent implements Action
 87  
 {
 88  
         /** Logging */
 89  33
         protected Logger log = LogManager.getLogger(this.getClass());
 90  
 
 91  
         /** The name of the button to look for. */
 92  
         protected static final String BUTTON = "eventSubmit_";
 93  
         /** The length of the button to look for. */
 94  6
         protected static final int BUTTON_LENGTH = BUTTON.length();
 95  
     /** The default method. */
 96  
     protected static final String DEFAULT_METHOD = "doPerform";
 97  
         /** The prefix of the method name. */
 98  
         protected static final String METHOD_NAME_PREFIX = "do";
 99  
         /** The length of the method name. */
 100  6
         protected static final int METHOD_NAME_LENGTH = METHOD_NAME_PREFIX.length();
 101  
         /** The length of the button to look for. */
 102  6
         protected static final int LENGTH = BUTTON.length();
 103  
 
 104  
         /**
 105  
          * If true, the eventSubmit_do&lt;xxx&gt; variable must contain
 106  
          * a not null value to be executed.
 107  
          */
 108  33
     @TurbineConfiguration( TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_KEY )
 109  
         private boolean submitValueKey = TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_DEFAULT;
 110  
 
 111  
         /**
 112  
          * If true, then exceptions raised in eventSubmit_do&lt;xxx&gt; methods
 113  
          * as well as in doPerform methods are bubbled up to the Turbine
 114  
          * servlet's handleException method.
 115  
          */
 116  33
     @TurbineConfiguration( TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP )
 117  
         protected boolean bubbleUpException = TurbineConstants.ACTION_EVENT_BUBBLE_EXCEPTION_UP_DEFAULT;
 118  
 
 119  
         /**
 120  
          * Cache for the methods to invoke
 121  
          */
 122  33
         private ConcurrentMap<String, Method> methodCache = new ConcurrentHashMap<String, Method>();
 123  
 
 124  
         /**
 125  
          * Retrieve a method of the given name and signature. The value is cached.
 126  
          *
 127  
          * @param name the name of the method
 128  
          * @param signature an array of classes forming the signature of the method
 129  
          * @param pp ParameterParser for correct folding
 130  
          *
 131  
          * @return the method object
 132  
          * @throws NoSuchMethodException if the method does not exist
 133  
          */
 134  
         protected Method getMethod(String name, Class<?>[] signature, ParameterParser pp) throws NoSuchMethodException
 135  
         {
 136  24
             StringBuilder cacheKey = new StringBuilder(name);
 137  72
             for (Class<?> clazz : signature)
 138  
             {
 139  48
                 cacheKey.append(':').append(clazz.getCanonicalName());
 140  
             }
 141  
 
 142  24
             Method method = this.methodCache.get(cacheKey.toString());
 143  
 
 144  24
             if (method == null)
 145  
             {
 146  
                 // Try annotations of public methods
 147  24
                 Method[] methods = getClass().getMethods();
 148  
 
 149  
         methodLoop:
 150  419
                 for (Method m : methods)
 151  
                 {
 152  398
                     Annotation[] annotations = AnnotationProcessor.getAnnotations(m);
 153  404
                     for (Annotation a : annotations)
 154  
                     {
 155  9
                         if (a instanceof TurbineActionEvent)
 156  
                         {
 157  9
                             TurbineActionEvent tae = (TurbineActionEvent) a;
 158  9
                             if (name.equals(pp.convert(tae.value()))
 159  3
                             && Arrays.equals(signature, m.getParameterTypes()))
 160  
                             {
 161  3
                                 method = m;
 162  3
                                 break methodLoop;
 163  
                             }
 164  
                         }
 165  
                     }
 166  
                 }
 167  
 
 168  
                 // Try legacy mode
 169  24
                 if (method == null)
 170  
                 {
 171  21
                 String tmp = name.toLowerCase().substring(METHOD_NAME_LENGTH);
 172  21
                     method = getClass().getMethod(METHOD_NAME_PREFIX + StringUtils.capitalize(tmp), signature);
 173  
                 }
 174  
 
 175  24
                 Method oldMethod = this.methodCache.putIfAbsent(cacheKey.toString(), method);
 176  24
                 if (oldMethod != null)
 177  
                 {
 178  0
                     method = oldMethod;
 179  
                 }
 180  
             }
 181  
 
 182  24
             return method;
 183  
         }
 184  
 
 185  
         /**
 186  
          * This overrides the default Action.doPerform() to execute the
 187  
          * doEvent() method. If that fails, then it will execute the
 188  
          * doPerform() method instead.
 189  
          *
 190  
          * @param pipelineData Turbine information.
 191  
          * @throws Exception a generic exception.
 192  
          */
 193  
         @Override
 194  
     public void doPerform(PipelineData pipelineData)
 195  
                         throws Exception
 196  
         {
 197  0
             ParameterParser pp = pipelineData.get(Turbine.class, ParameterParser.class);
 198  0
                 executeEvents(pp, new Class<?>[]{ PipelineData.class }, new Object[]{ pipelineData });
 199  0
         }
 200  
 
 201  
         /**
 202  
          * This method should be called to execute the event based system.
 203  
          *
 204  
          * @param pp the parameter parser
 205  
          * @param signature the signature of the method to call
 206  
          * @param parameters the parameters for the method to call
 207  
          *
 208  
          * @throws Exception a generic exception.
 209  
          */
 210  
         protected void executeEvents(ParameterParser pp, Class<?>[] signature, Object[] parameters)
 211  
                         throws Exception
 212  
         {
 213  
                 // Name of the button.
 214  24
                 String theButton = null;
 215  
 
 216  24
                 String button = pp.convert(BUTTON);
 217  24
                 String key = null;
 218  
 
 219  
                 // Loop through and find the button.
 220  24
                 for (String k : pp)
 221  
                 {
 222  15
                         key = k;
 223  15
                         if (key.startsWith(button))
 224  
                         {
 225  9
                                 if (considerKey(key, pp))
 226  
                                 {
 227  9
                                         theButton = key;
 228  9
                                         break;
 229  
                                 }
 230  
                         }
 231  6
                 }
 232  
 
 233  24
                 if (theButton == null)
 234  
                 {
 235  15
                     theButton = BUTTON + DEFAULT_METHOD;
 236  15
                     key = null;
 237  
                 }
 238  
 
 239  24
                 theButton = formatString(theButton, pp);
 240  24
                 Method method = null;
 241  
 
 242  
         try
 243  
         {
 244  24
             method = getMethod(theButton, signature, pp);
 245  
         }
 246  0
         catch (NoSuchMethodException e)
 247  
         {
 248  0
             method = getMethod(DEFAULT_METHOD, signature, pp);
 249  
         }
 250  
         finally
 251  
         {
 252  24
             if (key != null)
 253  
             {
 254  9
                 pp.remove(key);
 255  
             }
 256  0
         }
 257  
 
 258  
                 try
 259  
                 {
 260  24
                         log.debug("Invoking {}", method);
 261  
 
 262  24
                         method.invoke(this, parameters);
 263  
                 }
 264  9
                 catch (InvocationTargetException ite)
 265  
                 {
 266  9
                         Throwable t = ite.getTargetException();
 267  9
                         if (bubbleUpException)
 268  
                         {
 269  3
                 if (t instanceof Exception)
 270  
                 {
 271  3
                     throw (Exception) t;
 272  
                 }
 273  
                 else
 274  
                 {
 275  0
                     throw ite;
 276  
                 }
 277  
                         }
 278  
                         else
 279  
                         {
 280  6
                             log.error("Invokation of {}", method, t);
 281  
                         }
 282  15
                 }
 283  21
         }
 284  
 
 285  
         /**
 286  
          * This method does the conversion of the lowercase method name
 287  
          * into the proper case.
 288  
          *
 289  
          * @param input The unconverted method name.
 290  
          * @param pp The parameter parser (for correct folding)
 291  
          * @return A string with the method name in the proper case.
 292  
          */
 293  
         protected String formatString(String input, ParameterParser pp)
 294  
         {
 295  24
                 String tmp = input;
 296  
 
 297  24
                 if (StringUtils.isNotEmpty(input))
 298  
                 {
 299  24
                         tmp = input.toLowerCase();
 300  
 
 301  
                         // Chop off suffixes (for image type)
 302  24
                         String methodName = (tmp.endsWith(".x") || tmp.endsWith(".y"))
 303  0
                                         ? input.substring(0, input.length() - 2)
 304  
                                         : input;
 305  
 
 306  24
                         if (pp.getUrlFolding() == URLCaseFolding.NONE)
 307  
                         {
 308  24
                 tmp = methodName.substring(BUTTON_LENGTH);
 309  
                         }
 310  
                         else
 311  
                         {
 312  0
                 tmp = methodName.toLowerCase().substring(BUTTON_LENGTH);
 313  
                         }
 314  
                 }
 315  
 
 316  24
                 return tmp;
 317  
         }
 318  
 
 319  
         /**
 320  
          * Checks whether the selected key really is a valid event.
 321  
          *
 322  
          * @param key The selected key
 323  
          * @param pp The parameter parser to look for the key value
 324  
          *
 325  
          * @return true if this key is really an ActionEvent Key
 326  
          */
 327  
         protected boolean considerKey(String key, ParameterParser pp)
 328  
         {
 329  9
                 if (!submitValueKey)
 330  
                 {
 331  9
                         log.debug("No Value required, accepting {}", key);
 332  9
                         return true;
 333  
                 }
 334  
                 else
 335  
                 {
 336  
                         // If the action.eventsubmit.needsvalue key is true,
 337  
                         // events with a "0" or empty value are ignored.
 338  
                         // This can be used if you have multiple eventSubmit_do&lt;xxx&gt;
 339  
                         // fields in your form which are selected by client side code,
 340  
                         // e.g. JavaScript.
 341  
                         //
 342  
                         // If this key is unset or missing, nothing changes for the
 343  
                         // current behavior.
 344  
                         //
 345  0
                         String keyValue = pp.getString(key);
 346  0
                         log.debug("Key Value is {}", keyValue);
 347  0
                         if (StringUtils.isEmpty(keyValue))
 348  
                         {
 349  0
                                 log.debug("Key is empty, rejecting {}", key);
 350  0
                                 return false;
 351  
                         }
 352  
 
 353  
                         try
 354  
                         {
 355  0
                                 if (Integer.parseInt(keyValue) != 0)
 356  
                                 {
 357  0
                                         log.debug("Integer != 0, accepting {}", key);
 358  0
                                         return true;
 359  
                                 }
 360  
                         }
 361  0
                         catch (NumberFormatException nfe)
 362  
                         {
 363  
                                 // Not a number. So it might be a
 364  
                                 // normal Key like "continue" or "exit". Accept
 365  
                                 // it.
 366  0
                                 log.debug("Not a number, accepting " + key);
 367  0
                                 return true;
 368  0
                         }
 369  
                 }
 370  0
                 log.debug("Rejecting {}", key);
 371  0
                 return false;
 372  
         }
 373  
 }