Author: mreutegg
Date: Wed Mar 19 20:09:59 2014
New Revision: 1579378

URL: http://svn.apache.org/r1579378
Log:
OAK-1555: Inefficient node state diff with old revisions

Added:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MongoDiffCache.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java?rev=1579378&r1=1579377&r2=1579378&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java
 Wed Mar 19 20:09:59 2014
@@ -569,6 +569,7 @@ public class Commit {
             }
             list.add(p);
         }
+        DiffCache.Entry cacheEntry = nodeStore.getDiffCache().newEntry(before, 
revision);
         List<String> added = new ArrayList<String>();
         List<String> removed = new ArrayList<String>();
         List<String> changed = new ArrayList<String>();
@@ -592,10 +593,10 @@ public class Commit {
             boolean isNew = op != null && op.isNew();
             boolean pendingLastRev = op == null
                     || !NodeDocument.hasLastRev(op, revision.getClusterId());
-            boolean isDelete = op != null && op.isDelete();
-            nodeStore.applyChanges(revision, before, path, isNew, isDelete,
-                    pendingLastRev, isBranchCommit, added, removed, changed);
+            nodeStore.applyChanges(revision, path, isNew, pendingLastRev,
+                    isBranchCommit, added, removed, changed, cacheEntry);
         }
+        cacheEntry.done();
     }
 
     public void moveNode(String sourcePath, String targetPath) {

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java?rev=1579378&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java
 Wed Mar 19 20:09:59 2014
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * A cache for child node diffs.
+ */
+public interface DiffCache {
+
+    /**
+     * Returns a jsop diff for the child nodes at the given path. The returned
+     * String may contain the following changes on child nodes:
+     * <ul>
+     *     <li>Changed child nodes: e.g. {@code ^"foo":{}}</li>
+     *     <li>Added child nodes: e.g. {@code +"bar":{}}</li>
+     *     <li>Removed child nodes: e.g. {@code -"baz"}</li>
+     * </ul>
+     * A {@code null} value indicates that this cache does not have an entry
+     * for the given revision range at the path.
+     *
+     * @param from the from revision.
+     * @param to the to revision.
+     * @param path the path of the parent node.
+     * @return the diff or {@code null} if unknown.
+     */
+    @CheckForNull
+    public String getChanges(@Nonnull Revision from,
+                             @Nonnull Revision to,
+                             @Nonnull String path);
+
+    /**
+     * Starts a new cache entry for the diff cache. Actual changes are added
+     * to the entry with the {@link Entry#append(String, String)} method.
+     *
+     * @param from the from revision.
+     * @param to the to revision.
+     * @return the cache entry.
+     */
+    @Nonnull
+    public Entry newEntry(@Nonnull Revision from,
+                          @Nonnull Revision to);
+
+    public interface Entry {
+
+        /**
+         * Appends changes about children of the node at the given path.
+         *
+         * @param path the path of the parent node.
+         * @param changes the child node changes.
+         */
+        public void append(@Nonnull String path,
+                           @Nonnull String changes);
+
+        /**
+         * Called when all changes have been appended and the entry is ready
+         * to be used by the cache.
+         */
+        public void done();
+    }
+}

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

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java?rev=1579378&r1=1579377&r2=1579378&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
 Wed Mar 19 20:09:59 2014
@@ -445,6 +445,7 @@ public class DocumentMK implements Micro
         private static final long DEFAULT_MEMORY_CACHE_SIZE = 256 * 1024 * 
1024;
         private DocumentNodeStore nodeStore;
         private DocumentStore documentStore;
+        private DiffCache diffCache;
         private BlobStore blobStore;
         private int clusterId  = 
Integer.getInteger("oak.documentMK.clusterId", 0);
         private int asyncDelay = 1000;
@@ -479,6 +480,10 @@ public class DocumentMK implements Micro
                 if (this.blobStore == null) {
                     this.blobStore = new MongoBlobStore(db);
                 }
+
+                if (this.diffCache == null) {
+                    this.diffCache = new MongoDiffCache(db, this);
+                }
             }
             return this;
         }
@@ -581,6 +586,18 @@ public class DocumentMK implements Micro
             return nodeStore;
         }
 
+        public DiffCache getDiffCache() {
+            if (diffCache == null) {
+                diffCache = new MemoryDiffCache(this);
+            }
+            return diffCache;
+        }
+
+        public Builder setDiffCache(DiffCache diffCache) {
+            this.diffCache = diffCache;
+            return this;
+        }
+
         /**
          * Set the blob store to use. By default an in-memory store is used.
          *
@@ -643,7 +660,7 @@ public class DocumentMK implements Micro
         public Builder memoryCacheSize(long memoryCacheSize) {
             this.nodeCacheSize = memoryCacheSize * 25 / 100;
             this.childrenCacheSize = memoryCacheSize * 10 / 100;
-            this.diffCacheSize = memoryCacheSize * 2 / 100;
+            this.diffCacheSize = memoryCacheSize * 5 / 100;
             this.docChildrenCacheSize = memoryCacheSize * 3 / 100;
             this.documentCacheSize = memoryCacheSize - nodeCacheSize - 
childrenCacheSize - diffCacheSize - docChildrenCacheSize;
             return this;

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java?rev=1579378&r1=1579377&r2=1579378&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
 Wed Mar 19 20:09:59 2014
@@ -52,7 +52,6 @@ import com.google.common.collect.Iterato
 import com.google.common.collect.Maps;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static 
org.apache.jackrabbit.oak.plugins.document.DocumentMK.MANY_CHILDREN_THRESHOLD;
 import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
 
 /**
@@ -241,8 +240,8 @@ class DocumentNodeState extends Abstract
                     if (lastRevision.equals(mBase.lastRevision)) {
                         // no differences
                         return true;
-                    } else if (getChildNodeCount(MANY_CHILDREN_THRESHOLD) > 
MANY_CHILDREN_THRESHOLD) {
-                        // use DocumentNodeStore compare when there are many 
children
+                    } else {
+                        // use DocumentNodeStore compare
                         return dispatch(store.diffChildren(this, mBase), 
mBase, diff);
                     }
                 }

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java?rev=1579378&r1=1579377&r2=1579378&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
 Wed Mar 19 20:09:59 2014
@@ -249,12 +249,9 @@ public final class DocumentNodeStore
     private final CacheStats docChildrenCacheStats;
 
     /**
-     * Diff cache.
-     *
-     * Key: PathRev, value: StringValue
+     * The change log to keep track of commits for diff operations.
      */
-    private final Cache<CacheValue, StringValue> diffCache;
-    private final CacheStats diffCacheStats;
+    private final DiffCache diffCache;
 
     /**
      * The blob store.
@@ -329,9 +326,7 @@ public final class DocumentNodeStore
         docChildrenCacheStats = new CacheStats(docChildrenCache, 
"Document-DocChildren",
                 builder.getWeigher(), builder.getDocChildrenCacheSize());
 
-        diffCache = builder.buildCache(builder.getDiffCacheSize());
-        diffCacheStats = new CacheStats(diffCache, "Document-Diff",
-                builder.getWeigher(), builder.getDiffCacheSize());
+        diffCache = builder.getDiffCache();
 
         // check if root node exists
         if (store.find(Collection.NODES, Utils.getIdFromPath("/")) == null) {
@@ -526,10 +521,6 @@ public final class DocumentNodeStore
         return docChildrenCacheStats;
     }
 
-    public CacheStats getDiffCacheStats() {
-        return diffCacheStats;
-    }
-
     public int getPendingWriteCount() {
         return unsavedLastRevisions.getPaths().size();
     }
@@ -808,10 +799,8 @@ public final class DocumentNodeStore
      * Apply the changes of a node to the cache.
      *
      * @param rev the commit revision
-     * @param before the revision right before the commit.
      * @param path the path
      * @param isNew whether this is a new node
-     * @param isDelete whether the node is deleted
      * @param pendingLastRev whether the node has a pending _lastRev to write
      * @param isBranchCommit whether this is from a branch commit
      * @param added the list of added child nodes
@@ -819,10 +808,11 @@ public final class DocumentNodeStore
      * @param changed the list of changed child nodes.
      *
      */
-    public void applyChanges(Revision rev, Revision before, String path,
-                             boolean isNew, boolean isDelete, boolean 
pendingLastRev,
+    public void applyChanges(Revision rev, String path,
+                             boolean isNew, boolean pendingLastRev,
                              boolean isBranchCommit, List<String> added,
-                             List<String> removed, List<String> changed) {
+                             List<String> removed, List<String> changed,
+                             DiffCache.Entry cacheEntry) {
         UnsavedModifications unsaved = unsavedLastRevisions;
         if (isBranchCommit) {
             Revision branchRev = rev.asBranchRevision();
@@ -841,21 +831,21 @@ public final class DocumentNodeStore
             }
             c.children.addAll(set);
             nodeChildrenCache.put(key, c);
-        } else if (!isDelete) {
-            // update diff cache for modified nodes
-            PathRev key = diffCacheKey(path, before, rev);
-            JsopWriter w = new JsopStream();
-            for (String p : added) {
-                
w.tag('+').key(PathUtils.getName(p)).object().endObject().newline();
-            }
-            for (String p : removed) {
-                w.tag('-').value(PathUtils.getName(p)).newline();
-            }
-            for (String p : changed) {
-                
w.tag('^').key(PathUtils.getName(p)).object().endObject().newline();
-            }
-            diffCache.put(key, new StringValue(w.toString()));
         }
+
+        // update diff cache
+        JsopWriter w = new JsopStream();
+        for (String p : added) {
+            
w.tag('+').key(PathUtils.getName(p)).object().endObject().newline();
+        }
+        for (String p : removed) {
+            w.tag('-').value(PathUtils.getName(p)).newline();
+        }
+        for (String p : changed) {
+            
w.tag('^').key(PathUtils.getName(p)).object().endObject().newline();
+        }
+        cacheEntry.append(path, w.toString());
+
         // update docChildrenCache
         if (!added.isEmpty()) {
             CacheValue docChildrenKey = new StringValue(path);
@@ -1110,23 +1100,16 @@ public final class DocumentNodeStore
      * @return the json diff.
      */
     String diffChildren(@Nonnull final DocumentNodeState node,
-                    @Nonnull final DocumentNodeState base) {
-        PathRev key = diffCacheKey(node.getPath(),
-                base.getLastRevision(), node.getLastRevision());
-        try {
-            return diffCache.get(key, new Callable<StringValue>() {
-                @Override
-                public StringValue call() throws Exception {
-                    return new StringValue(diffImpl(base, node));
-                }
-            }).toString();
-        } catch (ExecutionException e) {
-            if (e.getCause() instanceof MicroKernelException) {
-                throw (MicroKernelException) e.getCause();
-            } else {
-                throw new MicroKernelException(e.getCause());
-            }
+                        @Nonnull final DocumentNodeState base) {
+        if (node.hasNoChildren() && base.hasNoChildren()) {
+            return "";
+        }
+        String diff = diffCache.getChanges(base.getLastRevision(),
+                node.getLastRevision(), node.getPath());
+        if (diff == null) {
+            diff = diffImpl(base, node);
         }
+        return diff;
     }
 
     String diff(@Nonnull final String fromRevisionId,
@@ -1146,46 +1129,36 @@ public final class DocumentNodeStore
                     path, fromRev, from != null, toRev, to != null);
             throw new MicroKernelException(msg);
         }
-        PathRev key = diffCacheKey(path, fromRev, toRev);
-        try {
-            JsopWriter writer = new JsopStream();
-            diffProperties(from, to, writer);
-            String compactDiff = diffCache.get(key, new 
Callable<StringValue>() {
-                @Override
-                public StringValue call() throws Exception {
-                    return new StringValue(diffImpl(from, to));
+        String compactDiff = diffCache.getChanges(fromRev, toRev, path);
+        if (compactDiff == null) {
+            // calculate the diff
+            compactDiff = diffImpl(from, to);
+        }
+        JsopWriter writer = new JsopStream();
+        diffProperties(from, to, writer);
+        JsopTokenizer t = new JsopTokenizer(compactDiff);
+        int r;
+        do {
+            r = t.read();
+            switch (r) {
+                case '+':
+                case '^': {
+                    String name = t.readString();
+                    t.read(':');
+                    t.read('{');
+                    t.read('}');
+                    writer.tag((char) r).key(PathUtils.concat(path, name));
+                    writer.object().endObject().newline();
+                    break;
                 }
-            }).toString();
-            JsopTokenizer t = new JsopTokenizer(compactDiff);
-            int r;
-            do {
-                r = t.read();
-                switch (r) {
-                    case '+':
-                    case '^': {
-                        String name = t.readString();
-                        t.read(':');
-                        t.read('{');
-                        t.read('}');
-                        writer.tag((char) r).key(PathUtils.concat(path, name));
-                        writer.object().endObject().newline();
-                        break;
-                    }
-                    case '-': {
-                        String name = t.readString();
-                        writer.tag('-').value(PathUtils.concat(path, name));
-                        writer.newline();
-                    }
+                case '-': {
+                    String name = t.readString();
+                    writer.tag('-').value(PathUtils.concat(path, name));
+                    writer.newline();
                 }
-            } while (r != JsopReader.END);
-            return writer.toString();
-        } catch (ExecutionException e) {
-            if (e.getCause() instanceof MicroKernelException) {
-                throw (MicroKernelException) e.getCause();
-            } else {
-                throw new MicroKernelException(e.getCause());
             }
-        }
+        } while (r != JsopReader.END);
+        return writer.toString();
     }
 
     //------------------------< Observable 
>------------------------------------
@@ -1562,12 +1535,6 @@ public final class DocumentNodeStore
         return new PathRev((name == null ? "" : name) + path, readRevision);
     }
 
-    private static PathRev diffCacheKey(@Nonnull String path,
-                                        @Nonnull Revision from,
-                                        @Nonnull Revision to) {
-        return new PathRev(from + path, to);
-    }
-
     private static DocumentRootBuilder asDocumentRootBuilder(NodeBuilder 
builder)
             throws IllegalArgumentException {
         if (!(builder instanceof DocumentRootBuilder)) {
@@ -1661,4 +1628,7 @@ public final class DocumentNodeStore
         return new BlobReferenceIterator(this);
     }
 
+    public DiffCache getDiffCache() {
+        return diffCache;
+    }
 }

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java?rev=1579378&r1=1579377&r2=1579378&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
 Wed Mar 19 20:09:59 2014
@@ -261,16 +261,21 @@ public class DocumentNodeStoreService {
         registrations.add(
                 registerMBean(wb,
                         CacheStatsMBean.class,
-                        store.getDiffCacheStats(),
-                        CacheStatsMBean.TYPE,
-                        store.getDiffCacheStats().getName()));
-        registrations.add(
-                registerMBean(wb,
-                        CacheStatsMBean.class,
                         store.getDocChildrenCacheStats(),
                         CacheStatsMBean.TYPE,
                         store.getDocChildrenCacheStats().getName())
         );
+        DiffCache cl = store.getDiffCache();
+        if (cl instanceof MemoryDiffCache) {
+            MemoryDiffCache mcl = (MemoryDiffCache) cl;
+            registrations.add(
+                    registerMBean(wb,
+                            CacheStatsMBean.class,
+                            mcl.getDiffCacheStats(),
+                            CacheStatsMBean.TYPE,
+                            mcl.getDiffCacheStats().getName()));
+            
+        }
 
         DocumentStore ds = store.getDocumentStore();
         if (ds instanceof CachingDocumentStore) {

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java?rev=1579378&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java
 Wed Mar 19 20:09:59 2014
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.cache.CacheValue;
+import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
+
+import com.google.common.cache.Cache;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * An in-memory diff cache implementation.
+ */
+class MemoryDiffCache implements DiffCache {
+
+    /**
+     * Diff cache.
+     *
+     * Key: PathRev, value: StringValue
+     */
+    protected final Cache<CacheValue, StringValue> diffCache;
+    protected final CacheStats diffCacheStats;
+
+
+    MemoryDiffCache(DocumentMK.Builder builder) {
+        diffCache = builder.buildCache(builder.getDiffCacheSize());
+        diffCacheStats = new CacheStats(diffCache, "Document-Diff",
+                builder.getWeigher(), builder.getDiffCacheSize());
+    }
+
+    @CheckForNull
+    @Override
+    public String getChanges(@Nonnull Revision from,
+                             @Nonnull Revision to,
+                             @Nonnull String path) {
+        PathRev key = diffCacheKey(path, from, to);
+        StringValue diff = diffCache.getIfPresent(key);
+        return diff != null ? diff.toString() : null;
+    }
+
+    @Nonnull
+    @Override
+    public Entry newEntry(@Nonnull Revision from,
+                          @Nonnull Revision to) {
+        return new MemoryEntry(from, to);
+    }
+
+    public CacheStats getDiffCacheStats() {
+        return diffCacheStats;
+    }
+
+    protected class MemoryEntry implements Entry {
+
+        private final Revision from;
+        private final Revision to;
+
+        protected MemoryEntry(Revision from, Revision to) {
+            this.from = checkNotNull(from);
+            this.to = checkNotNull(to);
+        }
+
+        @Override
+        public void append(@Nonnull String path, @Nonnull String changes) {
+            PathRev key = diffCacheKey(path, from, to);
+            diffCache.put(key, new StringValue(changes));
+        }
+
+        @Override
+        public void done() {
+        }
+    }
+
+    private static PathRev diffCacheKey(@Nonnull String path,
+                                        @Nonnull Revision from,
+                                        @Nonnull Revision to) {
+        return new PathRev(from + path, to);
+    }
+
+}

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

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MongoDiffCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MongoDiffCache.java?rev=1579378&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MongoDiffCache.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MongoDiffCache.java
 Wed Mar 19 20:09:59 2014
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.BasicDBObjectBuilder;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBObject;
+import com.mongodb.MongoException;
+import com.mongodb.WriteConcern;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A diff cache implementation using a capped collection as a secondary cache.
+ */
+public class MongoDiffCache extends MemoryDiffCache {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(MongoDiffCache.class);
+
+    private static final long MB = 1024 * 1024;
+
+    private static final String COLLECTION_NAME = "changeLog";
+
+    // TODO: make configurable
+    private static final DBObject COLLECTION_OPTIONS = 
BasicDBObjectBuilder.start()
+            .add("capped", true).add("size", 256 * MB).get();
+
+    private final DBCollection changes;
+
+    public MongoDiffCache(DB db, DocumentMK.Builder builder) {
+        super(builder);
+        if (db.collectionExists(COLLECTION_NAME)) {
+            changes = db.getCollection(COLLECTION_NAME);
+        } else {
+            changes = db.createCollection(COLLECTION_NAME, COLLECTION_OPTIONS);
+        }
+    }
+
+    @CheckForNull
+    @Override
+    public String getChanges(@Nonnull Revision from,
+                             @Nonnull Revision to,
+                             @Nonnull String path) {
+        // first try to serve from cache
+        String diff = super.getChanges(from, to, path);
+        if (diff != null) {
+            return diff;
+        }
+        // grab from mongo
+        DBObject obj = changes.findOne(new BasicDBObject("_id", 
to.toString()));
+        if (obj == null) {
+            return null;
+        }
+        if (obj.get("_b").equals(from.toString())) {
+            // apply to diff cache and serve later requests from cache
+            Entry entry = super.newEntry(from, to);
+            applyToDiffCache(obj, "/", entry);
+            entry.done();
+
+            DBObject current = obj;
+            for (String name : PathUtils.elements(path)) {
+                String n = Utils.unescapePropertyName(name);
+                current = (DBObject) obj.get(n);
+                if (current == null) {
+                    break;
+                }
+            }
+            if (current == null || !current.containsField("_c")) {
+                // no changes here
+                return "";
+            } else {
+                return current.get("_c").toString();
+            }
+        }
+        // diff request goes across multiple commits
+        // TODO: implement
+        return null;
+    }
+
+    @Nonnull
+    @Override
+    public Entry newEntry(@Nonnull final Revision from,
+                          @Nonnull final Revision to) {
+        return new MemoryEntry(from, to) {
+
+            private BasicDBObject commit = new BasicDBObject();
+
+            {
+                commit.put("_id", to.toString());
+                commit.put("_b", from.toString());
+            }
+
+            @Override
+            public void append(@Nonnull String path, @Nonnull String changes) {
+                // super.append() will apply to diff cache in base class
+                super.append(path, changes);
+                BasicDBObject current = commit;
+                for (String name : PathUtils.elements(path)) {
+                    String escName = Utils.escapePropertyName(name);
+                    if (current.containsField(escName)) {
+                        current = (BasicDBObject) current.get(escName);
+                    } else {
+                        BasicDBObject child = new BasicDBObject();
+                        current.append(escName, child);
+                        current = child;
+                    }
+                }
+                current.append("_c", checkNotNull(changes));
+            }
+
+            @Override
+            public void done() {
+                try {
+                    changes.insert(commit, WriteConcern.UNACKNOWLEDGED);
+                } catch (MongoException e) {
+                    LOG.warn("Write back of diff cache entry failed", e);
+                }
+            }
+        };
+    }
+
+    private void applyToDiffCache(DBObject obj,
+                                  String path,
+                                  Entry entry) {
+        String diff = (String) obj.get("_c");
+        if (diff != null) {
+            entry.append(path, diff);
+        }
+        for (String k : obj.keySet()) {
+            if (Utils.isPropertyName(k)) {
+                String name = Utils.unescapePropertyName(k);
+                applyToDiffCache((DBObject) obj.get(k),
+                        PathUtils.concat(path, name), entry);
+            }
+        }
+    }
+}

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


Reply via email to