##### 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;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 |  |
     }