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 2016/12/02 06:23:52 UTC

svn commit: r1772305 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/document/ main/java/org/apache/jackrabbit/oak/plugins/observation/ test/java/org/apache/jackrabbit/oak/plugins/document/ test/java/org/apache/jack...

Author: chetanm
Date: Fri Dec  2 06:23:52 2016
New Revision: 1772305

URL: http://svn.apache.org/viewvc?rev=1772305&view=rev
Log:
OAK-5101 - Enable prefiltering for external changes

Added:
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ExternalChangesTest.java   (with props)
Modified:
    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/JournalDiffLoader.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilder.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilderTest.java

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=1772305&r1=1772304&r2=1772305&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 Fri Dec  2 06:23:52 2016
@@ -36,6 +36,7 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation;
 import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getIdFromPath;
 import static org.apache.jackrabbit.oak.plugins.document.util.Utils.pathToId;
+import static org.apache.jackrabbit.oak.plugins.observation.ChangeCollectorProvider.COMMIT_CONTEXT_OBSERVATION_CHANGESET;
 
 import java.io.Closeable;
 import java.io.IOException;
@@ -73,6 +74,7 @@ import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
 import com.google.common.cache.Cache;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -82,6 +84,7 @@ import org.apache.jackrabbit.api.stats.T
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.commons.IOUtils;
 import org.apache.jackrabbit.oak.commons.jmx.AnnotatedStandardMBean;
+import org.apache.jackrabbit.oak.core.SimpleCommitContext;
 import org.apache.jackrabbit.oak.plugins.blob.BlobStoreBlob;
 import org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector;
 import org.apache.jackrabbit.oak.plugins.blob.ReferencedBlob;
@@ -92,6 +95,8 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
 import org.apache.jackrabbit.oak.plugins.document.persistentCache.broadcast.DynamicBroadcastConfig;
 import org.apache.jackrabbit.oak.plugins.document.util.ReadOnlyDocumentStoreWrapperFactory;
+import org.apache.jackrabbit.oak.plugins.observation.ChangeSet;
+import org.apache.jackrabbit.oak.plugins.observation.ChangeSetBuilder;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
 import org.apache.jackrabbit.oak.commons.json.JsopStream;
 import org.apache.jackrabbit.oak.commons.json.JsopWriter;
@@ -107,6 +112,7 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
 import org.apache.jackrabbit.oak.spi.commit.ChangeDispatcher;
+import org.apache.jackrabbit.oak.spi.commit.CommitContext;
 import org.apache.jackrabbit.oak.spi.commit.Observable;
 import org.apache.jackrabbit.oak.spi.commit.CommitHook;
 import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
@@ -217,6 +223,10 @@ public final class DocumentNodeStore
     protected int maxBackOffMillis =
             Integer.getInteger("oak.maxBackOffMS", asyncDelay * 2);
 
+    protected int changeSetMaxItems =  Integer.getInteger("oak.document.changeSet.maxItems", 50);
+
+    protected int changeSetMaxDepth =  Integer.getInteger("oak.document.changeSet.maxDepth", 9);
+
     /**
      * Whether this instance is disposed.
      */
@@ -586,6 +596,7 @@ public final class DocumentNodeStore
         journalCache = builder.getJournalCache();
 
         this.mbean = createMBean();
+        LOG.info("ChangeSetBuilder enabled and size set to maxItems: {}, maxDepth: {}", changeSetMaxItems, changeSetMaxDepth);
         LOG.info("Initialized DocumentNodeStore with clusterNodeId: {} ({})", clusterId,
                 getClusterNodeInfoDisplayString());
 
@@ -776,6 +787,7 @@ public final class DocumentNodeStore
                         c.applyToCache(before, false);
                         // track modified paths
                         changes.modified(c.getModifiedPaths());
+                        changes.addChangeSet(getChangeSet(info));
                         // update head revision
                         newHead[0] = before.update(c.getRevision());
                         setRoot(newHead[0]);
@@ -825,6 +837,22 @@ public final class DocumentNodeStore
         return maxBackOffMillis;
     }
 
+    public int getChangeSetMaxItems() {
+        return changeSetMaxItems;
+    }
+
+    public void setChangeSetMaxItems(int changeSetMaxItems) {
+        this.changeSetMaxItems = changeSetMaxItems;
+    }
+
+    public int getChangeSetMaxDepth() {
+        return changeSetMaxDepth;
+    }
+
+    public void setChangeSetMaxDepth(int changeSetMaxDepth) {
+        this.changeSetMaxDepth = changeSetMaxDepth;
+    }
+
     void setEnableConcurrentAddRemove(boolean b) {
         enableConcurrentAddRemove = b;
     }
@@ -1919,6 +1947,7 @@ public final class DocumentNodeStore
 
         Map<Integer, Revision> lastRevMap = doc.getLastRev();
         try {
+            ChangeSetBuilder changeSetBuilder = newChangeSetBuilder();
             RevisionVector headRevision = getHeadRevision();
             Set<Revision> externalChanges = Sets.newHashSet();
             for (Map.Entry<Integer, Revision> e : lastRevMap.entrySet()) {
@@ -1942,7 +1971,7 @@ public final class DocumentNodeStore
                     if (externalSort != null) {
                         // add changes for this particular clusterId to the externalSort
                         try {
-                            fillExternalChanges(externalSort, last, r, store);
+                            fillExternalChanges(externalSort, PathUtils.ROOT_PATH, last, r, store, changeSetBuilder);
                         } catch (IOException e1) {
                             LOG.error("backgroundRead: Exception while reading external changes from journal: " + e1, e1);
                             IOUtils.closeQuietly(externalSort);
@@ -1999,7 +2028,9 @@ public final class DocumentNodeStore
                     stats.populateDiffCache = clock.getTime() - time;
                     time = clock.getTime();
 
-                    dispatcher.contentChanged(getRoot().fromExternalChange(), CommitInfo.EMPTY_EXTERNAL);
+                    ChangeSet changeSet = changeSetBuilder.build();
+                    LOG.debug("Dispatching external change with ChangeSet {}", changeSet);
+                    dispatcher.contentChanged(getRoot().fromExternalChange(), newCommitInfo(changeSet));
                 } finally {
                     backgroundOperationLock.writeLock().unlock();
                 }
@@ -2012,6 +2043,25 @@ public final class DocumentNodeStore
         return stats;
     }
 
+    private static CommitInfo newCommitInfo(@Nonnull  ChangeSet changeSet) {
+        CommitContext commitContext = new SimpleCommitContext();
+        commitContext.set(COMMIT_CONTEXT_OBSERVATION_CHANGESET, changeSet);
+        Map<String, Object> info = ImmutableMap.<String, Object>of(CommitContext.NAME, commitContext);
+        return new CommitInfo(CommitInfo.OAK_UNKNOWN, CommitInfo.OAK_UNKNOWN, info, true);
+    }
+
+    private static ChangeSet getChangeSet(CommitInfo info) {
+        CommitContext commitContext = (CommitContext) info.getInfo().get(CommitContext.NAME);
+        if (commitContext == null){
+            return null;
+        }
+        return (ChangeSet) commitContext.get(COMMIT_CONTEXT_OBSERVATION_CHANGESET);
+    }
+
+    private ChangeSetBuilder newChangeSetBuilder() {
+        return new ChangeSetBuilder(changeSetMaxItems, changeSetMaxDepth);
+    }
+
     private void cleanOrphanedBranches() {
         Branch b;
         while ((b = branches.pollOrphanedBranch()) != null) {
@@ -2134,7 +2184,7 @@ public final class DocumentNodeStore
     }
 
     private JournalEntry newJournalEntry() {
-        return new JournalEntry(store, true);
+        return new JournalEntry(store, true, newChangeSetBuilder());
     }
 
     /**

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java?rev=1772305&r1=1772304&r2=1772305&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java Fri Dec  2 06:23:52 2016
@@ -159,7 +159,7 @@ class JournalDiffLoader implements DiffC
                 // use revision with a timestamp of zero
                 from = new Revision(0, 0, to.getClusterId());
             }
-            stats.numJournalEntries += fillExternalChanges(changes, path, from, to, ns.getDocumentStore());
+            stats.numJournalEntries += fillExternalChanges(changes, path, from, to, ns.getDocumentStore(), null);
         }
         // do we need to include changes from pending local changes?
         if (!max.isRevisionNewer(localLastRev)

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java?rev=1772305&r1=1772304&r2=1772305&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java Fri Dec  2 06:23:52 2016
@@ -26,6 +26,7 @@ import java.util.Set;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import com.google.common.collect.AbstractIterator;
 import com.google.common.collect.Lists;
@@ -37,6 +38,8 @@ import org.apache.jackrabbit.oak.commons
 import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
 import org.apache.jackrabbit.oak.commons.sort.StringSort;
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.plugins.observation.ChangeSet;
+import org.apache.jackrabbit.oak.plugins.observation.ChangeSetBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -63,6 +66,8 @@ public final class JournalEntry extends
 
     private static final String CHANGES = "_c";
 
+    private static final String CHANGE_SET = "_cs";
+
     private static final String BRANCH_COMMITS = "_bc";
 
     public static final String MODIFIED = "_modified";
@@ -77,17 +82,20 @@ public final class JournalEntry extends
 
     private final DocumentStore store;
 
+    private final ChangeSetBuilder changeSetBuilder;
+
     private volatile TreeNode changes = null;
 
     private boolean concurrent;
 
     JournalEntry(DocumentStore store) {
-        this(store, false);
+        this(store, false, null);
     }
 
-    JournalEntry(DocumentStore store, boolean concurrent) {
+    JournalEntry(DocumentStore store, boolean concurrent, ChangeSetBuilder changeSetBuilder) {
         this.store = store;
         this.concurrent = concurrent;
+        this.changeSetBuilder = changeSetBuilder;
     }
 
     static StringSort newSorter() {
@@ -207,7 +215,7 @@ public final class JournalEntry extends
                                    @Nonnull Revision to,
                                    @Nonnull DocumentStore store)
             throws IOException {
-        return fillExternalChanges(sorter, PathUtils.ROOT_PATH, from, to, store);
+        return fillExternalChanges(sorter, PathUtils.ROOT_PATH, from, to, store, null);
     }
 
     /**
@@ -225,6 +233,8 @@ public final class JournalEntry extends
      * @param from   the lower bound of the revision range (exclusive).
      * @param to     the upper bound of the revision range (inclusive).
      * @param store  the document store to query.
+     * @param changeSetBuilder a nullable ChangeSetBuilder to collect changes from
+     *                         the JournalEntry between given revisions     *
      * @return the number of journal entries read from the store.
      * @throws IOException
      */
@@ -232,7 +242,8 @@ public final class JournalEntry extends
                                    @Nonnull String path,
                                    @Nonnull Revision from,
                                    @Nonnull Revision to,
-                                   @Nonnull DocumentStore store)
+                                   @Nonnull DocumentStore store,
+                                   @Nullable ChangeSetBuilder changeSetBuilder)
             throws IOException {
         checkNotNull(path);
         checkArgument(checkNotNull(from).getClusterId() == checkNotNull(to).getClusterId());
@@ -271,6 +282,9 @@ public final class JournalEntry extends
 
             for (JournalEntry d : partialResult) {
                 d.addTo(sorter, path);
+                if (changeSetBuilder != null) {
+                    d.addTo(changeSetBuilder);
+                }
             }
             if (partialResult.size() < READ_CHUNK_SIZE) {
                 break;
@@ -312,6 +326,34 @@ public final class JournalEntry extends
         }
     }
 
+    void addChangeSet(@Nullable ChangeSet changeSet){
+        if (changeSet == null){
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Null changeSet found for caller. ChangeSetBuilder would be set to overflow mode",
+                        new Exception());
+            }
+        }
+        changeSetBuilder.add(changeSet);
+    }
+
+    private void addTo(ChangeSetBuilder changeSetBuilder) {
+        String cs = (String) get(CHANGE_SET);
+        ChangeSet set = null;
+
+        if (cs == null && getChanges().keySet().isEmpty()){
+            //Purely a branch commit. So ChangeSet can be empty
+            return;
+        }
+
+        if (cs != null) {
+            set = ChangeSet.fromString(cs);
+        } else {
+            LOG.debug("Null changeSet found for JournalEntry {}. ChangeSetBuilder would be set to overflow mode", getId());
+        }
+
+        changeSetBuilder.add(set);
+    }
+
     void branchCommit(@Nonnull Iterable<Revision> revisions) {
         String branchCommits = (String) get(BRANCH_COMMITS);
         if (branchCommits == null) {
@@ -330,6 +372,12 @@ public final class JournalEntry extends
         String id = asId(revision);
         UpdateOp op = new UpdateOp(id, true);
         op.set(CHANGES, getChanges().serialize());
+
+        //For branch commits builder would be null
+        if (changeSetBuilder != null) {
+            op.set(CHANGE_SET, changeSetBuilder.build().asString());
+        }
+
         // OAK-3085 : introduce a timestamp property
         // for later being used by OAK-3001
         op.set(MODIFIED, revision.getTimestamp());

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilder.java?rev=1772305&r1=1772304&r2=1772305&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilder.java Fri Dec  2 06:23:52 2016
@@ -20,6 +20,8 @@ package org.apache.jackrabbit.oak.plugin
 
 import java.util.Set;
 
+import javax.annotation.Nullable;
+
 import com.google.common.collect.Sets;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 
@@ -117,7 +119,16 @@ public class ChangeSetBuilder {
         return maxPathDepth;
     }
 
-    public ChangeSetBuilder add(ChangeSet cs){
+    public ChangeSetBuilder add(@Nullable ChangeSet cs){
+        if (cs == null){
+            parentPathOverflow = true;
+            parentNodeNameOverflow = true;
+            parentNodeTypeOverflow = true;
+            propertyNameOverflow = true;
+            allNodeTypeOverflow = true;
+            return this;
+        }
+
         if (cs.getParentPaths() == null){
             parentPathOverflow = true;
         } else {

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ExternalChangesTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ExternalChangesTest.java?rev=1772305&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ExternalChangesTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ExternalChangesTest.java Fri Dec  2 06:23:52 2016
@@ -0,0 +1,207 @@
+/*
+ * 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.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.oak.core.SimpleCommitContext;
+import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.apache.jackrabbit.oak.plugins.observation.ChangeCollectorProvider;
+import org.apache.jackrabbit.oak.plugins.observation.ChangeSet;
+import org.apache.jackrabbit.oak.spi.commit.CommitContext;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class ExternalChangesTest {
+    @Rule
+    public final DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider();
+
+    private DocumentNodeStore ns1;
+    private DocumentNodeStore ns2;
+
+    private CommitInfoCollector c1 = new CommitInfoCollector();
+    private CommitInfoCollector c2 = new CommitInfoCollector();
+
+    @Before
+    public void setUp() {
+        MemoryDocumentStore store = new MemoryDocumentStore();
+        ns1 = newDocumentNodeStore(store, 1);
+        ns2 = newDocumentNodeStore(store, 2);
+
+        ns1.addObserver(c1);
+        ns2.addObserver(c2);
+    }
+
+    @Test
+    public void defaultConfig() throws Exception{
+        assertEquals(50, ns1.getChangeSetMaxItems());
+        assertEquals(9, ns1.getChangeSetMaxDepth());
+    }
+
+    @Test
+    public void changeSetForExternalChanges() throws Exception{
+        NodeBuilder b1 = ns1.getRoot().builder();
+        b1.child("a");
+        b1.setProperty("foo1", "bar");
+        ns1.merge(b1, newCollectingHook(), newCommitInfo());
+
+        NodeBuilder b2 = ns1.getRoot().builder();
+        b2.child("b");
+        b2.setProperty("foo2", "bar");
+        ns1.merge(b2, newCollectingHook(), newCommitInfo());
+
+        ns1.backgroundWrite();
+
+        c2.reset();
+        ns2.backgroundRead();
+
+        CommitInfo ci = c2.getExternalChange();
+        CommitContext cc = (CommitContext) ci.getInfo().get(CommitContext.NAME);
+        assertNotNull(cc);
+        ChangeSet cs = (ChangeSet) cc.get(ChangeCollectorProvider.COMMIT_CONTEXT_OBSERVATION_CHANGESET);
+        assertNotNull(cs);
+        assertFalse(cs.anyOverflow());
+        assertThat(cs.getPropertyNames(), containsInAnyOrder("foo1", "foo2"));
+    }
+
+    @Test
+    public void missingChangeSetResultsInOverflow() throws Exception{
+        NodeBuilder b1 = ns1.getRoot().builder();
+        b1.child("a");
+        b1.setProperty("foo1", "bar");
+        ns1.merge(b1, newCollectingHook(), newCommitInfo());
+
+        NodeBuilder b2 = ns1.getRoot().builder();
+        b2.child("b");
+        b2.setProperty("foo2", "bar");
+        //Commit without ChangeSet
+        ns1.merge(b2, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+        ns1.backgroundWrite();
+
+        c2.reset();
+        ns2.backgroundRead();
+
+        CommitInfo ci = c2.getExternalChange();
+        CommitContext cc = (CommitContext) ci.getInfo().get(CommitContext.NAME);
+        assertNotNull(cc);
+        ChangeSet cs = (ChangeSet) cc.get(ChangeCollectorProvider.COMMIT_CONTEXT_OBSERVATION_CHANGESET);
+        assertNotNull(cs);
+
+        //ChangeSet should result in overflow
+        assertTrue(cs.anyOverflow());
+    }
+
+    @Test
+    public void changeSetForBranchCommit() throws Exception{
+        final int NUM_NODES = DocumentRootBuilder.UPDATE_LIMIT / 2;
+        final int NUM_PROPS = 10;
+
+        Set<String> propNames = Sets.newHashSet();
+        NodeBuilder b1 = ns1.getRoot().builder();
+        for (int i = 0; i < NUM_NODES; i++) {
+            NodeBuilder c = b1.child("n" + i);
+            for (int j = 0; j < NUM_PROPS; j++) {
+                c.setProperty("q" + j, "value");
+                c.setProperty("p" + j, "value");
+                propNames.add("q" + j);
+                propNames.add("p" + j);
+            }
+        }
+
+        ns1.merge(b1, newCollectingHook(), newCommitInfo());
+        ns1.backgroundWrite();
+
+        c2.reset();
+        ns2.backgroundRead();
+
+        CommitInfo ci = c2.getExternalChange();
+        CommitContext cc = (CommitContext) ci.getInfo().get(CommitContext.NAME);
+        assertNotNull(cc);
+        ChangeSet cs = (ChangeSet) cc.get(ChangeCollectorProvider.COMMIT_CONTEXT_OBSERVATION_CHANGESET);
+        assertNotNull(cs);
+        assertTrue(cs.getPropertyNames().containsAll(propNames));
+    }
+
+    private CommitHook newCollectingHook(){
+        return new EditorHook(new ChangeCollectorProvider());
+    }
+
+    private CommitInfo newCommitInfo(){
+        Map<String, Object> info = ImmutableMap.<String, Object>of(CommitContext.NAME, new SimpleCommitContext());
+        return new CommitInfo(CommitInfo.OAK_UNKNOWN, CommitInfo.OAK_UNKNOWN, info);
+    }
+
+    private DocumentNodeStore newDocumentNodeStore(DocumentStore store, int clusterId) {
+        return builderProvider.newBuilder()
+                .setAsyncDelay(0)
+                .setDocumentStore(store)
+                .setLeaseCheck(false) // disabled for debugging purposes
+                .setClusterId(clusterId)
+                .getNodeStore();
+    }
+
+    private static class CommitInfoCollector implements Observer {
+        List<CommitInfo> infos = Lists.newArrayList();
+
+        @Override
+        public void contentChanged(@Nonnull NodeState root, @Nonnull CommitInfo info) {
+           infos.add(info);
+        }
+
+        public CommitInfo getExternalChange(){
+            List<CommitInfo> result = Lists.newArrayList();
+            for (CommitInfo info : infos){
+                if (info.isExternal()) {
+                    result.add(info);
+                }
+            }
+            assertEquals(1, result.size());
+            return result.get(0);
+        }
+
+        void reset(){
+            infos.clear();
+        }
+    }
+}

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

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java?rev=1772305&r1=1772304&r2=1772305&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java Fri Dec  2 06:23:52 2016
@@ -248,7 +248,7 @@ public class JournalEntryTest {
         assertTrue(store.create(JOURNAL, Collections.singletonList(op)));
 
         StringSort sort = JournalEntry.newSorter();
-        JournalEntry.fillExternalChanges(sort, "/foo", r1, r2, store);
+        JournalEntry.fillExternalChanges(sort, "/foo", r1, r2, store, null);
         assertEquals(4, sort.getSize());
         sort.close();
     }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilderTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilderTest.java?rev=1772305&r1=1772304&r2=1772305&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilderTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/observation/ChangeSetBuilderTest.java Fri Dec  2 06:23:52 2016
@@ -195,6 +195,20 @@ public class ChangeSetBuilderTest {
         assertEquals(cb2.getMaxPrefilterPathDepth(), cs.getMaxPrefilterPathDepth());
     }
 
+    @Test
+    public void nullChangeSet() throws Exception{
+        ChangeSetBuilder cb1 = new ChangeSetBuilder(10, 8);
+        add(cb1, "1");
+
+        cb1.add(null);
+        ChangeSet cs = cb1.build();
+        assertNull(cs.getParentNodeTypes());
+        assertNull(cs.getParentNodeNames());
+        assertNull(cs.getParentPaths());
+        assertNull(cs.getAllNodeTypes());
+        assertNull(cs.getPropertyNames());
+    }
+
     private static void add(ChangeSetBuilder cb, String suffix){
         cb.addNodeType("nt-"+suffix)
                 .addParentPath("p-"+suffix)