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 mr...@apache.org on 2016/01/07 13:46:36 UTC

svn commit: r1723532 [2/5] - 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/util/ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/d...

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=1723532&r1=1723531&r2=1723532&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 Jan  7 12:46:35 2016
@@ -42,17 +42,14 @@ import java.lang.ref.WeakReference;
 import java.text.SimpleDateFormat;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.Set;
-import java.util.SortedMap;
 import java.util.TimeZone;
 import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -68,6 +65,7 @@ import javax.annotation.Nullable;
 import javax.management.NotCompliantMBeanException;
 
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
@@ -83,7 +81,6 @@ import org.apache.jackrabbit.oak.commons
 import org.apache.jackrabbit.oak.plugins.blob.BlobStoreBlob;
 import org.apache.jackrabbit.oak.plugins.blob.MarkSweepGarbageCollector;
 import org.apache.jackrabbit.oak.plugins.blob.ReferencedBlob;
-import org.apache.jackrabbit.oak.plugins.document.Checkpoints.Info;
 import org.apache.jackrabbit.oak.plugins.document.cache.CacheInvalidationStats;
 import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCache;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
@@ -95,7 +92,6 @@ import org.apache.jackrabbit.oak.api.Com
 import org.apache.jackrabbit.oak.cache.CacheStats;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.json.BlobSerializer;
-import org.apache.jackrabbit.oak.plugins.document.Branch.BranchCommit;
 import org.apache.jackrabbit.oak.plugins.document.util.LeaseCheckDocumentStoreWrapper;
 import org.apache.jackrabbit.oak.plugins.document.util.LoggingDocumentStoreWrapper;
 import org.apache.jackrabbit.oak.plugins.document.util.StringValue;
@@ -135,13 +131,6 @@ public final class DocumentNodeStore
     static final int NUM_CHILDREN_CACHE_LIMIT = Integer.getInteger("oak.documentMK.childrenCacheLimit", 16 * 1024);
 
     /**
-     * When trying to access revisions that are older than this many
-     * milliseconds, a warning is logged. The default is one minute.
-     */
-    private static final int WARN_REVISION_AGE =
-            Integer.getInteger("oak.documentMK.revisionAge", 60 * 1000);
-
-    /**
      * Feature flag to enable concurrent add/remove operations of hidden empty
      * nodes. See OAK-2673.
      */
@@ -155,12 +144,6 @@ public final class DocumentNodeStore
             Boolean.parseBoolean(System.getProperty("oak.fairBackgroundOperationLock", "true"));
 
     /**
-     * How long to remember the relative order of old revision of all cluster
-     * nodes, in milliseconds. The default is one hour.
-     */
-    static final int REMEMBER_REVISION_ORDER_MILLIS = 60 * 60 * 1000;
-
-    /**
      * The document store (might be used by multiple node stores).
      */
     protected final DocumentStore store;
@@ -214,24 +197,12 @@ public final class DocumentNodeStore
     private final int clusterId;
 
     /**
-     * Map of inactive cluster nodes and when the cluster node was last seen
-     * as inactive.
-     * Key: clusterId, value: timeInMillis
+     * Map of known cluster nodes and the last known state updated
+     * by {@link #updateClusterState()}.
+     * Key: clusterId, value: ClusterNodeInfoDocument
      */
-    private final ConcurrentMap<Integer, Long> inactiveClusterNodes
-            = new ConcurrentHashMap<Integer, Long>();
-
-    /**
-     * Map of active cluster nodes and when the cluster node's lease ends.
-     * Key: clusterId, value: leaseEndTimeInMillis
-     */
-    private final ConcurrentMap<Integer, Long> activeClusterNodes
-            = new ConcurrentHashMap<Integer, Long>();
-
-    /**
-     * The comparator for revisions.
-     */
-    private final Revision.RevisionComparator revisionComparator;
+    private final ConcurrentMap<Integer, ClusterNodeInfoDocument> clusterNodes
+            = Maps.newConcurrentMap();
 
     /**
      * Unmerged branches of this DocumentNodeStore instance.
@@ -261,17 +232,9 @@ public final class DocumentNodeStore
     private JournalEntry changes;
 
     /**
-     * The last known revision for each cluster instance.
-     *
-     * Key: the machine id, value: revision.
+     * The current root node state.
      */
-    private final Map<Integer, Revision> lastKnownRevision =
-            new ConcurrentHashMap<Integer, Revision>();
-
-    /**
-     * The last known head revision. This is the last-known revision.
-     */
-    private volatile Revision headRevision;
+    private volatile DocumentNodeState root;
 
     private Thread backgroundReadThread;
 
@@ -444,8 +407,7 @@ public final class DocumentNodeStore
         }
         this.store = s;
         this.clusterId = cid;
-        this.revisionComparator = new Revision.RevisionComparator(clusterId);
-        this.branches = new UnmergedBranches(getRevisionComparator());
+        this.branches = new UnmergedBranches();
         this.asyncDelay = builder.getAsyncDelay();
         this.versionGarbageCollector = new VersionGarbageCollector(
                 this, builder.createVersionGCSupport());
@@ -454,7 +416,8 @@ public final class DocumentNodeStore
         this.lastRevRecoveryAgent = new LastRevRecoveryAgent(this,
                 builder.createMissingLastRevSeeker());
         this.disableBranches = builder.isDisableBranches();
-        this.missing = new DocumentNodeState(this, "MISSING", new Revision(0, 0, 0)) {
+        this.missing = new DocumentNodeState(this, "MISSING",
+                new RevisionVector(new Revision(0, 0, 0))) {
             @Override
             public int getMemory() {
                 return 8;
@@ -482,14 +445,16 @@ public final class DocumentNodeStore
         NodeDocument rootDoc = store.find(NODES, Utils.getIdFromPath("/"));
         if (rootDoc == null) {
             // root node is missing: repository is not initialized
-            Revision head = newRevision();
-            Commit commit = new Commit(this, head, null, null);
+            Revision commitRev = newRevision();
+            Commit commit = new Commit(this, commitRev, null, null);
+            RevisionVector head = new RevisionVector(commitRev);
             DocumentNodeState n = new DocumentNodeState(this, "/", head);
             commit.addNode(n);
             commit.applyToDocumentStore();
             // use dummy Revision as before
-            commit.applyToCache(new Revision(0, 0, clusterId), false);
-            setHeadRevision(commit.getRevision());
+            RevisionVector before = new RevisionVector(new Revision(0, 0, clusterId));
+            commit.applyToCache(before, false);
+            setRoot(head);
             // make sure _lastRev is written back to store
             backgroundWrite();
             rootDoc = store.find(NODES, Utils.getIdFromPath("/"));
@@ -499,10 +464,10 @@ public final class DocumentNodeStore
             }
         } else {
             checkLastRevRecovery();
-            initializeHeadRevision(rootDoc);
+            initializeRootState(rootDoc);
             // check if _lastRev for our clusterId exists
             if (!rootDoc.getLastRev().containsKey(clusterId)) {
-                unsavedLastRevisions.put("/", headRevision);
+                unsavedLastRevisions.put("/", getRoot().getRevision().getRevision(clusterId));
                 backgroundWrite();
             }
         }
@@ -510,15 +475,13 @@ public final class DocumentNodeStore
         // Renew the lease because it may have been stale
         renewClusterIdLease();
 
-        getRevisionComparator().add(headRevision, Revision.newRevision(0));
-
         // initialize branchCommits
         branches.init(store, this);
 
         dispatcher = new ChangeDispatcher(getRoot());
         commitQueue = new CommitQueue(this);
         String threadNamePostfix = "(" + clusterId + ")";
-        batchCommitQueue = new BatchCommitQueue(store, revisionComparator);
+        batchCommitQueue = new BatchCommitQueue(store);
         backgroundReadThread = new Thread(
                 new BackgroundReadOperation(this, isDisposed),
                 "DocumentNodeStore background read thread " + threadNamePostfix);
@@ -617,14 +580,9 @@ public final class DocumentNodeStore
         return clusterNodeInfo.toString().replaceAll("[\r\n\t]", " ").trim();
     }
 
-    Revision setHeadRevision(@Nonnull Revision newHead) {
+    void setRoot(@Nonnull RevisionVector newHead) {
         checkArgument(!newHead.isBranch());
-        Revision previous = headRevision;
-        if (!checkNotNull(newHead).equals(previous)) {
-            // head changed
-            headRevision = newHead;
-        }
-        return previous;
+        root = getRoot(newHead);
     }
 
     @Nonnull
@@ -647,10 +605,10 @@ public final class DocumentNodeStore
      * @return a new commit.
      */
     @Nonnull
-    Commit newCommit(@Nullable Revision base,
+    Commit newCommit(@Nullable RevisionVector base,
                      @Nullable DocumentNodeStoreBranch branch) {
         if (base == null) {
-            base = headRevision;
+            base = getHeadRevision();
         }
         if (base.isBranch()) {
             return newBranchCommit(base, branch);
@@ -670,9 +628,9 @@ public final class DocumentNodeStore
      * @return a new merge commit.
      */
     @Nonnull
-    MergeCommit newMergeCommit(@Nullable Revision base, int numBranchCommits) {
+    MergeCommit newMergeCommit(@Nullable RevisionVector base, int numBranchCommits) {
         if (base == null) {
-            base = headRevision;
+            base = getHeadRevision();
         }
         backgroundOperationLock.readLock().lock();
         boolean success = false;
@@ -689,30 +647,34 @@ public final class DocumentNodeStore
         return c;
     }
 
-    void done(final @Nonnull Commit c, boolean isBranch, final @Nullable CommitInfo info) {
+    RevisionVector done(final @Nonnull Commit c, boolean isBranch, final @Nullable CommitInfo info) {
         if (commitQueue.contains(c.getRevision())) {
             try {
+                final RevisionVector[] newHead = new RevisionVector[1];
                 commitQueue.done(c.getRevision(), new CommitQueue.Callback() {
                     @Override
                     public void headOfQueue(@Nonnull Revision revision) {
                         // remember before revision
-                        Revision before = getHeadRevision();
+                        RevisionVector before = getHeadRevision();
                         // apply changes to cache based on before revision
                         c.applyToCache(before, false);
                         // track modified paths
                         changes.modified(c.getModifiedPaths());
                         // update head revision
-                        setHeadRevision(c.getRevision());
+                        newHead[0] = before.update(c.getRevision());
+                        setRoot(newHead[0]);
                         commitQueue.headRevisionChanged();
                         dispatcher.contentChanged(getRoot(), info);
                     }
                 });
+                return newHead[0];
             } finally {
                 backgroundOperationLock.readLock().unlock();
             }
         } else {
             // branch commit
             c.applyToCache(c.getBaseRevision(), isBranch);
+            return c.getBaseRevision().update(c.getRevision().asBranchRevision());
         }
     }
 
@@ -780,7 +742,7 @@ public final class DocumentNodeStore
         nodeChildrenCache.invalidateAll();
     }
 
-    void invalidateNodeCache(String path, Revision revision){
+    void invalidateNodeCache(String path, RevisionVector revision){
         nodeCache.invalidate(new PathRev(path, revision));
     }
 
@@ -793,17 +755,6 @@ public final class DocumentNodeStore
     }
 
     /**
-     * Checks that revision x is newer than another revision.
-     *
-     * @param x the revision to check
-     * @param previous the presumed earlier revision
-     * @return true if x is newer
-     */
-    boolean isRevisionNewer(@Nonnull Revision x, @Nonnull Revision previous) {
-        return getRevisionComparator().compare(x, previous) > 0;
-    }
-
-    /**
      * Enqueue the document with the given id as a split candidate.
      *
      * @param id the id of the document to check if it needs to be split.
@@ -821,7 +772,7 @@ public final class DocumentNodeStore
     }
 
     void markAsDeleted(DocumentNodeState node, Commit commit, boolean subTreeAlso) {
-        commit.removeNode(node.getPath());
+        commit.removeNode(node.getPath(), node);
 
         if (subTreeAlso) {
             // recurse down the tree
@@ -842,8 +793,10 @@ public final class DocumentNodeStore
      *          given revision.
      */
     @CheckForNull
-    DocumentNodeState getNode(@Nonnull final String path, @Nonnull final Revision rev) {
-        checkRevisionAge(checkNotNull(rev), checkNotNull(path));
+    DocumentNodeState getNode(@Nonnull final String path,
+                              @Nonnull final RevisionVector rev) {
+        checkNotNull(rev);
+        checkNotNull(path);
         final long start = PERFLOG.start();
         try {
             PathRev key = new PathRev(path, rev);
@@ -880,7 +833,7 @@ public final class DocumentNodeStore
             return DocumentNodeState.NO_CHILDREN;
         }
         final String path = checkNotNull(parent).getPath();
-        final Revision readRevision = parent.getLastRevision();
+        final RevisionVector readRevision = parent.getLastRevision();
         try {
             PathRev key = childNodeCacheKey(path, readRevision, name);
             DocumentNodeState.Children children = nodeChildrenCache.get(key, new Callable<DocumentNodeState.Children>() {
@@ -925,8 +878,8 @@ public final class DocumentNodeStore
                                             String name, int limit) {
         String queriedName = name;
         String path = parent.getPath();
-        Revision rev = parent.getLastRevision();
-        LOG.trace("Reading children for [{}] ast rev [{}]", path, rev);
+        RevisionVector rev = parent.getLastRevision();
+        LOG.trace("Reading children for [{}] at rev [{}]", path, rev);
         Iterable<NodeDocument> docs;
         DocumentNodeState.Children c = new DocumentNodeState.Children();
         // add one to the requested limit for the raw limit
@@ -1082,7 +1035,7 @@ public final class DocumentNodeStore
             return Collections.emptyList();
         }
 
-        final Revision readRevision = parent.getLastRevision();
+        final RevisionVector readRevision = parent.getLastRevision();
         return transform(getChildren(parent, name, limit).children, new Function<String, DocumentNodeState>() {
             @Override
             public DocumentNodeState apply(String input) {
@@ -1097,7 +1050,7 @@ public final class DocumentNodeStore
     }
 
     @CheckForNull
-    DocumentNodeState readNode(String path, Revision readRevision) {
+    DocumentNodeState readNode(String path, RevisionVector readRevision) {
         final long start = PERFLOG.start();
         String id = Utils.getIdFromPath(path);
         Revision lastRevision = getPendingModifications().get(path);
@@ -1124,7 +1077,7 @@ public final class DocumentNodeStore
      * @param changed the list of changed child nodes.
      *
      */
-    void applyChanges(Revision rev, String path,
+    void applyChanges(RevisionVector rev, String path,
                       boolean isNew, List<String> added,
                       List<String> removed, List<String> changed,
                       DiffCache.Entry cacheEntry) {
@@ -1245,7 +1198,7 @@ public final class DocumentNodeStore
      * @return the root node state at the given revision.
      */
     @Nonnull
-    DocumentNodeState getRoot(@Nonnull Revision revision) {
+    DocumentNodeState getRoot(@Nonnull RevisionVector revision) {
         DocumentNodeState root = getNode("/", revision);
         if (root == null) {
             throw new IllegalStateException(
@@ -1264,7 +1217,8 @@ public final class DocumentNodeStore
     }
 
     @Nonnull
-    Revision rebase(@Nonnull Revision branchHead, @Nonnull Revision base) {
+    RevisionVector rebase(@Nonnull RevisionVector branchHead,
+                          @Nonnull RevisionVector base) {
         checkNotNull(branchHead);
         checkNotNull(base);
         if (disableBranches) {
@@ -1274,33 +1228,33 @@ public final class DocumentNodeStore
         Branch b = getBranches().getBranch(branchHead);
         if (b == null) {
             // empty branch
-            return base.asBranchRevision();
+            return base.asBranchRevision(getClusterId());
         }
-        if (b.getBase(branchHead).equals(base)) {
+        if (b.getBase(branchHead.getBranchRevision()).equals(base)) {
             return branchHead;
         }
         // add a pseudo commit to make sure current head of branch
         // has a higher revision than base of branch
         Revision head = newRevision().asBranchRevision();
         b.rebase(head, base);
-        return head;
+        return base.update(head);
     }
 
     @Nonnull
-    Revision reset(@Nonnull Revision branchHead,
-                   @Nonnull Revision ancestor,
-                   @Nullable DocumentNodeStoreBranch branch) {
+    RevisionVector reset(@Nonnull RevisionVector branchHead,
+                         @Nonnull RevisionVector ancestor,
+                         @Nullable DocumentNodeStoreBranch branch) {
         checkNotNull(branchHead);
         checkNotNull(ancestor);
         Branch b = getBranches().getBranch(branchHead);
         if (b == null) {
             throw new DocumentStoreException("Empty branch cannot be reset");
         }
-        if (!b.getCommits().last().equals(branchHead)) {
+        if (!b.getCommits().last().equals(branchHead.getRevision(getClusterId()))) {
             throw new DocumentStoreException(branchHead + " is not the head " +
                     "of a branch");
         }
-        if (!b.containsCommit(ancestor)) {
+        if (!b.containsCommit(ancestor.getBranchRevision())) {
             throw new DocumentStoreException(ancestor + " is not " +
                     "an ancestor revision of " + branchHead);
         }
@@ -1311,15 +1265,17 @@ public final class DocumentNodeStore
         boolean success = false;
         Commit commit = newCommit(branchHead, branch);
         try {
-            Iterator<Revision> it = b.getCommits().tailSet(ancestor).iterator();
+            Iterator<Revision> it = b.getCommits().tailSet(ancestor.getBranchRevision()).iterator();
             // first revision is the ancestor (tailSet is inclusive)
             // do not undo changes for this revision
-            Revision base = it.next();
+            it.next();
             Map<String, UpdateOp> operations = Maps.newHashMap();
-            while (it.hasNext()) {
+            if (it.hasNext()) {
                 Revision reset = it.next();
-                getRoot(reset).compareAgainstBaseState(getRoot(base),
-                        new ResetDiff(reset.asTrunkRevision(), operations));
+                // TODO: correct?
+                getRoot(b.getCommit(reset).getBase().update(reset))
+                        .compareAgainstBaseState(getRoot(ancestor),
+                                new ResetDiff(reset.asTrunkRevision(), operations));
                 UpdateOp rootOp = operations.get("/");
                 if (rootOp == null) {
                     rootOp = new UpdateOp(Utils.getIdFromPath("/"), false);
@@ -1333,7 +1289,7 @@ public final class DocumentNodeStore
             if (store.findAndUpdate(Collection.NODES, operations.get("/")) != null) {
                 // clean up in-memory branch data
                 // first revision is the ancestor (tailSet is inclusive)
-                List<Revision> revs = Lists.newArrayList(b.getCommits().tailSet(ancestor));
+                List<Revision> revs = Lists.newArrayList(b.getCommits().tailSet(ancestor.getBranchRevision()));
                 for (Revision r : revs.subList(1, revs.size())) {
                     b.removeCommit(r);
                 }
@@ -1358,14 +1314,16 @@ public final class DocumentNodeStore
     }
 
     @Nonnull
-    Revision merge(@Nonnull Revision branchHead, @Nullable CommitInfo info)
+    RevisionVector merge(@Nonnull RevisionVector branchHead,
+                         @Nullable CommitInfo info)
             throws CommitFailedException {
         Branch b = getBranches().getBranch(branchHead);
-        Revision base = branchHead;
+        RevisionVector base = branchHead;
         if (b != null) {
-            base = b.getBase(branchHead);
+            base = b.getBase(branchHead.getBranchRevision());
         }
         int numBranchCommits = b != null ? b.getCommits().size() : 1;
+        RevisionVector newHead;
         boolean success = false;
         MergeCommit commit = newMergeCommit(base, numBranchCommits);
         try {
@@ -1387,22 +1345,21 @@ public final class DocumentNodeStore
                     getBranches().remove(b);
                 } else {
                     NodeDocument root = Utils.getRootDocument(store);
-                    Revision conflictRev = root.getMostRecentConflictFor(b.getCommits(), this);
+                    Set<Revision> conflictRevs = root.getConflictsFor(b.getCommits());
                     String msg = "Conflicting concurrent change. Update operation failed: " + op;
-                    throw new ConflictException(msg, conflictRev).asCommitFailedException();
+                    throw new ConflictException(msg, conflictRevs).asCommitFailedException();
                 }
             } else {
                 // no commits in this branch -> do nothing
             }
+            newHead = done(commit, false, info);
             success = true;
         } finally {
             if (!success) {
                 canceled(commit);
-            } else {
-                done(commit, false, info);
             }
         }
-        return commit.getRevision();
+        return newHead;
     }
 
     /**
@@ -1494,7 +1451,7 @@ public final class DocumentNodeStore
     @Nonnull
     @Override
     public DocumentNodeState getRoot() {
-        return getRoot(headRevision);
+        return root;
     }
 
     @Nonnull
@@ -1518,6 +1475,7 @@ public final class DocumentNodeStore
     }
 
     @Override
+    @Nonnull
     public BlobStoreBlob createBlob(InputStream inputStream) throws IOException {
         return new BlobStoreBlob(blobStore, blobStore.writeBlob(inputStream));
     }
@@ -1530,7 +1488,7 @@ public final class DocumentNodeStore
      * @return the blob.
      */
     @Override
-    public Blob getBlob(String reference) {
+    public Blob getBlob(@Nonnull String reference) {
         String blobId = blobStore.getBlobId(reference);
         if(blobId != null){
             return new BlobStoreBlob(blobStore, blobId);
@@ -1578,19 +1536,13 @@ public final class DocumentNodeStore
     @CheckForNull
     @Override
     public NodeState retrieve(@Nonnull String checkpoint) {
-        Revision r;
-        try {
-            r = Revision.fromString(checkpoint);
-        } catch (IllegalArgumentException e) {
-            LOG.warn("Malformed checkpoint reference: {}", checkpoint);
-            return null;
-        }
-        SortedMap<Revision, Info> checkpoints = this.checkpoints.getCheckpoints();
-        if (checkpoints != null && checkpoints.containsKey(r)) {
-            return getRoot(r);
-        } else {
+        RevisionVector rv = getCheckpoints().retrieve(checkpoint);
+        if (rv == null) {
             return null;
         }
+        // make sure all changes up to checkpoint are visible
+        suspendUntilAll(Sets.newHashSet(rv));
+        return getRoot(rv);
     }
 
     @Override
@@ -1612,18 +1564,13 @@ public final class DocumentNodeStore
     }
 
     @Override
-    public Revision.RevisionComparator getRevisionComparator() {
-        return revisionComparator;
-    }
-
-    @Override
     public int getClusterId() {
         return clusterId;
     }
 
     @Nonnull
-    public Revision getHeadRevision() {
-        return headRevision;
+    public RevisionVector getHeadRevision() {
+        return root.getRevision();
     }
 
     @Nonnull
@@ -1729,49 +1676,53 @@ public final class DocumentNodeStore
     }
 
     /**
-     * Updates the state about cluster nodes in {@link #activeClusterNodes}
-     * and {@link #inactiveClusterNodes}.
+     * Updates the state about cluster nodes in {@link #clusterNodes}.
+     *
      * @return true if the cluster state has changed, false if the cluster state
      * remained unchanged
      */
     boolean updateClusterState() {
         boolean hasChanged = false;
-        long now = clock.getTime();
-        Set<Integer> inactive = Sets.newHashSet();
+        Set<Integer> clusterIds = Sets.newHashSet();
         for (ClusterNodeInfoDocument doc : ClusterNodeInfoDocument.all(store)) {
             int cId = doc.getClusterId();
-            if (cId != this.clusterId && !doc.isActive()) {
-                inactive.add(cId);
-            } else {
-                hasChanged |= activeClusterNodes.put(cId, doc.getLeaseEndTime())==null;
+            clusterIds.add(cId);
+            ClusterNodeInfoDocument old = clusterNodes.get(cId);
+            // do not replace document for inactive cluster node
+            // in order to keep the created timestamp of the document
+            // for the time when the cluster node was first seen inactive
+            if (old != null && !old.isActive() && !doc.isActive()) {
+                continue;
+            }
+            clusterNodes.put(cId, doc);
+            if (old == null || old.isActive() != doc.isActive()) {
+                hasChanged = true;
             }
         }
-        hasChanged |= activeClusterNodes.keySet().removeAll(inactive);
-        hasChanged |= inactiveClusterNodes.keySet().retainAll(inactive);
-        for (Integer clusterId : inactive) {
-            hasChanged |= inactiveClusterNodes.putIfAbsent(clusterId, now)==null;
-        }
+        hasChanged |= clusterNodes.keySet().retainAll(clusterIds);
         return hasChanged;
     }
 
     /**
-     * Returns the cluster nodes currently known to be inactive.
-     *
-     * @return a map with the cluster id as key and the time in millis when it
-     *          was first seen inactive.
-     */
-    Map<Integer, Long> getInactiveClusterNodes() {
-        return new HashMap<Integer, Long>(inactiveClusterNodes);
-    }
-
-    /**
-     * Returns the cluster nodes currently known as active.
-     *
-     * @return a map with the cluster id as key and the time in millis when the
-     *          lease ends.
+     * @return the minimum revisions of foreign cluster nodes since they were
+     *          started. The revision is derived from the start time of the
+     *          cluster node.
      */
-    Map<Integer, Long> getActiveClusterNodes() {
-        return new HashMap<Integer, Long>(activeClusterNodes);
+    @Nonnull
+    RevisionVector getMinExternalRevisions() {
+        return new RevisionVector(transform(filter(clusterNodes.values(),
+                new Predicate<ClusterNodeInfoDocument>() {
+                    @Override
+                    public boolean apply(ClusterNodeInfoDocument input) {
+                        return input.getClusterId() != getClusterId();
+                    }
+                }),
+                new Function<ClusterNodeInfoDocument, Revision>() {
+            @Override
+            public Revision apply(ClusterNodeInfoDocument input) {
+                return new Revision(input.getStartTime(), 0, input.getClusterId());
+            }
+        }));
     }
 
     /**
@@ -1787,17 +1738,12 @@ public final class DocumentNodeStore
         }
         alignWithExternalRevisions(doc);
 
-        Revision.RevisionComparator revisionComparator = getRevisionComparator();
-        // the (old) head occurred first
-        Revision headSeen = Revision.newRevision(0);
-        // then we saw this new revision (from another cluster node)
-        Revision otherSeen = Revision.newRevision(0);
-
         StringSort externalSort = JournalEntry.newSorter();
 
         Map<Integer, Revision> lastRevMap = doc.getLastRev();
         try {
-            Map<Revision, Revision> externalChanges = Maps.newHashMap();
+            RevisionVector headRevision = getHeadRevision();
+            Set<Revision> externalChanges = Sets.newHashSet();
             for (Map.Entry<Integer, Revision> e : lastRevMap.entrySet()) {
                 int machineId = e.getKey();
                 if (machineId == clusterId) {
@@ -1805,20 +1751,18 @@ public final class DocumentNodeStore
                     continue;
                 }
                 Revision r = e.getValue();
-                Revision last = lastKnownRevision.get(machineId);
-                if (last == null || r.compareRevisionTime(last) > 0) {
-                    lastKnownRevision.put(machineId, r);
+                Revision last = headRevision.getRevision(machineId);
+                if (last == null) {
+                    // make sure we see all changes when a cluster node joins
+                    last = new Revision(0, 0, machineId);
+                }
+                if (r.compareRevisionTime(last) > 0) {
                     // OAK-2345
                     // only consider as external change if
-                    // - the revision changed for the machineId
-                    // or
-                    // - the revision is within the time frame we remember revisions
-                    if (last != null
-                            || r.getTimestamp() > revisionPurgeMillis()) {
-                        externalChanges.put(r, otherSeen);
-                    }
+                    // the revision changed for the machineId
+                    externalChanges.add(r);
                     // collect external changes
-                    if (last != null && externalSort != null) {
+                    if (externalSort != null) {
                         // add changes for this particular clusterId to the externalSort
                         try {
                             fillExternalChanges(externalSort, last, r, store);
@@ -1871,30 +1815,24 @@ public final class DocumentNodeStore
                 stats.cacheInvalidationTime = clock.getTime() - time;
                 time = clock.getTime();
 
-                // make sure update to revision comparator is atomic
-                // and no local commit is in progress
+                // make sure no local commit is in progress
                 backgroundOperationLock.writeLock().lock();
                 try {
                     stats.lock = clock.getTime() - time;
 
-                    // the latest revisions of the current cluster node
-                    // happened before the latest revisions of other cluster nodes
-                    revisionComparator.add(newRevision(), headSeen);
-                    // then we saw other revisions
-                    for (Map.Entry<Revision, Revision> e : externalChanges.entrySet()) {
-                        revisionComparator.add(e.getKey(), e.getValue());
+                    RevisionVector oldHead = getHeadRevision();
+                    RevisionVector newHead = oldHead;
+                    for (Revision r : externalChanges) {
+                        newHead = newHead.update(r);
                     }
-
-                    Revision oldHead = headRevision;
-                    // the new head revision is after other revisions
-                    setHeadRevision(newRevision());
+                    setRoot(newHead);
                     commitQueue.headRevisionChanged();
                     time = clock.getTime();
                     if (externalSort != null) {
                         // then there were external changes and reading them
                         // was successful -> apply them to the diff cache
                         try {
-                            JournalEntry.applyTo(externalSort, diffCache, oldHead, headRevision);
+                            JournalEntry.applyTo(externalSort, diffCache, oldHead, newHead);
                         } catch (Exception e1) {
                             LOG.error("backgroundRead: Exception while processing external changes from journal: {}", e1, e1);
                         }
@@ -1907,13 +1845,10 @@ public final class DocumentNodeStore
                     backgroundOperationLock.writeLock().unlock();
                 }
                 stats.dispatchChanges = clock.getTime() - time;
-                time = clock.getTime();
             }
         } finally {
             IOUtils.closeQuietly(externalSort);
         }
-        revisionComparator.purge(revisionPurgeMillis());
-        stats.purge = clock.getTime() - time;
 
         return stats;
     }
@@ -1925,7 +1860,6 @@ public final class DocumentNodeStore
         long populateDiffCache;
         long lock;
         long dispatchChanges;
-        long purge;
 
         @Override
         public String toString() {
@@ -1940,21 +1874,10 @@ public final class DocumentNodeStore
                     ", diff: " + populateDiffCache +
                     ", lock:" + lock +
                     ", dispatch:" + dispatchChanges +
-                    ", purge:" + purge +
                     '}';
         }
     }
 
-    /**
-     * Returns the time in milliseconds when revisions can be purged from the
-     * revision comparator.
-     *
-     * @return time in milliseconds.
-     */
-    private static long revisionPurgeMillis() {
-        return Revision.getCurrentTimestamp() - REMEMBER_REVISION_ORDER_MILLIS;
-    }
-
     private void cleanOrphanedBranches() {
         Branch b;
         while ((b = branches.pollOrphanedBranch()) != null) {
@@ -1975,7 +1898,7 @@ public final class DocumentNodeStore
         if (root == null) {
             return;
         }
-        Revision head = getHeadRevision();
+        RevisionVector head = getHeadRevision();
         Map<Revision, String> map = root.getLocalMap(NodeDocument.COLLISIONS);
         UpdateOp op = new UpdateOp(id, false);
         for (Revision r : map.keySet()) {
@@ -1985,7 +1908,7 @@ public final class DocumentNodeStore
                 // head. That is, the collision cannot be related to commit
                 // which is progress.
                 if (branches.getBranchCommit(r) == null 
-                        && isRevisionNewer(head, r)) {
+                        && !head.isRevisionNewer(r)) {
                     NodeDocument.removeCollision(op, r);
                 }
             }
@@ -1997,7 +1920,7 @@ public final class DocumentNodeStore
     }
 
     private void backgroundSplit() {
-        Revision head = getHeadRevision();
+        RevisionVector head = getHeadRevision();
         for (Iterator<String> it = splitCandidates.keySet().iterator(); it.hasNext();) {
             String id = it.next();
             NodeDocument doc = store.find(Collection.NODES, id);
@@ -2046,30 +1969,18 @@ public final class DocumentNodeStore
     //-----------------------------< internal >---------------------------------
 
     /**
-     * Performs an initial read of the _lastRevs on the root document,
-     * initializes the {@link #revisionComparator} and sets the head revision.
+     * Performs an initial read of the _lastRevs on the root document and sets
+     * the root state.
      *
      * @param rootDoc the current root document.
      */
-    private void initializeHeadRevision(NodeDocument rootDoc) {
-        checkState(headRevision == null);
+    private void initializeRootState(NodeDocument rootDoc) {
+        checkState(root == null);
 
         alignWithExternalRevisions(rootDoc);
-        Map<Integer, Revision> lastRevMap = rootDoc.getLastRev();
-        Revision seenAt = Revision.newRevision(0);
-        long purgeMillis = revisionPurgeMillis();
-        for (Map.Entry<Integer, Revision> entry : lastRevMap.entrySet()) {
-            Revision r = entry.getValue();
-            if (r.getTimestamp() > purgeMillis) {
-                revisionComparator.add(r, seenAt);
-            }
-            if (entry.getKey() == clusterId) {
-                continue;
-            }
-            lastKnownRevision.put(entry.getKey(), entry.getValue());
-        }
-        revisionComparator.purge(purgeMillis);
-        setHeadRevision(newRevision());
+        RevisionVector headRevision = new RevisionVector(
+                rootDoc.getLastRev().values()).update(newRevision());
+        setRoot(headRevision);
     }
 
     /**
@@ -2108,7 +2019,7 @@ public final class DocumentNodeStore
     }
 
     @Nonnull
-    private Commit newTrunkCommit(@Nonnull Revision base) {
+    private Commit newTrunkCommit(@Nonnull RevisionVector base) {
         checkArgument(!checkNotNull(base).isBranch(),
                 "base must not be a branch revision: " + base);
 
@@ -2128,7 +2039,7 @@ public final class DocumentNodeStore
     }
 
     @Nonnull
-    private Commit newBranchCommit(@Nonnull Revision base,
+    private Commit newBranchCommit(@Nonnull RevisionVector base,
                                    @Nullable DocumentNodeStoreBranch branch) {
         checkArgument(checkNotNull(base).isBranch(),
                 "base must be a branch revision: " + base);
@@ -2200,7 +2111,8 @@ public final class DocumentNodeStore
      * and that list does not have the given child node. A <code>false</code> indicates that node <i>might</i>
      * exist
      */
-    private boolean checkNodeNotExistsFromChildrenCache(String path, Revision rev) {
+    private boolean checkNodeNotExistsFromChildrenCache(String path,
+                                                        RevisionVector rev) {
         if (PathUtils.denotesRoot(path)) {
             return false;
         }
@@ -2247,8 +2159,8 @@ public final class DocumentNodeStore
         final long getChildrenDoneIn = debug ? now() : 0;
 
         String diffAlgo;
-        Revision fromRev = from.getLastRevision();
-        Revision toRev = to.getLastRevision();
+        RevisionVector fromRev = from.getLastRevision();
+        RevisionVector toRev = to.getLastRevision();
         if (!fromChildren.hasMore && !toChildren.hasMore) {
             diffAlgo = "diffFewChildren";
             diffFewChildren(w, from.getPath(), fromChildren,
@@ -2279,10 +2191,11 @@ public final class DocumentNodeStore
         return diff;
     }
 
-    private void diffManyChildren(JsopWriter w, String path, Revision fromRev, Revision toRev) {
-        long minTimestamp = Math.min(
-                revisionComparator.getMinimumTimestamp(fromRev, inactiveClusterNodes),
-                revisionComparator.getMinimumTimestamp(toRev, inactiveClusterNodes));
+    private void diffManyChildren(JsopWriter w, String path,
+                                  RevisionVector fromRev,
+                                  RevisionVector toRev) {
+        long minTimestamp = Utils.getMinTimestampForDiff(
+                fromRev, toRev, getMinExternalRevisions());
         long minValue = NodeDocument.getModifiedInSecs(minTimestamp);
         String fromKey = Utils.getKeyLowerLimit(path);
         String toKey = Utils.getKeyUpperLimit(path);
@@ -2299,9 +2212,10 @@ public final class DocumentNodeStore
         // also consider nodes with not yet stored modifications (OAK-1107)
         Revision minRev = new Revision(minTimestamp, 0, getClusterId());
         addPathsForDiff(path, paths, getPendingModifications().getPaths(minRev));
-        for (Revision r : new Revision[]{fromRev, toRev}) {
-            if (r.isBranch()) {
-                Branch b = branches.getBranch(r);
+        for (RevisionVector rv : new RevisionVector[]{fromRev, toRev}) {
+            if (rv.isBranch()) {
+                Revision r = rv.getBranchRevision();
+                Branch b = branches.getBranch(rv);
                 if (b != null) {
                     addPathsForDiff(path, paths, b.getModifiedPathsUntil(r));
                 }
@@ -2319,8 +2233,8 @@ public final class DocumentNodeStore
                 if (toNode != null) {
                     // exists in both revisions
                     // check if different
-                    Revision a = fromNode.getLastRevision();
-                    Revision b = toNode.getLastRevision();
+                    RevisionVector a = fromNode.getLastRevision();
+                    RevisionVector b = toNode.getLastRevision();
                     if (a == null && b == null) {
                         // ok
                     } else if (a == null || b == null || !a.equals(b)) {
@@ -2357,7 +2271,11 @@ public final class DocumentNodeStore
         }
     }
 
-    private void diffFewChildren(JsopWriter w, String parentPath, DocumentNodeState.Children fromChildren, Revision fromRev, DocumentNodeState.Children toChildren, Revision toRev) {
+    private void diffFewChildren(JsopWriter w, String parentPath,
+                                 DocumentNodeState.Children fromChildren,
+                                 RevisionVector fromRev,
+                                 DocumentNodeState.Children toChildren,
+                                 RevisionVector toRev) {
         Set<String> childrenSet = Sets.newHashSet(toChildren.children);
         for (String n : fromChildren.children) {
             if (!childrenSet.contains(n)) {
@@ -2386,7 +2304,7 @@ public final class DocumentNodeStore
     }
 
     private static PathRev childNodeCacheKey(@Nonnull String path,
-                                             @Nonnull Revision readRevision,
+                                             @Nonnull RevisionVector readRevision,
                                              @Nullable String name) {
         String p = (name == null ? "" : name) + path;
         return new PathRev(p, readRevision);
@@ -2417,7 +2335,8 @@ public final class DocumentNodeStore
         // of this commit i.e. transient nodes. If its required it would need to be looked
         // into
 
-        DocumentNodeState newNode = new DocumentNodeState(this, targetPath, commit.getRevision());
+        RevisionVector destRevision = commit.getBaseRevision().update(commit.getRevision());
+        DocumentNodeState newNode = new DocumentNodeState(this, targetPath, destRevision);
         source.copyTo(newNode);
 
         commit.addNode(newNode);
@@ -2431,15 +2350,6 @@ public final class DocumentNodeStore
         }
     }
 
-    private void checkRevisionAge(Revision r, String path) {
-        if (LOG.isDebugEnabled()) {
-            if ("/".equals(path) && headRevision.getTimestamp() - r.getTimestamp() > WARN_REVISION_AGE) {
-                LOG.debug("Requesting an old revision for path " + path + ", " +
-                        ((headRevision.getTimestamp() - r.getTimestamp()) / 1000) + " seconds old");
-            }
-        }
-    }
-
     /**
      * Creates and returns a MarkSweepGarbageCollector if the current BlobStore
      * supports garbage collection
@@ -2502,12 +2412,12 @@ public final class DocumentNodeStore
 
         @Override
         public String getRevisionComparatorState() {
-            return revisionComparator.toString();
+            return "";
         }
 
         @Override
         public String getHead(){
-            return headRevision.toString();
+            return getRoot().getRevision().toString();
         }
 
         @Override
@@ -2522,33 +2432,50 @@ public final class DocumentNodeStore
 
         @Override
         public String[] getInactiveClusterNodes() {
-            return toArray(transform(inactiveClusterNodes.entrySet(),
-                    new Function<Map.Entry<Integer, Long>, String>() {
+            return toArray(transform(filter(clusterNodes.values(),
+                    new Predicate<ClusterNodeInfoDocument>() {
+                        @Override
+                        public boolean apply(ClusterNodeInfoDocument input) {
+                            return !input.isActive();
+                        }
+                    }),
+                    new Function<ClusterNodeInfoDocument, String>() {
                         @Override
-                        public String apply(Map.Entry<Integer, Long> input) {
-                            return input.toString();
+                        public String apply(ClusterNodeInfoDocument input) {
+                            return input.getClusterId() + "=" + input.getCreated();
                         }
                     }), String.class);
         }
 
         @Override
         public String[] getActiveClusterNodes() {
-            return toArray(transform(activeClusterNodes.entrySet(),
-                    new Function<Map.Entry<Integer, Long>, String>() {
+            return toArray(transform(filter(clusterNodes.values(),
+                    new Predicate<ClusterNodeInfoDocument>() {
                         @Override
-                        public String apply(Map.Entry<Integer, Long> input) {
-                            return input.toString();
+                        public boolean apply(ClusterNodeInfoDocument input) {
+                            return input.isActive();
+                        }
+                    }),
+                    new Function<ClusterNodeInfoDocument, String>() {
+                        @Override
+                        public String apply(ClusterNodeInfoDocument input) {
+                            return input.getClusterId() + "=" + input.getLeaseEndTime();
                         }
                     }), String.class);
         }
 
         @Override
         public String[] getLastKnownRevisions() {
-            return toArray(transform(lastKnownRevision.entrySet(),
-                    new Function<Map.Entry<Integer, Revision>, String>() {
+            return toArray(transform(filter(getHeadRevision(), new Predicate<Revision>() {
+                        @Override
+                        public boolean apply(Revision input) {
+                            return input.getClusterId() != getClusterId();
+                        }
+                    }),
+                    new Function<Revision, String>() {
                         @Override
-                        public String apply(Map.Entry<Integer, Revision> input) {
-                            return input.toString();
+                        public String apply(Revision input) {
+                            return input.getClusterId() + "=" + input.toString();
                         }
                     }), String.class);
         }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranch.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranch.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranch.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranch.java Thu Jan  7 12:46:35 2016
@@ -34,6 +34,8 @@ import java.util.concurrent.locks.ReadWr
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
@@ -279,7 +281,7 @@ class DocumentNodeStoreBranch implements
             CommitInfo info) {
         boolean success = false;
         Commit c = store.newCommit(base.getRevision(), this);
-        Revision rev;
+        RevisionVector rev;
         try {
             op.with(c);
             if (c.isEmpty()) {
@@ -287,12 +289,11 @@ class DocumentNodeStoreBranch implements
                 // finally clause cancel the commit
                 return base;
             }
-            rev = c.apply();
+            c.apply();
+            rev = store.done(c, base.getRevision().isBranch(), info);
             success = true;
         } finally {
-            if (success) {
-                store.done(c, base.getRevision().isBranch(), info);
-            } else {
+            if (!success) {
                 store.canceled(c);
             }
         }
@@ -546,7 +547,7 @@ class DocumentNodeStoreBranch implements
          * @return the branch state.
          */
         final DocumentNodeState createBranch(DocumentNodeState state) {
-            return store.getRoot(state.getRevision().asBranchRevision());
+            return store.getRoot(state.getRevision().asBranchRevision(store.getClusterId()));
         }
 
         @Override
@@ -641,8 +642,15 @@ class DocumentNodeStoreBranch implements
                 return;
             }
             NodeDocument doc = Utils.getRootDocument(store.getDocumentStore());
-            Set<Revision> collisions = doc.getLocalMap(COLLISIONS).keySet();
-            Set<Revision> conflicts = Sets.intersection(collisions, b.getCommits());
+            Set<Revision> collisions = Sets.newHashSet(doc.getLocalMap(COLLISIONS).keySet());
+            Set<Revision> commits = Sets.newHashSet(Iterables.transform(b.getCommits(),
+                    new Function<Revision, Revision>() {
+                        @Override
+                        public Revision apply(Revision input) {
+                            return input.asTrunkRevision();
+                        }
+                    }));
+            Set<Revision> conflicts = Sets.intersection(collisions, commits);
             if (!conflicts.isEmpty()) {
                 throw new CommitFailedException(STATE, 2,
                         "Conflicting concurrent change on branch commits " + conflicts);

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreMBean.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreMBean.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreMBean.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreMBean.java Thu Jan  7 12:46:35 2016
@@ -25,6 +25,7 @@ import org.apache.jackrabbit.oak.commons
 public interface DocumentNodeStoreMBean {
     String TYPE = "DocumentNodeStore";
 
+    @Deprecated
     String getRevisionComparatorState();
 
     String getHead();

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=1723532&r1=1723531&r2=1723532&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 Thu Jan  7 12:46:35 2016
@@ -93,8 +93,8 @@ public final class JournalEntry extends
 
     static void applyTo(@Nonnull StringSort externalSort,
                         @Nonnull DiffCache diffCache,
-                        @Nonnull Revision from,
-                        @Nonnull Revision to) throws IOException {
+                        @Nonnull RevisionVector from,
+                        @Nonnull RevisionVector to) throws IOException {
         LOG.debug("applyTo: starting for {} to {}", from, to);
         // note that it is not de-duplicated yet
         LOG.debug("applyTo: sorting done.");

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevs.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevs.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevs.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevs.java Thu Jan  7 12:46:35 2016
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.oak.plugins.document;
 
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 
 import javax.annotation.CheckForNull;
@@ -28,17 +29,19 @@ import org.apache.jackrabbit.oak.plugins
 /**
  * Helper class to track when a node was last modified.
  */
-final class LastRevs {
+final class LastRevs implements Iterable<Revision> {
 
     private final Map<Integer, Revision> revs;
 
-    private final Revision readRevision;
+    private final RevisionVector readRevision;
 
     private final Branch branch;
 
     private Revision branchRev;
 
-    LastRevs(Map<Integer, Revision> revs, Revision readRevision, Branch branch) {
+    LastRevs(Map<Integer, Revision> revs,
+             RevisionVector readRevision,
+             Branch branch) {
         this.revs = new HashMap<Integer, Revision>(revs);
         this.readRevision = readRevision;
         this.branch = branch;
@@ -60,7 +63,7 @@ final class LastRevs {
         }
         rev = rev.asBranchRevision();
         if (branch != null && branch.containsCommit(rev)
-                && readRevision.compareRevisionTime(rev) >= 0) {
+                && readRevision.getBranchRevision().compareRevisionTime(rev) >= 0) {
             branchRev = Utils.max(branchRev, rev);
         }
     }
@@ -70,8 +73,8 @@ final class LastRevs {
         return branchRev;
     }
 
-    @Nonnull
-    Map<Integer, Revision> get() {
-        return revs;
+    @Override
+    public Iterator<Revision> iterator() {
+        return revs.values().iterator();
     }
 }

Modified: 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=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java Thu Jan  7 12:46:35 2016
@@ -54,8 +54,8 @@ public class LocalDiffCache extends Diff
     }
 
     @Override
-    public String getChanges(@Nonnull Revision from,
-                             @Nonnull Revision to,
+    public String getChanges(@Nonnull RevisionVector from,
+                             @Nonnull RevisionVector to,
                              @Nonnull String path,
                              @Nullable Loader loader) {
         RevisionsKey key = new RevisionsKey(from, to);
@@ -72,8 +72,8 @@ public class LocalDiffCache extends Diff
 
     @Nonnull
     @Override
-    public Entry newEntry(final @Nonnull Revision from,
-                          final @Nonnull Revision to,
+    public Entry newEntry(final @Nonnull RevisionVector from,
+                          final @Nonnull RevisionVector to,
                           boolean local /*ignored*/) {
         return new Entry() {
             private final Map<String, String> changesPerPath = Maps.newHashMap();

Modified: 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=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java Thu Jan  7 12:46:35 2016
@@ -56,8 +56,8 @@ public class MemoryDiffCache extends Dif
 
     @CheckForNull
     @Override
-    public String getChanges(@Nonnull final Revision from,
-                             @Nonnull final Revision to,
+    public String getChanges(@Nonnull final RevisionVector from,
+                             @Nonnull final RevisionVector to,
                              @Nonnull final String path,
                              @Nullable final Loader loader) {
         PathRev key = diffCacheKey(path, from, to);
@@ -89,8 +89,8 @@ public class MemoryDiffCache extends Dif
 
     @Nonnull
     @Override
-    public Entry newEntry(@Nonnull Revision from,
-                          @Nonnull Revision to,
+    public Entry newEntry(@Nonnull RevisionVector from,
+                          @Nonnull RevisionVector to,
                           boolean local /*ignored*/) {
         return new MemoryEntry(from, to);
     }
@@ -103,10 +103,10 @@ public class MemoryDiffCache extends Dif
 
     protected class MemoryEntry implements Entry {
 
-        private final Revision from;
-        private final Revision to;
+        private final RevisionVector from;
+        private final RevisionVector to;
 
-        protected MemoryEntry(Revision from, Revision to) {
+        protected MemoryEntry(RevisionVector from, RevisionVector to) {
             this.from = checkNotNull(from);
             this.to = checkNotNull(to);
         }
@@ -124,8 +124,8 @@ public class MemoryDiffCache extends Dif
     }
 
     private static PathRev diffCacheKey(@Nonnull String path,
-                                        @Nonnull Revision from,
-                                        @Nonnull Revision to) {
+                                        @Nonnull RevisionVector from,
+                                        @Nonnull RevisionVector to) {
         return new PathRev(from + path, to);
     }
 
@@ -142,15 +142,15 @@ public class MemoryDiffCache extends Dif
      * @return {@code true} if there are cache entries that indicate the node
      *      at the given path was modified between the two revisions.
      */
-    private boolean isUnchanged(@Nonnull final Revision from,
-                                @Nonnull final Revision to,
+    private boolean isUnchanged(@Nonnull final RevisionVector from,
+                                @Nonnull final RevisionVector to,
                                 @Nonnull final String path) {
         return !denotesRoot(path)
                 && isChildUnchanged(from, to, getParentPath(path), getName(path));
     }
 
-    private boolean isChildUnchanged(@Nonnull final Revision from,
-                                     @Nonnull final Revision to,
+    private boolean isChildUnchanged(@Nonnull final RevisionVector from,
+                                     @Nonnull final RevisionVector to,
                                      @Nonnull final String parent,
                                      @Nonnull final String name) {
         PathRev parentKey = diffCacheKey(parent, from, to);

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MergeCommit.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MergeCommit.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MergeCommit.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MergeCommit.java Thu Jan  7 12:46:35 2016
@@ -33,7 +33,7 @@ class MergeCommit extends Commit {
     private final Set<Revision> branchCommits = Sets.newHashSet();
 
     MergeCommit(DocumentNodeStore nodeStore,
-                Revision baseRevision,
+                RevisionVector baseRevision,
                 SortedSet<Revision> revisions) {
         super(nodeStore, revisions.last(), baseRevision, null);
         this.mergeRevs = revisions;
@@ -52,7 +52,7 @@ class MergeCommit extends Commit {
     }
 
     @Override
-    public void applyToCache(Revision before, boolean isBranchCommit) {
+    public void applyToCache(RevisionVector before, boolean isBranchCommit) {
         // do nothing for a merge commit, only notify node
         // store about merged revisions
         nodeStore.revisionsMerged(branchCommits);