Author: mduerig
Date: Thu Dec 19 16:09:09 2013
New Revision: 1552323

URL: http://svn.apache.org/r1552323
Log:
OAK-1298: Improve MoveDetector to also include moves from transient locations 
Correctly track moves of nodes whose parent was moved before

Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/MoveDetector.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/MoveDetectorTest.java
    jackrabbit/oak/trunk/oak-doc/src/site/markdown/differences.md
    
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java
    jackrabbit/oak/trunk/oak-parent/pom.xml

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java?rev=1552323&r1=1552322&r2=1552323&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeBuilder.java
 Thu Dec 19 16:09:09 2013
@@ -75,11 +75,9 @@ public class KernelNodeBuilder extends M
         if (newParent instanceof FastCopyMove) {
             checkNotNull(newParent);
             checkNotNull(newName);
+            annotateSourcePath();
             boolean success = !isRoot() && exists() && 
!newParent.hasChildNode(newName) &&
                     ((FastCopyMove) newParent).moveFrom(this, newName);
-            if (success) {
-                annotateSourcePath(newParent.getChildNode(newName), getPath());
-            }
             return success;
         } else {
             return super.moveTo(newParent, newName);

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java?rev=1552323&r1=1552322&r2=1552323&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/memory/MemoryNodeBuilder.java
 Thu Dec 19 16:09:09 2013
@@ -29,10 +29,10 @@ import javax.annotation.Nonnull;
 
 import com.google.common.base.Objects;
 import com.google.common.io.ByteStreams;
-
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.spi.state.MoveDetector;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -357,9 +357,7 @@ public class MemoryNodeBuilder implement
             return false;
         } else {
             if (newParent.exists()) {
-                if (!isNew()) {
-                    annotateSourcePath(this, getPath());
-                }
+                annotateSourcePath();
                 NodeState nodeState = getNodeState();
                 newParent.setChildNode(newName, nodeState);
                 remove();
@@ -370,12 +368,68 @@ public class MemoryNodeBuilder implement
         }
     }
 
-    protected static void annotateSourcePath(NodeBuilder builder, String path) 
{
+    /**
+     * Annotate this builder with its source path if this builder has not
+     * been transiently added. The source path is written to a property with
+     * then name {@link MoveDetector#SOURCE_PATH}.
+     * <p>
+     * The source path of a builder is its current path if its current
+     * source path is empty and all none of its parents has its source
+     * path set. Otherwise it is the source path of the first parent (or self)
+     * that has its source path set appended with the relative path from
+     * that parent to this builder.
+     * <p>
+     * This builder has been transiently added when there exists no a
+     * base node at its source path.
+     */
+    protected final void annotateSourcePath() {
+        String sourcePath = getSourcePath();
+        if (!isTransientlyAdded(sourcePath)) {
+            setProperty(MoveDetector.SOURCE_PATH, sourcePath);
+        }
+    }
+
+    private final String getSourcePath() {
+        // Traverse up the hierarchy until we encounter the first builder
+        // having a source path annotation or until we hit the root
+        MemoryNodeBuilder builder = this;
+        String sourcePath = getSourcePathAnnotation(builder);
+        while (sourcePath == null && builder.parent != null) {
+            builder = builder.parent;
+            sourcePath = getSourcePathAnnotation(builder);
+        }
+
+        if (sourcePath == null) {
+            // Neither self nor any parent has a source path annotation. The 
source
+            // path is just the path of this builder
+            return getPath();
+        } else {
+            // The source path is the source path of the first parent having a 
source
+            // path annotation with the relative path from this builder up to 
that
+            // parent appended.
+            return PathUtils.concat(sourcePath,
+                    PathUtils.relativize(builder.getPath(), getPath()));
+        }
+    }
+
+    private static String getSourcePathAnnotation(MemoryNodeBuilder builder) {
         PropertyState base = 
builder.getBaseState().getProperty(MoveDetector.SOURCE_PATH);
         PropertyState head = 
builder.getNodeState().getProperty(MoveDetector.SOURCE_PATH);
         if (Objects.equal(base, head)) {
-            builder.setProperty(MoveDetector.SOURCE_PATH, path);
+            // Both null: no source path annotation
+            // Both non null but equals: source path annotation is from a 
previous commit
+            return null;
+        } else {
+            return head.getValue(Type.STRING);
+        }
+    }
+
+    private boolean isTransientlyAdded(String path) {
+        NodeState node = rootBuilder.getBaseState();
+        for (String name : PathUtils.elements(path)) {
+            node = node.getChildNode(name);
         }
+        return !node.exists();
     }
 
     @Override

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/MoveDetector.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/MoveDetector.java?rev=1552323&r1=1552322&r2=1552323&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/MoveDetector.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/state/MoveDetector.java
 Thu Dec 19 16:09:09 2013
@@ -43,8 +43,10 @@ import org.apache.jackrabbit.oak.spi.com
  *     target.</li>
  *     <li>Moving a transiently added node is not reported as a move operation 
but as an
  *     add operation on the move target.</li>
- *     <li>Moving a child node of a transiently moved node is not reported as 
a move operation
- *     but as an add operation on the move target.</li>
+ *     <li>Removing a moved node is not reported as a move operation but as a 
remove operation
+ *     from the source of the move.</li>
+ *     <li>Moving a child node of a transiently moved node is not reported 
from the original
+ *     source node, not from the intermediate one.</li>
  *     <li>Moving a node back and forth to its original location is not 
reported at all.</li>
  * </ul>
  */

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/MoveDetectorTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/MoveDetectorTest.java?rev=1552323&r1=1552322&r2=1552323&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/MoveDetectorTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/state/MoveDetectorTest.java
 Thu Dec 19 16:09:09 2013
@@ -127,6 +127,26 @@ public class MoveDetectorTest {
         move(rootBuilder, "/test/z", "/test/y/z");
         NodeState moved = move(rootBuilder, "/test/y/z/zz", 
"/test/x/zz").getNodeState();
         MoveExpectation moveExpectation = new MoveExpectation(
+                ImmutableMap.of("/test/z", "/test/y/z", "/test/z/zz", 
"/test/x/zz"));
+        MoveDetector moveDetector = new MoveDetector(moveExpectation);
+        CommitFailedException exception = EditorDiff.process(moveDetector, 
root, moved);
+        if (exception != null) {
+            throw exception;
+        }
+        moveExpectation.assertAllFound();
+    }
+
+    /**
+     * Moving a transiently added node from a moved subtree doesn't generate a 
move event.
+     * @throws CommitFailedException
+     */
+    @Test
+    public void moveAddedFromMovedSubtree() throws CommitFailedException {
+        NodeBuilder rootBuilder = root.builder();
+        
rootBuilder.getChildNode("test").getChildNode("z").setChildNode("added");
+        move(rootBuilder, "/test/z", "/test/y/z");
+        NodeState moved = move(rootBuilder, "/test/y/z/added", 
"/test/x/added").getNodeState();
+        MoveExpectation moveExpectation = new MoveExpectation(
                 ImmutableMap.of("/test/z", "/test/y/z"));
         MoveDetector moveDetector = new MoveDetector(moveExpectation);
         CommitFailedException exception = EditorDiff.process(moveDetector, 
root, moved);

Modified: jackrabbit/oak/trunk/oak-doc/src/site/markdown/differences.md
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/differences.md?rev=1552323&r1=1552322&r2=1552323&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-doc/src/site/markdown/differences.md (original)
+++ jackrabbit/oak/trunk/oak-doc/src/site/markdown/differences.md Thu Dec 19 
16:09:09 2013
@@ -119,12 +119,26 @@ Observation
 
     * Limited support for `Event.NODE_MOVED`:
 
-      + `NODE_MOVED` are only reported for nodes whose source location is not 
transient. A source
-        location is transient if it is transiently added or a child node of a 
transiently moved
-        tree.
+      + A node that is added and subsequently moved will not generate a 
`Event.NODE_MOVED`
+        but a `Event.NODE_ADDED` for its final location.
 
-      + Removing a node and adding a node with the same name at the same 
parent will be reported as
-        `NODE_MOVED` event as if it where caused by `Node.orderBefore()`.
+      + A node that is moved and subsequently removed will not generate a 
`Event.NODE_MOVED`
+        but a `Event.NODE_REMOVED` for its initial location.
+
+      + A node that is moved and subsequently moved again will only generate a 
single
+        `Event.NODE_MOVED` reporting its initial location as `srcAbsPath` and 
its
+         final location as `destAbsPath`.
+
+      + A node whose parent was moved and that moved itself subsequently 
reports its initial
+        location as `srcAbsPath` instead of the location it had under the 
moved parent.
+
+      + A node that was moved and subsequently its parent is moved will report 
its final
+        location as `destAbsPath` instead of the location it had before its 
parent moved.
+
+      + Removing a node and adding a node with the same name at the same 
parent will be
+        reported as `NODE_MOVED` event as if it where caused by 
`Node.orderBefore()` if
+        the parent node is orderable and the sequence of operations caused a 
change in
+        the order of the child nodes.
 
       + The exact sequence of `Node.orderBefore()` will not be reflected 
through `NODE_MOVED`
         events: given two child nodes `a` and `b`, ordering `a` after `b` may 
be reported as

Modified: 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java?rev=1552323&r1=1552322&r2=1552323&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/observation/ObservationTest.java
 Thu Dec 19 16:09:09 2013
@@ -426,6 +426,37 @@ public class ObservationTest extends Abs
     }
 
     @Test
+    public void testMove() throws RepositoryException, ExecutionException, 
InterruptedException {
+        Node testNode = getNode(TEST_PATH);
+        Session session = testNode.getSession();
+        Node nodeA = testNode.addNode("a");
+        Node nodeAA = nodeA.addNode("aa");
+        Node nodeT = testNode.addNode("t");
+        Node nodeS = testNode.addNode("s");
+        session.save();
+
+        ExpectationListener listener = new ExpectationListener();
+        observationManager.addEventListener(listener, NODE_MOVED, "/", true, 
null, null, false);
+
+        String src1 = nodeA.getPath();
+        String dst1 = nodeT.getPath() + "/b";
+        session.move(src1, dst1);
+        listener.expectMove(src1, dst1);
+
+        String src2 = nodeT.getPath() + "/b/aa";
+        String dst2 = nodeS.getPath() + "/bb";
+        session.move(src2, dst2);
+        listener.expectMove(src1 + "/aa", dst2);
+
+        session.save();
+
+        List<Expectation> missing = listener.getMissing(2, TimeUnit.SECONDS);
+        assertTrue("Missing events: " + missing, missing.isEmpty());
+        List<Event> unexpected = listener.getUnexpected();
+        assertTrue("Unexpected events: " + unexpected, unexpected.isEmpty());
+    }
+
+    @Test
     public void testReorder() throws RepositoryException, 
InterruptedException, ExecutionException {
         Node testNode = getNode(TEST_PATH);
         Node nodeA = testNode.addNode("a", "nt:unstructured");
@@ -433,7 +464,7 @@ public class ObservationTest extends Abs
         testNode.getSession().save();
 
         ExpectationListener listener = new ExpectationListener();
-        observationManager.addEventListener(listener, Event.NODE_MOVED, "/", 
true, null, null, false);
+        observationManager.addEventListener(listener, NODE_MOVED, "/", true, 
null, null, false);
         listener.expect(new Expectation("orderBefore"){
             @Override
             public boolean onEvent(Event event) throws Exception {
@@ -677,6 +708,18 @@ public class ObservationTest extends Abs
             return property;
         }
 
+        public void expectMove(final String src, final String dst) {
+            expect(new Expectation(">" + src + ':' + dst){
+                @Override
+                public boolean onEvent(Event event) throws Exception {
+                    return event.getType() == NODE_MOVED &&
+                            equal(dst, event.getPath()) &&
+                            equal(src, event.getInfo().get("srcAbsPath")) &&
+                            equal(dst, event.getInfo().get("destAbsPath"));
+                }
+            });
+        }
+
         public List<Expectation> getMissing(int time, TimeUnit timeUnit)
                 throws ExecutionException, InterruptedException {
             List<Expectation> missing = Lists.newArrayList();

Modified: jackrabbit/oak/trunk/oak-parent/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-parent/pom.xml?rev=1552323&r1=1552322&r2=1552323&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-parent/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-parent/pom.xml Thu Dec 19 16:09:09 2013
@@ -233,7 +233,7 @@
             <dependency>
               <groupId>org.apache.maven.doxia</groupId>
               <artifactId>doxia-module-markdown</artifactId>
-              <version>1.4</version>
+              <version>1.5</version>
             </dependency>
           </dependencies>
         </plugin>


Reply via email to