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 th...@apache.org on 2013/02/26 10:10:07 UTC

svn commit: r1450089 - in /jackrabbit/oak/trunk/oak-mongomk/src: main/java/org/apache/jackrabbit/mongomk/prototype/ test/java/org/apache/jackrabbit/mongomk/prototype/

Author: thomasm
Date: Tue Feb 26 09:10:06 2013
New Revision: 1450089

URL: http://svn.apache.org/r1450089
Log:
OAK-619 Lock-free MongoMK implementation (cache)

Modified:
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Commit.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java
    jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java
    jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStoreTest.java
    jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/SimpleTest.java

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Commit.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Commit.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Commit.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Commit.java Tue Feb 26 09:10:06 2013
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.mongomk.pr
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 
 import org.apache.jackrabbit.mk.api.MicroKernelException;
 import org.apache.jackrabbit.mk.json.JsopStream;
@@ -31,20 +32,27 @@ import org.apache.jackrabbit.oak.commons
  */
 public class Commit {
     
+    private final MongoMK mk;
     private final Revision revision;
     private HashMap<String, UpdateOp> operations = new HashMap<String, UpdateOp>();
     private JsopWriter diff = new JsopStream();
-    private HashSet<String> changedParents = new HashSet<String>();
+    private HashSet<String> changedNodes = new HashSet<String>();
     
-    Commit(Revision revision) {
+    private HashSet<String> addedNodes = new HashSet<String>();
+    private HashSet<String> removedNodes = new HashSet<String>();
+    
+    private HashMap<String, Long> writeCounts = new HashMap<String, Long>();
+    
+    Commit(MongoMK mk, Revision revision) {
         this.revision = revision;
+        this.mk = mk;
     }
 
-    UpdateOp getUpdateOperationForNode(String path) {
+    private UpdateOp getUpdateOperationForNode(String path) {
         UpdateOp op = operations.get(path);
         if (op == null) {
             String id = Node.convertPathToDocumentId(path);
-            op = new UpdateOp(id, false);
+            op = new UpdateOp(path, id, false);
             operations.put(path, op);
         }
         return op;
@@ -53,33 +61,46 @@ public class Commit {
     public Revision getRevision() {
         return revision;
     }
+    
+    void addNodeDiff(Node n) {
+        diff.tag('+').key(n.path);
+        diff.object();
+        n.append(diff, false);
+        diff.endObject();
+        diff.newline();
+    }
+    
+    void updateProperty(String path, String propertyName, String value) {
+        UpdateOp op = getUpdateOperationForNode(path);
+        op.addMapEntry(propertyName, revision.toString(), value);
+        long increment = mk.getWriteCountIncrement(path);
+        op.increment("_writeCount", 1 + increment);
+    }
 
     void addNode(Node n) {
         if (operations.containsKey(n.path)) {
             throw new MicroKernelException("Node already added: " + n.path);
         }
         operations.put(n.path, n.asOperation(true));
+        addedNodes.add(n.path);
     }
 
-    void addNodeWithDiff(Node n) {
-        addNode(n);
-        diff.tag('+').key(n.path);
-        diff.object();
-        n.append(diff, false);
-        diff.endObject();
-        diff.newline();
-    }
-    
     boolean isEmpty() {
         return operations.isEmpty();
     }
 
-    void apply(DocumentStore store) {
+    /**
+     * Apply the changes to the document store (to update MongoDB).
+     * 
+     * @param store the store
+     */
+    void applyToDocumentStore() {
+        DocumentStore store = mk.getDocumentStore();
         String commitRoot = null;
         ArrayList<UpdateOp> newNodes = new ArrayList<UpdateOp>();
         ArrayList<UpdateOp> changedNodes = new ArrayList<UpdateOp>();
         for (String p : operations.keySet()) {
-            addChangedParent(p);
+            markChanged(p);
             if (commitRoot == null) {
                 commitRoot = p;
             } else {
@@ -112,24 +133,80 @@ public class Commit {
             store.create(Collection.NODES, newNodes);
         }
         for (UpdateOp op : changedNodes) {
-            store.createOrUpdate(Collection.NODES, op);
+            createOrUpdateNode(store, op);
         }
         if (root != null) {
+            long increment = mk.getWriteCountIncrement(commitRoot);
+            root.increment("_writeCount", 1 + increment);
             root.addMapEntry("_revisions", revision.toString(), "true");
-            store.createOrUpdate(Collection.NODES, root);
+            createOrUpdateNode(store, root);
+            operations.put(commitRoot, root);
         }
     }
     
-    public void apply(MongoMK mk) {
-        // increment write counters
-        for (String path : changedParents) {
-            mk.incrementWriteCount(path);
+    private void createOrUpdateNode(DocumentStore store, UpdateOp op) {
+        Map<String, Object> map = store.createOrUpdate(Collection.NODES, op);
+        // TODO detect conflicts here
+        Long count = (Long) map.get("_writeCount");
+        if (count == null) {
+            count = 0L;
         }
-        // TODO update the cache
+        String path = op.getPath();
+        writeCounts.put(path, count);
     }
-
-    public void removeNode(String path) {
-        diff.tag('-').value(path).newline();
+    
+    /**
+     * Apply the changes to the MongoMK (to update the cache).
+     */
+    public void applyToCache() {
+        HashMap<String, ArrayList<String>> nodesWithChangedChildren = new HashMap<String, ArrayList<String>>();
+        ArrayList<String> addOrRemove = new ArrayList<String>();
+        addOrRemove.addAll(addedNodes);
+        addOrRemove.addAll(removedNodes);
+        for (String p : addOrRemove) {
+            String parent = PathUtils.getParentPath(p);
+            ArrayList<String> list = nodesWithChangedChildren.get(parent);
+            if (list == null) {
+                list = new ArrayList<String>();
+                nodesWithChangedChildren.put(parent, list);
+            }
+            list.add(p);
+        }
+        for (String path : changedNodes) {
+            ArrayList<String> added = new ArrayList<String>();
+            ArrayList<String> removed = new ArrayList<String>();
+            ArrayList<String> changed = nodesWithChangedChildren.get(path);
+            if (changed != null) {
+                for (String s : changed) {
+                    if (addedNodes.contains(s)) {
+                        added.add(s);
+                    } else if (removedNodes.contains(s)) {
+                        removed.add(s);
+                    }
+                }
+            }
+            UpdateOp op = operations.get(path);
+            boolean isNew = op != null && op.isNew;
+            boolean isWritten = op != null;
+            long writeCountInc = mk.getWriteCountIncrement(path);
+            Long writeCount = writeCounts.get(path);
+            if (writeCount == null) {
+                if (isNew) {
+                    writeCount = 0L;
+                    writeCountInc = 0;
+                } else {
+                    writeCountInc++;
+                    String id = Node.convertPathToDocumentId(path);
+                    Map<String, Object> map = mk.getDocumentStore().find(Collection.NODES, id);
+                    Long oldWriteCount = (Long) map.get("_writeCount");
+                    writeCount = oldWriteCount == null ? 0 : oldWriteCount;
+                }
+            }
+            mk.applyChanges(revision, path, 
+                    isNew, isWritten, 
+                    writeCount, writeCountInc,
+                    added, removed);
+        }
     }
 
     public void moveNode(String sourcePath, String targetPath) {
@@ -140,14 +217,9 @@ public class Commit {
         return diff;
     }
 
-    private void addChangedParent(String path) {
+    private void markChanged(String path) {
         while (true) {
-            UpdateOp op = operations.get(path);
-            if (op == null || !op.isNew) {
-                // no need to update the write count 
-                // for new nodes
-                changedParents.add(path);
-            }
+            changedNodes.add(path);
             if (PathUtils.denotesRoot(path)) {
                 break;
             }
@@ -155,5 +227,20 @@ public class Commit {
         }
     }
 
+    public void updatePropertyDiff(String path, String propertyName, String value) {
+        diff.tag('^').key(PathUtils.concat(path, propertyName)).value(value);
+    }
+    
+    public void removeNodeDiff(String path) {
+        diff.tag('-').value(path).newline();
+    }
+
+    public void removeNode(String path) {
+        removedNodes.add(path);
+        UpdateOp op = getUpdateOperationForNode(path);
+        op.addMapEntry("_deleted", revision.toString(), "true");
+        long increment = mk.getWriteCountIncrement(path);
+        op.increment("_writeCount", 1 + increment);
+    }
 
 }

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java Tue Feb 26 09:10:06 2013
@@ -29,14 +29,36 @@ public interface DocumentStore {
      */
     enum Collection { NODES }
 
+    /**
+     * Get a document. The returned map is a clone (the caller
+     * can modify it without affecting the stored version).
+     *
+     * @param collection the collection
+     * @param path the path
+     * @return the map, or null if not found
+     */
     Map<String, Object> find(Collection collection, String key);
 
     List<Map<String, Object>> query(Collection collection, String fromKey, String toKey, int limit);
     
+    /**
+     * Remove a document.
+     *
+     * @param collection the collection
+     * @param path the path
+     */
     void remove(Collection collection, String key);
 
     void create(Collection collection, List<UpdateOp> updateOps);
     
+    /**
+     * Create or update a document. For MongoDb, this is using "findAndModify" with
+     * the "upsert" flag (insert or update).
+     *
+     * @param collection the collection
+     * @param update the update operation
+     * @return the new document
+     */    
     Map<String, Object> createOrUpdate(Collection collection, UpdateOp update); 
     
     void dispose();

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java Tue Feb 26 09:10:06 2013
@@ -45,14 +45,6 @@ public class MemoryDocumentStore impleme
     private ConcurrentSkipListMap<String, Map<String, Object>> nodes =
             new ConcurrentSkipListMap<String, Map<String, Object>>();
 
-    /**
-     * Get a document. The returned map is a clone (the caller
-     * can modify it without affecting the stored version).
-     *
-     * @param collection the collection
-     * @param path the path
-     * @return the map, or null if not found
-     */
     public Map<String, Object> find(Collection collection, String path) {
         ConcurrentSkipListMap<String, Map<String, Object>> map = getMap(collection);
         Map<String, Object> n = map.get(path);
@@ -83,12 +75,6 @@ public class MemoryDocumentStore impleme
         return list;
     }
 
-    /**
-     * Remove a document.
-     *
-     * @param collection the collection
-     * @param path the path
-     */
     public void remove(Collection collection, String path) {
         getMap(collection).remove(path);
     }
@@ -108,14 +94,6 @@ public class MemoryDocumentStore impleme
         }
     }
 
-    /**
-     * Create or update a document. For MongoDb, this is using "findAndModify" with
-     * the "upsert" flag (insert or update).
-     *
-     * @param collection the collection
-     * @param update the update operation
-     * @return the old document, or null if there was no
-     */
     public Map<String, Object> createOrUpdate(Collection collection, UpdateOp update) {
         ConcurrentSkipListMap<String, Map<String, Object>> map = getMap(collection);
         Map<String, Object> n;
@@ -147,7 +125,7 @@ public class MemoryDocumentStore impleme
         synchronized (n) {
             applyChanges(n, update);
         }
-        return oldNode;
+        return n;
     }
     
     public static void applyChanges(Map<String, Object> target, UpdateOp update) {

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java Tue Feb 26 09:10:06 2013
@@ -42,7 +42,7 @@ public class MongoDocumentStore implemen
 
     public static final String KEY_PATH = "_id";
     
-    private static final boolean LOG = true;
+    private static final boolean LOG = false;
     private static final boolean LOG_TIME = true;
 
     private final DBCollection nodesCollection;

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java Tue Feb 26 09:10:06 2013
@@ -18,10 +18,12 @@ package org.apache.jackrabbit.mongomk.pr
 
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.annotation.Nonnull;
@@ -82,13 +84,19 @@ public class MongoMK implements MicroKer
      * Key: path@rev
      * Value: node
      */
-    // TODO: should be path@id
     private final Map<String, Node> nodeCache = new Cache<String, Node>(1024);
     
     /**
+     * Child node cache.
+     */
+    private Cache<String, Node.Children> nodeChildrenCache =
+            new Cache<String, Node.Children>(1024);
+
+    /**
      * The unsaved write count increments.
      */
     private final Map<String, Long> writeCountIncrements = new HashMap<String, Long>();
+    
     /**
      * The set of known valid revision.
      * The key is the revision id, the value is 1 (because a cache can't be a set).
@@ -104,9 +112,6 @@ public class MongoMK implements MicroKer
     
     private final Map<String, String> branchCommits = new HashMap<String, String>();
 
-    private Cache<String, Node.Children> nodeChildrenCache =
-            new Cache<String, Node.Children>(1024);
-
     /**
      * Create a new in-memory MongoMK used for testing.
      */
@@ -146,10 +151,10 @@ public class MongoMK implements MicroKer
         Node n = readNode("/", headRevision);
         if (n == null) {
             // root node is missing: repository is not initialized
-            Commit commit = new Commit(headRevision);
+            Commit commit = new Commit(this, headRevision);
             n = new Node("/", headRevision);
             commit.addNode(n);
-            commit.apply(store);
+            commit.applyToDocumentStore();
         }
     }
     
@@ -349,7 +354,7 @@ public class MongoMK implements MicroKer
         }
         JsopStream json = new JsopStream();
         boolean includeId = filter != null && filter.contains(":id");
-        includeId = filter != null && filter.contains(":hash");
+        includeId |= filter != null && filter.contains(":hash");
         json.object();
         n.append(json, includeId);
         Children c = readChildren(path, n.getId(), rev, maxChildNodes);
@@ -372,7 +377,7 @@ public class MongoMK implements MicroKer
         revisionId = revisionId == null ? headRevision.toString() : revisionId;
         JsopReader t = new JsopTokenizer(json);
         Revision rev = Revision.newRevision(clusterId);
-        Commit commit = new Commit(rev);
+        Commit commit = new Commit(this, rev);
         while (true) {
             int r = t.read();
             if (r == JsopReader.END) {
@@ -387,7 +392,8 @@ public class MongoMK implements MicroKer
                 break;
             case '-':
                 commit.removeNode(path);
-                markAsDeleted(path, commit,true);
+                markAsDeleted(path, commit, true);
+                commit.removeNodeDiff(path);
                 break;
             case '^':
                 t.read(':');
@@ -397,13 +403,12 @@ public class MongoMK implements MicroKer
                     commit.getDiff().tag('^').key(path).value(null);
                 } else {
                     value = t.readRawValue().trim();
-                    commit.getDiff().tag('^').key(path).value(null);
+                    commit.getDiff().tag('^').key(path).value(value);
                 }
                 String p = PathUtils.getParentPath(path);
                 String propertyName = PathUtils.getName(path);
-                UpdateOp op = commit.getUpdateOperationForNode(p);
-                op.addMapEntry(propertyName, rev.toString(), value);
-                op.increment("_writeCount", 1);
+                commit.updateProperty(p, propertyName, value);
+                commit.updatePropertyDiff(p, propertyName, value);
                 break;
             case '>': {
                 t.read(':');
@@ -447,52 +452,56 @@ public class MongoMK implements MicroKer
     }
 
     private void moveNode(String sourcePath, String targetPath, Commit commit) {
-        //TODO Optimize - Move logic would not work well with very move of very large subtrees
-        //At minimum we can optimize by traversing breadth wise and collect node id
-        //and fetch them via '$in' queries
-
-        //TODO Transient Node - Current logic does not account for operations which are part
-        //of this commit i.e. transient nodes. If its required it would need to be looked
-        //into
+        // TODO Optimize - Move logic would not work well with very move of very large subtrees
+        // At minimum we can optimize by traversing breadth wise and collect node id
+        // and fetch them via '$in' queries
+
+        // TODO Transient Node - Current logic does not account for operations which are part
+        // of this commit i.e. transient nodes. If its required it would need to be looked
+        // into
 
         Node n = getNode(sourcePath, commit.getRevision());
 
-        //Node might be deleted already
-        if(n == null){
+        // Node might be deleted already
+        if (n == null) {
             return;
         }
 
-        Node newNode = new Node(targetPath,commit.getRevision());
+        Node newNode = new Node(targetPath, commit.getRevision());
         n.copyTo(newNode);
 
         commit.addNode(newNode);
-        markAsDeleted(sourcePath,commit,false);
+        markAsDeleted(sourcePath, commit, false);
         Node.Children c = readChildren(sourcePath, n.getId(),
                 commit.getRevision(), Integer.MAX_VALUE);
         for (String srcChildPath : c.children) {
             String childName = PathUtils.getName(srcChildPath);
             String destChildPath = PathUtils.concat(targetPath, childName);
-            moveNode(srcChildPath,destChildPath,commit);
+            moveNode(srcChildPath, destChildPath, commit);
         }
     }
 
     private void markAsDeleted(String path, Commit commit, boolean subTreeAlso) {
         Revision rev = commit.getRevision();
-        UpdateOp op = commit.getUpdateOperationForNode(path);
-        op.addMapEntry("_deleted", rev.toString(), "true");
-        op.increment("_writeCount", 1);
-
-        if(subTreeAlso){
-            // TODO Would cause issue with large number of children.
-            // Need to be changed
+        commit.removeNode(path);
+
+        if (subTreeAlso) {
+
+            // recurse down the tree
+            // TODO causes issue with large number of children
             Node n = getNode(path, rev);
-            Node.Children c = readChildren(path, n.getId(), rev, Integer.MAX_VALUE);
+
+            // remove from the cache
+            nodeCache.remove(path + "@" + rev);
+
+            Node.Children c = readChildren(path, n.getId(), rev,
+                    Integer.MAX_VALUE);
             for (String childPath : c.children) {
-                markAsDeleted(childPath, commit,true);
+                markAsDeleted(childPath, commit, true);
             }
         }
 
-        //Remove the node from the cache
+        // Remove the node from the cache
         nodeCache.remove(path + "@" + rev);
     }
 
@@ -521,8 +530,8 @@ public class MongoMK implements MicroKer
         if (commit.isEmpty()) {
             return;
         }
-        commit.apply(store);
-        commit.apply(this);
+        commit.applyToDocumentStore();
+        commit.applyToCache();
     }
     
     public static void parseAddNode(Commit commit, JsopReader t, String path) {
@@ -541,7 +550,8 @@ public class MongoMK implements MicroKer
             } while (t.matches(','));
             t.read('}');
         }
-        commit.addNodeWithDiff(n);
+        commit.addNode(n);
+        commit.addNodeDiff(n);
     }
 
     @Override
@@ -627,6 +637,10 @@ public class MongoMK implements MicroKer
         protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
             return size() > size;
         }
+        
+        public String toString() {
+            return super.toString().replace(',', '\n');
+        }
 
     }
 
@@ -657,10 +671,41 @@ public class MongoMK implements MicroKer
         }
     }
 
-    public void incrementWriteCount(String path) {
-        Long value = writeCountIncrements.get(path);
-        value = value == null ? 1 : value + 1;
-        writeCountIncrements.put(path, value);
+    public void applyChanges(Revision rev, String path, 
+            boolean isNew, boolean isWritten, 
+            long oldWriteCount, long writeCountInc,
+            ArrayList<String> added, ArrayList<String> removed) {
+        if (!isWritten) {
+            if (writeCountInc == 0) {
+                writeCountIncrements.remove(path);
+            } else {
+                writeCountIncrements.put(path, writeCountInc);
+            }
+        } else {
+            writeCountIncrements.remove(path);
+        }
+        long newWriteCount = oldWriteCount + writeCountInc;
+        Children c = nodeChildrenCache.get(path + "@" + (newWriteCount - 1));
+        if (isNew || c != null) {
+            String id = path + "@" + newWriteCount;
+            Children c2 = new Children(path, id, rev);
+            TreeSet<String> set = new TreeSet<String>();
+            if (c != null) {
+                set.addAll(c.children);
+            }
+            set.removeAll(removed);
+            set.addAll(added);
+            c2.children.addAll(set);
+            if (nodeChildrenCache.get(id) != null) {
+                throw new AssertionError("New child list already cached");
+            }
+            nodeChildrenCache.put(id, c2);
+        }
+    }
+
+    public long getWriteCountIncrement(String path) {
+        Long x = writeCountIncrements.get(path);
+        return x == null ? 0 : x;
     }
 
 }

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/Node.java Tue Feb 26 09:10:06 2013
@@ -44,10 +44,10 @@ public class Node {
         return properties.get(propertyName);
     }
 
-    public void copyTo(Node newNode){
-        for(Map.Entry<String,String> e : properties.entrySet()){
-            if(!filter(e.getKey())){
-                newNode.setProperty(e.getKey(),e.getValue());
+    public void copyTo(Node newNode) {
+        for (Map.Entry<String, String> e : properties.entrySet()) {
+            if (!filter(e.getKey())) {
+                newNode.setProperty(e.getKey(), e.getValue());
             }
         }
     }
@@ -56,6 +56,7 @@ public class Node {
         StringBuilder buff = new StringBuilder();
         buff.append("path: ").append(path).append('\n');
         buff.append("rev: ").append(rev).append('\n');
+        buff.append("writeCount: ").append(writeCount).append('\n');
         buff.append(properties);
         buff.append('\n');
         return buff.toString();
@@ -66,7 +67,7 @@ public class Node {
      */
     UpdateOp asOperation(boolean isNew) {
         String id = convertPathToDocumentId(path);
-        UpdateOp op = new UpdateOp(id, isNew);
+        UpdateOp op = new UpdateOp(path, id, isNew);
         op.set("_id", id);
         if (!isNew) {
             op.increment("_changeCount", 1L);

Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/UpdateOp.java Tue Feb 26 09:10:06 2013
@@ -24,6 +24,8 @@ import java.util.TreeMap;
  */
 public class UpdateOp {
     
+    final String path;
+    
     final String key;
     
     final boolean isNew;
@@ -34,15 +36,21 @@ public class UpdateOp {
      * Create an update operation for the given document. The commit root is assumed
      * to be the path, unless this is changed later on.
      * 
+     * @param path the node path (for nodes)
      * @param key the primary key
      * @param isNew whether this is a new document
      * @param rev the revision
      */
-    UpdateOp(String key, boolean isNew) {
+    UpdateOp(String path, String key, boolean isNew) {
+        this.path = path;
         this.key = key;
         this.isNew = isNew;
     }
     
+    String getPath() {
+        return path;
+    }
+    
     boolean isNew() {
         return isNew;
     }
@@ -87,6 +95,17 @@ public class UpdateOp {
         changes.put(property, op);
     }
     
+    public Long getIncrement(String property) {
+        Operation op = changes.get(property);
+        if (op == null) {
+            return null;
+        }
+        if (op.type != Operation.Type.INCREMENT) {
+            throw new IllegalArgumentException("Not an increment operation");
+        }
+        return (Long) op.value;
+    }
+    
     public String toString() {
         return "key: " + key + " " + (isNew ? "new" : "update") + " " + changes;
     }

Modified: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStoreTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStoreTest.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStoreTest.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStoreTest.java Tue Feb 26 09:10:06 2013
@@ -63,7 +63,7 @@ public class MongoDocumentStoreTest {
         dropCollections();
         DocumentStore docStore = openDocumentStore();
 
-        UpdateOp updateOp = new UpdateOp("/", true);
+        UpdateOp updateOp = new UpdateOp("/", "/", true);
         updateOp.addMapEntry("property1", "key1", "value1");
         updateOp.increment("property2", 1);
         updateOp.set("property3", "value3");
@@ -94,7 +94,8 @@ public class MongoDocumentStoreTest {
         int nUpdates = 10;
         List<UpdateOp> updateOps = new ArrayList<UpdateOp>();
         for (int i = 0; i < nUpdates; i++) {
-            UpdateOp updateOp = new UpdateOp("/node" + i, true);
+            String path = "/node" + i;
+            UpdateOp updateOp = new UpdateOp(path, path, true);
             updateOp.set(MongoDocumentStore.KEY_PATH, "/node" + i);
             updateOp.addMapEntry("property1", "key1", "value1");
             updateOp.increment("property2", 1);
@@ -209,7 +210,8 @@ public class MongoDocumentStoreTest {
 
         private void addNodes() {
             for (int i = 0; i < nNodes; i++) {
-                UpdateOp updateOp = new UpdateOp("/" + nodeName + i, true);
+                String path = "/" + nodeName + i;
+                UpdateOp updateOp = new UpdateOp(path, path, true);
                 updateOp.addMapEntry("property1", "key1", "value1");
                 updateOp.set("property3", "value3");
                 docStore.createOrUpdate(Collection.NODES, updateOp);
@@ -218,7 +220,8 @@ public class MongoDocumentStoreTest {
 
         private void updateNodes() {
             for (int i = 0; i < nNodes; i++) {
-                UpdateOp updateOp = new UpdateOp("/" + nodeName + i, false);
+                String path = "/" + nodeName + i;
+                UpdateOp updateOp = new UpdateOp(path, path, false);
                 updateOp.addMapEntry("property1", "key2", "value2");
                 updateOp.set("property4", "value4");
                 docStore.createOrUpdate(Collection.NODES, updateOp);

Modified: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/SimpleTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/SimpleTest.java?rev=1450089&r1=1450088&r2=1450089&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/SimpleTest.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/SimpleTest.java Tue Feb 26 09:10:06 2013
@@ -33,8 +33,8 @@ import com.mongodb.DB;
  */
 public class SimpleTest {
     
-//    private static final boolean MONGO_DB = true;
     private static final boolean MONGO_DB = false;
+//    private static final boolean MONGO_DB = true;
 
     @Test
     public void test() {
@@ -94,6 +94,57 @@ public class SimpleTest {
         // System.out.println(test);
         mk.dispose();
     }
+    
+    @Test
+    public void cache() {
+        MongoMK mk = createMK();
+        
+        // BAD
+        String rev = mk.commit("/", "+\"testRoot\":{} +\"index\":{}", null, null);
+        
+        // GOOD
+//        String rev = mk.commit("/", "+\"testRoot\":{} ", null, null);
+        
+        String test = mk.getNodes("/", rev, 0, 0, Integer.MAX_VALUE, ":id");
+        // System.out.println("  " + test);
+//        test = mk.getNodes("/testRoot", rev, 0, 0, Integer.MAX_VALUE, ":id");
+//        System.out.println("  " + test);
+        rev = mk.commit("/testRoot", "+\"a\":{}", null, null);
+//        test = mk.getNodes("/testRoot", rev, 0, 0, Integer.MAX_VALUE, ":id");
+//        System.out.println("  " + test);
+//        rev = mk.commit("/testRoot/a", "+\"b\":{}", null, null);
+//        rev = mk.commit("/testRoot/a/b", "+\"c\":{} +\"d\":{}", null, null);
+//        test = mk.getNodes("/testRoot", rev, 0, 0, Integer.MAX_VALUE, ":id");
+//        System.out.println("  " + test);
+//        test = mk.getNodes("/", rev, 0, 0, Integer.MAX_VALUE, ":id");
+//        System.out.println("  " + test);
+//        rev = mk.commit("/index", "+\"a\":{}", null, null);
+        test = mk.getNodes("/", rev, 0, 0, Integer.MAX_VALUE, ":id");
+        // System.out.println("  " + test);
+//        test = mk.getNodes("/testRoot", rev, 0, 0, Integer.MAX_VALUE, ":id");
+//        System.out.println("  " + test);
+        
+//        assertEquals("{\"name\":\"Hello\",\":childNodeCount\":0}", test);
+//        
+//        rev = mk.commit("/test", "+\"a\":{\"name\": \"World\"}", null, null);
+//        rev = mk.commit("/test", "+\"b\":{\"name\": \"!\"}", null, null);
+//        test = mk.getNodes("/test", rev, 0, 0, Integer.MAX_VALUE, null);
+//        Children c;
+//        c = mk.readChildren("/", "1",
+//                Revision.fromString(rev), Integer.MAX_VALUE);
+//        assertEquals("/: [/test]", c.toString());
+//        c = mk.readChildren("/test", "2",
+//                Revision.fromString(rev), Integer.MAX_VALUE);
+//        assertEquals("/test: [/test/a, /test/b]", c.toString());
+//
+//        rev = mk.commit("", "^\"/test\":1", null, null);
+//        test = mk.getNodes("/", rev, 0, 0, Integer.MAX_VALUE, null);
+//        assertEquals("{\"test\":1,\"test\":{},\":childNodeCount\":1}", test);
+
+        // System.out.println(test);
+        mk.dispose();
+    }
+
 
     @Test
     public void testDeletion() {