View Javadoc

    1   /*
    2    * $Id: DefaultActionMapper.java 498085 2007-01-20 08:13:11Z mrdon $
    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  package org.apache.struts2.dispatcher.mapper;
    22
    23  import java.util.ArrayList;
    24  import java.util.Arrays;
    25  import java.util.HashSet;
    26  import java.util.Iterator;
    27  import java.util.List;
    28  import java.util.Map;
    29  import java.util.Set;
    30
    31  import javax.servlet.http.HttpServletRequest;
    32
    33  import org.apache.struts2.RequestUtils;
    34  import org.apache.struts2.StrutsConstants;
    35  import org.apache.struts2.dispatcher.ServletRedirectResult;
    36  import org.apache.struts2.util.PrefixTrie;
    37
    38  import com.opensymphony.xwork2.config.Configuration;
    39  import com.opensymphony.xwork2.config.ConfigurationManager;
    40  import com.opensymphony.xwork2.config.entities.PackageConfig;
    41  import com.opensymphony.xwork2.inject.Inject;
    42  import com.opensymphony.xwork2.inject.Container;
    43
    44  /***
    45   * <!-- START SNIPPET: javadoc -->
    46   *
    47   * Default action mapper implementation, using the standard *.[ext] (where ext
    48   * usually "action") pattern. The extension is looked up from the Struts
    49   * configuration key <b>struts.action.exection</b>.
    50   *
    51   * <p/> To help with dealing with buttons and other related requirements, this
    52   * mapper (and other {@link ActionMapper}s, we hope) has the ability to name a
    53   * button with some predefined prefix and have that button name alter the
    54   * execution behaviour. The four prefixes are:
    55   *
    56   * <ul>
    57   *
    58   * <li>Method prefix - <i>method:default</i></li>
    59   *
    60   * <li>Action prefix - <i>action:dashboard</i></li>
    61   *
    62   * <li>Redirect prefix - <i>redirect:cancel.jsp</i></li>
    63   *
    64   * <li>Redirect-action prefix - <i>redirect-action:cancel</i></li>
    65   *
    66   * </ul>
    67   *
    68   * <p/> In addition to these four prefixes, this mapper also understands the
    69   * action naming pattern of <i>foo!bar</i> in either the extension form (eg:
    70   * foo!bar.action) or in the prefix form (eg: action:foo!bar). This syntax tells
    71   * this mapper to map to the action named <i>foo</i> and the method <i>bar</i>.
    72   *
    73   * <!-- END SNIPPET: javadoc -->
    74   *
    75   * <p/> <b>Method Prefix</b> <p/>
    76   *
    77   * <!-- START SNIPPET: method -->
    78   *
    79   * With method-prefix, instead of calling baz action's execute() method (by
    80   * default if it isn't overriden in struts.xml to be something else), the baz
    81   * action's anotherMethod() will be called. A very elegant way determine which
    82   * button is clicked. Alternatively, one would have submit button set a
    83   * particular value on the action when clicked, and the execute() method decides
    84   * on what to do with the setted value depending on which button is clicked.
    85   *
    86   * <!-- END SNIPPET: method -->
    87   *
    88   * <pre>
    89   *  &lt;!-- START SNIPPET: method-example --&gt;
    90   *  &lt;a:form action=&quot;baz&quot;&gt;
    91   *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
    92   *      &lt;a:submit value=&quot;Create person&quot;/&gt;
    93   *      &lt;a:submit name=&quot;method:anotherMethod&quot; value=&quot;Cancel&quot;/&gt;
    94   *  &lt;/a:form&gt;
    95   *  &lt;!-- END SNIPPET: method-example --&gt;
    96   * </pre>
    97   *
    98   * <p/> <b>Action prefix</b> <p/>
    99   *
    100  * <!-- START SNIPPET: action -->
    101  *
    102  * With action-prefix, instead of executing baz action's execute() method (by
    103  * default if it isn't overriden in struts.xml to be something else), the
    104  * anotherAction action's execute() method (assuming again if it isn't overriden
    105  * with something else in struts.xml) will be executed.
    106  *
    107  * <!-- END SNIPPET: action -->
    108  *
    109  * <pre>
    110  *  &lt;!-- START SNIPPET: action-example --&gt;
    111  *  &lt;a:form action=&quot;baz&quot;&gt;
    112  *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
    113  *      &lt;a:submit value=&quot;Create person&quot;/&gt;
    114  *      &lt;a:submit name=&quot;action:anotherAction&quot; value=&quot;Cancel&quot;/&gt;
    115  *  &lt;/a:form&gt;
    116  *  &lt;!-- END SNIPPET: action-example --&gt;
    117  * </pre>
    118  *
    119  * <p/> <b>Redirect prefix</b> <p/>
    120  *
    121  * <!-- START SNIPPET: redirect -->
    122  *
    123  * With redirect-prefix, instead of executing baz action's execute() method (by
    124  * default it isn't overriden in struts.xml to be something else), it will get
    125  * redirected to, in this case to www.google.com. Internally it uses
    126  * ServletRedirectResult to do the task.
    127  *
    128  * <!-- END SNIPPET: redirect -->
    129  *
    130  * <pre>
    131  *  &lt;!-- START SNIPPET: redirect-example --&gt;
    132  *  &lt;a:form action=&quot;baz&quot;&gt;
    133  *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
    134  *      &lt;a:submit value=&quot;Create person&quot;/&gt;
    135  *      &lt;a:submit name=&quot;redirect:www.google.com&quot; value=&quot;Cancel&quot;/&gt;
    136  *  &lt;/a:form&gt;
    137  *  &lt;!-- END SNIPPET: redirect-example --&gt;
    138  * </pre>
    139  *
    140  * <p/> <b>Redirect-action prefix</b> <p/>
    141  *
    142  * <!-- START SNIPPET: redirect-action -->
    143  *
    144  * With redirect-action-prefix, instead of executing baz action's execute()
    145  * method (by default it isn't overriden in struts.xml to be something else), it
    146  * will get redirected to, in this case 'dashboard.action'. Internally it uses
    147  * ServletRedirectResult to do the task and read off the extension from the
    148  * struts.properties.
    149  *
    150  * <!-- END SNIPPET: redirect-action -->
    151  *
    152  * <pre>
    153  *  &lt;!-- START SNIPPET: redirect-action-example --&gt;
    154  *  &lt;a:form action=&quot;baz&quot;&gt;
    155  *      &lt;a:textfield label=&quot;Enter your name&quot; name=&quot;person.name&quot;/&gt;
    156  *      &lt;a:submit value=&quot;Create person&quot;/&gt;
    157  *      &lt;a:submit name=&quot;redirect-action:dashboard&quot; value=&quot;Cancel&quot;/&gt;
    158  *  &lt;/a:form&gt;
    159  *  &lt;!-- END SNIPPET: redirect-action-example --&gt;
    160  * </pre>
    161  *
    162  */
    163 public class DefaultActionMapper implements ActionMapper {
    164
    165     static final String METHOD_PREFIX = "method:";
    166
    167     static final String ACTION_PREFIX = "action:";
    168
    169     static final String REDIRECT_PREFIX = "redirect:";
    170
    171     static final String REDIRECT_ACTION_PREFIX = "redirect-action:";
    172
    173     private boolean allowDynamicMethodCalls = true;
    174
    175     private boolean allowSlashesInActionNames = false;
    176
    177     private boolean alwaysSelectFullNamespace = false;
    178
    179     private PrefixTrie prefixTrie = null;
    180
    181     List extensions = new ArrayList() {{ add("action");}};
    182
    183     private Container container;
    184
    185     public DefaultActionMapper() {
    186         prefixTrie = new PrefixTrie() {
    187             {
    188                 put(METHOD_PREFIX, new ParameterAction() {
    189                     public void execute(String key, ActionMapping mapping) {
    190                         mapping
    191                                 .setMethod(key
    192                                         .substring(METHOD_PREFIX.length()));
    193                     }
    194                 });
    195
    196                 put(ACTION_PREFIX, new ParameterAction() {
    197                     public void execute(String key, ActionMapping mapping) {
    198                         String name = key.substring(ACTION_PREFIX.length());
    199                         if (allowDynamicMethodCalls) {
    200                             int bang = name.indexOf('!');
    201                             if (bang != -1) {
    202                                 String method = name.substring(bang + 1);
    203                                 mapping.setMethod(method);
    204                                 name = name.substring(0, bang);
    205                             }
    206                         }
    207                         mapping.setName(name);
    208                     }
    209                 });
    210
    211                 put(REDIRECT_PREFIX, new ParameterAction() {
    212                     public void execute(String key, ActionMapping mapping) {
    213                         ServletRedirectResult redirect = new ServletRedirectResult();
    214                         container.inject(redirect);
    215                         redirect.setLocation(key.substring(REDIRECT_PREFIX
    216                                 .length()));
    217                         mapping.setResult(redirect);
    218                     }
    219                 });
    220
    221                 put(REDIRECT_ACTION_PREFIX, new ParameterAction() {
    222                     public void execute(String key, ActionMapping mapping) {
    223                         String location = key.substring(REDIRECT_ACTION_PREFIX
    224                                 .length());
    225                         ServletRedirectResult redirect = new ServletRedirectResult();
    226                         container.inject(redirect);
    227                         String extension = getDefaultExtension();
    228                         if (extension != null) {
    229                             location += "." + extension;
    230                         }
    231                         redirect.setLocation(location);
    232                         mapping.setResult(redirect);
    233                     }
    234                 });
    235             }
    236         };
    237     }
    238
    239     @Inject(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION)
    240     public void setAllowDynamicMethodCalls(String allow) {
    241         allowDynamicMethodCalls = "true".equals(allow);
    242     }
    243
    244     @Inject(StrutsConstants.STRUTS_ENABLE_SLASHES_IN_ACTION_NAMES)
    245     public void setSlashesInActionNames(String allow) {
    246         allowSlashesInActionNames = "true".equals(allow);
    247     }
    248
    249     @Inject(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE)
    250     public void setAlwaysSelectFullNamespace(String val) {
    251         this.alwaysSelectFullNamespace = "true".equals(val);
    252     }
    253
    254     @Inject
    255     public void setContainer(Container container) {
    256         this.container = container;
    257     }
    258
    259     /*
    260      * (non-Javadoc)
    261      *
    262      * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest)
    263      */
    264     public ActionMapping getMapping(HttpServletRequest request,
    265             ConfigurationManager configManager) {
    266         ActionMapping mapping = new ActionMapping();
    267         String uri = getUri(request);
    268
    269         uri = dropExtension(uri);
    270         if (uri == null) {
    271             return null;
    272         }
    273
    274         parseNameAndNamespace(uri, mapping, configManager);
    275
    276         handleSpecialParameters(request, mapping);
    277
    278         if (mapping.getName() == null) {
    279             return null;
    280         }
    281
    282         if (allowDynamicMethodCalls) {
    283             // handle "name!method" convention.
    284             String name = mapping.getName();
    285             int exclamation = name.lastIndexOf("!");
    286             if (exclamation != -1) {
    287                 mapping.setName(name.substring(0, exclamation));
    288                 mapping.setMethod(name.substring(exclamation + 1));
    289             }
    290         }
    291
    292         return mapping;
    293     }
    294
    295     /***
    296      * Special parameters, as described in the class-level comment, are searched
    297      * for and handled.
    298      *
    299      * @param request
    300      *            The request
    301      * @param mapping
    302      *            The action mapping
    303      */
    304     public void handleSpecialParameters(HttpServletRequest request,
    305             ActionMapping mapping) {
    306         // handle special parameter prefixes.
    307         Set<String> uniqueParameters = new HashSet<String>();
    308         Map parameterMap = request.getParameterMap();
    309         for (Iterator iterator = parameterMap.keySet().iterator(); iterator
    310                 .hasNext();) {
    311             String key = (String) iterator.next();
    312
    313             // Strip off the image button location info, if found
    314             if (key.endsWith(".x") || key.endsWith(".y")) {
    315                 key = key.substring(0, key.length() - 2);
    316             }
    317
    318             // Ensure a parameter doesn't get processed twice
    319             if (!uniqueParameters.contains(key)) {
    320                 ParameterAction parameterAction = (ParameterAction) prefixTrie
    321                         .get(key);
    322                 if (parameterAction != null) {
    323                     parameterAction.execute(key, mapping);
    324                     uniqueParameters.add(key);
    325                     break;
    326                 }
    327             }
    328         }
    329     }
    330
    331     /***
    332      * Parses the name and namespace from the uri
    333      *
    334      * @param uri
    335      *            The uri
    336      * @param mapping
    337      *            The action mapping to populate
    338      */
    339     void parseNameAndNamespace(String uri, ActionMapping mapping,
    340             ConfigurationManager configManager) {
    341         String namespace, name;
    342         int lastSlash = uri.lastIndexOf("/");
    343         if (lastSlash == -1) {
    344             namespace = "";
    345             name = uri;
    346         } else if (lastSlash == 0) {
    347             // ww-1046, assume it is the root namespace, it will fallback to
    348             // default
    349             // namespace anyway if not found in root namespace.
    350             namespace = "/";
    351             name = uri.substring(lastSlash + 1);
    352         } else if (alwaysSelectFullNamespace) {
    353             // Simply select the namespace as everything before the last slash
    354             namespace = uri.substring(0, lastSlash);
    355             name = uri.substring(lastSlash + 1);
    356         } else {
    357             // Try to find the namespace in those defined, defaulting to ""
    358             Configuration config = configManager.getConfiguration();
    359             String prefix = uri.substring(0, lastSlash);
    360             namespace = "";
    361             // Find the longest matching namespace, defaulting to the default
    362             for (Iterator i = config.getPackageConfigs().values().iterator(); i
    363                     .hasNext();) {
    364                 String ns = ((PackageConfig) i.next()).getNamespace();
    365                 if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
    366                     if (ns.length() > namespace.length()) {
    367                         namespace = ns;
    368                     }
    369                 }
    370             }
    371
    372             name = uri.substring(namespace.length() + 1);
    373         }
    374
    375         if (!allowSlashesInActionNames && name != null) {
    376             int pos = name.lastIndexOf('/');
    377             if (pos > -1 && pos < name.length() - 1) {
    378                 name = name.substring(pos + 1);
    379             }
    380         }
    381
    382         mapping.setNamespace(namespace);
    383         mapping.setName(name);
    384     }
    385
    386     /***
    387      * Drops the extension from the action name
    388      *
    389      * @param name
    390      *            The action name
    391      * @return The action name without its extension
    392      */
    393     String dropExtension(String name) {
    394         if (extensions == null) {
    395             return name;
    396         }
    397         Iterator it = extensions.iterator();
    398         while (it.hasNext()) {
    399             String extension = "." + (String) it.next();
    400             if (name.endsWith(extension)) {
    401                 name = name.substring(0, name.length() - extension.length());
    402                 return name;
    403             }
    404         }
    405         return null;
    406     }
    407
    408     /***
    409      * Returns null if no extension is specified.
    410      */
    411     String getDefaultExtension() {
    412         if (extensions == null) {
    413             return null;
    414         } else {
    415             return (String) extensions.get(0);
    416         }
    417     }
    418
    419     @Inject(StrutsConstants.STRUTS_ACTION_EXTENSION)
    420     public void setExtensions(String extensions) {
    421         if (!"".equals(extensions)) {
    422             this.extensions = Arrays.asList(extensions.split(","));
    423         } else {
    424             this.extensions = null;
    425         }
    426     }
    427
    428     /***
    429      * Gets the uri from the request
    430      *
    431      * @param request
    432      *            The request
    433      * @return The uri
    434      */
    435     String getUri(HttpServletRequest request) {
    436         // handle http dispatcher includes.
    437         String uri = (String) request
    438                 .getAttribute("javax.servlet.include.servlet_path");
    439         if (uri != null) {
    440             return uri;
    441         }
    442
    443         uri = RequestUtils.getServletPath(request);
    444         if (uri != null && !"".equals(uri)) {
    445             return uri;
    446         }
    447
    448         uri = request.getRequestURI();
    449         return uri.substring(request.getContextPath().length());
    450     }
    451
    452     /*
    453      * (non-Javadoc)
    454      *
    455      * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getUriFromActionMapping(org.apache.struts2.dispatcher.mapper.ActionMapping)
    456      */
    457     public String getUriFromActionMapping(ActionMapping mapping) {
    458         StringBuffer uri = new StringBuffer();
    459
    460         uri.append(mapping.getNamespace());
    461         if (!"/".equals(mapping.getNamespace())) {
    462             uri.append("/");
    463         }
    464         String name = mapping.getName();
    465         String params = "";
    466         if (name.indexOf('?') != -1) {
    467             params = name.substring(name.indexOf('?'));
    468             name = name.substring(0, name.indexOf('?'));
    469         }
    470         uri.append(name);
    471
    472         if (null != mapping.getMethod() && !"".equals(mapping.getMethod())) {
    473             uri.append("!").append(mapping.getMethod());
    474         }
    475
    476         String extension = getDefaultExtension();
    477         if (extension != null) {
    478             if (uri.indexOf('.' + extension) == -1) {
    479                 uri.append(".").append(extension);
    480                 if (params.length() > 0) {
    481                     uri.append(params);
    482                 }
    483             }
    484         }
    485
    486         return uri.toString();
    487     }
    488
    489     /***
    490      * Defines a parameter action prefix
    491      */
    492     interface ParameterAction {
    493         void execute(String key, ActionMapping mapping);
    494     }
    495 }