    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    }