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 st...@apache.org on 2012/07/27 14:35:11 UTC

svn commit: r1366367 - in /jackrabbit/oak/trunk/oak-mk/src: main/java/org/apache/jackrabbit/mk/core/ main/java/org/apache/jackrabbit/mk/json/ main/java/org/apache/jackrabbit/mk/model/ main/java/org/apache/jackrabbit/mk/store/ test/java/org/apache/jackr...

Author: stefan
Date: Fri Jul 27 12:35:11 2012
New Revision: 1366367

URL: http://svn.apache.org/viewvc?rev=1366367&view=rev
Log:
OAK-210: persistence granularity (WIP)

Added:
    jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java
    jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java
Modified:
    jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
    jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
    jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java
    jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
    jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java

Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/core/MicroKernelImpl.java Fri Jul 27 12:35:11 2012
@@ -23,8 +23,8 @@ import org.apache.jackrabbit.mk.json.Jso
 import org.apache.jackrabbit.mk.json.JsopTokenizer;
 import org.apache.jackrabbit.mk.model.Commit;
 import org.apache.jackrabbit.mk.model.CommitBuilder;
-import org.apache.jackrabbit.mk.model.CommitBuilder.NodeTree;
 import org.apache.jackrabbit.mk.model.Id;
+import org.apache.jackrabbit.mk.json.JsonObject;
 import org.apache.jackrabbit.mk.model.StoredCommit;
 import org.apache.jackrabbit.mk.model.tree.ChildNode;
 import org.apache.jackrabbit.mk.model.tree.DiffBuilder;
@@ -380,7 +380,7 @@ public class MicroKernelImpl implements 
                             }
                             String parentPath = PathUtils.getParentPath(nodePath);
                             String nodeName = PathUtils.getName(nodePath);
-                            cb.addNode(parentPath, nodeName, parseNode(t));
+                            cb.addNode(parentPath, nodeName, JsonObject.create(t));
                         } else {
                             String value;
                             if (t.matches(JsopReader.NULL)) {
@@ -630,23 +630,6 @@ public class MicroKernelImpl implements 
         }
     }
 
-    NodeTree parseNode(JsopTokenizer t) throws Exception {
-        NodeTree node = new NodeTree();
-        if (!t.matches('}')) {
-            do {
-                String key = t.readString();
-                t.read(':');
-                if (t.matches('{')) {
-                    node.nodes.put(key, parseNode(t));
-                } else {
-                    node.props.put(key, t.readRawValue().trim());
-                }
-            } while (t.matches(','));
-            t.read('}');
-        }
-        return node;
-    }
-
     //-------------------------------------------------------< inner classes >
 
     static class NodeFilter {

Added: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java?rev=1366367&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java (added)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/json/JsonObject.java Fri Jul 27 12:35:11 2012
@@ -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.mk.json;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Simple JSON Object representation
+ */
+public class JsonObject {
+
+    private Map<String, String> props = new HashMap<String, String>();
+    private Map<String, JsonObject> children = new HashMap<String, JsonObject>();
+
+    public static JsonObject create(JsopTokenizer t) {
+        JsonObject obj = new JsonObject();
+        if (!t.matches('}')) {
+            do {
+                String key = t.readString();
+                t.read(':');
+                if (t.matches('{')) {
+                    obj.children.put(key, create(t));
+                } else {
+                    obj.props.put(key, t.readRawValue().trim());
+                }
+            } while (t.matches(','));
+            t.read('}');
+        }
+        return obj;
+    }
+
+    public void toJson(JsopBuilder buf) {
+        toJson(buf, this);
+    }
+
+    public Map<String, String> getProperties() {
+        return props;
+    }
+
+    public Map<String, JsonObject> getChildren() {
+        return children;
+    }
+
+    private static void toJson(JsopBuilder buf, JsonObject obj) {
+        buf.object();
+        for (String name : obj.props.keySet()) {
+            buf.key(name).encodedValue(obj.props.get(name));
+        }
+        for (String name : obj.children.keySet()) {
+            buf.key(name);
+            toJson(buf, obj.children.get(name));
+        }
+        buf.endObject();
+    }
+}
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/CommitBuilder.java Fri Jul 27 12:35:11 2012
@@ -16,20 +16,15 @@
  */
 package org.apache.jackrabbit.mk.model;
 
+import org.apache.jackrabbit.mk.json.JsonObject;
 import org.apache.jackrabbit.mk.json.JsopBuilder;
 import org.apache.jackrabbit.mk.model.tree.DiffBuilder;
-import org.apache.jackrabbit.mk.model.tree.NodeDelta;
 import org.apache.jackrabbit.mk.store.NotFoundException;
 import org.apache.jackrabbit.mk.store.RevisionStore;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  *
@@ -43,8 +38,9 @@ public class CommitBuilder {
 
     private final RevisionStore store;
 
-    // key is a path
-    private final Map<String, MutableNode> staged = new HashMap<String, MutableNode>();
+    // staging area
+    private final StagedNodeTree stagedTree;
+
     // change log
     private final List<Change> changeLog = new ArrayList<Change>();
 
@@ -52,9 +48,10 @@ public class CommitBuilder {
         this.baseRevId = baseRevId;
         this.msg = msg;
         this.store = store;
+        stagedTree = new StagedNodeTree(store, baseRevId);
     }
 
-    public void addNode(String parentNodePath, String nodeName, NodeTree node) throws Exception {
+    public void addNode(String parentNodePath, String nodeName, JsonObject node) throws Exception {
         Change change = new AddNode(parentNodePath, nodeName, node);
         change.apply();
         // update change log
@@ -94,7 +91,7 @@ public class CommitBuilder {
     }
 
     public Id /* new revId */ doCommit(boolean createBranch) throws Exception {
-        if (staged.isEmpty() && !createBranch) {
+        if (stagedTree.isEmpty() && !createBranch) {
             // nothing to commit
             return baseRevId;
         }
@@ -106,15 +103,14 @@ public class CommitBuilder {
 
         boolean privateCommit = createBranch || baseCommit.getBranchRootId() != null;
 
-
         if (!privateCommit) {
             Id currentHead = store.getHeadCommitId();
             if (!currentHead.equals(baseRevId)) {
                 // todo gracefully handle certain conflicts (e.g. changes on moved sub-trees, competing deletes etc)
-                // update base revision to new head
+                // update base revision to more recent current head
                 baseRevId = currentHead;
-                // clear staging area
-                staged.clear();
+                // reset staging area
+                stagedTree.reset(baseRevId);
                 // replay change log on new base revision
                 for (Change change : changeLog) {
                     change.apply();
@@ -124,7 +120,7 @@ public class CommitBuilder {
 
         RevisionStore.PutToken token = store.createPutToken();
         Id rootNodeId =
-                changeLog.isEmpty() ? baseCommit.getRootNodeId() : persistStagedNodes(token);
+                changeLog.isEmpty() ? baseCommit.getRootNodeId() : stagedTree.persist(token);
 
         Id newRevId;
 
@@ -133,12 +129,10 @@ public class CommitBuilder {
             try {
                 Id currentHead = store.getHeadCommitId();
                 if (!currentHead.equals(baseRevId)) {
-                    StoredNode baseRoot = store.getRootNode(baseRevId);
-                    StoredNode theirRoot = store.getRootNode(currentHead);
-                    StoredNode ourRoot = store.getNode(rootNodeId);
-
-                    rootNodeId = mergeTree(baseRoot, ourRoot, theirRoot, token);
-
+                    // there's a more recent head revision
+                    // perform a three-way merge
+                    rootNodeId = stagedTree.merge(store.getNode(rootNodeId), currentHead, baseRevId, token);
+                    // update base revision to more recent current head
                     baseRevId = currentHead;
                 }
 
@@ -147,6 +141,7 @@ public class CommitBuilder {
                     // no need to create new commit object/update head revision
                     return currentHead;
                 }
+                // persist new commit
                 MutableCommit newCommit = new MutableCommit();
                 newCommit.setParentId(baseRevId);
                 newCommit.setCommitTS(System.currentTimeMillis());
@@ -189,7 +184,7 @@ public class CommitBuilder {
         }
 
         // reset instance
-        staged.clear();
+        stagedTree.reset(newRevId);
         changeLog.clear();
 
         return newRevId;
@@ -204,7 +199,7 @@ public class CommitBuilder {
 
         RevisionStore.PutToken token = store.createPutToken();
         Id rootNodeId =
-                changeLog.isEmpty() ? branchCommit.getRootNodeId() : persistStagedNodes(token);
+                changeLog.isEmpty() ? branchCommit.getRootNodeId() : stagedTree.persist(token);
 
         Id newRevId;
 
@@ -212,11 +207,9 @@ public class CommitBuilder {
         try {
             Id currentHead = store.getHeadCommitId();
 
-            StoredNode baseRoot = store.getRootNode(branchRootId);
-            StoredNode theirRoot = store.getRootNode(currentHead);
             StoredNode ourRoot = store.getNode(rootNodeId);
 
-            rootNodeId = mergeTree(baseRoot, ourRoot, theirRoot, token);
+            rootNodeId = stagedTree.merge(ourRoot, currentHead, branchRootId, token);
 
             if (store.getCommit(currentHead).getRootNodeId().equals(rootNodeId)) {
                 // the merge didn't cause any changes,
@@ -229,7 +222,7 @@ public class CommitBuilder {
             newCommit.setMsg(msg);
             // dynamically build diff of merged commit
             String diff = new DiffBuilder(
-                    store.getNodeState(theirRoot),
+                    store.getNodeState(store.getRootNode(currentHead)),
                     store.getNodeState(store.getNode(rootNodeId)),
                     "/", store, "").build();
             newCommit.setChanges(diff);
@@ -241,231 +234,14 @@ public class CommitBuilder {
         }
 
         // reset instance
-        staged.clear();
+        stagedTree.reset(newRevId);
         changeLog.clear();
 
         return newRevId;
     }
 
-    //-------------------------------------------------------< implementation >
-
-    MutableNode getOrCreateStagedNode(String nodePath) throws Exception {
-        MutableNode node = staged.get(nodePath);
-        if (node == null) {
-            MutableNode parent = staged.get("/");
-            if (parent == null) {
-                parent = new MutableNode(store.getRootNode(baseRevId), store, "/");
-                staged.put("/", parent);
-            }
-            node = parent;
-
-            int nth = PathUtils.getDepth(nodePath) - 1;
-            for (String name : PathUtils.elements(nodePath)) {
-                String path = PathUtils.getAncestorPath(nodePath, nth--);
-                node = staged.get(path);
-                if (node == null) {
-                    // not yet staged, resolve id using staged parent
-                    // to allow for staged move operations
-                    ChildNodeEntry cne = parent.getChildNodeEntry(name);
-                    if (cne == null) {
-                        throw new NotFoundException(nodePath);
-                    }
-                    node = new MutableNode(store.getNode(cne.getId()), store, path);
-                    staged.put(path, node);
-                }
-                parent = node;
-            }
-        }
-        return node;
-    }
-
-    void moveStagedNodes(String srcPath, String destPath) throws Exception {
-        MutableNode node = staged.get(srcPath);
-        if (node != null) {
-            staged.remove(srcPath);
-            staged.put(destPath, node);
-            for (Iterator<String> it = node.getChildNodeNames(0, -1); it.hasNext(); ) {
-                String childName = it.next();
-                moveStagedNodes(PathUtils.concat(srcPath, childName), PathUtils.concat(destPath, childName));
-            }
-        }
-    }
-
-    void copyStagedNodes(String srcPath, String destPath, String breakAtSrcPath) throws Exception {
-        if (srcPath.equals(breakAtSrcPath)) {
-            // OAK-83: prevent infinite recursion when copying to descendant path
-            return;
-        }
-
-        MutableNode node = staged.get(srcPath);
-        if (node != null) {
-            staged.put(destPath, new MutableNode(node, store, destPath));
-            for (Iterator<String> it = node.getChildNodeNames(0, -1); it.hasNext(); ) {
-                String childName = it.next();
-                copyStagedNodes(PathUtils.concat(srcPath, childName), PathUtils.concat(destPath, childName), breakAtSrcPath);
-            }
-        }
-    }
-
-    void removeStagedNodes(String nodePath) throws Exception {
-        MutableNode node = staged.get(nodePath);
-        if (node != null) {
-            staged.remove(nodePath);
-            for (Iterator<String> it = node.getChildNodeNames(0, -1); it.hasNext(); ) {
-                String childName = it.next();
-                removeStagedNodes(PathUtils.concat(nodePath, childName));
-            }
-        }
-    }
-
-    Id /* new id of root node */ persistStagedNodes(RevisionStore.PutToken token) throws Exception {
-        // sort paths in in depth-descending order
-        ArrayList<String> orderedPaths = new ArrayList<String>(staged.keySet());
-        Collections.sort(orderedPaths, new Comparator<String>() {
-            public int compare(String path1, String path2) {
-                // paths should be ordered by depth, descending
-                int result = getDepth(path2) - getDepth(path1);
-                if (result != 0) {
-                    return result;
-                } else {
-                    return path2.compareTo(path1);
-                }
-            }
-
-            int getDepth(String path) {
-                return PathUtils.getDepth(path);
-            }
-        });
-        // iterate over staged entries in depth-descending order
-        Id rootNodeId = null;
-        for (String path : orderedPaths) {
-            // persist node
-            Id id = store.putNode(token, staged.get(path));
-            if (PathUtils.denotesRoot(path)) {
-                rootNodeId = id;
-            } else {
-                staged.get(PathUtils.getParentPath(path)).add(new ChildNodeEntry(PathUtils.getName(path), id));
-            }
-        }
-        if (rootNodeId == null) {
-            throw new Exception("internal error: inconsistent staging area content");
-        }
-        return rootNodeId;
-    }
-
-    /**
-     * Performs a three-way merge of the trees rooted at {@code ourRoot},
-     * {@code theirRoot}, using the tree at {@code baseRoot} as reference.
-     *
-     * @param baseRoot
-     * @param ourRoot
-     * @param theirRoot
-     * @return id of merged root node
-     * @throws Exception
-     */
-    Id /* id of merged root node */ mergeTree(StoredNode baseRoot, StoredNode ourRoot, StoredNode theirRoot,
-            RevisionStore.PutToken token) throws Exception {
-        
-        // as we're going to use the staging area for the merge process,
-        // we need to clear it first
-        staged.clear();
-
-        // recursively merge 'our' changes with 'their' changes...
-        mergeNode(baseRoot, ourRoot, theirRoot, "/");
-
-        return persistStagedNodes(token);
-    }
-
-    void mergeNode(StoredNode baseNode, StoredNode ourNode, StoredNode theirNode, String path) throws Exception {
-        NodeDelta theirChanges = new NodeDelta(
-                store, store.getNodeState(baseNode), store.getNodeState(theirNode));
-        NodeDelta ourChanges = new NodeDelta(
-                store, store.getNodeState(baseNode), store.getNodeState(ourNode));
-
-        // merge non-conflicting changes
-        MutableNode mergedNode = new MutableNode(theirNode, store, path);
-        staged.put(path, mergedNode);
-
-        mergedNode.getProperties().putAll(ourChanges.getAddedProperties());
-        mergedNode.getProperties().putAll(ourChanges.getChangedProperties());
-        for (String name : ourChanges.getRemovedProperties().keySet()) {
-            mergedNode.getProperties().remove(name);
-        }
-
-        for (Map.Entry<String, Id> entry : ourChanges.getAddedChildNodes ().entrySet()) {
-            mergedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
-        }
-        for (Map.Entry<String, Id> entry : ourChanges.getChangedChildNodes ().entrySet()) {
-            mergedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
-        }
-        for (String name : ourChanges.getRemovedChildNodes().keySet()) {
-            mergedNode.remove(name);
-        }
-
-        List<NodeDelta.Conflict> conflicts = theirChanges.listConflicts(ourChanges);
-        // resolve/report merge conflicts
-        for (NodeDelta.Conflict conflict : conflicts) {
-            String conflictName = conflict.getName();
-            String conflictPath = PathUtils.concat(path, conflictName);
-            switch (conflict.getType()) {
-                case PROPERTY_VALUE_CONFLICT:
-                    throw new Exception(
-                            "concurrent modification of property " + conflictPath
-                                    + " with conflicting values: \""
-                                    + ourNode.getProperties().get(conflictName)
-                                    + "\", \""
-                                    + theirNode.getProperties().get(conflictName));
-
-                case NODE_CONTENT_CONFLICT: {
-                    if (ourChanges.getChangedChildNodes().containsKey(conflictName)) {
-                        // modified subtrees
-                        StoredNode baseChild = store.getNode(baseNode.getChildNodeEntry(conflictName).getId());
-                        StoredNode ourChild = store.getNode(ourNode.getChildNodeEntry(conflictName).getId());
-                        StoredNode theirChild = store.getNode(theirNode.getChildNodeEntry(conflictName).getId());
-                        // merge the dirty subtrees recursively
-                        mergeNode(baseChild, ourChild, theirChild, PathUtils.concat(path, conflictName));
-                    } else {
-                        // todo handle/merge colliding node creation
-                        throw new Exception("colliding concurrent node creation: " + conflictPath);
-                    }
-                    break;
-                }
-
-                case REMOVED_DIRTY_PROPERTY_CONFLICT:
-                    mergedNode.getProperties().remove(conflictName);
-                    break;
-
-                case REMOVED_DIRTY_NODE_CONFLICT:
-                    mergedNode.remove(conflictName);
-                    break;
-            }
-
-        }
-    }
-
     //--------------------------------------------------------< inner classes >
 
-    public static class NodeTree {
-        public Map<String, String> props = new HashMap<String, String>();
-        public Map<String, NodeTree> nodes = new HashMap<String, NodeTree>();
-
-        void toJson(JsopBuilder buf) {
-            toJson(buf, this);
-        }
-
-        private static void toJson(JsopBuilder buf, NodeTree node) {
-            buf.object();
-            for (String name : node.props.keySet()) {
-                buf.key(name).encodedValue(node.props.get(name));
-            }
-            for (String name : node.nodes.keySet()) {
-                buf.key(name);
-                toJson(buf, node.nodes.get(name));
-            }
-            buf.endObject();
-        }
-    }
-
     abstract class Change {
         abstract void apply() throws Exception;
         abstract String asDiff();
@@ -474,9 +250,9 @@ public class CommitBuilder {
     class AddNode extends Change {
         String parentNodePath;
         String nodeName;
-        NodeTree node;
+        JsonObject node;
 
-        AddNode(String parentNodePath, String nodeName, NodeTree node) {
+        AddNode(String parentNodePath, String nodeName, JsonObject node) {
             this.parentNodePath = parentNodePath;
             this.nodeName = nodeName;
             this.node = node;
@@ -484,7 +260,7 @@ public class CommitBuilder {
 
         @Override
         void apply() throws Exception {
-            recursiveAddNode(parentNodePath, nodeName, node);
+            stagedTree.add(parentNodePath, nodeName, node);
         }
 
         @Override
@@ -494,24 +270,6 @@ public class CommitBuilder {
             node.toJson(diff);
             return diff.toString();
         }
-
-        private void recursiveAddNode(String parentPath, String name, NodeTree node) throws Exception {
-            MutableNode modParent = getOrCreateStagedNode(parentPath);
-            if (modParent.getChildNodeEntry(name) != null) {
-                throw new Exception("there's already a child node with name '" + name + "'");
-            }
-            String newPath = PathUtils.concat(parentPath, name);
-            MutableNode newChild = new MutableNode(store, newPath);
-            newChild.getProperties().putAll(node.props);
-
-            // id will be computed on commit
-            modParent.add(new ChildNodeEntry(name, null));
-            staged.put(newPath, newChild);
-
-            for (String childName : node.nodes.keySet()) {
-                recursiveAddNode(PathUtils.concat(parentPath, name), childName, node.nodes.get(childName));
-            }
-        }
     }
 
     class RemoveNode extends Change {
@@ -523,16 +281,7 @@ public class CommitBuilder {
 
         @Override
         void apply() throws Exception {
-            String parentPath = PathUtils.getParentPath(nodePath);
-            String nodeName = PathUtils.getName(nodePath);
-
-            MutableNode parent = getOrCreateStagedNode(parentPath);
-            if (parent.remove(nodeName) == null) {
-                throw new NotFoundException(nodePath);
-            }
-
-            // update staging area
-            removeStagedNodes(nodePath);
+            stagedTree.remove(nodePath);
         }
 
         @Override
@@ -554,39 +303,7 @@ public class CommitBuilder {
 
         @Override
         void apply() throws Exception {
-            if (PathUtils.isAncestor(srcPath, destPath)) {
-                throw new Exception("target path cannot be descendant of source path: " + destPath);
-            }
-
-            String srcParentPath = PathUtils.getParentPath(srcPath);
-            String srcNodeName = PathUtils.getName(srcPath);
-
-            String destParentPath = PathUtils.getParentPath(destPath);
-            String destNodeName = PathUtils.getName(destPath);
-
-            MutableNode srcParent = getOrCreateStagedNode(srcParentPath);
-            if (srcParentPath.equals(destParentPath)) {
-                if (srcParent.getChildNodeEntry(destNodeName) != null) {
-                    throw new Exception("node already exists at move destination path: " + destPath);
-                }
-                if (srcParent.rename(srcNodeName, destNodeName) == null) {
-                    throw new NotFoundException(srcPath);
-                }
-            } else {
-                ChildNodeEntry srcCNE = srcParent.remove(srcNodeName);
-                if (srcCNE == null) {
-                    throw new NotFoundException(srcPath);
-                }
-
-                MutableNode destParent = getOrCreateStagedNode(destParentPath);
-                if (destParent.getChildNodeEntry(destNodeName) != null) {
-                    throw new Exception("node already exists at move destination path: " + destPath);
-                }
-                destParent.add(new ChildNodeEntry(destNodeName, srcCNE.getId()));
-            }
-
-            // update staging area
-            moveStagedNodes(srcPath, destPath);
+            stagedTree.move(srcPath, destPath);
         }
 
         @Override
@@ -608,28 +325,7 @@ public class CommitBuilder {
 
         @Override
         void apply() throws Exception {
-            String srcParentPath = PathUtils.getParentPath(srcPath);
-            String srcNodeName = PathUtils.getName(srcPath);
-
-            String destParentPath = PathUtils.getParentPath(destPath);
-            String destNodeName = PathUtils.getName(destPath);
-
-            MutableNode srcParent = getOrCreateStagedNode(srcParentPath);
-            ChildNodeEntry srcCNE = srcParent.getChildNodeEntry(srcNodeName);
-            if (srcCNE == null) {
-                throw new NotFoundException(srcPath);
-            }
-
-            if (staged.containsKey(srcPath)) {
-                // the copied subtree is modified
-
-                // update staging area
-                copyStagedNodes(srcPath, destPath, destPath);
-            }
-
-            MutableNode destParent = getOrCreateStagedNode(destParentPath);
-            destParent.add(new ChildNodeEntry(destNodeName, srcCNE.getId()));
-
+            stagedTree.copy(srcPath, destPath);
         }
 
         @Override
@@ -638,7 +334,6 @@ public class CommitBuilder {
             diff.tag('*').key(srcPath).value(destPath);
             return diff.toString();
         }
-
     }
 
     class SetProperty extends Change {
@@ -654,14 +349,7 @@ public class CommitBuilder {
 
         @Override
         void apply() throws Exception {
-            MutableNode node = getOrCreateStagedNode(nodePath);
-
-            Map<String, String> properties = node.getProperties();
-            if (propValue == null) {
-                properties.remove(propName);
-            } else {
-                properties.put(propName, propValue);
-            }
+            stagedTree.setProperty(nodePath, propName, propValue);
         }
 
         @Override
@@ -676,5 +364,4 @@ public class CommitBuilder {
             return diff.toString();
         }
     }
-
 }

Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/MutableNode.java Fri Jul 27 12:35:11 2012
@@ -27,21 +27,12 @@ import java.util.Iterator;
  */
 public class MutableNode extends AbstractNode implements PersistHook {
 
-    /**
-     * Node path, for informational purpose only.
-     */
-    private final transient String path;
-    
-    public MutableNode(RevisionProvider provider, String path) {
+    public MutableNode(RevisionProvider provider) {
         super(provider);
-        
-        this.path = path;
     }
 
-    public MutableNode(Node other, RevisionProvider provider, String path) {
+    public MutableNode(Node other, RevisionProvider provider) {
         super(other, provider);
-
-        this.path = path;
     }
 
     public ChildNodeEntry add(ChildNodeEntry newEntry) {
@@ -80,9 +71,4 @@ public class MutableNode extends Abstrac
     public void postPersist(RevisionStore store, RevisionStore.PutToken token) throws Exception {
         // there's nothing to do
     }
-
-    @Override
-    public String toString() {
-        return path;
-    }
 }

Added: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java?rev=1366367&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java (added)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/model/StagedNodeTree.java Fri Jul 27 12:35:11 2012
@@ -0,0 +1,482 @@
+/*
+ * 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.mk.model;
+
+import org.apache.jackrabbit.mk.json.JsonObject;
+import org.apache.jackrabbit.mk.model.tree.NodeDelta;
+import org.apache.jackrabbit.mk.store.NotFoundException;
+import org.apache.jackrabbit.mk.store.RevisionStore;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@code StagedNodeTree} provides methods to manipulate a specific revision
+ * of the tree. The changes are recorded and can be persisted by calling
+ {@link #persist(RevisionStore.PutToken)}.
+ */
+public class StagedNodeTree {
+
+    private final RevisionStore store;
+
+    private StagedNode root;
+    private Id baseRevisionId;
+
+    /**
+     * Creates a new {@code StagedNodeTree} instance.
+     *
+     * @param store revision store used to read from and persist changes
+     * @param baseRevisionId id of revision the changes should be based upon
+     */
+    public StagedNodeTree(RevisionStore store, Id baseRevisionId) {
+        this.store = store;
+        this.baseRevisionId = baseRevisionId;
+    }
+
+    /**
+     * Discards all staged changes and resets the base revision to the
+     * specified new revision id.
+     *
+     * @param newBaseRevisionId id of revision the changes should be based upon
+     */
+    public void reset(Id newBaseRevisionId) {
+        root = null;
+        baseRevisionId = newBaseRevisionId;
+    }
+
+    /**
+     * Returns {@code true} if there are no staged changes, otherwise returns {@code false}.
+     * @return {@code true} if there are no staged changes, otherwise returns {@code false}.
+     */
+    public boolean isEmpty() {
+        return root == null;
+    }
+
+    /**
+     * Persists the staged nodes and returns the {@code Id} of new root node.
+     *
+     * @param token
+     * @return {@code Id} of new root node
+     * @throws Exception
+     */
+    public Id /* new id of root node */ persist(RevisionStore.PutToken token) throws Exception {
+        return root != null ? root.persist(token) : null;
+    }
+
+    /**
+     * Performs a three-way merge merging <i>our</i> tree (rooted at {@code ourRoot})
+     * and <i>their</i> tree (identified by {@code newBaseRevisionId}),
+     * using the common ancestor revision {@code commonAncestorRevisionId} as
+     * base reference.
+     * <p/>
+     * This instance will be initially reset to {@code newBaseRevisionId}, discarding
+     * all currently staged changes.
+     *
+     * @param ourRoot
+     * @param newBaseRevisionId
+     * @param commonAncestorRevisionId
+     * @param token
+     * @return {@code Id} of new root node
+     * @throws Exception
+     */
+    public Id /* new id of merged root node */ merge(StoredNode ourRoot,
+                                                 Id newBaseRevisionId,
+                                                 Id commonAncestorRevisionId,
+                                                 RevisionStore.PutToken token) throws Exception {
+        // reset staging area to new base revision
+        reset(newBaseRevisionId);
+
+        StoredNode baseRoot = store.getRootNode(commonAncestorRevisionId);
+        StoredNode theirRoot = store.getRootNode(newBaseRevisionId);
+
+        // recursively merge 'our' changes with 'their' changes...
+        mergeNode(baseRoot, ourRoot, theirRoot, "/");
+
+        // persist staged nodes
+        return persist(token);
+    }
+
+    //-----------------------------------------< tree manipulation operations >
+
+    public void add(String parentNodePath, String nodeName, JsonObject node) throws Exception {
+        StagedNode parent = getStagedNode(parentNodePath, true);
+        if (parent.getChildNodeEntry(nodeName) != null) {
+            throw new Exception("there's already a child node with name '" + nodeName + "'");
+        }
+        parent.add(nodeName, node);
+    }
+
+    public void remove(String nodePath) throws Exception {
+        String parentPath = PathUtils.getParentPath(nodePath);
+        String nodeName = PathUtils.getName(nodePath);
+
+        StagedNode parent = getStagedNode(parentPath, true);
+        if (parent.remove(nodeName) == null) {
+            throw new NotFoundException(nodePath);
+        }
+
+        // discard any staged changes at nodePath
+        unstageNode(nodePath);
+    }
+
+    public void setProperty(String nodePath, String propName, String propValue) throws Exception {
+        StagedNode node = getStagedNode(nodePath, true);
+
+        Map<String, String> properties = node.getProperties();
+        if (propValue == null) {
+            properties.remove(propName);
+        } else {
+            properties.put(propName, propValue);
+        }
+    }
+
+    public void move(String srcPath, String destPath) throws Exception {
+        if (PathUtils.isAncestor(srcPath, destPath)) {
+            throw new Exception("target path cannot be descendant of source path: " + destPath);
+        }
+
+        String srcParentPath = PathUtils.getParentPath(srcPath);
+        String srcNodeName = PathUtils.getName(srcPath);
+
+        String destParentPath = PathUtils.getParentPath(destPath);
+        String destNodeName = PathUtils.getName(destPath);
+
+        StagedNode srcParent = getStagedNode(srcParentPath, true);
+        if (srcParent.getChildNodeEntry(srcNodeName) == null) {
+            throw new NotFoundException(srcPath);
+        }
+        StagedNode destParent = getStagedNode(destParentPath, true);
+        if (destParent.getChildNodeEntry(destNodeName) != null) {
+            throw new Exception("node already exists at move destination path: " + destPath);
+        }
+
+        if (srcParentPath.equals(destParentPath)) {
+            // rename
+            srcParent.rename(srcNodeName, destNodeName);
+        } else {
+            // move
+            srcParent.move(srcNodeName, destPath);
+        }
+    }
+
+    public void copy(String srcPath, String destPath) throws Exception {
+        String srcParentPath = PathUtils.getParentPath(srcPath);
+        String srcNodeName = PathUtils.getName(srcPath);
+
+        String destParentPath = PathUtils.getParentPath(destPath);
+        String destNodeName = PathUtils.getName(destPath);
+
+        StagedNode srcParent = getStagedNode(srcParentPath, false);
+        if (srcParent == null) {
+            // the subtree to be copied has not been modified
+            ChildNodeEntry entry = getStoredNode(srcParentPath).getChildNodeEntry(srcNodeName);
+            if (entry == null) {
+                throw new NotFoundException(srcPath);
+            }
+            StagedNode destParent = getStagedNode(destParentPath, true);
+            if (destParent.getChildNodeEntry(destNodeName) != null) {
+                throw new Exception("node already exists at copy destination path: " + destPath);
+            }
+            destParent.add(new ChildNodeEntry(destNodeName, entry.getId()));
+            return;
+        }
+
+        ChildNodeEntry srcEntry = srcParent.getChildNodeEntry(srcNodeName);
+        if (srcEntry == null) {
+            throw new NotFoundException(srcPath);
+        }
+
+        StagedNode destParent = getStagedNode(destParentPath, true);
+        StagedNode srcNode = getStagedNode(srcPath, false);
+        if (srcNode != null) {
+            // copy the modified subtree
+            destParent.add(destNodeName, srcNode.copy());
+        } else {
+            destParent.add(new ChildNodeEntry(destNodeName, srcEntry.getId()));
+        }
+    }
+
+    //-------------------------------------------------------< implementation >
+
+    private StagedNode getStagedNode(String path, boolean createIfNotStaged) throws Exception {
+        assert PathUtils.isAbsolute(path);
+
+        if (root == null) {
+            if (!createIfNotStaged) {
+                return null;
+            }
+            root = new StagedNode(store.getRootNode(baseRevisionId), store);
+        }
+
+        if (PathUtils.denotesRoot(path)) {
+            return root;
+        }
+
+        StagedNode parent = root, node = null;
+        for (String name : PathUtils.elements(path)) {
+            node = parent.getStagedChildNode(name, createIfNotStaged);
+            if (node == null) {
+                return null;
+            }
+            parent = node;
+        }
+        return node;
+    }
+
+    private StagedNode unstageNode(String path) throws Exception {
+        assert PathUtils.isAbsolute(path);
+
+        if (PathUtils.denotesRoot(path)) {
+            StagedNode unstaged = root;
+            root = null;
+            return unstaged;
+        }
+
+        String parentPath = PathUtils.getParentPath(path);
+        String name = PathUtils.getName(path);
+
+        StagedNode parent = getStagedNode(parentPath, false);
+        if (parent == null) {
+            return null;
+        }
+
+        return parent.unstageChildNode(name);
+    }
+
+    private StoredNode getStoredNode(String path) throws Exception {
+        assert PathUtils.isAbsolute(path);
+
+        if (PathUtils.denotesRoot(path)) {
+            return store.getRootNode(baseRevisionId);
+        }
+
+        StoredNode parent = store.getRootNode(baseRevisionId), node = null;
+        for (String name : PathUtils.elements(path)) {
+            ChildNodeEntry entry = parent.getChildNodeEntry(name);
+            if (entry == null) {
+                throw new NotFoundException(path);
+            }
+            node = store.getNode(entry.getId());
+            if (node == null) {
+                throw new NotFoundException(path);
+            }
+            parent = node;
+        }
+        return node;
+    }
+
+    /**
+     * Performs a three-way merge of the trees rooted at {@code ourRoot},
+     * {@code theirRoot}, using the tree at {@code baseRoot} as reference.
+     */
+    private void mergeNode(StoredNode baseNode, StoredNode ourNode, StoredNode theirNode, String path) throws Exception {
+        NodeDelta theirChanges = new NodeDelta(
+                store, store.getNodeState(baseNode), store.getNodeState(theirNode));
+        NodeDelta ourChanges = new NodeDelta(
+                store, store.getNodeState(baseNode), store.getNodeState(ourNode));
+
+        StagedNode stagedNode = getStagedNode(path, true);
+
+        // merge non-conflicting changes
+        stagedNode.getProperties().putAll(ourChanges.getAddedProperties());
+        stagedNode.getProperties().putAll(ourChanges.getChangedProperties());
+        for (String name : ourChanges.getRemovedProperties().keySet()) {
+            stagedNode.getProperties().remove(name);
+        }
+
+        for (Map.Entry<String, Id> entry : ourChanges.getAddedChildNodes ().entrySet()) {
+            stagedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
+        }
+        for (Map.Entry<String, Id> entry : ourChanges.getChangedChildNodes ().entrySet()) {
+            stagedNode.add(new ChildNodeEntry(entry.getKey(), entry.getValue()));
+        }
+        for (String name : ourChanges.getRemovedChildNodes().keySet()) {
+            stagedNode.remove(name);
+        }
+
+        List<NodeDelta.Conflict> conflicts = theirChanges.listConflicts(ourChanges);
+        // resolve/report merge conflicts
+        for (NodeDelta.Conflict conflict : conflicts) {
+            String conflictName = conflict.getName();
+            String conflictPath = PathUtils.concat(path, conflictName);
+            switch (conflict.getType()) {
+                case PROPERTY_VALUE_CONFLICT:
+                    throw new Exception(
+                            "concurrent modification of property " + conflictPath
+                                    + " with conflicting values: \""
+                                    + ourNode.getProperties().get(conflictName)
+                                    + "\", \""
+                                    + theirNode.getProperties().get(conflictName));
+
+                case NODE_CONTENT_CONFLICT: {
+                    if (ourChanges.getChangedChildNodes().containsKey(conflictName)) {
+                        // modified subtrees
+                        StoredNode baseChild = store.getNode(baseNode.getChildNodeEntry(conflictName).getId());
+                        StoredNode ourChild = store.getNode(ourNode.getChildNodeEntry(conflictName).getId());
+                        StoredNode theirChild = store.getNode(theirNode.getChildNodeEntry(conflictName).getId());
+                        // merge the dirty subtrees recursively
+                        mergeNode(baseChild, ourChild, theirChild, PathUtils.concat(path, conflictName));
+                    } else {
+                        // todo handle/merge colliding node creation
+                        throw new Exception("colliding concurrent node creation: " + conflictPath);
+                    }
+                    break;
+                }
+
+                case REMOVED_DIRTY_PROPERTY_CONFLICT:
+                    stagedNode.getProperties().remove(conflictName);
+                    break;
+
+                case REMOVED_DIRTY_NODE_CONFLICT:
+                    stagedNode.remove(conflictName);
+                    break;
+            }
+
+        }
+    }
+
+    //--------------------------------------------------------< inner classes >
+
+    private class StagedNode extends MutableNode {
+
+        private final Map<String, StagedNode> stagedChildNodes = new HashMap<String, StagedNode>();
+
+        private StagedNode(RevisionStore store) {
+            super(store);
+        }
+
+        private StagedNode(Node base, RevisionStore store) {
+            super(base, store);
+        }
+
+        /**
+         * Returns a {@code StagedNode} representation of the given child node.
+         * If a {@code StagedNode} representation doesn't exist yet a new
+         * {@code StagedNode} instance will be returned if {@code createIfNotStaged == true},
+         * otherwise {@code null} will be returned.
+         * <p/>
+         * A {@code NotFoundException} will be thrown if there's no child node
+         * with the given name.
+         *
+         * @param name child node name
+         * @param createIfNotStaged flag controlling whether a new {@code StagedNode}
+         *                          instance should be created on demand
+         * @return a {@code StagedNode} instance or null if there's no {@code StagedNode}
+         *         representation of the given child node and {@code createIfNotStaged == false}
+         * @throws NotFoundException if there's no child node with the given name
+         * @throws Exception if another error occurs
+         */
+        StagedNode getStagedChildNode(String name, boolean createIfNotStaged) throws Exception {
+            StagedNode child = stagedChildNodes.get(name);
+            if (child == null) {
+                ChildNodeEntry entry = getChildNodeEntry(name);
+                if (entry != null) {
+                    if (createIfNotStaged) {
+                        child = new StagedNode(store.getNode(entry.getId()), store);
+                        stagedChildNodes.put(name, child);
+                    }
+                } else {
+                    throw new NotFoundException(name);
+                }
+            }
+            return child;
+        }
+
+        StagedNode unstageChildNode(String name) {
+            return stagedChildNodes.remove(name);
+        }
+
+        StagedNode add(String name, StagedNode node) {
+            stagedChildNodes.put(name, node);
+            // child id will be computed on persist
+            add(new ChildNodeEntry(name, null));
+            return node;
+        }
+
+        StagedNode copy() {
+            StagedNode copy = new StagedNode(this, store);
+            // recursively copy staged child nodes
+            for (Map.Entry<String, StagedNode> entry : stagedChildNodes.entrySet()) {
+                copy.add(entry.getKey(), entry.getValue().copy());
+            }
+            return copy;
+        }
+
+        StagedNode add(String name, JsonObject obj) {
+            StagedNode node = new StagedNode(store);
+            node.getProperties().putAll(obj.getProperties());
+            for (Map.Entry<String, JsonObject> entry : obj.getChildren().entrySet()) {
+                node.add(entry.getKey(), entry.getValue());
+            }
+            stagedChildNodes.put(name, node);
+            // child id will be computed on persist
+            add(new ChildNodeEntry(name, null));
+            return node;
+        }
+
+        void move(String name, String destPath) throws Exception {
+            ChildNodeEntry srcEntry = getChildNodeEntry(name);
+            assert srcEntry != null;
+
+            String destParentPath = PathUtils.getParentPath(destPath);
+            String destName = PathUtils.getName(destPath);
+
+            StagedNode destParent = getStagedNode(destParentPath, true);
+
+            StagedNode target = stagedChildNodes.get(name);
+
+            remove(name);
+            destParent.add(new ChildNodeEntry(destName, srcEntry.getId()));
+
+            if (target != null) {
+                // move staged child node
+                destParent.add(destName, target);
+            }
+        }
+
+        @Override
+        public ChildNodeEntry remove(String name) {
+            stagedChildNodes.remove(name);
+            return super.remove(name);
+        }
+
+        @Override
+        public ChildNodeEntry rename(String oldName, String newName) {
+            StagedNode child = stagedChildNodes.remove(oldName);
+            if (child != null) {
+                stagedChildNodes.put(newName, child);
+            }
+            return super.rename(oldName, newName);
+        }
+
+        Id persist(RevisionStore.PutToken token) throws Exception {
+            // recursively persist staged nodes
+            for (Map.Entry<String, StagedNode> entry : stagedChildNodes.entrySet()) {
+                String name = entry.getKey();
+                StagedNode childNode = entry.getValue();
+                // todo decide whether to inline/store child node separately based on some filter criteria
+                Id id = childNode.persist(token);
+                // update child node entry
+                add(new ChildNodeEntry(name, id));
+            }
+            // persist this node
+            return store.putNode(token, this);
+        }
+    }
+}

Modified: jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/main/java/org/apache/jackrabbit/mk/store/DefaultRevisionStore.java Fri Jul 27 12:35:11 2012
@@ -135,7 +135,7 @@ public class DefaultRevisionStore extend
                     .getBytes();
             head = new Id(rawHead);
 
-            Id rootNodeId = pm.writeNode(new MutableNode(this, "/"));
+            Id rootNodeId = pm.writeNode(new MutableNode(this));
             MutableCommit initialCommit = new MutableCommit();
             initialCommit.setCommitTS(System.currentTimeMillis());
             initialCommit.setRootNodeId(rootNodeId);

Modified: jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java?rev=1366367&r1=1366366&r2=1366367&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java (original)
+++ jackrabbit/oak/trunk/oak-mk/src/test/java/org/apache/jackrabbit/mk/persistence/GCPersistenceTest.java Fri Jul 27 12:35:11 2012
@@ -78,7 +78,7 @@ public class GCPersistenceTest {
 
     @Test
     public void testOldNodeIsSwept() throws Exception {
-        MutableNode node = new MutableNode(null, "/");
+        MutableNode node = new MutableNode(null);
         Id id = pm.writeNode(node);
 
         Thread.sleep(1);
@@ -95,7 +95,7 @@ public class GCPersistenceTest {
 
     @Test
     public void testMarkedNodeIsNotSwept() throws Exception {
-        MutableNode node = new MutableNode(null, "/");
+        MutableNode node = new MutableNode(null);
         Id id = pm.writeNode(node);
 
         // small delay needed
@@ -114,7 +114,7 @@ public class GCPersistenceTest {
     public void testNewNodeIsNotSwept() throws Exception {
         pm.start();
         
-        MutableNode node = new MutableNode(null, "/");
+        MutableNode node = new MutableNode(null);
         Id id = pm.writeNode(node);
         
         // new node must already be marked