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());


Reply via email to