You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2013/06/07 12:39:28 UTC

svn commit: r1490575 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/observation/ main/java/org/apache/jackrabbit/oak/spi/state/ test/java/org/apache/jackrabbit/oak/

Author: mduerig
Date: Fri Jun  7 10:39:27 2013
New Revision: 1490575

URL: http://svn.apache.org/r1490575
Log:
OAK-775 Implement backward compatible observation
wrap diffing of changes into ac check wrapper

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/RecursingNodeStateDiff.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecurableNodeStateDiff.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecureNodeStateDiff.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/SecureNodeStateDiffTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeDispatcher.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/VisibleDiff.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeDispatcher.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeDispatcher.java?rev=1490575&r1=1490574&r2=1490575&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeDispatcher.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeDispatcher.java Fri Jun  7 10:39:27 2013
@@ -262,9 +262,9 @@ public class ChangeDispatcher {
          * {@link NodeStateDiff} of the changes
          * @param diff  node state diff instance for traversing the changes.
          */
-        public void diff(NodeStateDiff diff) {
-            // TODO wrap diff into access check wrapper
-            after.compareAgainstBaseState(before, diff);
+        public void diff(RecursingNodeStateDiff diff) {
+            NodeStateDiff secureDiff = SecureNodeStateDiff.wrap(diff);
+            after.compareAgainstBaseState(before, secureDiff);
         }
 
         @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java?rev=1490575&r1=1490574&r2=1490575&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeProcessor.java Fri Jun  7 10:39:27 2013
@@ -26,6 +26,7 @@ import java.util.concurrent.ScheduledFut
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.annotation.Nonnull;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventListener;
 
@@ -40,7 +41,6 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.observation.ChangeDispatcher.Listener;
 import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
-import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
 import org.apache.jackrabbit.oak.spi.state.VisibleDiff;
 import org.slf4j.Logger;
@@ -48,9 +48,6 @@ import org.slf4j.LoggerFactory;
 import org.slf4j.Marker;
 import org.slf4j.MarkerFactory;
 
-/**
- * TODO document
- */
 class ChangeProcessor implements Runnable {
     private static final Logger log = LoggerFactory.getLogger(ChangeProcessor.class);
     private static final Marker DEPRECATED = MarkerFactory.getMarker("deprecated");
@@ -189,7 +186,7 @@ class ChangeProcessor implements Runnabl
 
     //------------------------------------------------------------< private >---
 
-    private class EventGeneratingNodeStateDiff implements NodeStateDiff {
+    private class EventGeneratingNodeStateDiff extends RecursingNodeStateDiff {
         public static final int PURGE_LIMIT = 8192;
 
         private final ChangeSet changes;
@@ -209,6 +206,7 @@ class ChangeProcessor implements Runnabl
         }
 
         public EventGeneratingNodeStateDiff(ChangeSet changes) {
+            // FIXME associatedParentNode should be the root node here
             this(changes, "/", new ArrayList<Iterator<Event>>(PURGE_LIMIT), null);
         }
 
@@ -280,17 +278,19 @@ class ChangeProcessor implements Runnabl
 
         @Override
         public boolean childNodeChanged(String name, NodeState before, NodeState after) {
+            return !stopping;
+        }
+
+        @Nonnull
+        @Override
+        public RecursingNodeStateDiff createChildDiff(String name, NodeState before, NodeState after) {
             if (filterRef.get().includeChildren(jcrPath())) {
                 EventGeneratingNodeStateDiff diff = new EventGeneratingNodeStateDiff(
                         changes, PathUtils.concat(path, name), events, after);
-                if (!after.compareAgainstBaseState(before, VisibleDiff.wrap(diff))) {
-                    return false;
-                }
-                if (events.size() > PURGE_LIMIT) {
-                    diff.sendEvents();
-                }
+                return VisibleDiff.wrap(diff);
+            } else {
+                return new RecursingNodeStateDiff();
             }
-            return !stopping;
         }
 
         private EventImpl createEvent(int eventType, String jcrPath) {
@@ -350,6 +350,7 @@ class ChangeProcessor implements Runnabl
             return Iterators.concat(nodeEvent, propertyEvents, childNodeEvents);
         }
 
+        // FIXME this doesn't correctly track the associated parent node
         private Iterator<Iterator<Event>> generateChildEvents(final int eventType, final String parentPath, NodeState node) {
             return Iterators.transform(
                     Iterators.filter(node.getChildNodeEntries().iterator(),

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/RecursingNodeStateDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/RecursingNodeStateDiff.java?rev=1490575&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/RecursingNodeStateDiff.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/RecursingNodeStateDiff.java Fri Jun  7 10:39:27 2013
@@ -0,0 +1,34 @@
+/*
+ * 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.jackrabbit.oak.plugins.observation;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.spi.state.DefaultNodeStateDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+public class RecursingNodeStateDiff extends DefaultNodeStateDiff {
+    public static final RecursingNodeStateDiff EMPTY = new RecursingNodeStateDiff();
+
+    @Nonnull
+    public RecursingNodeStateDiff createChildDiff(String name, NodeState before, NodeState after) {
+        return this;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/RecursingNodeStateDiff.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/RecursingNodeStateDiff.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecurableNodeStateDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecurableNodeStateDiff.java?rev=1490575&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecurableNodeStateDiff.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecurableNodeStateDiff.java Fri Jun  7 10:39:27 2013
@@ -0,0 +1,153 @@
+/*
+ * 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.jackrabbit.oak.plugins.observation;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+
+public abstract class SecurableNodeStateDiff implements NodeStateDiff {
+    private final SecurableNodeStateDiff parent;
+
+    private RecursingNodeStateDiff diff;
+    private Deferred deferred = Deferred.EMPTY;
+
+    private SecurableNodeStateDiff(SecurableNodeStateDiff parent, RecursingNodeStateDiff diff) {
+        this.diff = diff;
+        this.parent = parent;
+    }
+
+    protected SecurableNodeStateDiff(SecurableNodeStateDiff parent) {
+        this(parent, RecursingNodeStateDiff.EMPTY);
+    }
+
+    protected SecurableNodeStateDiff(RecursingNodeStateDiff diff) {
+        this(null, diff);
+    }
+
+    protected abstract SecurableNodeStateDiff create(SecurableNodeStateDiff parent,
+            String name, NodeState before, NodeState after);
+
+    protected boolean canRead(PropertyState before, PropertyState after) {
+        return true;
+    }
+
+    protected boolean canRead(String name, NodeState before, NodeState after) {
+        return true;
+    }
+
+    protected NodeState secureBefore(String name, NodeState nodeState) {
+        return nodeState;
+    }
+
+    protected NodeState secureAfter(String name, NodeState nodeState) {
+        return nodeState;
+    }
+
+    @Override
+    public boolean propertyAdded(PropertyState after) {
+        if (canRead(null, after)) {
+            return applyDeferred() && diff.propertyAdded(after);
+        }
+        else {
+            return true;
+        }
+    }
+
+    @Override
+    public boolean propertyChanged(PropertyState before, PropertyState after) {
+        if (canRead(before, after)) {
+            return applyDeferred() && diff.propertyChanged(before, after);
+        }
+        else {
+            return true;
+        }
+    }
+
+    @Override
+    public boolean propertyDeleted(PropertyState before) {
+        if (canRead(before, null)) {
+            return applyDeferred() && diff.propertyDeleted(before);
+        }
+        else {
+            return true;
+        }
+    }
+
+    @Override
+    public boolean childNodeAdded(String name, NodeState after) {
+        if (canRead(name, null, after)) {
+            return applyDeferred() && diff.childNodeAdded(name, secureAfter(name, after));
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public boolean childNodeChanged(final String name, final NodeState before, final NodeState after) {
+        final SecurableNodeStateDiff childDiff = create(this, name, before, after);
+        deferred = new Deferred() {
+            @Override
+            boolean call() {
+                if (applyDeferred() && diff.childNodeChanged(name, secureBefore(name, before), secureAfter(name, after))) {
+                    childDiff.diff = diff.createChildDiff(name, secureBefore(name, before), secureAfter(name, after));
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        };
+        return after.compareAgainstBaseState(before, childDiff);
+    }
+
+    @Override
+    public boolean childNodeDeleted(String name, NodeState before) {
+        if (canRead(name, before, null)) {
+            return applyDeferred() && diff.childNodeDeleted(name, secureBefore(name, before));
+        } else {
+            return true;
+        }
+    }
+
+    private boolean applyDeferred() {
+        return parent == null || parent.deferred.apply();
+    }
+
+    //------------------------------------------------------------< Deferred >---
+
+    private abstract static class Deferred {
+        public static final Deferred EMPTY = new Deferred() {
+            @Override
+            boolean call() {
+                return true;
+            }
+        };
+
+        private Boolean result;
+
+        boolean apply() {
+            if (result == null) {
+                result = call();
+            }
+            return result;
+        }
+        abstract boolean call();
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecurableNodeStateDiff.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecurableNodeStateDiff.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecureNodeStateDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecureNodeStateDiff.java?rev=1490575&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecureNodeStateDiff.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecureNodeStateDiff.java Fri Jun  7 10:39:27 2013
@@ -0,0 +1,70 @@
+/*
+ * 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.jackrabbit.oak.plugins.observation;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+
+public class SecureNodeStateDiff extends SecurableNodeStateDiff {
+    private SecureNodeStateDiff(RecursingNodeStateDiff diff) {
+        super(diff);
+    }
+
+    private SecureNodeStateDiff(SecurableNodeStateDiff parent) {
+        super(parent);
+    }
+
+    public static NodeStateDiff wrap(RecursingNodeStateDiff diff) {
+        return new SecureNodeStateDiff(diff);
+    }
+
+    @Override
+    protected SecurableNodeStateDiff create(SecurableNodeStateDiff parent,
+            String name, NodeState before, NodeState after) {
+
+        return new SecureNodeStateDiff(parent);
+    }
+
+    @Override
+    protected boolean canRead(PropertyState before, PropertyState after) {
+        // TODO implement canRead
+        return true;
+    }
+
+    @Override
+    protected boolean canRead(String name, NodeState before, NodeState after) {
+        // TODO implement canRead
+        return true;
+    }
+
+    @Override
+    protected NodeState secureBefore(String name, NodeState nodeState) {
+        // TODO implement secureBefore
+        return nodeState;
+    }
+
+    @Override
+    protected NodeState secureAfter(String name, NodeState nodeState) {
+        // TODO implement secureAfter
+        return nodeState;
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecureNodeStateDiff.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/SecureNodeStateDiff.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/VisibleDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/VisibleDiff.java?rev=1490575&r1=1490574&r2=1490575&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/VisibleDiff.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/VisibleDiff.java Fri Jun  7 10:39:27 2013
@@ -25,6 +25,7 @@ import static org.apache.jackrabbit.oak.
 import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.plugins.observation.RecursingNodeStateDiff;
 
 /**
  * {@code NodeStateDiff} wrapper that passes only changes to non-hidden nodes and properties
@@ -32,16 +33,16 @@ import org.apache.jackrabbit.oak.api.Pro
  *
  * @since Oak 0.9
  */
-public class VisibleDiff implements NodeStateDiff {
+public class VisibleDiff extends RecursingNodeStateDiff {
     private final NodeStateDiff diff;
 
     @Nonnull
-    public static NodeStateDiff wrap(@Nonnull NodeStateDiff diff) {
+    public static RecursingNodeStateDiff wrap(@Nonnull NodeStateDiff diff) {
         return new VisibleDiff(checkNotNull(diff));
     }
 
-    public VisibleDiff(NodeStateDiff diff) {
-        this.diff = checkNotNull(diff);
+    private VisibleDiff(NodeStateDiff diff) {
+        this.diff = diff;
     }
 
     @Override
@@ -97,4 +98,18 @@ public class VisibleDiff implements Node
             return true;
         }
     }
+
+    @Nonnull
+    @Override
+    public RecursingNodeStateDiff createChildDiff(String name, NodeState before, NodeState after) {
+        if (diff instanceof RecursingNodeStateDiff) {
+            if (isHidden(name)) {
+                return RecursingNodeStateDiff.EMPTY;
+            } else {
+                return ((RecursingNodeStateDiff) diff).createChildDiff(name, before, after);
+            }
+        } else {
+            return super.createChildDiff(name, before, after);
+        }
+    }
 }

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/SecureNodeStateDiffTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/SecureNodeStateDiffTest.java?rev=1490575&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/SecureNodeStateDiffTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/SecureNodeStateDiffTest.java Fri Jun  7 10:39:27 2013
@@ -0,0 +1,204 @@
+/*
+ * 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.jackrabbit.oak;
+
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.junit.Assert.assertEquals;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.plugins.observation.RecursingNodeStateDiff;
+import org.apache.jackrabbit.oak.plugins.observation.SecurableNodeStateDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SecureNodeStateDiffTest {
+    private NodeState base;
+
+    @Before
+    public void setUp() {
+        NodeBuilder builder = EMPTY_NODE.builder();
+        builder.setProperty("a", 1L);
+        builder.setProperty("b", 2L);
+        builder.setProperty("c", 3L);
+        builder.child("x");
+        builder.child("y");
+        builder.child("z");
+        builder.child("NA1").child("x1");
+        builder.child("NA2").child("x2").child("y3");
+        builder.child("NA3").child("x3").child("NA3a").child("y3");
+        base = builder.getNodeState();
+    }
+
+    @Test
+    public void testRemoveNode() {
+        NodeBuilder builder = base.builder();
+        builder.child("x").remove();
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("-x");
+    }
+
+    @Test
+    public void testAddNode() {
+        NodeBuilder builder = base.builder();
+        builder.child("v");
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("+v");
+    }
+
+    @Test
+    public void testRemoveProperty() {
+        NodeBuilder builder = base.builder();
+        builder.removeProperty("a");
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("-a");
+    }
+
+    @Test
+    public void testAddProperty() {
+        NodeBuilder builder = base.builder();
+        builder.setProperty("d", "d");
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("+d");
+    }
+
+    @Test
+    public void testChangeProperty() {
+        NodeBuilder builder = base.builder();
+        builder.setProperty("c", 42);
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("^c");
+    }
+
+    @Test
+    public void testChangeNode() {
+        NodeBuilder builder = base.builder();
+        builder.child("NA1").setProperty("p", "p");
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("^NA1+p");
+    }
+
+    @Test
+    public void testAddInaccessibleChild() {
+        NodeBuilder builder = base.builder();
+        builder.child("NA3").child("x3").child("NA3a").child("y3").child("NA3a");
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("");
+    }
+
+    @Test
+    public void testChangeInaccessibleChild() {
+        NodeBuilder builder = base.builder();
+        builder.child("NA3").child("x3").child("NA3a").child("y3").remove();
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("^NA3^x3^NA3a-y3");
+    }
+
+    @Test
+    public void testRemoveInaccessibleChild() {
+        NodeBuilder builder = base.builder();
+        builder.child("NA3").child("x3").child("NA3a").remove();
+        new AssertingNodeStateDiff(base, builder.getNodeState()).expect("");
+    }
+
+    private static class SecureNodeStateDiff extends SecurableNodeStateDiff {
+        protected SecureNodeStateDiff(SecurableNodeStateDiff parent) {
+            super(parent);
+        }
+
+        public static NodeStateDiff wrap(RecursingNodeStateDiff diff) {
+            return new SecureNodeStateDiff(diff);
+        }
+
+        private SecureNodeStateDiff(RecursingNodeStateDiff diff) {
+            super(diff);
+        }
+
+        @Override
+        protected SecurableNodeStateDiff create(SecurableNodeStateDiff parent,
+                String name, NodeState before, NodeState after) {
+            return new SecureNodeStateDiff(parent);
+        }
+
+        @Override
+        protected boolean canRead(PropertyState before, PropertyState after) {
+            return canRead(before) && canRead(after);
+        }
+
+        private static boolean canRead(PropertyState property) {
+            return property == null || canRead(property.getName());
+        }
+
+        @Override
+        protected boolean canRead(String name, NodeState before, NodeState after) {
+            return canRead(name);
+        }
+
+        private static boolean canRead(String name) {
+            return name == null || !name.startsWith("NA");
+        }
+    }
+
+    private static class AssertingNodeStateDiff extends RecursingNodeStateDiff {
+        private final StringBuilder actual = new StringBuilder();
+        private final NodeState before;
+        private final NodeState after;
+
+        public AssertingNodeStateDiff(NodeState before, NodeState after) {
+            this.before = before;
+            this.after = after;
+        }
+
+        public void expect(String expected) {
+            after.compareAgainstBaseState(before, SecureNodeStateDiff.wrap(this));
+            assertEquals(expected, actual.toString());
+        }
+
+        @Override
+        public boolean propertyAdded(PropertyState after) {
+            actual.append('+').append(after.getName());
+            return true;
+        }
+
+        @Override
+        public boolean propertyChanged(PropertyState before, PropertyState after) {
+            actual.append('^').append(after.getName());
+            return true;
+        }
+
+        @Override
+        public boolean propertyDeleted(PropertyState before) {
+            actual.append('-').append(before.getName());
+            return true;
+        }
+
+        @Override
+        public boolean childNodeAdded(String name, NodeState after) {
+            actual.append('+').append(name);
+            return true;
+        }
+
+        @Override
+        public boolean childNodeChanged(String name, NodeState before, NodeState after) {
+            actual.append('^').append(name);
+            return true;
+        }
+
+        @Override
+        public boolean childNodeDeleted(String name, NodeState before) {
+            actual.append('-').append(name);
+            return true;
+        }
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/SecureNodeStateDiffTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/SecureNodeStateDiffTest.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL