Author: enorman
Date: Sun Aug 22 16:19:41 2010
New Revision: 987925
URL: http://svn.apache.org/viewvc?rev=987925&view=rev
Log:
SLING-897 Updating a property fires the wrong JCR events
Modified:
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
sling/trunk/launchpad/integration-tests/src/main/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletUpdateTest.java
Modified:
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
URL:
http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java?rev=987925&r1=987924&r2=987925&view=diff
==============================================================================
---
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
(original)
+++
sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingPropertyValueHandler.java
Sun Aug 22 16:19:41 2010
@@ -227,9 +227,9 @@ public class SlingPropertyValueHandler {
));
}
} else if (values.length == 1) {
- final String removePath = removePropertyIfExists(parent,
prop.getName());
- // if the provided value is the empty string, we don't have to do
anything.
+ // if the provided value is the empty string, just remove the
existing property (if any).
if ( values[0].length() == 0 ) {
+ final String removePath = removePropertyIfExists(parent,
prop.getName());
if ( removePath != null ) {
changes.add(Modification.onDeleted(removePath));
}
@@ -287,8 +287,6 @@ public class SlingPropertyValueHandler {
changes.add(Modification.onModified(p.getPath()));
}
} else {
- removePropertyIfExists(parent, prop.getName());
-
if (type == PropertyType.DATE) {
// try conversion
Value[] c = dateParser.parse(values, valFac);
Modified:
sling/trunk/launchpad/integration-tests/src/main/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletUpdateTest.java
URL:
http://svn.apache.org/viewvc/sling/trunk/launchpad/integration-tests/src/main/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletUpdateTest.java?rev=987925&r1=987924&r2=987925&view=diff
==============================================================================
---
sling/trunk/launchpad/integration-tests/src/main/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletUpdateTest.java
(original)
+++
sling/trunk/launchpad/integration-tests/src/main/java/org/apache/sling/launchpad/webapp/integrationtest/servlets/post/PostServletUpdateTest.java
Sun Aug 22 16:19:41 2010
@@ -17,21 +17,37 @@
package org.apache.sling.launchpad.webapp.integrationtest.servlets.post;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.httpclient.Credentials;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.UsernamePasswordCredentials;
+import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
-import org.apache.sling.commons.testing.integration.HttpTestBase;
+import
org.apache.sling.launchpad.webapp.integrationtest.AbstractAuthenticatedTest;
import org.apache.sling.servlets.post.SlingPostConstants;
/** Test node updates via the MicrojaxPostServlet */
-public class PostServletUpdateTest extends HttpTestBase {
+public class PostServletUpdateTest extends AbstractAuthenticatedTest {
public static final String TEST_BASE_PATH = "/sling-tests";
private String postUrl;
+ private String testUserId = null;
@Override
protected void setUp() throws Exception {
@@ -39,7 +55,23 @@ public class PostServletUpdateTest exten
postUrl = HTTP_BASE_URL + TEST_BASE_PATH + "/" +
System.currentTimeMillis();
}
- public void testPostPathIsUnique() throws IOException {
+
+ /* (non-Javadoc)
+ * @see
org.apache.sling.commons.testing.integration.HttpTestBase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ if (testUserId != null) {
+ //remove the test user if it exists.
+ String postUrl = HTTP_BASE_URL +
"/system/userManager/user/" + testUserId + ".delete.html";
+ List<NameValuePair> postParams = new
ArrayList<NameValuePair>();
+ assertAuthenticatedAdminPostStatus(postUrl,
HttpServletResponse.SC_OK, postParams, null);
+ }
+ super.tearDown();
+ }
+
+
+public void testPostPathIsUnique() throws IOException {
assertHttpStatus(postUrl, HttpServletResponse.SC_NOT_FOUND,
"Path must not exist before test: " + postUrl);
}
@@ -151,4 +183,143 @@ public class PostServletUpdateTest exten
json = new JSONObject(content);
assertTrue("no jcr:mixinTypes expected after clearing it",
!json.has("jcr:mixinTypes"));
}
+
+ /**
+ * Test for SLING-897 fix:
+ * 1. Updating a property requires jcr:modifyProperties privilege on node.
+ * 2. When changing an existing property observers should receive a
PROPERTY_CHANGED event instead
+ * of a PROPERTY_REMOVED event and a PROPERTY_ADDED event
+ */
+ public void testUpdatePropertyPrivilegesAndEvents() throws IOException,
JSONException, RepositoryException, InterruptedException {
+ //1. Create user as admin (OK)
+ // curl -F:name=myuser -Fpwd=password -FpwdConfirm=password
http://admin:ad...@localhost:8080/system/userManager/user.create.html
+ testUserId = createTestUser();
+
+ //2. Create node as admin (OK)
+ // curl -F:name=node -FpropOne=propOneValue1 -FpropOne=propOneValue2
http://admin:ad...@localhost:8080/test/
+ final String createTestNodeUrl = postUrl +
SlingPostConstants.DEFAULT_CREATE_SUFFIX;
+ Map<String, String> nodeProperties = new HashMap<String, String>();
+ nodeProperties.put(SlingPostConstants.RP_NODE_NAME_HINT, getName());
+ nodeProperties.put("propOne", "propOneValue1");
+ nodeProperties.put("propOne", "propOneValue2");
+ String testNodeUrl = testClient.createNode(createTestNodeUrl,
nodeProperties);
+
+ //3. Attempt to update property of node as testUser (500:
javax.jcr.AccessDeniedException: /test/node/propOne: not allowed to add or
modify item)
+ // curl -FpropOne=propOneValueChanged
http://myuser:passw...@localhost:8080/test/node
+ List<NameValuePair> postParams = new ArrayList<NameValuePair>();
+ postParams.add(new NameValuePair("propOne", "propOneValueChanged"));
+ Credentials testUserCreds = new
UsernamePasswordCredentials(testUserId, "testPwd");
+ String expectedMessage = "Expected
javax.jcr.AccessDeniedException";
+ assertAuthenticatedPostStatus(testUserCreds, testNodeUrl,
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, postParams, expectedMessage);
+
+ //4. Grant jcr:modifyProperties rights to testUser as admin (OK)
+ // curl -FprincipalId=myuser -fprivil...@jcr:modifyProperties=granted
http://admin:ad...@localhost:8080/test/node.modifyAce.html
+ Map<String, String> nodeAceProperties = new HashMap<String, String>();
+ nodeAceProperties.put("principalId", testUserId);
+ nodeAceProperties.put("privil...@jcr:modifyProperties", "granted");
+ testClient.createNode(testNodeUrl + ".modifyAce.html",
nodeAceProperties);
+
+ //use a davex session to verify the correct JCR events are delivered
+ Repository repository = JcrUtils.getRepository(HTTP_BASE_URL +
"/server/");
+ Session jcrSession = null;
+ TestEventListener listener = new TestEventListener();
+ ObservationManager observationManager = null;
+ try {
+ jcrSession = repository.login(new SimpleCredentials("admin",
"admin".toCharArray()));
+ observationManager =
jcrSession.getWorkspace().getObservationManager();
+ String testNodePath =
testNodeUrl.substring(HTTP_BASE_URL.length());
+ observationManager.addEventListener(listener,
+ Event.PROPERTY_ADDED |
Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED, //event types
+ testNodePath, //absPath
+ true, //isDeep
+ null, //uuid
+ null, //nodeTypeName new String[]
{"mws:folder"}
+ false); //noLocal
+
+ //5. Attempt to update properties of node (OK)
+ // curl -FpropOne=propOneValueChanged
http://myuser:passw...@localhost:8080/test/node
+ assertAuthenticatedPostStatus(testUserCreds, testNodeUrl,
HttpServletResponse.SC_OK, postParams, expectedMessage);
+
+ //verify the change happened
+ String content = getContent(testNodeUrl + ".json",
CONTENT_TYPE_JSON);
+ JSONObject json = new JSONObject(content);
+ Object propOneObj = json.opt("propOne");
+ assertEquals("propOneValueChanged", propOneObj);
+
+ //wait for the expected JCR events to be delivered
+ for (int second = 0; second < 15; second++) {
+ if (listener.getEventBundlesProcessed() > 0) {
+ break;
+ }
+ Thread.sleep(1000);
+ }
+
+ assertEquals("Property Added Event was not expected: "
+ listener.toString(),
+ 0, listener.removedProperties.size());
+ assertEquals("Property Removed Event was not expected:
" + listener.toString(),
+ 0, listener.addedProperties.size());
+ assertEquals("Property Changed Event was expected: " +
listener.toString(),
+ 1, listener.changedProperties.size());
+ } finally {
+ //cleanup
+ if (observationManager != null) {
+ observationManager.removeEventListener(listener);
+ }
+ jcrSession.logout();
+ repository = null;
+ }
+ }
+
+ protected class TestEventListener implements EventListener {
+ protected List<String> changedProperties = new
ArrayList<String>();
+ protected List<String> addedProperties = new
ArrayList<String>();
+ protected List<String> removedProperties = new
ArrayList<String>();
+
+ protected int eventBundlesProcessed = 0;
+
+ public void onEvent(EventIterator eventIterator) {
+ try {
+ while (eventIterator.hasNext()) {
+ Event event = eventIterator.nextEvent();
+ int type = event.getType();
+ switch (type) {
+ case Event.PROPERTY_CHANGED:
+
changedProperties.add(event.getPath());
+ break;
+ case Event.PROPERTY_ADDED:
+
addedProperties.add(event.getPath());
+ break;
+ case Event.PROPERTY_REMOVED:
+
removedProperties.add(event.getPath());
+ break;
+ }
+ }
+ eventBundlesProcessed++;
+ } catch (RepositoryException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public int getEventBundlesProcessed() {
+ return eventBundlesProcessed;
+ }
+
+ public void clear() {
+ changedProperties.clear();
+ addedProperties.clear();
+ removedProperties.clear();
+ eventBundlesProcessed = 0;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "TestEventListener [addedProperties=" +
Arrays.toString(addedProperties.toArray())
+ + ", changedProperties=" +
Arrays.toString(changedProperties.toArray())
+ + ", removedProperties=" +
Arrays.toString(removedProperties.toArray()) + "]";
+ }
+ }
+
}
\ No newline at end of file