View Javadoc

1   package org.apache.turbine.modules;
2   
3   /*
4    * Copyright 2001-2004 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.util.Iterator;
22  
23  import org.apache.commons.lang.StringUtils;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import org.apache.turbine.Turbine;
29  import org.apache.turbine.TurbineConstants;
30  import org.apache.turbine.util.RunData;
31  import org.apache.turbine.util.parser.ParameterParser;
32  import org.apache.turbine.util.parser.ParserUtils;
33  
34  /***
35   * <p>
36   *
37   * This is an alternative to the Action class that allows you to do
38   * event based actions. Essentially, you label all your submit buttons
39   * with the prefix of "eventSubmit_" and the suffix of "methodName".
40   * For example, "eventSubmit_doDelete". Then any class that subclasses
41   * this class will get its "doDelete(RunData data)" method executed.
42   * If for any reason, it was not able to execute the method, it will
43   * fall back to executing the doPeform() method which is required to
44   * be implemented.
45   *
46   * <p>
47   *
48   * Limitations:
49   *
50   * <p>
51   *
52   * Because ParameterParser makes all the key values lowercase, we have
53   * to do some work to format the string into a method name. For
54   * example, a button name eventSubmit_doDelete gets converted into
55   * eventsubmit_dodelete. Thus, we need to form some sort of naming
56   * convention so that dodelete can be turned into doDelete.
57   *
58   * <p>
59   *
60   * Thus, the convention is this:
61   *
62   * <ul>
63   * <li>The variable name MUST have the prefix "eventSubmit_".</li>
64   * <li>The variable name after the prefix MUST begin with the letters
65   * "do".</li>
66   * <li>The first letter after the "do" will be capitalized and the
67   * rest will be lowercase</li>
68   * </ul>
69   *
70   * If you follow these conventions, then you should be ok with your
71   * method naming in your Action class.
72   *
73   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens </a>
74   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
75   * @author <a href="quintonm@bellsouth.net">Quinton McCombs</a>
76   * @version $Id: ActionEvent.java,v 1.20.2.4 2004/05/20 03:03:52 seade Exp $
77   */
78  public abstract class ActionEvent extends Action
79  {
80      /*** Logging */
81      protected Log log = LogFactory.getLog(this.getClass());
82  
83      /*** Constant needed for Reflection */
84      private static final Class [] methodParams
85              = new Class [] { RunData.class };
86  
87      /***
88       * You need to implement this in your classes that extend this class.
89       *
90       * @param data Turbine information.
91       * @exception Exception a generic exception.
92       */
93      public abstract void doPerform(RunData data)
94              throws Exception;
95  
96      /*** The name of the button to look for. */
97      protected static final String BUTTON = "eventSubmit_";
98      /*** The length of the button to look for. */
99      protected static final int BUTTON_LENGTH = BUTTON.length();
100     /*** The prefix of the method name. */
101     protected static final String METHOD_NAME_PREFIX = "do";
102     /*** The length of the method name. */
103     protected static final int METHOD_NAME_LENGTH = METHOD_NAME_PREFIX.length();
104     /*** The length of the button to look for. */
105     protected static final int LENGTH = BUTTON.length();
106 
107     /*** 
108      * If true, the eventSubmit_do<xxx> variable must contain
109      * a not null value to be executed.
110      */
111     private boolean submitValueKey = false;
112     
113     /***
114      * C'tor
115      */
116     public ActionEvent()
117     {
118         super();
119         
120         submitValueKey = Turbine.getConfiguration()
121                 .getBoolean(TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_KEY,
122                         TurbineConstants.ACTION_EVENTSUBMIT_NEEDSVALUE_DEFAULT);
123 
124         log.debug(submitValueKey 
125                 ? "ActionEvent accepts only eventSubmit_do Keys with a value != 0"
126                 : "ActionEvent accepts all eventSubmit_do Keys");
127     }
128     
129     /***
130      * This overrides the default Action.perform() to execute the
131      * doEvent() method. If that fails, then it will execute the
132      * doPerform() method instead.
133      *
134      * @param data Turbine information.
135      * @exception Exception a generic exception.
136      */
137     protected void perform(RunData data)
138             throws Exception
139     {
140         try
141         {
142             executeEvents(data);
143         }
144         catch (NoSuchMethodException e)
145         {
146             doPerform(data);
147         }
148     }
149 
150     /***
151      * This method should be called to execute the event based system.
152      *
153      * @param data Turbine information.
154      * @exception Exception a generic exception.
155      */
156     public void executeEvents(RunData data)
157             throws Exception
158     {
159         // Name of the button.
160         String theButton = null;
161         // Parameter parser.
162         ParameterParser pp = data.getParameters();
163 
164         String button = pp.convert(BUTTON);
165         String key = null;
166 
167         // Loop through and find the button.
168         for (Iterator it = pp.keySet().iterator(); it.hasNext();)
169         {
170             key = (String) it.next();
171             if (key.startsWith(button))
172             {
173                 if (considerKey(key, pp))
174                 {
175                     theButton = formatString(key);
176                     break;
177                 }
178             }
179         }
180 
181         if (theButton == null)
182         {
183             throw new NoSuchMethodException("ActionEvent: The button was null");
184         }
185 
186         Method method = null;
187 
188         try
189         {
190             method = getClass().getMethod(theButton, methodParams);
191             Object[] methodArgs = new Object[] { data };
192 
193             if (log.isDebugEnabled())
194             {
195                 log.debug("Invoking " + method);
196             }
197 
198             method.invoke(this, methodArgs);
199         }
200         catch (InvocationTargetException ite)
201         {
202             Throwable t = ite.getTargetException();
203             log.error("Invokation of " + method , t);
204         }
205         finally
206         {
207             pp.remove(key);
208         }
209     }
210 
211     /***
212      * This method does the conversion of the lowercase method name
213      * into the proper case.
214      *
215      * @param input The unconverted method name.
216      * @return A string with the method name in the proper case.
217      */
218     protected final String formatString(String input)
219     {
220         String tmp = input;
221         
222         if (StringUtils.isNotEmpty(input))
223         {
224             tmp = input.toLowerCase();
225             
226             // Chop off suffixes (for image type)
227             input = (tmp.endsWith(".x") || tmp.endsWith(".y"))
228                     ? input.substring(0, input.length() - 2)
229                     : input;
230             
231             if (ParserUtils.getUrlFolding() 
232                     != ParserUtils.URL_CASE_FOLDING_NONE)
233             {
234                 tmp = input.toLowerCase().substring(BUTTON_LENGTH + METHOD_NAME_LENGTH);
235                 tmp = METHOD_NAME_PREFIX + StringUtils.capitalise(tmp);
236             }
237             else
238             {
239                 tmp = input.substring(BUTTON_LENGTH);
240             }
241         }
242         return tmp;
243     }
244 
245     /***
246      * Checks whether the selected key really is a valid event.
247      *
248      * @param key The selected key
249      * @param pp The parameter parser to look for the key value
250      *
251      * @return true if this key is really an ActionEvent Key
252      */
253     protected boolean considerKey(String key, ParameterParser pp)
254     {
255         if (!submitValueKey)
256         {
257             log.debug("No Value required, accepting " + key);
258             return true;
259         }
260         else
261         {
262             // If the action.eventsubmit.needsvalue key is true, 
263             // events with a "0" or empty value are ignored.
264             // This can be used if you have multiple eventSubmit_do<xxx>
265             // fields in your form which are selected by client side code, 
266             // e.g. JavaScript.
267             //
268             // If this key is unset or missing, nothing changes for the
269             // current behaviour.
270             //
271             String keyValue = pp.getString(key);
272             log.debug("Key Value is " + keyValue);
273             if (StringUtils.isEmpty(keyValue))
274             {
275                 log.debug("Key is empty, rejecting " + key);
276                 return false;
277             }
278 
279             try
280             {
281                 if (Integer.parseInt(keyValue) != 0)
282                 {
283                     log.debug("Integer != 0, accepting " + key);
284                     return true;
285                 }
286             }
287             catch (NumberFormatException nfe)
288             {
289                 // Not a number. So it might be a
290                 // normal Key like "continue" or "exit". Accept
291                 // it.
292                 log.debug("Not a number, accepting " + key);
293                 return true;
294             }
295         }
296         log.debug("Rejecting " + key);
297         return false;
298     }
299 }