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 [1/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...

Author: mreutegg
Date: Thu Jan  7 12:46:35 2016
New Revision: 1723532

URL: http://svn.apache.org/viewvc?rev=1723532&view=rev
Log:
OAK-3646: Inconsistent read of hierarchy

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/RevisionVector.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateTest.java   (with props)
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RevisionVectorTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/BatchCommitQueue.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java
    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/CommitDiff.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitQueue.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentCheckpointMBean.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranch.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreMBean.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/document/LastRevs.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/MergeCommit.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Revision.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/RevisionContext.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitOperations.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/StableRevisionComparator.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CheckpointsTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ClusterRevisionComparisonTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/ClusterTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitQueueTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingTieredDiffCache.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DummyRevisionContext.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/document/JournalTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgentTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoDocumentStoreTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/OrphanedBranchTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/RevisionTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranchTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/CollisionMarkerTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BroadcastTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKeyTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java
    jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/BatchCommitQueue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/BatchCommitQueue.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/BatchCommitQueue.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/BatchCommitQueue.java Thu Jan  7 12:46:35 2016
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.oak.plugins.document;
 
-import java.util.Comparator;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
@@ -49,12 +48,8 @@ final class BatchCommitQueue {
 
     private final DocumentStore store;
 
-    private final Comparator<Revision> comparator;
-
-    BatchCommitQueue(@Nonnull DocumentStore store,
-                     @Nonnull Comparator<Revision> comparator) {
+    BatchCommitQueue(@Nonnull DocumentStore store) {
         this.store = checkNotNull(store);
-        this.comparator = checkNotNull(comparator);
     }
 
     Callable<NodeDocument> updateDocument(UpdateOp op) {
@@ -103,8 +98,4 @@ final class BatchCommitQueue {
     DocumentStore getStore() {
         return store;
     }
-
-    Comparator<Revision> getComparator() {
-        return comparator;
-    }
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java Thu Jan  7 12:46:35 2016
@@ -53,7 +53,7 @@ class Branch {
     /**
      * The initial base revision of this branch.
      */
-    private final Revision base;
+    private final RevisionVector base;
 
     /**
      * The branch reference.
@@ -76,7 +76,7 @@ class Branch {
      * @throws IllegalArgumentException if base is a branch revision.
      */
     Branch(@Nonnull SortedSet<Revision> commits,
-           @Nonnull Revision base,
+           @Nonnull RevisionVector base,
            @Nonnull ReferenceQueue<Object> queue,
            @Nullable Object guard) {
         checkArgument(!checkNotNull(base).isBranch(), "base is not a trunk revision: %s", base);
@@ -97,7 +97,7 @@ class Branch {
      * @return the initial base of this branch.
      */
     @Nonnull
-    Revision getBase() {
+    RevisionVector getBase() {
         return base;
     }
 
@@ -110,7 +110,7 @@ class Branch {
      *                                  this branch.
      */
     @Nonnull
-    Revision getBase(@Nonnull Revision r) {
+    RevisionVector getBase(@Nonnull Revision r) {
         BranchCommit c = commits.get(checkNotNull(r).asBranchRevision());
         if (c == null) {
             throw new IllegalArgumentException(
@@ -127,11 +127,11 @@ class Branch {
      * @throws IllegalArgumentException if head is a trunk revision or base is a
      *                                  branch revision.
      */
-    void rebase(@Nonnull Revision head, @Nonnull Revision base) {
+    void rebase(@Nonnull Revision head, @Nonnull RevisionVector base) {
         checkArgument(checkNotNull(head).isBranch(), "Not a branch revision: %s", head);
         checkArgument(!checkNotNull(base).isBranch(), "Not a trunk revision: %s", base);
         Revision last = commits.lastKey();
-        checkArgument(commits.comparator().compare(head, last) > 0);
+        checkArgument(head.compareRevisionTime(last) > 0);
         commits.put(head, new RebaseCommit(base, head, commits));
     }
 
@@ -294,15 +294,15 @@ class Branch {
      */
     abstract static class BranchCommit implements LastRevTracker {
 
-        protected final Revision base;
+        protected final RevisionVector base;
         protected final Revision commit;
 
-        BranchCommit(Revision base, Revision commit) {
+        BranchCommit(RevisionVector base, Revision commit) {
             this.base = base;
             this.commit = commit;
         }
 
-        Revision getBase() {
+        RevisionVector getBase() {
             return base;
         }
 
@@ -322,7 +322,7 @@ class Branch {
 
         private final Set<String> modifications = Sets.newHashSet();
 
-        BranchCommitImpl(Revision base, Revision commit) {
+        BranchCommitImpl(RevisionVector base, Revision commit) {
             super(base, commit);
         }
 
@@ -365,7 +365,7 @@ class Branch {
 
         private final NavigableMap<Revision, BranchCommit> previous;
 
-        RebaseCommit(Revision base, Revision commit,
+        RebaseCommit(RevisionVector base, Revision commit,
                      NavigableMap<Revision, BranchCommit> previous) {
             super(base, commit);
             this.previous = squash(previous);

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/Checkpoints.java Thu Jan  7 12:46:35 2016
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.Atomi
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
 import org.apache.jackrabbit.oak.commons.json.JsopReader;
@@ -37,12 +38,17 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.Maps;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 
 /**
  * Checkpoints provide details around which revision are to be kept. These
  * are stored in Settings collection.
  */
 class Checkpoints {
+
+    private static final Logger LOG = LoggerFactory.getLogger(Checkpoints.class);
+
     private static final String ID = "checkpoint";
 
     /**
@@ -76,10 +82,11 @@ class Checkpoints {
     public Revision create(long lifetimeInMillis, Map<String, String> info) {
         // create a unique dummy commit we can use as checkpoint revision
         Revision r = nodeStore.commitQueue.createRevision();
+        final RevisionVector[] rv = new RevisionVector[1];
         nodeStore.commitQueue.done(r, new CommitQueue.Callback() {
             @Override
             public void headOfQueue(@Nonnull Revision revision) {
-                // do nothing
+                rv[0] = nodeStore.getHeadRevision();
             }
         });
         createCounter.getAndIncrement();
@@ -88,7 +95,7 @@ class Checkpoints {
         long endTime = BigInteger.valueOf(nodeStore.getClock().getTime())
                 .add(BigInteger.valueOf(lifetimeInMillis))
                 .min(BigInteger.valueOf(Long.MAX_VALUE)).longValue();
-        op.setMapEntry(PROP_CHECKPOINT, r, new Info(endTime, info).toString());
+        op.setMapEntry(PROP_CHECKPOINT, r, new Info(endTime, rv[0], info).toString());
         store.createOrUpdate(Collection.SETTINGS, op);
         return r;
     }
@@ -113,7 +120,7 @@ class Checkpoints {
         //Get uncached doc
         SortedMap<Revision, Info> checkpoints = getCheckpoints();
 
-        if(checkpoints == null){
+        if(checkpoints.isEmpty()){
             log.debug("No checkpoint registered so far");
             return null;
         }
@@ -142,24 +149,53 @@ class Checkpoints {
     }
 
     @SuppressWarnings("unchecked")
-    @CheckForNull
+    @Nonnull
     SortedMap<Revision, Info> getCheckpoints() {
         Document cdoc = store.find(Collection.SETTINGS, ID, 0);
-        SortedMap<Revision, String> data =
-                (SortedMap<Revision, String>) cdoc.get(PROP_CHECKPOINT);
-        if (data == null) {
+        SortedMap<Revision, String> data = null;
+        if (cdoc != null) {
+            data = (SortedMap<Revision, String>) cdoc.get(PROP_CHECKPOINT);
+        }
+        SortedMap<Revision, Info> checkpoints = Maps.newTreeMap(StableRevisionComparator.REVERSE);
+        if (data != null) {
+            for (Map.Entry<Revision, String> entry : data.entrySet()) {
+                checkpoints.put(entry.getKey(), Info.fromString(entry.getValue()));
+            }
+        }
+        return checkpoints;
+    }
+
+    /**
+     * Retrieves the head revision for the given {@code checkpoint}.
+     *
+     * @param checkpoint the checkpoint reference.
+     * @return the head revision associated with the checkpoint or {@code null}
+     *      if there is no such checkpoint.
+     * @throws IllegalArgumentException if the checkpoint is malformed.
+     */
+    @CheckForNull
+    RevisionVector retrieve(@Nonnull String checkpoint)
+            throws IllegalArgumentException {
+        Revision r;
+        try {
+            r = Revision.fromString(checkNotNull(checkpoint));
+        } catch (IllegalArgumentException e) {
+            LOG.warn("Malformed checkpoint reference: {}", checkpoint);
             return null;
         }
-        SortedMap<Revision, Info> checkpoints = Maps.newTreeMap(data.comparator());
-        for (Map.Entry<Revision, String> entry : data.entrySet()) {
-            checkpoints.put(entry.getKey(), Info.fromString(entry.getValue()));
+        Info info = getCheckpoints().get(r);
+        if (info == null) {
+            return null;
         }
-        return checkpoints;
+        RevisionVector rv = info.getCheckpoint();
+        if (rv == null) {
+            rv = expand(r);
+        }
+        return rv;
     }
 
-    int size(){
-        SortedMap<Revision, Info> checkpoints = getCheckpoints();
-        return checkpoints == null ? 0 : checkpoints.size();
+    int size() {
+        return getCheckpoints().size();
     }
 
     /**
@@ -182,21 +218,47 @@ class Checkpoints {
         }
     }
 
+    private RevisionVector expand(Revision checkpoint) {
+        LOG.warn("Expanding {} single revision checkpoint into a " +
+                "RevisionVector. Please make sure all cluster nodes run " +
+                "with the same Oak version.", checkpoint);
+        // best effort conversion
+        Map<Integer, Revision> revs = Maps.newHashMap();
+        RevisionVector head = nodeStore.getHeadRevision();
+        for (Revision r : head) {
+            int cId = r.getClusterId();
+            if (cId == checkpoint.getClusterId()) {
+                revs.put(cId, r);
+            } else {
+                revs.put(cId, new Revision(checkpoint.getTimestamp(), 0, cId));
+            }
+        }
+        return head.pmin(new RevisionVector(revs.values()));
+    }
+
     static final class Info {
 
         private static final String EXPIRES = "expires";
 
+        private static final String REVISION_VECTOR = "rv";
+
         private final long expiryTime;
 
+        private final RevisionVector checkpoint;
+
         private final Map<String, String> info;
 
-        private Info(long expiryTime, Map<String, String> info) {
+        private Info(long expiryTime,
+                     @Nullable  RevisionVector checkpoint,
+                     @Nonnull Map<String, String> info) {
             this.expiryTime = expiryTime;
+            this.checkpoint = checkpoint;
             this.info = Collections.unmodifiableMap(info);
         }
 
         static Info fromString(String info) {
             long expiryTime;
+            RevisionVector rv = null;
             Map<String, String> map;
             if (info.startsWith("{")) {
                 map = Maps.newHashMap();
@@ -212,7 +274,19 @@ class Checkpoints {
                 while (reader.matches(',')) {
                     key = reader.readString();
                     reader.read(':');
-                    map.put(key, reader.readString());
+                    String value = reader.readString();
+                    // second entry is potentially checkpoint revision vector
+                    if (rv == null && map.isEmpty() && REVISION_VECTOR.equals(key)) {
+                        // try to read checkpoint
+                        try {
+                            rv = RevisionVector.fromString(value);
+                        } catch (IllegalArgumentException e) {
+                            // not a revision vector, read as regular info entry
+                            map.put(key, value);
+                        }
+                    } else {
+                        map.put(key, value);
+                    }
                 }
                 reader.read('}');
                 reader.read(JsopReader.END);
@@ -221,7 +295,7 @@ class Checkpoints {
                 map = Collections.emptyMap();
                 expiryTime = Long.parseLong(info);
             }
-            return new Info(expiryTime, map);
+            return new Info(expiryTime, rv, map);
         }
 
         Map<String, String> get() {
@@ -232,11 +306,26 @@ class Checkpoints {
             return expiryTime;
         }
 
+        /**
+         * The revision vector associated with this checkpoint or {@code null}
+         * if this checkpoint was created with a version of Oak, which did not
+         * yet support revision vectors.
+         *
+         * @return the revision vector checkpoint or {@code null}.
+         */
+        @CheckForNull
+        RevisionVector getCheckpoint() {
+            return checkpoint;
+        }
+
         @Override
         public String toString() {
             JsopWriter writer = new JsopBuilder();
             writer.object();
             writer.key(EXPIRES).value(Long.toString(expiryTime));
+            if (checkpoint != null) {
+                writer.key(REVISION_VECTOR).value(checkpoint.toString());
+            }
             for (Map.Entry<String, String> entry : info.entrySet()) {
                 writer.key(entry.getKey()).value(entry.getValue());
             }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfo.java Thu Jan  7 12:46:35 2016
@@ -71,6 +71,11 @@ public class ClusterNodeInfo {
     public static final String LEASE_END_KEY = "leaseEnd";
 
     /**
+     * The start time.
+     */
+    public static final String START_TIME_KEY = "startTime";
+
+    /**
      * The key for the root-revision of the last background write (of unsaved
      * modifications) - that is: the last root-revision written by the instance
      * in case of a clear shutdown or via recovery of another instance in case
@@ -385,6 +390,7 @@ public class ClusterNodeInfo {
             } else {
                 update.set(LEASE_END_KEY, clusterNode.leaseEndTime);
             }
+            update.set(START_TIME_KEY, clusterNode.startTime);
             update.set(INFO_KEY, clusterNode.toString());
             update.set(STATE, clusterNode.state.name());
             update.set(REV_RECOVERY_LOCK, clusterNode.revRecoveryLock.name());

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/ClusterNodeInfoDocument.java Thu Jan  7 12:46:35 2016
@@ -39,10 +39,34 @@ public class ClusterNodeInfoDocument ext
      */
     private static final String MAX_ID_VALUE = "a";
 
+    /**
+     * The timestamp when this document was created.
+     */
+    private final long created = Revision.getCurrentTimestamp();
+
+    /**
+     * @return the timestamp when this document was created.
+     */
+    public long getCreated() {
+        return created;
+    }
+
     public long getLeaseEndTime(){
         return checkNotNull((Long) get(ClusterNodeInfo.LEASE_END_KEY), "Lease End Time not set");
     }
 
+    /**
+     * @return the time when this cluster node was started or {@code -1} if not
+     *          available.
+     */
+    public long getStartTime() {
+        Long startTime = (Long) get(ClusterNodeInfo.START_TIME_KEY);
+        if (startTime == null) {
+            startTime = -1L;
+        }
+        return startTime;
+    }
+
     public boolean isActive(){
         return getState() == ClusterNodeState.ACTIVE;
     }

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=1723532&r1=1723531&r2=1723532&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 Jan  7 12:46:35 2016
@@ -31,10 +31,13 @@ import javax.annotation.Nullable;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Sets;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.commons.json.JsopStream;
 import org.apache.jackrabbit.oak.commons.json.JsopWriter;
 import org.apache.jackrabbit.oak.plugins.document.util.Utils;
 import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,7 +50,6 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
 import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.COLLISIONS;
 import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.SPLIT_CANDIDATE_THRESHOLD;
-import static org.apache.jackrabbit.oak.plugins.document.util.Utils.isRevisionNewer;
 
 /**
  * A higher level object representing a commit.
@@ -58,7 +60,7 @@ public class Commit {
 
     protected final DocumentNodeStore nodeStore;
     private final DocumentNodeStoreBranch branch;
-    private final Revision baseRevision;
+    private final RevisionVector baseRevision;
     private final Revision revision;
     private final HashMap<String, UpdateOp> operations = new LinkedHashMap<String, UpdateOp>();
     private final JsopWriter diff = new JsopStream();
@@ -90,7 +92,7 @@ public class Commit {
      */
     Commit(@Nonnull DocumentNodeStore nodeStore,
            @Nonnull Revision revision,
-           @Nullable Revision baseRevision,
+           @Nullable RevisionVector baseRevision,
            @Nullable DocumentNodeStoreBranch branch) {
         this.nodeStore = checkNotNull(nodeStore);
         this.revision = checkNotNull(revision);
@@ -129,7 +131,7 @@ public class Commit {
      * @return the base revision of this commit or <code>null</code>.
      */
     @CheckForNull
-    Revision getBaseRevision() {
+    RevisionVector getBaseRevision() {
         return baseRevision;
     }
 
@@ -167,7 +169,7 @@ public class Commit {
             LOG.error(msg);
             throw new DocumentStoreException(msg);
         }
-        operations.put(path, n.asOperation(true));
+        operations.put(path, n.asOperation(revision));
         addedNodes.add(path);
     }
 
@@ -178,13 +180,11 @@ public class Commit {
     /**
      * Applies this commit to the store.
      *
-     * @return the commit revision.
      * @throws DocumentStoreException if the commit cannot be applied.
      */
-    @Nonnull
-    Revision apply() throws DocumentStoreException {
+    void apply() throws DocumentStoreException {
         boolean success = false;
-        Revision baseRev = getBaseRevision();
+        RevisionVector baseRev = getBaseRevision();
         boolean isBranch = baseRev != null && baseRev.isBranch();
         Revision rev = getRevision();
         if (isBranch && !nodeStore.isDisableBranches()) {
@@ -218,10 +218,6 @@ public class Commit {
         } else {
             applyInternal();
         }
-        if (isBranch) {
-            rev = rev.asBranchRevision();
-        }
-        return rev;
     }
 
     /**
@@ -235,7 +231,7 @@ public class Commit {
         }
     }
 
-    private void prepare(Revision baseRevision) {
+    private void prepare(RevisionVector baseRevision) {
         if (!operations.isEmpty()) {
             updateParentChildStatus();
             updateBinaryStatus();
@@ -271,7 +267,7 @@ public class Commit {
      * @param baseBranchRevision the base revision of this commit. Currently only
      *                     used for branch commits.
      */
-    private void applyToDocumentStore(Revision baseBranchRevision) {
+    private void applyToDocumentStore(RevisionVector baseBranchRevision) {
         // the value in _revisions.<revision> property of the commit root node
         // regular commits use "c", which makes the commit visible to
         // other readers. branch commits use the base revision to indicate
@@ -402,8 +398,8 @@ public class Commit {
                             dse = new DocumentStoreException(msg);
                         } else {
                             dse = new ConflictException(msg,
-                                    commitRootDoc.getMostRecentConflictFor(
-                                        Collections.singleton(revision), nodeStore));
+                                    commitRootDoc.getConflictsFor(
+                                        Collections.singleton(revision)));
                         }
                         throw dse;
                     } else {
@@ -513,7 +509,7 @@ public class Commit {
         if (baseRevision != null) {
             Revision newestRev = null;
             if (before != null) {
-                Revision base = baseRevision;
+                RevisionVector base = baseRevision;
                 if (nodeStore.isDisableBranches()) {
                     base = base.asTrunkRevision();
                 }
@@ -536,7 +532,7 @@ public class Commit {
                     conflictMessage = "The node " +
                             op.getId() + " was already added in revision\n" +
                             formatConflictRevision(newestRev);
-                } else if (nodeStore.isRevisionNewer(newestRev, baseRevision)
+                } else if (baseRevision.isRevisionNewer(newestRev)
                         && (op.isDelete() || isConflicting(before, op))) {
                     conflictMessage = "The node " +
                             op.getId() + " was changed in revision\n" +
@@ -576,9 +572,7 @@ public class Commit {
                 conflictMessage += ", before\n" + revision;
                 if (LOG.isDebugEnabled()) {
                     LOG.debug(conflictMessage  + "; document:\n" +
-                            (before == null ? "" : before.format()) +
-                            ",\nrevision order:\n" +
-                            nodeStore.getRevisionComparator());
+                            (before == null ? "" : before.format()));
                 }
                 throw new ConflictException(conflictMessage, conflictRevision);
             }
@@ -586,7 +580,7 @@ public class Commit {
     }
 
     private String formatConflictRevision(Revision r) {
-        if (isRevisionNewer(nodeStore, r, nodeStore.getHeadRevision())) {
+        if (nodeStore.getHeadRevision().isRevisionNewer(r)) {
             return r + " (not yet visible)";
         } else {
             return r.toString();
@@ -612,7 +606,7 @@ public class Commit {
             // or document did not exist before
             return false;
         }
-        return doc.isConflicting(op, baseRevision, revision, nodeStore,
+        return doc.isConflicting(op, baseRevision, revision,
                 nodeStore.getEnableConcurrentAddRemove());
     }
 
@@ -644,7 +638,8 @@ public class Commit {
             return null;
         }
         if (b == null) {
-            b = nodeStore.getBranches().getBranch(revision);
+            b = nodeStore.getBranches().getBranch(
+                    new RevisionVector(revision.asBranchRevision()));
         }
         return b;
     }
@@ -655,7 +650,7 @@ public class Commit {
      * @param before the revision right before this commit.
      * @param isBranchCommit whether this is a commit to a branch
      */
-    public void applyToCache(Revision before, boolean isBranchCommit) {
+    public void applyToCache(RevisionVector before, boolean isBranchCommit) {
         HashMap<String, ArrayList<String>> nodesWithChangedChildren = new HashMap<String, ArrayList<String>>();
         for (String p : modifiedNodes) {
             if (denotesRoot(p)) {
@@ -669,7 +664,8 @@ public class Commit {
             }
             list.add(p);
         }
-        DiffCache.Entry cacheEntry = nodeStore.getDiffCache().newEntry(before, revision, true);
+        RevisionVector after = before.update(revision);
+        DiffCache.Entry cacheEntry = nodeStore.getDiffCache().newEntry(before, after, true);
         LastRevTracker tracker = nodeStore.createTracker(revision, isBranchCommit);
         List<String> added = new ArrayList<String>();
         List<String> removed = new ArrayList<String>();
@@ -696,7 +692,7 @@ public class Commit {
                 // track intermediate node and root
                 tracker.track(path);
             }
-            nodeStore.applyChanges(revision, path, isNew,
+            nodeStore.applyChanges(after, path, isNew,
                     added, removed, changed, cacheEntry);
         }
         cacheEntry.done();
@@ -733,11 +729,14 @@ public class Commit {
         diff.tag('-').value(path).newline();
     }
 
-    public void removeNode(String path) {
+    public void removeNode(String path, NodeState state) {
         removedNodes.add(path);
         UpdateOp op = getUpdateOperationForNode(path);
         op.setDelete(true);
         NodeDocument.setDeleted(op, revision, true);
+        for (PropertyState p : state.getProperties()) {
+            updateProperty(path, p.getName(), null);
+        }
     }
 
     private static final Function<UpdateOp.Key, String> KEY_TO_NAME =

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitDiff.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitDiff.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitDiff.java Thu Jan  7 12:46:35 2016
@@ -83,7 +83,8 @@ class CommitDiff implements NodeStateDif
     @Override
     public boolean childNodeAdded(String name, NodeState after) {
         String p = PathUtils.concat(path, name);
-        commit.addNode(new DocumentNodeState(store, p, commit.getRevision()));
+        commit.addNode(new DocumentNodeState(store, p,
+                new RevisionVector(commit.getRevision())));
         return after.compareAgainstBaseState(EMPTY_NODE,
                 new CommitDiff(store, commit, p, builder, blobs));
     }
@@ -100,7 +101,7 @@ class CommitDiff implements NodeStateDif
     @Override
     public boolean childNodeDeleted(String name, NodeState before) {
         String p = PathUtils.concat(path, name);
-        commit.removeNode(p);
+        commit.removeNode(p, before);
         return MISSING_NODE.compareAgainstBaseState(before,
                 new CommitDiff(store, commit, p, builder, blobs));
     }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitQueue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitQueue.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitQueue.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitQueue.java Thu Jan  7 12:46:35 2016
@@ -19,7 +19,6 @@ package org.apache.jackrabbit.oak.plugin
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
@@ -116,14 +115,13 @@ final class CommitQueue {
      * @param conflictRevisions the revisions to become visible.
      */
     void suspendUntilAll(@Nonnull Set<Revision> conflictRevisions) {
-        Comparator<Revision> comparator = context.getRevisionComparator();
         Semaphore s;
         int addedRevisions;
         synchronized (suspendedCommits) {
-            Revision headRevision = context.getHeadRevision();
+            RevisionVector headRevision = context.getHeadRevision();
             Set<Revision> afterHead = new HashSet<Revision>(conflictRevisions.size());
             for (Revision r : conflictRevisions) {
-                if (comparator.compare(r, headRevision) > 0) {
+                if (headRevision.isRevisionNewer(r)) {
                     afterHead.add(r);
                 }
             }
@@ -182,11 +180,11 @@ final class CommitQueue {
             if (suspendedCommits.isEmpty()) {
                 return;
             }
-            Revision headRevision = context.getHeadRevision();
+            RevisionVector headRevision = context.getHeadRevision();
             Iterator<SuspendedCommit> it = suspendedCommits.values().iterator();
             while (it.hasNext()) {
                 SuspendedCommit suspended = it.next();
-                if (suspended.removeRevisionsYoungerThan(headRevision) && suspended.revisions.isEmpty()) {
+                if (suspended.removeRevisionsVisibleFrom(headRevision) && suspended.revisions.isEmpty()) {
                     it.remove();
                 }
             }
@@ -308,12 +306,11 @@ final class CommitQueue {
             this.revisions = revisions;
         }
 
-        private boolean removeRevisionsYoungerThan(Revision revision) {
-            Comparator<Revision> comparator = context.getRevisionComparator();
+        private boolean removeRevisionsVisibleFrom(RevisionVector revision) {
             Iterator<Revision> it = revisions.iterator();
             boolean removed = false;
             while (it.hasNext()) {
-                if (comparator.compare(it.next(), revision) <= 0) {
+                if (!revision.isRevisionNewer(it.next())) {
                     it.remove();
                     semaphore.release();
                     removed = true;

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java Thu Jan  7 12:46:35 2016
@@ -49,8 +49,8 @@ abstract class DiffCache {
      * @return the diff or {@code null} if unknown and no loader was passed.
      */
     @CheckForNull
-    abstract String getChanges(@Nonnull Revision from,
-                               @Nonnull Revision to,
+    abstract String getChanges(@Nonnull RevisionVector from,
+                               @Nonnull RevisionVector to,
                                @Nonnull String path,
                                @Nullable Loader loader);
 
@@ -65,8 +65,8 @@ abstract class DiffCache {
      * @return the cache entry.
      */
     @Nonnull
-    abstract Entry newEntry(@Nonnull Revision from,
-                            @Nonnull Revision to,
+    abstract Entry newEntry(@Nonnull RevisionVector from,
+                            @Nonnull RevisionVector to,
                             boolean local);
 
     /**
@@ -77,7 +77,7 @@ abstract class DiffCache {
 
     /**
      * Parses the jsop diff returned by
-     * {@link #getChanges(Revision, Revision, String, Loader)} and reports the
+     * {@link #getChanges(RevisionVector, RevisionVector, String, Loader)} and reports the
      * changes by calling the appropriate methods on {@link Diff}.
      *
      * @param jsop the jsop diff to parse.

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentCheckpointMBean.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentCheckpointMBean.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentCheckpointMBean.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentCheckpointMBean.java Thu Jan  7 12:46:35 2016
@@ -44,9 +44,6 @@ public class DocumentCheckpointMBean ext
     @Override
     protected void collectCheckpoints(TabularDataSupport tab) throws OpenDataException {
         Map<Revision, Info> checkpoints = store.getCheckpoints().getCheckpoints();
-        if (checkpoints == null) {
-            checkpoints = Collections.emptyMap();
-        }
 
         for (Entry<Revision, Info> checkpoint : checkpoints.entrySet()) {
             String id = checkpoint.getKey().toString();

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=1723532&r1=1723531&r2=1723532&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 Jan  7 12:46:35 2016
@@ -183,8 +183,8 @@ public class DocumentMK {
         if (path == null || path.equals("")) {
             path = "/";
         }
-        Revision fromRev = Revision.fromString(fromRevisionId);
-        Revision toRev = Revision.fromString(toRevisionId);
+        RevisionVector fromRev = RevisionVector.fromString(fromRevisionId);
+        RevisionVector toRev = RevisionVector.fromString(toRevisionId);
         final DocumentNodeState before = nodeStore.getNode(path, fromRev);
         final DocumentNodeState after = nodeStore.getNode(path, toRev);
         if (before == null || after == null) {
@@ -206,7 +206,7 @@ public class DocumentMK {
             throw new DocumentStoreException("Path is not absolute: " + path);
         }
         revisionId = revisionId != null ? revisionId : nodeStore.getHeadRevision().toString();
-        Revision rev = Revision.fromString(revisionId);
+        RevisionVector rev = RevisionVector.fromString(revisionId);
         DocumentNodeState n;
         try {
             n = nodeStore.getNode(path, rev);
@@ -223,7 +223,7 @@ public class DocumentMK {
             throw new DocumentStoreException("Only depth 0 is supported, depth is " + depth);
         }
         revisionId = revisionId != null ? revisionId : nodeStore.getHeadRevision().toString();
-        Revision rev = Revision.fromString(revisionId);
+        RevisionVector rev = RevisionVector.fromString(revisionId);
         try {
             DocumentNodeState n = nodeStore.getNode(path, rev);
             if (n == null) {
@@ -267,22 +267,21 @@ public class DocumentMK {
     public String commit(String rootPath, String jsonDiff, String baseRevId,
             String message) throws DocumentStoreException {
         boolean success = false;
-        boolean isBranch = false;
-        Revision rev;
-        Commit commit = nodeStore.newCommit(baseRevId != null ? Revision.fromString(baseRevId) : null, null);
+        boolean isBranch;
+        RevisionVector rev;
+        Commit commit = nodeStore.newCommit(baseRevId != null ? RevisionVector.fromString(baseRevId) : null, null);
         try {
-            Revision baseRev = commit.getBaseRevision();
+            RevisionVector baseRev = commit.getBaseRevision();
             isBranch = baseRev != null && baseRev.isBranch();
             parseJsonDiff(commit, jsonDiff, rootPath);
-            rev = commit.apply();
+            commit.apply();
+            rev = nodeStore.done(commit, isBranch, null);
             success = true;
         } catch (DocumentStoreException e) {
             throw new DocumentStoreException(e);
         } finally {
             if (!success) {
                 nodeStore.canceled(commit);
-            } else {
-                nodeStore.done(commit, isBranch, null);
             }
         }
         return rev.toString();
@@ -291,15 +290,15 @@ public class DocumentMK {
     public String branch(@Nullable String trunkRevisionId) throws DocumentStoreException {
         // nothing is written when the branch is created, the returned
         // revision simply acts as a reference to the branch base revision
-        Revision revision = trunkRevisionId != null
-                ? Revision.fromString(trunkRevisionId) : nodeStore.getHeadRevision();
-        return revision.asBranchRevision().toString();
+        RevisionVector revision = trunkRevisionId != null
+                ? RevisionVector.fromString(trunkRevisionId) : nodeStore.getHeadRevision();
+        return revision.asBranchRevision(nodeStore.getClusterId()).toString();
     }
 
     public String merge(String branchRevisionId, String message)
             throws DocumentStoreException {
         // TODO improve implementation if needed
-        Revision revision = Revision.fromString(branchRevisionId);
+        RevisionVector revision = RevisionVector.fromString(branchRevisionId);
         if (!revision.isBranch()) {
             throw new DocumentStoreException("Not a branch: " + branchRevisionId);
         }
@@ -316,9 +315,9 @@ public class DocumentMK {
     public String rebase(@Nonnull String branchRevisionId,
                          @Nullable String newBaseRevisionId)
             throws DocumentStoreException {
-        Revision r = Revision.fromString(branchRevisionId);
-        Revision base = newBaseRevisionId != null ?
-                Revision.fromString(newBaseRevisionId) :
+        RevisionVector r = RevisionVector.fromString(branchRevisionId);
+        RevisionVector base = newBaseRevisionId != null ?
+                RevisionVector.fromString(newBaseRevisionId) :
                 nodeStore.getHeadRevision();
         return nodeStore.rebase(r, base).toString();
     }
@@ -327,11 +326,11 @@ public class DocumentMK {
     public String reset(@Nonnull String branchRevisionId,
                         @Nonnull String ancestorRevisionId)
             throws DocumentStoreException {
-        Revision branch = Revision.fromString(branchRevisionId);
+        RevisionVector branch = RevisionVector.fromString(branchRevisionId);
         if (!branch.isBranch()) {
             throw new DocumentStoreException("Not a branch revision: " + branchRevisionId);
         }
-        Revision ancestor = Revision.fromString(ancestorRevisionId);
+        RevisionVector ancestor = RevisionVector.fromString(ancestorRevisionId);
         if (!ancestor.isBranch()) {
             throw new DocumentStoreException("Not a branch revision: " + ancestorRevisionId);
         }
@@ -377,7 +376,7 @@ public class DocumentMK {
     //------------------------------< internal >--------------------------------
 
     private void parseJsonDiff(Commit commit, String json, String rootPath) {
-        Revision baseRev = commit.getBaseRevision();
+        RevisionVector baseRev = commit.getBaseRevision();
         String baseRevId = baseRev != null ? baseRev.toString() : null;
         Set<String> added = Sets.newHashSet();
         JsopReader t = new JsopTokenizer(json);
@@ -399,7 +398,7 @@ public class DocumentMK {
                     if (toRemove == null) {
                         throw new DocumentStoreException("Node not found: " + path + " in revision " + baseRevId);
                     }
-                    commit.removeNode(path);
+                    commit.removeNode(path, toRemove);
                     nodeStore.markAsDeleted(toRemove, commit, true);
                     commit.removeNodeDiff(path);
                     break;
@@ -460,7 +459,8 @@ public class DocumentMK {
     }
 
     private void parseAddNode(Commit commit, JsopReader t, String path) {
-        DocumentNodeState n = new DocumentNodeState(nodeStore, path, commit.getRevision());
+        DocumentNodeState n = new DocumentNodeState(nodeStore, path,
+                new RevisionVector(commit.getRevision()));
         if (!t.matches('}')) {
             do {
                 String key = t.readString();

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java?rev=1723532&r1=1723531&r2=1723532&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java Thu Jan  7 12:46:35 2016
@@ -81,9 +81,9 @@ public class DocumentNodeState extends A
     static final int MAX_FETCH_SIZE = INITIAL_FETCH_SIZE << 4;
 
     final String path;
-    final Revision rev;
-    Revision lastRevision;
-    final Revision rootRevision;
+    final RevisionVector readRevision;
+    RevisionVector lastRevision;
+    final RevisionVector rootRevision;
     final boolean fromExternalChange;
     final Map<String, PropertyState> properties;
     final boolean hasChildren;
@@ -92,29 +92,29 @@ public class DocumentNodeState extends A
 
     DocumentNodeState(@Nonnull DocumentNodeStore store,
                       @Nonnull String path,
-                      @Nonnull Revision rev) {
-        this(store, path, rev, false);
+                      @Nonnull RevisionVector readRevision) {
+        this(store, path, readRevision, false);
     }
 
     DocumentNodeState(@Nonnull DocumentNodeStore store, @Nonnull String path,
-                      @Nonnull Revision rev, boolean hasChildren) {
-        this(store, path, rev, new HashMap<String, PropertyState>(),
+                      @Nonnull RevisionVector readRevision, boolean hasChildren) {
+        this(store, path, readRevision, new HashMap<String, PropertyState>(),
                 hasChildren, null, null, false);
     }
 
     private DocumentNodeState(@Nonnull DocumentNodeStore store,
                               @Nonnull String path,
-                              @Nonnull Revision rev,
+                              @Nonnull RevisionVector readRevision,
                               @Nonnull Map<String, PropertyState> properties,
                               boolean hasChildren,
-                              @Nullable Revision lastRevision,
-                              @Nullable Revision rootRevision,
+                              @Nullable RevisionVector lastRevision,
+                              @Nullable RevisionVector rootRevision,
                               boolean fromExternalChange) {
         this.store = checkNotNull(store);
         this.path = checkNotNull(path);
-        this.rev = checkNotNull(rev);
+        this.readRevision = checkNotNull(readRevision);
         this.lastRevision = lastRevision;
-        this.rootRevision = rootRevision != null ? rootRevision : rev;
+        this.rootRevision = rootRevision != null ? rootRevision : readRevision;
         this.fromExternalChange = fromExternalChange;
         this.hasChildren = hasChildren;
         this.properties = checkNotNull(properties);
@@ -133,12 +133,12 @@ public class DocumentNodeState extends A
      * @return a copy of this node state with the given root revision and
      *          external change flag.
      */
-    private DocumentNodeState withRootRevision(@Nonnull Revision root,
+    private DocumentNodeState withRootRevision(@Nonnull RevisionVector root,
                                                boolean externalChange) {
         if (rootRevision.equals(root) && fromExternalChange == externalChange) {
             return this;
         } else {
-            return new DocumentNodeState(store, path, rev, properties,
+            return new DocumentNodeState(store, path, readRevision, properties,
                     hasChildren, lastRevision, root, externalChange);
         }
     }
@@ -149,7 +149,7 @@ public class DocumentNodeState extends A
      */
     @Nonnull
     DocumentNodeState fromExternalChange() {
-        return new DocumentNodeState(store, path, rev, properties, hasChildren,
+        return new DocumentNodeState(store, path, readRevision, properties, hasChildren,
                 lastRevision, rootRevision, true);
     }
 
@@ -162,8 +162,8 @@ public class DocumentNodeState extends A
     }
 
     @Nonnull
-    Revision getRevision() {
-        return rev;
+    RevisionVector getRevision() {
+        return readRevision;
     }
 
     /**
@@ -178,7 +178,7 @@ public class DocumentNodeState extends A
      *          same value as returned by {@link #getRevision()}.
      */
     @Nonnull
-    Revision getRootRevision() {
+    RevisionVector getRootRevision() {
         return rootRevision;
     }
 
@@ -295,9 +295,9 @@ public class DocumentNodeState extends A
     @Override
     public NodeBuilder builder() {
         if ("/".equals(getPath())) {
-            if (rev.isBranch()) {
+            if (readRevision.isBranch()) {
                 // check if this node state is head of a branch
-                Branch b = store.getBranches().getBranch(rev);
+                Branch b = store.getBranches().getBranch(readRevision);
                 if (b == null) {
                     if (store.isDisableBranches()) {
                         if (DocumentNodeStoreBranch.getCurrentBranch() != null) {
@@ -306,10 +306,10 @@ public class DocumentNodeState extends A
                             return new MemoryNodeBuilder(this);
                         }
                     } else {
-                        throw new IllegalStateException("No branch for revision: " + rev);
+                        throw new IllegalStateException("No branch for revision: " + readRevision);
                     }
                 }
-                if (b.isHead(rev)
+                if (b.isHead(readRevision.getBranchRevision())
                         && DocumentNodeStoreBranch.getCurrentBranch() != null) {
                     return new DocumentRootBuilder(this, store);
                 } else {
@@ -346,9 +346,9 @@ public class DocumentNodeState extends A
                             perfLogger
                                     .end(start,
                                             1,
-                                            "compareAgainstBaseState, path={}, rev={}, lastRevision={}, base.path={}, base.rev={}, base.lastRevision={}",
-                                            path, rev, lastRevision,
-                                            mBase.path, mBase.rev,
+                                            "compareAgainstBaseState, path={}, readRevision={}, lastRevision={}, base.path={}, base.readRevision={}, base.lastRevision={}",
+                                            path, readRevision, lastRevision,
+                                            mBase.path, mBase.readRevision,
                                             mBase.lastRevision);
                         }
                     }
@@ -400,26 +400,28 @@ public class DocumentNodeState extends A
     public String toString() {
         StringBuilder buff = new StringBuilder();
         buff.append("{ path: '").append(path).append("', ");
-        buff.append("rev: '").append(rev).append("', ");
+        buff.append("readRevision: '").append(readRevision).append("', ");
         buff.append("properties: '").append(properties.values()).append("' }");
         return buff.toString();
     }
 
     /**
-     * Create an add node operation for this node.
+     * Create an add operation for this node at the given revision.
+     *
+     * @param revision the revision this node is created.
      */
-    UpdateOp asOperation(boolean isNew) {
+    UpdateOp asOperation(@Nonnull Revision revision) {
         String id = Utils.getIdFromPath(path);
-        UpdateOp op = new UpdateOp(id, isNew);
+        UpdateOp op = new UpdateOp(id, true);
         op.set(Document.ID, id);
         if (Utils.isLongPath(path)) {
             op.set(NodeDocument.PATH, path);
         }
-        NodeDocument.setModified(op, rev);
-        NodeDocument.setDeleted(op, rev, false);
+        NodeDocument.setModified(op, revision);
+        NodeDocument.setDeleted(op, revision, false);
         for (String p : properties.keySet()) {
             String key = Utils.escapePropertyName(p);
-            op.setMapEntry(key, rev, getPropertyAsString(p));
+            op.setMapEntry(key, revision, getPropertyAsString(p));
         }
         return op;
     }
@@ -441,17 +443,21 @@ public class DocumentNodeState extends A
         }
     }
 
-    void setLastRevision(Revision lastRevision) {
+    void setLastRevision(RevisionVector lastRevision) {
         this.lastRevision = lastRevision;
     }
 
-    Revision getLastRevision() {
+    RevisionVector getLastRevision() {
         return lastRevision;
     }
 
     @Override
     public int getMemory() {
-        int size = 164 + estimateMemoryUsage(path);
+        int size = 40 // shallow
+                + readRevision.getMemory()
+                + (lastRevision != null ? lastRevision.getMemory() : 0)
+                + rootRevision.getMemory()
+                + estimateMemoryUsage(path);
         // rough approximation for properties
         for (Map.Entry<String, PropertyState> entry : properties.entrySet()) {
             // name
@@ -461,10 +467,11 @@ public class DocumentNodeState extends A
                     && propState.getType() != Type.BINARIES) {
                 for (int i = 0; i < propState.count(); i++) {
                     // size() returns length of string
-                    // overhead:
+                    // shallow memory:
                     // - 8 bytes per reference in values list
                     // - 48 bytes per string
-                    size += 56 + propState.size(i) * 2;
+                    // double useage per property because of parsed PropertyState
+                    size += (56 + propState.size(i) * 2) * 2;
                 }
             } else {
                 // calculate size based on blobId value
@@ -481,14 +488,14 @@ public class DocumentNodeState extends A
 
     /**
      * Returns {@code true} if this state has the same revision as the
-     * {@code other} state. This method first compares the read {@link #rev}
+     * {@code other} state. This method first compares the {@link #readRevision}
      * and then the {@link #lastRevision}.
      *
      * @param other the other state to compare with.
      * @return {@code true} if the revisions are equal, {@code false} otherwise.
      */
     private boolean revisionEquals(DocumentNodeState other) {
-        return this.rev.equals(other.rev)
+        return this.readRevision.equals(other.readRevision)
                 || this.lastRevision.equals(other.lastRevision);
     }
 
@@ -529,12 +536,12 @@ public class DocumentNodeState extends A
     public String asString() {
         JsopWriter json = new JsopBuilder();
         json.key("path").value(path);
-        json.key("rev").value(rev.toString());
+        json.key("rev").value(readRevision.toString());
         if (lastRevision != null) {
             json.key("lastRev").value(lastRevision.toString());
         }
         if (hasChildren) {
-            json.key("hasChildren").value(hasChildren);
+            json.key("hasChildren").value(true);
         }
         if (properties.size() > 0) {
             json.key("prop").object();
@@ -549,8 +556,8 @@ public class DocumentNodeState extends A
     public static DocumentNodeState fromString(DocumentNodeStore store, String s) {
         JsopTokenizer json = new JsopTokenizer(s);
         String path = null;
-        Revision rev = null;
-        Revision lastRev = null;
+        RevisionVector rev = null;
+        RevisionVector lastRev = null;
         boolean hasChildren = false;
         DocumentNodeState state = null;
         HashMap<String, String> map = new HashMap<String, String>();
@@ -560,9 +567,9 @@ public class DocumentNodeState extends A
             if ("path".equals(k)) {
                 path = json.readString();
             } else if ("rev".equals(k)) {
-                rev = Revision.fromString(json.readString());
+                rev = RevisionVector.fromString(json.readString());
             } else if ("lastRev".equals(k)) {
-                lastRev = Revision.fromString(json.readString());
+                lastRev = RevisionVector.fromString(json.readString());
             } else if ("hasChildren".equals(k)) {
                 hasChildren = json.read() == JsopReader.TRUE;
             } else if ("prop".equals(k)) {