Author: lukaszlenart Date: Fri Nov 26 22:23:25 2010 New Revision: 1039579 URL: http://svn.apache.org/viewvc?rev=1039579&view=rev Log: Added Dynamic Method Invocation support
Modified: struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionMapper.java struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java Modified: struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionMapper.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionMapper.java?rev=1039579&r1=1039578&r2=1039579&view=diff ============================================================================== --- struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionMapper.java (original) +++ struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/main/java/org/apache/struts2/rest/RestActionMapper.java Fri Nov 26 22:23:25 2010 @@ -37,56 +37,56 @@ import java.util.Iterator; /** * <!-- START SNIPPET: description --> - * - * This Restful action mapper enforces Ruby-On-Rails Rest-style mappings. If the method - * is not specified (via '!' or 'method:' prefix), the method is "guessed" at using + * <p/> + * This Restful action mapper enforces Ruby-On-Rails Rest-style mappings. 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. Special care has * been given to ensure this mapper works correctly with the codebehind plugin so that * XML configuration is unnecessary. - * + * <p/> * <p> - * This mapper supports the following parameters: + * This mapper supports the following parameters: * </p> * <ul> - * <li><code>struts.mapper.idParameterName</code> - 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. Whether or not the method is specified, the mapper will - * try to truncate the identifier from the url and store it as a parameter. - * </li> - * <li><code>struts.mapper.indexMethodName</code> - The method name to call for a GET - * request with no id parameter. Defaults to 'index'. - * </li> - * <li><code>struts.mapper.getMethodName</code> - The method name to call for a GET - * request with an id parameter. Defaults to 'show'. - * </li> - * <li><code>struts.mapper.postMethodName</code> - The method name to call for a POST - * request with no id parameter. Defaults to 'create'. - * </li> - * <li><code>struts.mapper.putMethodName</code> - The method name to call for a PUT - * request with an id parameter. Defaults to 'update'. - * </li> - * <li><code>struts.mapper.deleteMethodName</code> - The method name to call for a DELETE - * request with an id parameter. Defaults to 'destroy'. - * </li> - * <li><code>struts.mapper.editMethodName</code> - The method name to call for a GET - * request with an id parameter and the 'edit' view specified. Defaults to 'edit'. - * </li> - * <li><code>struts.mapper.newMethodName</code> - The method name to call for a GET - * request with no id parameter and the 'new' view specified. Defaults to 'editNew'. - * </li> + * <li><code>struts.mapper.idParameterName</code> - 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. Whether or not the method is specified, the mapper will + * try to truncate the identifier from the url and store it as a parameter. + * </li> + * <li><code>struts.mapper.indexMethodName</code> - The method name to call for a GET + * request with no id parameter. Defaults to 'index'. + * </li> + * <li><code>struts.mapper.getMethodName</code> - The method name to call for a GET + * request with an id parameter. Defaults to 'show'. + * </li> + * <li><code>struts.mapper.postMethodName</code> - The method name to call for a POST + * request with no id parameter. Defaults to 'create'. + * </li> + * <li><code>struts.mapper.putMethodName</code> - The method name to call for a PUT + * request with an id parameter. Defaults to 'update'. + * </li> + * <li><code>struts.mapper.deleteMethodName</code> - The method name to call for a DELETE + * request with an id parameter. Defaults to 'destroy'. + * </li> + * <li><code>struts.mapper.editMethodName</code> - The method name to call for a GET + * request with an id parameter and the 'edit' view specified. Defaults to 'edit'. + * </li> + * <li><code>struts.mapper.newMethodName</code> - The method name to call for a GET + * request with no id parameter and the 'new' view specified. Defaults to 'editNew'. + * </li> * </ul> * <p> * The following URL's will invoke its methods: * </p> - * <ul> - * <li><code>GET: /movies => method="index"</code></li> - * <li><code>GET: /movies/Thrillers => method="show", id="Thrillers"</code></li> - * <li><code>GET: /movies/Thrillers;edit => method="edit", id="Thrillers"</code></li> - * <li><code>GET: /movies/Thrillers/edit => method="edit", id="Thrillers"</code></li> - * <li><code>GET: /movies/new => method="editNew"</code></li> - * <li><code>POST: /movies => method="create"</code></li> - * <li><code>PUT: /movies/Thrillers => method="update", id="Thrillers"</code></li> - * <li><code>DELETE: /movies/Thrillers => method="destroy", id="Thrillers"</code></li> + * <ul> + * <li><code>GET: /movies => method="index"</code></li> + * <li><code>GET: /movies/Thrillers => method="show", id="Thrillers"</code></li> + * <li><code>GET: /movies/Thrillers;edit => method="edit", id="Thrillers"</code></li> + * <li><code>GET: /movies/Thrillers/edit => method="edit", id="Thrillers"</code></li> + * <li><code>GET: /movies/new => method="editNew"</code></li> + * <li><code>POST: /movies => method="create"</code></li> + * <li><code>PUT: /movies/Thrillers => method="update", id="Thrillers"</code></li> + * <li><code>DELETE: /movies/Thrillers => method="destroy", id="Thrillers"</code></li> * </ul> * <p> * To simulate the HTTP methods PUT and DELETE, since they aren't supported by HTML, @@ -106,56 +106,61 @@ public class RestActionMapper extends De private String newMethodName = "editNew"; private String deleteMethodName = "destroy"; private String putMethodName = "update"; - + private boolean allowDynamicMethodCalls = true; + public RestActionMapper() { } - + public String getIdParameterName() { return idParameterName; } - @Inject(required=false,value=StrutsConstants.STRUTS_ID_PARAMETER_NAME) + @Inject(required = false, value = StrutsConstants.STRUTS_ID_PARAMETER_NAME) public void setIdParameterName(String idParameterName) { this.idParameterName = idParameterName; } - @Inject(required=false,value="struts.mapper.indexMethodName") + @Inject(required = false, value = "struts.mapper.indexMethodName") public void setIndexMethodName(String indexMethodName) { this.indexMethodName = indexMethodName; } - @Inject(required=false,value="struts.mapper.getMethodName") + @Inject(required = false, value = "struts.mapper.getMethodName") public void setGetMethodName(String getMethodName) { this.getMethodName = getMethodName; } - @Inject(required=false,value="struts.mapper.postMethodName") + @Inject(required = false, value = "struts.mapper.postMethodName") public void setPostMethodName(String postMethodName) { this.postMethodName = postMethodName; } - @Inject(required=false,value="struts.mapper.editMethodName") + @Inject(required = false, value = "struts.mapper.editMethodName") public void setEditMethodName(String editMethodName) { this.editMethodName = editMethodName; } - @Inject(required=false,value="struts.mapper.newMethodName") + @Inject(required = false, value = "struts.mapper.newMethodName") public void setNewMethodName(String newMethodName) { this.newMethodName = newMethodName; } - @Inject(required=false,value="struts.mapper.deleteMethodName") + @Inject(required = false, value = "struts.mapper.deleteMethodName") public void setDeleteMethodName(String deleteMethodName) { this.deleteMethodName = deleteMethodName; } - @Inject(required=false,value="struts.mapper.putMethodName") + @Inject(required = false, value = "struts.mapper.putMethodName") public void setPutMethodName(String putMethodName) { this.putMethodName = putMethodName; } - public ActionMapping getMapping(HttpServletRequest request, - ConfigurationManager configManager) { + @Inject(required = false, value = StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION) + public void setAllowDynamicMethodCalls(String allowDynamicMethodCalls) { + this.allowDynamicMethodCalls = "true".equalsIgnoreCase(allowDynamicMethodCalls); + } + + public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); String uri = getUri(request); @@ -173,12 +178,7 @@ public class RestActionMapper extends De } // handle "name!method" convention. - String name = mapping.getName(); - int exclamation = name.lastIndexOf("!"); - if (exclamation != -1) { - mapping.setName(name.substring(0, exclamation)); - mapping.setMethod(name.substring(exclamation + 1)); - } + handleDynamicMethodInvocation(mapping, mapping.getName()); String fullName = mapping.getName(); // Only try something if the action name is specified @@ -186,7 +186,7 @@ public class RestActionMapper extends De // cut off any ;jsessionid= type appendix but allow the rails-like ;edit int scPos = fullName.indexOf(';'); - if (scPos > -1 && !"edit".equals(fullName.substring(scPos+1))) { + if (scPos > -1 && !"edit".equals(fullName.substring(scPos + 1))) { fullName = fullName.substring(0, scPos); } @@ -197,52 +197,51 @@ public class RestActionMapper extends De // fun trickery to parse 'actionName/id/methodName' in the case of 'animals/dog/edit' int prevSlashPos = fullName.lastIndexOf('/', lastSlashPos - 1); if (prevSlashPos > -1) { - mapping.setMethod(fullName.substring(lastSlashPos+1)); + mapping.setMethod(fullName.substring(lastSlashPos + 1)); fullName = fullName.substring(0, lastSlashPos); lastSlashPos = prevSlashPos; } - id = fullName.substring(lastSlashPos+1); + id = fullName.substring(lastSlashPos + 1); } - // If a method hasn't been explicitly named, try to guess using ReST-style patterns if (mapping.getMethod() == null) { // Handle uris with no id, possibly ending in '/' - if (lastSlashPos == -1 || lastSlashPos == fullName.length() -1) { + if (lastSlashPos == -1 || lastSlashPos == fullName.length() - 1) { // Index e.g. foo if (isGet(request)) { mapping.setMethod(indexMethodName); - - // Creating a new entry on POST e.g. foo + + // Creating a new entry on POST e.g. foo } else if (isPost(request)) { mapping.setMethod(postMethodName); } - // Handle uris with an id at the end + // Handle uris with an id at the end } else if (id != null) { - + // Viewing the form to edit an item e.g. foo/1;edit if (isGet(request) && id.endsWith(";edit")) { id = id.substring(0, id.length() - ";edit".length()); mapping.setMethod(editMethodName); - - // Viewing the form to create a new item e.g. foo/new + + // Viewing the form to create a new item e.g. foo/new } else if (isGet(request) && "new".equals(id)) { mapping.setMethod(newMethodName); - // Removing an item e.g. foo/1 + // Removing an item e.g. foo/1 } else if (isDelete(request)) { mapping.setMethod(deleteMethodName); - - // Viewing an item e.g. foo/1 + + // Viewing an item e.g. foo/1 } else if (isGet(request)) { mapping.setMethod(getMethodName); - - // Updating an item e.g. foo/1 - } else if (isPut(request)) { + + // Updating an item e.g. foo/1 + } else if (isPut(request)) { mapping.setMethod(putMethodName); } } @@ -264,18 +263,28 @@ public class RestActionMapper extends De return mapping; } - + + private void handleDynamicMethodInvocation(ActionMapping mapping, String name) { + int exclamation = name.lastIndexOf("!"); + if (exclamation != -1) { + mapping.setName(name.substring(0, exclamation)); + if (allowDynamicMethodCalls) { + mapping.setMethod(name.substring(exclamation + 1)); + } else { + mapping.setMethod(null); + } + } + } + /** - * Parses the name and namespace from the uri. Uses the configured package + * Parses the name and namespace from the uri. Uses the configured package * namespaces to determine the name and id parameter, to be parsed later. * - * @param uri - * The uri - * @param mapping - * The action mapping to populate + * @param uri The uri + * @param mapping The action mapping to populate */ protected void parseNameAndNamespace(String uri, ActionMapping mapping, - ConfigurationManager configManager) { + ConfigurationManager configManager) { String namespace, name; int lastSlash = uri.lastIndexOf("/"); if (lastSlash == -1) { Modified: struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java?rev=1039579&r1=1039578&r2=1039579&view=diff ============================================================================== --- struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java (original) +++ struts/struts2/branches/STRUTS_2_2_1_1/plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java Fri Nov 26 22:23:25 2010 @@ -35,6 +35,7 @@ public class RestActionMapperTest extend private ConfigurationManager configManager; private Configuration config; private MockHttpServletRequest req; + private String allowDynamicMethodInvocation = "true"; protected void setUp() throws Exception { super.setUp(); @@ -184,9 +185,65 @@ public class RestActionMapperTest extend public void testParseNameAndNamespaceWithEdit() { tryUri("/my/foo/23;edit", "/my", "foo/23;edit"); } - + + public void testShouldAllowExclamation() throws Exception { + req.setRequestURI("/myapp/animals/dog/fido!edit"); + req.setServletPath("/animals/dog/fido!edit"); + req.setMethod("GET"); + + ActionMapping mapping = mapper.getMapping(req, configManager); + + assertEquals("/animals", mapping.getNamespace()); + assertEquals("dog", mapping.getName()); + assertEquals("fido", ((String[])mapping.getParams().get("id"))[0]); + assertEquals("edit", mapping.getMethod()); + } + + public void testShouldBlockDynamicMethodInvocationAnsUseShow() throws Exception { + req.setRequestURI("/myapp/animals/dog/fido!edit"); + req.setServletPath("/animals/dog/fido!edit"); + req.setMethod("GET"); + + mapper.setAllowDynamicMethodCalls("false"); + ActionMapping mapping = mapper.getMapping(req, configManager); + + assertEquals("/animals", mapping.getNamespace()); + assertEquals("dog", mapping.getName()); + assertEquals("fido", ((String[])mapping.getParams().get("id"))[0]); + assertEquals("show", mapping.getMethod()); + } + + public void testShouldBlockDynamicMethodInvocationAnsUseDestroy() throws Exception { + req.setRequestURI("/myapp/animals/dog/fido!destroy"); + req.setServletPath("/animals/dog/fido!destroy"); + req.setMethod("DELETE"); + + mapper.setAllowDynamicMethodCalls("false"); + ActionMapping mapping = mapper.getMapping(req, configManager); + + assertEquals("/animals", mapping.getNamespace()); + assertEquals("dog", mapping.getName()); + assertEquals("fido", ((String[])mapping.getParams().get("id"))[0]); + assertEquals("destroy", mapping.getMethod()); + } + + public void testShouldBlockDynamicMethodInvocationAndUseUpdate() throws Exception { + req.setRequestURI("/myapp/animals/dog/fido!update"); + req.setServletPath("/animals/dog/fido!update"); + req.setMethod("PUT"); + + mapper.setAllowDynamicMethodCalls("false"); + ActionMapping mapping = mapper.getMapping(req, configManager); + + assertEquals("/animals", mapping.getNamespace()); + assertEquals("dog", mapping.getName()); + assertEquals("fido", ((String[])mapping.getParams().get("id"))[0]); + assertEquals("update", mapping.getMethod()); + } + private void tryUri(String uri, String expectedNamespace, String expectedName) { ActionMapping mapping = new ActionMapping(); + mapper.setAllowDynamicMethodCalls(allowDynamicMethodInvocation); mapper.parseNameAndNamespace(uri, mapping, configManager); assertEquals(expectedName, mapping.getName()); assertEquals(expectedNamespace, mapping.getNamespace());