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    }