FileDocCategorySizeDatePackage
Restful2ActionMapper.javaAPI DocExample10031Mon Jul 23 13:26:36 BST 2007org.apache.struts2.dispatcher.mapper

Restful2ActionMapper

public class Restful2ActionMapper extends DefaultActionMapper
Improved restful action mapper that adds several ReST-style improvements to action mapping, but supports fully-customized URL's via XML. The two primary ReST enhancements are:
  • If the method is not specified (via '!' or 'method:' prefix), the method is "guessed" at using ReST-style conventions that examine the URL and the HTTP method.
  • Parameters are extracted from the action name, if parameter name/value pairs are specified using PARAM_NAME/PARAM_VALUE syntax.

These two improvements allow a GET request for 'category/action/movie/Thrillers' to be mapped to the action name 'movie' with an id of 'Thrillers' with an extra parameter named 'category' with a value of 'action'. A single action mapping can then handle all CRUD operations using wildcards, e.g.

<action name="movie/*" className="app.MovieAction">
<param name="id">{0}</param>
...
</action>

This mapper supports the following parameters:

  • struts.mapper.idParameterName - If set, this value will be the name of the parameter under which the id is stored. The id will then be removed from the action name. This allows restful actions to not require wildcards.

The following URL's will invoke its methods:

  • GET: /movie/ => method="index"
  • GET: /movie/Thrillers => method="view", id="Thrillers"
  • GET: /movie/Thrillers!edit => method="edit", id="Thrillers"
  • GET: /movie/new => method="editNew"
  • POST: /movie/ => method="create"
  • PUT: /movie/Thrillers => method="update", id="Thrillers"
  • DELETE: /movie/Thrillers => method="remove", id="Thrillers"

To simulate the HTTP methods PUT and DELETE, since they aren't supported by HTML, the HTTP parameter "__http_method" will be used.

The syntax and design for this feature was inspired by the ReST support in Ruby on Rails. See http://ryandaigle.com/articles/2006/08/01/whats-new-in-edge-rails-simply-restful-support-and-how-to-use-it

Fields Summary
protected static final Log
LOG
public static final String
HTTP_METHOD_PARAM
private String
idParameterName
Constructors Summary
public Restful2ActionMapper()

    
      
    	setSlashesInActionNames("true");
    
Methods Summary
public java.lang.StringgetIdParameterName()

		return idParameterName;
	
public ActionMappinggetMapping(javax.servlet.http.HttpServletRequest request, com.opensymphony.xwork2.config.ConfigurationManager configManager)


    	if (!isSlashesInActionNames()) {
    		throw new IllegalStateException("This action mapper requires the setting 'slashesInActionNames' to be set to 'true'");
    	}
        ActionMapping mapping = super.getMapping(request, configManager);
        
        if (mapping == null) {
            return null;
        }

        String actionName = mapping.getName();

        // Only try something if the action name is specified
        if (actionName != null && actionName.length() > 0) {
            int lastSlashPos = actionName.lastIndexOf('/");

            // If a method hasn't been explicitly named, try to guess using ReST-style patterns
            if (mapping.getMethod() == null) {

                if (lastSlashPos == actionName.length() -1) {

                    // Index e.g. foo/
                    if (isGet(request)) {
                        mapping.setMethod("index");
                        
                    // Creating a new entry on POST e.g. foo/
                    } else if (isPost(request)) {
                        mapping.setMethod("create");
                    }

                } else if (lastSlashPos > -1) {
                    String id = actionName.substring(lastSlashPos+1);

                    // Viewing the form to create a new item e.g. foo/new
                    if (isGet(request) && "new".equals(id)) {
                        mapping.setMethod("editNew");

                    // Viewing an item e.g. foo/1
                    } else if (isGet(request)) {
                        mapping.setMethod("view");

                    // Removing an item e.g. foo/1
                    } else if (isDelete(request)) {
                        mapping.setMethod("remove");
                    
                    // Updating an item e.g. foo/1    
                    }  else if (isPut(request)) {
                        mapping.setMethod("update");
                    }
                    
                    if (idParameterName != null) {
                    	if (mapping.getParams() == null) {
                            mapping.setParams(new HashMap());
                        }
                    	mapping.getParams().put(idParameterName, id);
                    }
                }
                
                if (idParameterName != null && lastSlashPos > -1) {
                	actionName = actionName.substring(0, lastSlashPos);
                }
            }

            // Try to determine parameters from the url before the action name
            int actionSlashPos = actionName.lastIndexOf('/", lastSlashPos - 1);
            if (actionSlashPos > 0 && actionSlashPos < lastSlashPos) {
                String params = actionName.substring(0, actionSlashPos);
                HashMap<String,String> parameters = new HashMap<String,String>();
                try {
                    StringTokenizer st = new StringTokenizer(params, "/");
                    boolean isNameTok = true;
                    String paramName = null;
                    String paramValue;

                    while (st.hasMoreTokens()) {
                        if (isNameTok) {
                            paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
                            isNameTok = false;
                        } else {
                            paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");

                            if ((paramName != null) && (paramName.length() > 0)) {
                                parameters.put(paramName, paramValue);
                            }

                            isNameTok = true;
                        }
                    }
                    if (parameters.size() > 0) {
                        if (mapping.getParams() == null) {
                            mapping.setParams(new HashMap());
                        }
                        mapping.getParams().putAll(parameters);
                    }
                } catch (Exception e) {
                    LOG.warn(e);
                }
                mapping.setName(actionName.substring(actionSlashPos+1));
            }
        }

        return mapping;
    
protected booleanisDelete(javax.servlet.http.HttpServletRequest request)

        if ("delete".equalsIgnoreCase(request.getMethod())) {
            return true;
        } else {
            return isPost(request) && "delete".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
        }
    
protected booleanisGet(javax.servlet.http.HttpServletRequest request)

        return "get".equalsIgnoreCase(request.getMethod());
    
protected booleanisPost(javax.servlet.http.HttpServletRequest request)

        return "post".equalsIgnoreCase(request.getMethod());
    
protected booleanisPut(javax.servlet.http.HttpServletRequest request)

        if ("put".equalsIgnoreCase(request.getMethod())) {
            return true;
        } else {
            return isPost(request) && "put".equalsIgnoreCase(request.getParameter(HTTP_METHOD_PARAM));
        }
    
public voidsetIdParameterName(java.lang.String idParameterName)

		this.idParameterName = idParameterName;