Save This Page
Home » struts-2.0.11.2-src » org.apache » struts2 » dispatcher » mapper » [javadoc | source]
    1   /*
    2    * $Id: Restful2ActionMapper.java 540814 2007-05-23 02:46:05Z 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 com.opensymphony.xwork2.config.ConfigurationManager;
   24   import com.opensymphony.xwork2.inject.Inject;
   25   
   26   import javax.servlet.http.HttpServletRequest;
   27   import java.util.HashMap;
   28   import java.util.StringTokenizer;
   29   import java.net.URLDecoder;
   30   
   31   import org.apache.commons.logging.Log;
   32   import org.apache.commons.logging.LogFactory;
   33   import org.apache.struts2.StrutsConstants;
   34   
   35   /**
   36    * <!-- START SNIPPET: description -->
   37    *
   38    * Improved restful action mapper that adds several ReST-style improvements to
   39    * action mapping, but supports fully-customized URL's via XML.  The two primary
   40    * ReST enhancements are:
   41    * <ul>
   42    *  <li>If the method is not specified (via '!' or 'method:' prefix), the method is
   43    *      "guessed" at using ReST-style conventions that examine the URL and the HTTP
   44    *      method.</li>
   45    *  <li>Parameters are extracted from the action name, if parameter name/value pairs
   46    *      are specified using PARAM_NAME/PARAM_VALUE syntax.
   47    * </ul>
   48    * <p>
   49    * These two improvements allow a GET request for 'category/action/movie/Thrillers' to
   50    * be mapped to the action name 'movie' with an id of 'Thrillers' with an extra parameter
   51    * named 'category' with a value of 'action'.  A single action mapping can then handle
   52    * all CRUD operations using wildcards, e.g.
   53    * </p>
   54    * <pre>
   55    *   &lt;action name="movie/*" className="app.MovieAction"&gt;
   56    *     &lt;param name="id"&gt;{0}&lt;/param&gt;
   57    *     ...
   58    *   &lt;/action&gt;
   59    * </pre>
   60    * <p>
   61    *   This mapper supports the following parameters:
   62    * </p>
   63    * <ul>
   64    *   <li><code>struts.mapper.idParameterName</code> - If set, this value will be the name
   65    *       of the parameter under which the id is stored.  The id will then be removed
   66    *       from the action name.  This allows restful actions to not require wildcards.
   67    *   </li>
   68    * </ul>
   69    * <p>
   70    * The following URL's will invoke its methods:
   71    * </p>
   72    * <ul>
   73    *  <li><code>GET:    /movie/               => method="index"</code></li>
   74    *  <li><code>GET:    /movie/Thrillers      => method="view", id="Thrillers"</code></li>
   75    *  <li><code>GET:    /movie/Thrillers!edit => method="edit", id="Thrillers"</code></li>
   76    *  <li><code>GET:    /movie/new            => method="editNew"</code></li>
   77    *  <li><code>POST:   /movie/               => method="create"</code></li>
   78    *  <li><code>PUT:    /movie/Thrillers      => method="update", id="Thrillers"</code></li>
   79    *  <li><code>DELETE: /movie/Thrillers      => method="remove", id="Thrillers"</code></li>
   80    * </ul>
   81    * <p>
   82    * To simulate the HTTP methods PUT and DELETE, since they aren't supported by HTML,
   83    * the HTTP parameter "__http_method" will be used.
   84    * </p>
   85    * <p>
   86    * The syntax and design for this feature was inspired by the ReST support in Ruby on Rails.
   87    * See <a href="http://ryandaigle.com/articles/2006/08/01/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it">
   88    * http://ryandaigle.com/articles/2006/08/01/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it
   89    * </a>
   90    * </p>
   91    *
   92    * <!-- END SNIPPET: description -->
   93    */
   94   public class Restful2ActionMapper extends DefaultActionMapper {
   95   
   96       protected static final Log LOG = LogFactory.getLog(Restful2ActionMapper.class);
   97       public static final String HTTP_METHOD_PARAM = "__http_method";
   98       private String idParameterName = null;
   99       
  100       public Restful2ActionMapper() {
  101       	setSlashesInActionNames("true");
  102       }
  103       
  104   
  105       /*
  106       * (non-Javadoc)
  107       *
  108       * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest)
  109       */
  110       public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
  111   
  112       	if (!isSlashesInActionNames()) {
  113       		throw new IllegalStateException("This action mapper requires the setting 'slashesInActionNames' to be set to 'true'");
  114       	}
  115           ActionMapping mapping = super.getMapping(request, configManager);
  116           
  117           if (mapping == null) {
  118               return null;
  119           }
  120   
  121           String actionName = mapping.getName();
  122   
  123           // Only try something if the action name is specified
  124           if (actionName != null && actionName.length() > 0) {
  125               int lastSlashPos = actionName.lastIndexOf('/');
  126   
  127               // If a method hasn't been explicitly named, try to guess using ReST-style patterns
  128               if (mapping.getMethod() == null) {
  129   
  130                   if (lastSlashPos == actionName.length() -1) {
  131   
  132                       // Index e.g. foo/
  133                       if (isGet(request)) {
  134                           mapping.setMethod("index");
  135                           
  136                       // Creating a new entry on POST e.g. foo/
  137                       } else if (isPost(request)) {
  138                           mapping.setMethod("create");
  139                       }
  140   
  141                   } else if (lastSlashPos > -1) {
  142                       String id = actionName.substring(lastSlashPos+1);
  143   
  144                       // Viewing the form to create a new item e.g. foo/new
  145                       if (isGet(request) && "new".equals(id)) {
  146                           mapping.setMethod("editNew");
  147   
  148                       // Viewing an item e.g. foo/1
  149                       } else if (isGet(request)) {
  150                           mapping.setMethod("view");
  151   
  152                       // Removing an item e.g. foo/1
  153                       } else if (isDelete(request)) {
  154                           mapping.setMethod("remove");
  155                       
  156                       // Updating an item e.g. foo/1    
  157                       }  else if (isPut(request)) {
  158                           mapping.setMethod("update");
  159                       }
  160                       
  161                       if (idParameterName != null) {
  162                       	if (mapping.getParams() == null) {
  163                               mapping.setParams(new HashMap());
  164                           }
  165                       	mapping.getParams().put(idParameterName, id);
  166                       }
  167                   }
  168                   
  169                   if (idParameterName != null && lastSlashPos > -1) {
  170                   	actionName = actionName.substring(0, lastSlashPos);
  171                   }
  172               }
  173   
  174               // Try to determine parameters from the url before the action name
  175               int actionSlashPos = actionName.lastIndexOf('/', lastSlashPos - 1);
  176               if (actionSlashPos > 0 && actionSlashPos < lastSlashPos) {
  177                   String params = actionName.substring(0, actionSlashPos);
  178                   HashMap<String,String> parameters = new HashMap<String,String>();
  179                   try {
  180                       StringTokenizer st = new StringTokenizer(params, "/");
  181                       boolean isNameTok = true;
  182                       String paramName = null;
  183                       String paramValue;
  184   
  185                       while (st.hasMoreTokens()) {
  186                           if (isNameTok) {
  187                               paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
  188                               isNameTok = false;
  189                           } else {
  190                               paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");
  191   
  192                               if ((paramName != null) && (paramName.length() > 0)) {
  193                                   parameters.put(paramName, paramValue);
  194                               }
  195   
  196                               isNameTok = true;
  197                           }
  198                       }
  199                       if (parameters.size() > 0) {
  200                           if (mapping.getParams() == null) {
  201                               mapping.setParams(new HashMap());
  202                           }
  203                           mapping.getParams().putAll(parameters);
  204                       }
  205                   } catch (Exception e) {
  206                       LOG.warn(e);
  207                   }
  208                   mapping.setName(actionName.substring(actionSlashPos+1));
  209               }
  210           }
  211   
  212           return mapping;
  213       }
  214   
  215       protected boolean isGet(HttpServletRequest request) {
  216           return "get".equalsIgnoreCase(request.getMethod());
  217       }
  218   
  219       protected boolean isPost(HttpServletRequest request) {
  220           return "post".equalsIgnoreCase(request.getMethod());
  221       }
  222   
  223       protected boolean isPut(HttpServletRequest request) {
  224           if ("put".equalsIgnoreCase(request.getMethod())) {
  225               return true;
  226           } else {
  227               return isPost(request) && "put".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
  228           }
  229       }
  230   
  231       protected boolean isDelete(HttpServletRequest request) {
  232           if ("delete".equalsIgnoreCase(request.getMethod())) {
  233               return true;
  234           } else {
  235               return isPost(request) && "delete".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
  236           }
  237       }
  238   
  239   	public String getIdParameterName() {
  240   		return idParameterName;
  241   	}
  242   
  243   	@Inject(required=false,value=StrutsConstants.STRUTS_ID_PARAMETER_NAME)
  244   	public void setIdParameterName(String idParameterName) {
  245   		this.idParameterName = idParameterName;
  246   	}
  247       
  248       
  249   
  250   }

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