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

Save This Page
Home » struts-2.1.8.1-src » org.apache » struts2 » dispatcher » mapper » [javadoc | source]