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 ch...@apache.org on 2015/04/09 11:48:45 UTC

svn commit: r1672277 - in /jackrabbit/oak/trunk: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ oak-core/src/main/java/org/apache/jackrabbit/oak/plug...

Author: chetanm
Date: Thu Apr  9 09:48:44 2015
New Revision: 1672277

URL: http://svn.apache.org/r1672277
Log:
OAK-2669 - Use Consolidated diff for local changes with persistent cache to avoid calculating diff again

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.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/DocumentNodeStore.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.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=1672277&r1=1672276&r2=1672277&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 Thu Apr  9 09:48:44 2015
@@ -569,7 +569,7 @@ public class Commit {
             }
             list.add(p);
         }
-        DiffCache.Entry cacheEntry = nodeStore.getDiffCache().newEntry(before, revision);
+        DiffCache.Entry cacheEntry = nodeStore.getLocalDiffCache().newEntry(before, revision);
         LastRevTracker tracker = nodeStore.createTracker(revision, isBranchCommit);
         List<String> added = new ArrayList<String>();
         List<String> removed = new ArrayList<String>();

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=1672277&r1=1672276&r2=1672277&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 Thu Apr  9 09:48:44 2015
@@ -499,11 +499,13 @@ public class DocumentMK implements Micro
         private DocumentNodeStore nodeStore;
         private DocumentStore documentStore;
         private DiffCache diffCache;
+        private LocalDiffCache localDiffCache;
         private BlobStore blobStore;
         private int clusterId  = Integer.getInteger("oak.documentMK.clusterId", 0);
         private int asyncDelay = 1000;
         private boolean timing;
         private boolean logging;
+        private boolean disableLocalDiffCache = Boolean.getBoolean("oak.documentMK.disableLocalDiffCache");
         private Weigher<CacheValue, CacheValue> weigher = new EmpiricalWeigher();
         private long memoryCacheSize = DEFAULT_MEMORY_CACHE_SIZE;
         private int nodeCachePercentage = DEFAULT_NODE_CACHE_PERCENTAGE;
@@ -669,6 +671,18 @@ public class DocumentMK implements Micro
             return diffCache;
         }
 
+        public LocalDiffCache getLocalDiffCache() {
+            if (localDiffCache == null && !disableLocalDiffCache) {
+                localDiffCache = new LocalDiffCache(this);
+            }
+            return localDiffCache;
+        }
+
+        public Builder setDisableLocalDiffCache(boolean disableLocalDiffCache) {
+            this.disableLocalDiffCache = disableLocalDiffCache;
+            return this;
+        }
+
         public Builder setDiffCache(DiffCache diffCache) {
             this.diffCache = diffCache;
             return this;
@@ -880,6 +894,10 @@ public class DocumentMK implements Micro
             return buildCache(CacheType.DIFF, getDiffCacheSize(), null, null);
         }
 
+        public Cache<StringValue, LocalDiffCache.ConsolidatedDiff> buildConsolidatedDiffCache() {
+            return buildCache(CacheType.CONSOLIDATED_DIFF, getDiffCacheSize(), null, null);
+        }
+
         public Cache<CacheValue, NodeDocument> buildDocumentCache(DocumentStore docStore) {
             return buildCache(CacheType.DOCUMENT, getDocumentCacheSize(), null, docStore);
         }

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=1672277&r1=1672276&r2=1672277&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 Thu Apr  9 09:48:44 2015
@@ -81,6 +81,8 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.document.mongo.MongoBlobReferenceIterator;
 import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore;
 import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
+import org.apache.jackrabbit.oak.plugins.observation.ContentChangeInfo;
+import org.apache.jackrabbit.oak.plugins.observation.ContentChangeInfoProvider;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
 import org.apache.jackrabbit.oak.commons.json.JsopStream;
 import org.apache.jackrabbit.oak.commons.json.JsopWriter;
@@ -312,6 +314,8 @@ public final class DocumentNodeStore
      */
     private final DiffCache diffCache;
 
+    private final LocalDiffCache localDiffCache;
+
     /**
      * The blob store.
      */
@@ -418,6 +422,7 @@ public final class DocumentNodeStore
                 builder.getWeigher(), builder.getDocChildrenCacheSize());
 
         diffCache = builder.getDiffCache();
+        localDiffCache = builder.getLocalDiffCache();
         checkpoints = new Checkpoints(this);
 
         // check if root node exists
@@ -1324,33 +1329,62 @@ public final class DocumentNodeStore
      */
     boolean compare(@Nonnull final DocumentNodeState node,
                     @Nonnull final DocumentNodeState base,
-                    @Nonnull final NodeStateDiff diff) {
+                    @Nonnull NodeStateDiff diff) {
         if (!AbstractNodeState.comparePropertiesAgainstBaseState(node, base, diff)) {
             return false;
         }
         if (node.hasNoChildren() && base.hasNoChildren()) {
             return true;
         }
+        String jsop = getJsopDiffIfLocalChange(diff, node);
+
         boolean useReadRevision = true;
-        // first lookup with read revisions of nodes and without loader
-        String jsop = diffCache.getChanges(base.getRevision(), 
-                node.getRevision(), node.getPath(), null);
         if (jsop == null) {
-            useReadRevision = false;
-            // fall back to last revisions with loader, this
-            // guarantees we get a diff
-            jsop = diffCache.getChanges(base.getLastRevision(),
-                    node.getLastRevision(), node.getPath(),
-                    new DiffCache.Loader() {
-                        @Override
-                        public String call() {
-                            return diffImpl(base, node);
-                        }
-                    });
+            // first lookup with read revisions of nodes and without loader
+            jsop = diffCache.getChanges(base.getRevision(),
+                    node.getRevision(), node.getPath(), null);
+            if (jsop == null) {
+                useReadRevision = false;
+                // fall back to last revisions with loader, this
+                // guarantees we get a diff
+                jsop = diffCache.getChanges(base.getLastRevision(),
+                        node.getLastRevision(), node.getPath(),
+                        new DiffCache.Loader() {
+                            @Override
+                            public String call() {
+                                return diffImpl(base, node);
+                            }
+                        });
+            }
         }
         return dispatch(jsop, node, base, diff, useReadRevision);
     }
 
+    @CheckForNull
+    private String getJsopDiffIfLocalChange(NodeStateDiff diff, DocumentNodeState nodeState) {
+        if (localDiffCache == null){
+            //Local diff cache support not enabled
+            return null;
+        }
+
+        if (diff instanceof ContentChangeInfoProvider){
+            ContentChangeInfo info = ((ContentChangeInfoProvider) diff).getChangeInfo();
+            if (info.isLocalChange() && info.getAfter() instanceof DocumentNodeState){
+                DocumentNodeState rootAfterState = (DocumentNodeState) info.getAfter();
+                DocumentNodeState rootBeforeState = (DocumentNodeState) info.getBefore();
+                String jsopDiff = localDiffCache.getChanges(rootBeforeState.getRevision(),
+                        rootAfterState.getRevision(),
+                        nodeState.getPath(),
+                        null);
+                if (jsopDiff != null){
+                    LOG.trace("Got diff from local cache for path {}", nodeState.getPath());
+                    return jsopDiff;
+                }
+            }
+        }
+        return null;
+    }
+
     String diff(@Nonnull final String fromRevisionId,
                 @Nonnull final String toRevisionId,
                 @Nonnull final String path) throws DocumentStoreException {
@@ -2455,6 +2489,13 @@ public final class DocumentNodeStore
         return diffCache;
     }
 
+    public DiffCache getLocalDiffCache(){
+        if (localDiffCache != null){
+            return localDiffCache;
+        }
+        return diffCache;
+    }
+
     public Clock getClock() {
         return clock;
     }

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=1672277&r1=1672276&r2=1672277&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 Thu Apr  9 09:48:44 2015
@@ -547,6 +547,17 @@ public class DocumentNodeStoreService {
                             mcl.getDiffCacheStats().getName()));
         }
 
+        DiffCache localCache = store.getLocalDiffCache();
+        if (localCache instanceof LocalDiffCache) {
+            LocalDiffCache mcl = (LocalDiffCache) localCache;
+            registrations.add(
+                    registerMBean(whiteboard,
+                            CacheStatsMBean.class,
+                            mcl.getDiffCacheStats(),
+                            CacheStatsMBean.TYPE,
+                            mcl.getDiffCacheStats().getName()));
+        }
+
         DocumentStore ds = store.getDocumentStore();
         if (ds.getCacheStats() != null) {
             registrations.add(

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java?rev=1672277&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java Thu Apr  9 09:48:44 2015
@@ -0,0 +1,178 @@
+/*
+ * 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 java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.cache.Cache;
+import com.google.common.collect.Maps;
+import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.cache.CacheValue;
+import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class LocalDiffCache implements DiffCache {
+    /**
+     * Limit is arbitrary for now i.e. 16 MB. Same as in MongoDiffCache
+     */
+    private static int MAX_ENTRY_SIZE = 16 * 1024 * 1024;
+    private static final String NO_DIFF = "";
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final Cache<StringValue, ConsolidatedDiff> diffCache;
+    private final CacheStats diffCacheStats;
+
+    public LocalDiffCache(DocumentMK.Builder builder) {
+        diffCache = builder.buildConsolidatedDiffCache();
+        diffCacheStats = new CacheStats(diffCache, "Document-Diff2",
+                builder.getWeigher(), builder.getDiffCacheSize());
+    }
+
+    @Override
+    public String getChanges(@Nonnull Revision from,
+                             @Nonnull Revision to,
+                             @Nonnull String path,
+                             @Nullable Loader loader) {
+        ConsolidatedDiff diff = diffCache.getIfPresent(new StringValue(to.toString()));
+        if (diff != null){
+            String result = diff.get(path);
+            if (result == null){
+                return NO_DIFF;
+            }
+            return result;
+        } else {
+            log.debug("Did not got the diff for local change in the cache for change {} => {} ", from, to);
+        }
+        return null;
+    }
+
+    ConsolidatedDiff getDiff(@Nonnull Revision from,
+                             @Nonnull Revision to){
+        return diffCache.getIfPresent(new StringValue(to.toString()));
+    }
+
+    @Nonnull
+    @Override
+    public Entry newEntry(@Nonnull Revision from, final @Nonnull Revision to) {
+        return new Entry() {
+            private final Map<String, String> changesPerPath = Maps.newHashMap();
+            private int size;
+            @Override
+            public void append(@Nonnull String path, @Nonnull String changes) {
+                if (exceedsSize()){
+                    return;
+                }
+                size += size(path) + size(changes);
+                changesPerPath.put(path, changes);
+            }
+
+            @Override
+            public boolean done() {
+                if (exceedsSize()){
+                    return false;
+                }
+                diffCache.put(new StringValue(to.toString()), new ConsolidatedDiff(changesPerPath, size));
+                return true;
+            }
+
+            private boolean exceedsSize(){
+                return size > MAX_ENTRY_SIZE;
+            }
+        };
+    }
+
+    public CacheStats getDiffCacheStats() {
+        return diffCacheStats;
+    }
+
+    public static final class ConsolidatedDiff implements CacheValue{
+        //TODO need to come up with better serialization strategy as changes are json themselves
+        //cannot use JSON. '/' and '*' are considered invalid chars so would not
+        //cause issue
+        static final Joiner.MapJoiner mapJoiner = Joiner.on("//").withKeyValueSeparator("**");
+        static final Splitter.MapSplitter splitter = Splitter.on("//").withKeyValueSeparator("**");
+        private final Map<String, String> changes;
+        private int memory;
+
+        public ConsolidatedDiff(Map<String, String> changes, int memory) {
+            this.changes = changes;
+            this.memory = memory;
+        }
+
+        public static ConsolidatedDiff fromString(String value){
+            return new ConsolidatedDiff(splitter.split(value), 0);
+        }
+
+        public String asString(){
+            return mapJoiner.join(changes);
+        }
+
+        @Override
+        public int getMemory() {
+            if (memory == 0) {
+                int m = 0;
+                for (Map.Entry<String, String> e : changes.entrySet()){
+                    m += size(e.getKey()) + size(e.getValue());
+                }
+                memory = m;
+            }
+            return memory;
+        }
+
+        @Override
+        public String toString() {
+            return changes.toString();
+        }
+
+        String get(String path) {
+            return changes.get(path);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ConsolidatedDiff that = (ConsolidatedDiff) o;
+
+            if (!changes.equals(that.changes)) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return changes.hashCode();
+        }
+    }
+
+    private static int size(String s){
+        //Taken from StringValue
+        return 16                           // shallow size
+                + 40 + s.length() * 2;
+    }
+
+}

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

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java?rev=1672277&r1=1672276&r2=1672277&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java Thu Apr  9 09:48:44 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.document.persistentCache;
 
+import org.apache.jackrabbit.oak.plugins.document.LocalDiffCache;
 import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState;
 import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
 import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
@@ -102,6 +103,32 @@ public enum CacheType {
             return (V) StringValue.fromString(value);
         }
     },
+
+    CONSOLIDATED_DIFF {
+        @Override
+        public <K> String keyToString(K key) {
+            return ((StringValue) key).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <K> K keyFromString(String key) {
+            return (K) StringValue.fromString(key);
+        }
+        @Override
+        public <K> int compareKeys(K a, K b) {
+            return ((StringValue) a).asString().compareTo(((StringValue) b).asString());
+        }
+        @Override
+        public <V> String valueToString(V value) {
+            return ((LocalDiffCache.ConsolidatedDiff) value).asString();
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public <V> V valueFromString(
+                DocumentNodeStore store, DocumentStore docStore, String value) {
+            return (V) LocalDiffCache.ConsolidatedDiff.fromString(value);
+        }
+    },
     
     DOC_CHILDREN {
         @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java?rev=1672277&r1=1672276&r2=1672277&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/PersistentCache.java Thu Apr  9 09:48:44 2015
@@ -281,6 +281,9 @@ public class PersistentCache {
         case DIFF:
             wrap = cacheDiff;
             break;
+        case CONSOLIDATED_DIFF:
+            wrap = cacheDiff;
+            break;
         case DOC_CHILDREN:
             wrap = cacheDocChildren;
             break;

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java?rev=1672277&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfo.java Thu Apr  9 09:48:44 2015
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.observation;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+public class ContentChangeInfo {
+    private final NodeState before;
+    private final NodeState after;
+    private final CommitInfo commitInfo;
+
+    public ContentChangeInfo(@Nonnull NodeState before,
+                             @Nonnull NodeState after,
+                             @Nullable CommitInfo commitInfo) {
+        this.before = before;
+        this.after = after;
+        this.commitInfo = commitInfo;
+    }
+
+    public NodeState getBefore() {
+        return before;
+    }
+
+    public NodeState getAfter() {
+        return after;
+    }
+
+    public boolean isLocalChange(){
+        return commitInfo != null;
+    }
+}

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

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java?rev=1672277&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ContentChangeInfoProvider.java Thu Apr  9 09:48:44 2015
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.observation;
+
+public interface ContentChangeInfoProvider {
+
+    ContentChangeInfo getChangeInfo();
+}

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

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java?rev=1672277&r1=1672276&r2=1672277&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/EventGenerator.java Thu Apr  9 09:48:44 2015
@@ -76,18 +76,25 @@ public class EventGenerator {
 
     private final LinkedList<Continuation> continuations = newLinkedList();
 
+    private final ContentChangeInfo changeInfo;
+
     /**
      * Creates a new generator instance. Changes to process need to be added
      * through {@link #addHandler(NodeState, NodeState, EventHandler)}
+     * @param changeInfo
      */
-    public EventGenerator() {}
+    public EventGenerator(ContentChangeInfo changeInfo) {
+        this.changeInfo = changeInfo;
+    }
 
     /**
      * Creates a new generator instance for processing the given changes.
      */
     public EventGenerator(
             @Nonnull NodeState before, @Nonnull NodeState after,
+            @Nonnull ContentChangeInfo changeInfo,
             @Nonnull EventHandler handler) {
+        this(changeInfo);
         continuations.addFirst(new Continuation(handler, before, after, 0));
     }
 
@@ -120,7 +127,7 @@ public class EventGenerator {
         }
     }
 
-    private class Continuation implements NodeStateDiff, Runnable {
+    private class Continuation implements NodeStateDiff, Runnable, ContentChangeInfoProvider {
 
         /**
          * Filtered handler of detected content changes.
@@ -176,6 +183,13 @@ public class EventGenerator {
             }
         }
 
+        //-------------------------------------------------< ContentChangeInfoProvider >--
+
+        @Override
+        public ContentChangeInfo getChangeInfo() {
+            return changeInfo;
+        }
+
         //-------------------------------------------------< NodeStateDiff >--
 
         @Override

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java?rev=1672277&r1=1672276&r2=1672277&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/NodeObserver.java Thu Apr  9 09:48:44 2015
@@ -157,7 +157,8 @@ public abstract class NodeObserver imple
                     handler = handler.getChildHandler(oakName, before, after);
                 }
 
-                EventGenerator generator = new EventGenerator(before, after, handler);
+                ContentChangeInfo changeInfo = new ContentChangeInfo(before, after, info);
+                EventGenerator generator = new EventGenerator(before, after, changeInfo, handler);
                 while (!generator.isDone()) {
                     generator.generate();
                 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java?rev=1672277&r1=1672276&r2=1672277&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMongoConnectionTest.java Thu Apr  9 09:48:44 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.document;
 
+import com.mongodb.DB;
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection;
 import org.apache.jackrabbit.oak.stats.Clock;
@@ -44,13 +45,17 @@ public abstract class AbstractMongoConne
         mongoConnection = MongoUtils.getConnection();
         MongoUtils.dropCollections(mongoConnection.getDB());
         Revision.setClock(getTestClock());
-        mk = new DocumentMK.Builder().clock(getTestClock()).setMongoDB(mongoConnection.getDB()).open();
+        mk = prepare(new DocumentMK.Builder().clock(getTestClock()),mongoConnection.getDB()).open();
     }
 
     protected Clock getTestClock() throws InterruptedException {
         return Clock.SIMPLE;
     }
 
+    protected DocumentMK.Builder prepare(DocumentMK.Builder builder, DB db){
+        return builder.setMongoDB(db);
+    }
+
     @After
     public void tearDownConnection() throws Exception {
         mk.dispose();

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java?rev=1672277&r1=1672276&r2=1672277&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreDiffTest.java Thu Apr  9 09:48:44 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.document;
 
+import com.mongodb.DB;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.cache.CacheStats;
 import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
@@ -72,6 +73,12 @@ public class DocumentNodeStoreDiffTest e
     }
 
     @Override
+    protected DocumentMK.Builder prepare(DocumentMK.Builder builder, DB db) {
+        builder.setDisableLocalDiffCache(true);
+        return super.prepare(builder, db);
+    }
+
+    @Override
     protected Clock getTestClock() throws InterruptedException {
         return new Clock.Virtual();
     }

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java?rev=1672277&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java Thu Apr  9 09:48:44 2015
@@ -0,0 +1,170 @@
+/*
+ * 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 java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.plugins.document.LocalDiffCache.ConsolidatedDiff;
+import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.apache.jackrabbit.oak.plugins.observation.NodeObserver;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.junit.Test;
+
+import static com.google.common.collect.Maps.newHashMap;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class LocalDiffCacheTest {
+
+    DocumentNodeStore store;
+
+    @Test
+    public void simpleDiff() throws Exception{
+        TestNodeObserver o = new TestNodeObserver("/");
+         store = createMK().getNodeStore();
+        store.addObserver(o);
+
+        o.reset();
+
+        LocalDiffCache cache = getLocalDiffCache();
+        CacheStats stats = cache.getDiffCacheStats();
+
+        DocumentNodeState beforeState = store.getRoot();
+        NodeBuilder builder = store.getRoot().builder();
+        builder.child("a").child("a2").setProperty("foo", "bar");
+        builder.child("b");
+        DocumentNodeState afterState = merge(store, builder);
+
+        assertTrue(stats.getHitCount() > 0);
+        assertEquals(0, stats.getMissCount());
+        assertEquals(3, o.added.size());
+
+        ConsolidatedDiff diff = cache.getDiff(beforeState.getRevision(), afterState.getRevision());
+        String serailized = diff.asString();
+        ConsolidatedDiff diff2 = ConsolidatedDiff.fromString(serailized);
+        assertEquals(diff, diff2);
+
+        builder = store.getRoot().builder();
+        builder.child("a").child("a2").removeProperty("foo");
+
+        o.reset();
+        stats.resetStats();
+        merge(store, builder);
+
+        assertTrue(stats.getHitCount() > 0);
+        assertEquals(0, stats.getMissCount());
+        assertEquals(1, o.changed.size());
+
+        store.dispose();
+    }
+
+    private LocalDiffCache getLocalDiffCache(){
+        return (LocalDiffCache) store.getLocalDiffCache();
+    }
+
+    private static DocumentNodeState merge(NodeStore store, NodeBuilder builder)
+            throws CommitFailedException {
+        return (DocumentNodeState) store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+    }
+
+    private static DocumentMK createMK(){
+        return create(new MemoryDocumentStore(), 0);
+    }
+
+    private static DocumentMK create(DocumentStore ds, int clusterId){
+        return new DocumentMK.Builder()
+                .setAsyncDelay(0)
+                .setDocumentStore(ds)
+                .setClusterId(clusterId)
+                .setPersistentCache("target/persistentCache,time")
+                .open();
+    }
+
+    //------------------------------------------------------------< TestNodeObserver >---
+
+    private static class TestNodeObserver extends NodeObserver {
+        private final Map<String, Set<String>> added = newHashMap();
+        private final Map<String, Set<String>> deleted = newHashMap();
+        private final Map<String, Set<String>> changed = newHashMap();
+        private final Map<String, Map<String, String>> properties = newHashMap();
+
+        protected TestNodeObserver(String path, String... propertyNames) {
+            super(path, propertyNames);
+        }
+
+        @Override
+        protected void added(
+                @Nonnull String path,
+                @Nonnull Set<String> added,
+                @Nonnull Set<String> deleted,
+                @Nonnull Set<String> changed,
+                @Nonnull Map<String, String> properties,
+                @Nonnull CommitInfo commitInfo) {
+            this.added.put(path, newHashSet(added));
+            if (!properties.isEmpty()) {
+                this.properties.put(path, newHashMap(properties));
+            }
+        }
+
+        @Override
+        protected void deleted(
+                @Nonnull String path,
+                @Nonnull Set<String> added,
+                @Nonnull Set<String> deleted,
+                @Nonnull Set<String> changed,
+                @Nonnull Map<String, String> properties,
+                @Nonnull CommitInfo commitInfo) {
+            this.deleted.put(path, newHashSet(deleted));
+            if (!properties.isEmpty()) {
+                this.properties.put(path, newHashMap(properties));
+            }
+        }
+
+        @Override
+        protected void changed(
+                @Nonnull String path,
+                @Nonnull Set<String> added,
+                @Nonnull Set<String> deleted,
+                @Nonnull Set<String> changed,
+                @Nonnull Map<String, String> properties,
+                @Nonnull CommitInfo commitInfo) {
+            this.changed.put(path, newHashSet(changed));
+            if (!properties.isEmpty()) {
+                this.properties.put(path, newHashMap(properties));
+            }
+        }
+
+        public void reset(){
+            added.clear();
+            deleted.clear();
+            changed.clear();
+            properties.clear();
+        }
+    }
+}

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

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java?rev=1672277&r1=1672276&r2=1672277&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/observation/EventQueue.java Thu Apr  9 09:48:44 2015
@@ -29,6 +29,7 @@ import javax.jcr.observation.EventIterat
 
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.observation.ContentChangeInfo;
 import org.apache.jackrabbit.oak.plugins.observation.EventGenerator;
 import org.apache.jackrabbit.oak.plugins.observation.EventHandler;
 import org.apache.jackrabbit.oak.plugins.observation.FilteredHandler;
@@ -51,7 +52,7 @@ class EventQueue implements EventIterato
             @Nonnull NamePathMapper mapper, CommitInfo info,
             @Nonnull NodeState before, @Nonnull NodeState after,
             @Nonnull Iterable<String> basePaths, @Nonnull EventFilter filter) {
-        this.generator = new EventGenerator();
+        this.generator = new EventGenerator(new ContentChangeInfo(before, after, info));
         EventFactory factory = new EventFactory(mapper, info);
         EventHandler handler = new FilteredHandler(
                 filter, new QueueingHandler(this, factory, before, after));