You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2008/05/19 12:58:16 UTC

svn commit: r657787 - in /incubator/sling/trunk: launchpad/webapp/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/ servlets/post/src/main/java/org/apache/sling/servlets/post/ servlets/post/src/main/java/org/apache/sling/se...

Author: fmeschbe
Date: Mon May 19 03:58:16 2008
New Revision: 657787

URL: http://svn.apache.org/viewvc?rev=657787&view=rev
Log:
SLING-458 Add support for @Delete suffix to property name to remove the named property

Added:
    incubator/sling/trunk/launchpad/webapp/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletAtDeleteTest.java
Modified:
    incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
    incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
    incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java

Added: incubator/sling/trunk/launchpad/webapp/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletAtDeleteTest.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/launchpad/webapp/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletAtDeleteTest.java?rev=657787&view=auto
==============================================================================
--- incubator/sling/trunk/launchpad/webapp/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletAtDeleteTest.java (added)
+++ incubator/sling/trunk/launchpad/webapp/src/test/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletAtDeleteTest.java Mon May 19 03:58:16 2008
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.launchpad.webapp.integrationtest.servlets.post;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.launchpad.webapp.integrationtest.HttpTestBase;
+import org.apache.sling.servlets.post.SlingPostConstants;
+
+/** Test support of @Delete suffix of SLING-458 */
+public class PostServletAtDeleteTest extends HttpTestBase {
+    public static final String TEST_BASE_PATH = "/sling-tests";
+    
+    private static final String PROP_NAME = "text";
+    private static final String PROP_NAME2 = "title";
+    
+    private static final String PROP_VALUE = "some value";
+    private static final String PROP_VALUE2 = "title value";
+    
+    private String postUrl;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        postUrl = HTTP_BASE_URL + TEST_BASE_PATH + "/" + getClass().getSimpleName() + "/" + System.currentTimeMillis();
+
+        assertHttpStatus(postUrl, HttpServletResponse.SC_NOT_FOUND,
+                "Path must not exist before test: " + postUrl);
+    }
+
+    public void testDeleteOnly() throws Exception {
+        final Map<String, String> props = new HashMap<String, String>();
+        props.put(PROP_NAME, PROP_VALUE);
+        final String contentURL = testClient.createNode(postUrl + SlingPostConstants.DEFAULT_CREATE_SUFFIX, props);
+        
+        try {
+            // assert property is set correctly
+            final String propURL = contentURL + "/" + PROP_NAME;
+            final String data = getContent(propURL + ".json", CONTENT_TYPE_JSON);
+            final JSONObject json = new JSONObject(data);
+            assertEquals(PROP_VALUE, json.get(PROP_NAME));
+            
+            final List <NameValuePair> params = new LinkedList<NameValuePair> ();
+            params.add(new NameValuePair(PROP_NAME + SlingPostConstants.SUFFIX_DELETE, "don't care"));
+            
+            assertPostStatus(contentURL, HttpServletResponse.SC_OK, params, PROP_NAME + " must be deleted");
+            assertHttpStatus(propURL, HttpServletResponse.SC_NOT_FOUND, PROP_NAME + " must be deleted");
+        } finally {
+            deleteNode(contentURL);
+        }
+    }
+    
+    public void testDeleteWithModify() throws Exception {
+        final Map<String, String> props = new HashMap<String, String>();
+        props.put(PROP_NAME, PROP_VALUE);
+        final String contentURL = testClient.createNode(postUrl + SlingPostConstants.DEFAULT_CREATE_SUFFIX, props);
+
+        try {
+            // assert property is set correctly
+            final String propURL = contentURL + "/" + PROP_NAME;
+            final String data = getContent(propURL + ".json", CONTENT_TYPE_JSON);
+            final JSONObject json = new JSONObject(data);
+            assertEquals(PROP_VALUE, json.get(PROP_NAME));
+            
+            final List <NameValuePair> params = new LinkedList<NameValuePair> ();
+            params.add(new NameValuePair(PROP_NAME + SlingPostConstants.SUFFIX_DELETE, "don't care"));
+            params.add(new NameValuePair(PROP_NAME2, PROP_VALUE2));
+    
+            assertPostStatus(contentURL, HttpServletResponse.SC_OK, params, PROP_NAME + " must be deleted");
+            assertHttpStatus(propURL, HttpServletResponse.SC_NOT_FOUND, PROP_NAME + " must be deleted");
+            assertHttpStatus(contentURL + "/" + PROP_NAME2, HttpServletResponse.SC_OK, PROP_NAME2 + " must exist");
+
+            final String data2 = getContent(contentURL + ".json", CONTENT_TYPE_JSON);
+            final JSONObject json2 = new JSONObject(data2);
+            assertFalse(json2.has(PROP_VALUE));
+            assertEquals(PROP_VALUE2, json2.get(PROP_NAME2));
+
+        } finally {
+            deleteNode(contentURL);
+        }
+    }
+    
+    protected void deleteNode(String nodeURL) throws IOException {
+        // delete one and check
+        final List <NameValuePair> params = new LinkedList<NameValuePair> ();
+        params.add(new NameValuePair(SlingPostConstants.RP_OPERATION, SlingPostConstants.OPERATION_DELETE));
+        assertPostStatus(nodeURL, HttpServletResponse.SC_OK, params, nodeURL + " must be deleted");
+    }
+}
\ No newline at end of file

Modified: incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java?rev=657787&r1=657786&r2=657787&view=diff
==============================================================================
--- incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java (original)
+++ incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java Mon May 19 03:58:16 2008
@@ -239,13 +239,19 @@
     public static final String VALUE_FROM_SUFFIX = "@ValueFrom";
 
     /**
-     * suffix that indicates a type hint parameter
+     * Suffix indicating a type hint for the property (value is "@TypeHint").
      */
     public static final String TYPE_HINT_SUFFIX = "@TypeHint";
 
     /**
-     * suffix that indicates a default value parameter
+     * Suffix indicating a default value for a property (value is
+     * "@DefaultValue").
      */
     public static final String DEFAULT_VALUE_SUFFIX = "@DefaultValue";
 
+    /**
+     * Suffix indicating that the named property is to be removed before
+     * applying any new content (value is "@Delete").
+     */
+    public static final String SUFFIX_DELETE = "@Delete";
 }

Modified: incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java?rev=657787&r1=657786&r2=657787&view=diff
==============================================================================
--- incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java (original)
+++ incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java Mon May 19 03:58:16 2008
@@ -38,7 +38,7 @@
 
     private final String parentPath;
 
-    private final RequestParameter[] values;
+    private RequestParameter[] values;
 
     private String[] stringValues;
 
@@ -46,10 +46,11 @@
 
     private RequestParameter[] defaultValues = EMPTY_PARAM_ARRAY;
 
-    public RequestProperty(String path, RequestParameter[] values) {
+    private boolean isDelete;
+    
+    public RequestProperty(String path) {
         assert path.startsWith("/");
         this.path = ResourceUtil.normalize(path);
-        this.values = values;
         this.parentPath = ResourceUtil.getParent(path);
         this.name = ResourceUtil.getName(path);
     }
@@ -75,10 +76,18 @@
         return parentPath;
     }
 
+    public boolean hasValues() {
+        return values != null;
+    }
+    
     public RequestParameter[] getValues() {
         return values;
     }
 
+    public void setValues(RequestParameter[] values) {
+        this.values = values;
+    }
+    
     public RequestParameter[] getDefaultValues() {
         return defaultValues;
     }
@@ -151,4 +160,12 @@
         }
         return stringValues;
     }
+
+    public void setDelete(boolean isDelete) {
+        this.isDelete = isDelete;
+    }
+
+    public boolean isDelete() {
+        return isDelete;
+    }
 }
\ No newline at end of file

Modified: incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java
URL: http://svn.apache.org/viewvc/incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java?rev=657787&r1=657786&r2=657787&view=diff
==============================================================================
--- incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java (original)
+++ incubator/sling/trunk/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/ModifyOperation.java Mon May 19 03:58:16 2008
@@ -82,6 +82,7 @@
         Session session = request.getResourceResolver().adaptTo(Session.class);
 
         processCreate(session, reqProperties, response);
+        processDeletes(session, reqProperties, response);
         writeContent(session, reqProperties, response);
 
         String path = response.getPath();
@@ -189,7 +190,6 @@
      * Create node(s) according to current request
      * 
      * @throws RepositoryException if a repository error occurs
-     * @throws ServletException if an internal error occurs
      */
     private void processCreate(Session session,
             Map<String, RequestProperty> reqProperties, HtmlResponse response)
@@ -204,6 +204,35 @@
     }
 
     /**
+     * Removes all properties listed as {@link RequestProperty#isDelete()} from
+     * the repository.
+     * 
+     * @param session The <code>javax.jcr.Session</code> used to access the
+     *            repository to delete the properties.
+     * @param reqProperties The map of request properties to check for
+     *            properties to be removed.
+     * @param response The <code>HtmlResponse</code> to be updated with
+     *            information on deleted properties.
+     * @throws RepositoryException Is thrown if an error occurrs checking or
+     *             removing properties.
+     */
+    private void processDeletes(Session session,
+            Map<String, RequestProperty> reqProperties, HtmlResponse response)
+            throws RepositoryException {
+
+        for (RequestProperty property : reqProperties.values()) {
+            if (property.isDelete()) {
+                String propPath = property.getPath();
+                if (session.itemExists(propPath)) {
+                    session.getItem(propPath).remove();
+                    response.onDeleted(propPath);
+                }
+            }
+        }
+
+    }
+
+    /**
      * Writes back the content
      * 
      * @throws RepositoryException if a repository error occurs
@@ -217,17 +246,19 @@
             dateParser, response);
 
         for (RequestProperty prop : reqProperties.values()) {
-            Node parent = deepGetOrCreateNode(session, prop.getParentPath(),
-                reqProperties, response);
-            // skip jcr special properties
-            if (prop.getName().equals("jcr:primaryType")
-                || prop.getName().equals("jcr:mixinTypes")) {
-                continue;
-            }
-            if (prop.isFileUpload()) {
-                uploadHandler.setFile(parent, prop, response);
-            } else {
-                propHandler.setProperty(parent, prop);
+            if (prop.hasValues()) {
+                Node parent = deepGetOrCreateNode(session,
+                    prop.getParentPath(), reqProperties, response);
+                // skip jcr special properties
+                if (prop.getName().equals("jcr:primaryType")
+                    || prop.getName().equals("jcr:mixinTypes")) {
+                    continue;
+                }
+                if (prop.isFileUpload()) {
+                    uploadHandler.setFile(parent, prop, response);
+                } else {
+                    propHandler.setProperty(parent, prop);
+                }
             }
         }
     }
@@ -253,15 +284,6 @@
             if (paramName.startsWith(SlingPostConstants.RP_PREFIX)) {
                 continue;
             }
-            // ignore field with a '@TypeHint' suffix. this is dealt with later
-            if (paramName.endsWith(SlingPostConstants.TYPE_HINT_SUFFIX)) {
-                continue;
-            }
-            // ignore field with a '@DefaultValue' suffix. this is dealt with
-            // later
-            if (paramName.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) {
-                continue;
-            }
             // SLING-298: skip form encoding parameter
             if (paramName.equals("_charset_")) {
                 continue;
@@ -270,62 +292,126 @@
             if (requireItemPrefix && !hasItemPathPrefix(paramName)) {
                 continue;
             }
-            String propertyName = paramName;
-            // SLING-130: VALUE_FROM_SUFFIX means take the value of this
-            // property from a different field
-            RequestParameter[] values = e.getValue();
-            final int vfIndex = propertyName.indexOf(SlingPostConstants.VALUE_FROM_SUFFIX);
-            if (vfIndex >= 0) {
-                // @ValueFrom example:
-                // <input name="./Text@ValueFrom" type="hidden" value="fulltext"
-                // />
-                // causes the JCR Text property to be set to the value of the
-                // fulltext form field.
-                propertyName = propertyName.substring(0, vfIndex);
-                final RequestParameter[] rp = request.getRequestParameterMap().get(
-                    paramName);
-                if (rp == null || rp.length > 1) {
-                    // @ValueFrom params must have exactly one value, else
-                    // ignored
-                    continue;
-                }
-                String mappedName = rp[0].getString();
-                values = request.getRequestParameterMap().get(mappedName);
-                if (values == null) {
-                    // no value for "fulltext" in our example, ignore parameter
-                    continue;
-                }
-            }
-            // create property helper and add it to the list
-            String propPath = propertyName;
-            if (!propPath.startsWith("/")) {
-                propPath = response.getPath() + "/" + propertyName;
-            }
-            RequestProperty prop = new RequestProperty(propPath, values);
+
+            // ensure the paramName is an absolute property name
+            String propPath = toPropertyPath(paramName, response);
 
             // @TypeHint example
             // <input type="text" name="./age" />
             // <input type="hidden" name="./age@TypeHint" value="long" />
             // causes the setProperty using the 'long' property type
-            final String thName = propertyName
-                + SlingPostConstants.TYPE_HINT_SUFFIX;
-            final RequestParameter rp = request.getRequestParameter(thName);
-            if (rp != null) {
-                prop.setTypeHint(rp.getString());
+            if (propPath.endsWith(SlingPostConstants.TYPE_HINT_SUFFIX)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.TYPE_HINT_SUFFIX);
+
+                final RequestParameter[] rp = e.getValue();
+                if (rp.length > 0) {
+                    prop.setTypeHint(rp[0].getString());
+                }
+
+                continue;
             }
 
             // @DefaultValue
-            final String dvName = propertyName
-                + SlingPostConstants.DEFAULT_VALUE_SUFFIX;
-            prop.setDefaultValues(request.getRequestParameters(dvName));
+            if (propPath.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.DEFAULT_VALUE_SUFFIX);
+
+                prop.setDefaultValues(e.getValue());
+
+                continue;
+            }
+
+            // SLING-130: VALUE_FROM_SUFFIX means take the value of this
+            // property from a different field
+            // @ValueFrom example:
+            // <input name="./Text@ValueFrom" type="hidden" value="fulltext" />
+            // causes the JCR Text property to be set to the value of the
+            // fulltext form field.
+            if (propPath.endsWith(SlingPostConstants.VALUE_FROM_SUFFIX)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath,
+                    SlingPostConstants.VALUE_FROM_SUFFIX);
+
+                // @ValueFrom params must have exactly one value, else
+                // ignored
+                if (e.getValue().length == 1) {
+                    String refName = e.getValue()[0].getString();
+                    RequestParameter[] refValues = request.getRequestParameters(refName);
+                    if (refValues != null) {
+                        prop.setValues(refValues);
+                    }
+                }
+
+                continue;
+            }
+
+            // SLING-458: Allow Removal of properties prior to update
+            // @Delete example:
+            // <input name="./Text@Delete" type="hidden" />
+            // causes the JCR Text property to be deleted before update
+            if (propPath.endsWith(SlingPostConstants.SUFFIX_DELETE)) {
+                RequestProperty prop = getOrCreateRequestProperty(
+                    reqProperties, propPath, SlingPostConstants.SUFFIX_DELETE);
+
+                prop.setDelete(true);
+
+                continue;
+            }
 
-            reqProperties.put(propPath, prop);
+            // plain property, create from values
+            RequestProperty prop = getOrCreateRequestProperty(reqProperties,
+                propPath, null);
+            prop.setValues(e.getValue());
         }
 
         return reqProperties;
     }
 
     /**
+     * Returns the <code>paramName</code> as an absolute (unnormalized)
+     * property path by prepending the response path (<code>response.getPath</code>)
+     * to the parameter name if not already absolute.
+     */
+    private String toPropertyPath(String paramName, HtmlResponse response) {
+        if (!paramName.startsWith("/")) {
+            paramName = response.getPath() + "/" + paramName;
+        }
+
+        return paramName;
+    }
+
+    /**
+     * Returns the request property for the given property path. If such a
+     * request property does not exist yet it is created and stored in the
+     * <code>props</code>.
+     * 
+     * @param props The map of already seen request properties.
+     * @param paramName The absolute path of the property including the
+     *            <code>suffix</code> to be looked up.
+     * @param suffix The (optional) suffix to remove from the
+     *            <code>paramName</code> before looking it up.
+     * @return The {@link RequestProperty} for the <code>paramName</code>.
+     */
+    private RequestProperty getOrCreateRequestProperty(
+            Map<String, RequestProperty> props, String paramName, String suffix) {
+        if (suffix != null && paramName.endsWith(suffix)) {
+            paramName = paramName.substring(0, paramName.length()
+                - suffix.length());
+        }
+
+        RequestProperty prop = props.get(paramName);
+        if (prop == null) {
+            prop = new RequestProperty(paramName);
+            props.put(paramName, prop);
+        }
+
+        return prop;
+    }
+
+    /**
      * Checks the collected content for a jcr:primaryType property at the
      * specified path.
      *