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.
*