You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-dev@jackrabbit.apache.org by Angela Schreiber <an...@adobe.com> on 2013/09/12 19:12:13 UTC

Re: svn commit: r1522553 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/mongomk/ test/java/org/apache/jackrabbit/oak/plugins/mongomk/

hi marcel

could it be that this commit is related to the following test failure?
i experience it in a up to date checkout of the trunk.

regards
angela

concurrent[1](org.apache.jackrabbit.oak.jcr.ConcurrentFileOperationsTest)
Time elapsed: 0.473 sec  <<< ERROR!
javax.jcr.RepositoryException: OakKernel0001: Failed to merge changes to
the underlying MicroKernel
        at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(C
ommitFailedException.java:242)
        at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(C
ommitFailedException.java:207)
        at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.newRepositoryExcepti
on(SessionDelegate.java:434)
        at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegate
.java:297)
        at
org.apache.jackrabbit.oak.jcr.session.SessionImpl$8.perform(SessionImpl.jav
a:399)
        at
org.apache.jackrabbit.oak.jcr.session.SessionImpl$8.perform(SessionImpl.jav
a:396)
        at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.perform(SessionDeleg
ate.java:124)
        at
org.apache.jackrabbit.oak.jcr.session.SessionImpl.perform(SessionImpl.java:
117)
        at
org.apache.jackrabbit.oak.jcr.session.SessionImpl.save(SessionImpl.java:396
)
        at
org.apache.jackrabbit.oak.jcr.ConcurrentFileOperationsTest$1.run(Concurrent
FileOperationsTest.java:103)
        at java.lang.Thread.run(Thread.java:680)
Caused by: org.apache.jackrabbit.oak.api.CommitFailedException:
OakKernel0001: Failed to merge changes to the underlying MicroKernel
        at
org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch$Persisted.merge(Kern
elNodeStoreBranch.java:384)
        at
org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch.merge(KernelNodeStor
eBranch.java:136)
        at
org.apache.jackrabbit.oak.core.AbstractRoot$2.run(AbstractRoot.java:247)
        at
org.apache.jackrabbit.oak.core.AbstractRoot$2.run(AbstractRoot.java:243)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:337)
        at
org.apache.jackrabbit.oak.core.AbstractRoot.commit(AbstractRoot.java:242)
        at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegate
.java:295)
        ... 7 more
Caused by: org.apache.jackrabbit.mk.api.MicroKernelException: Conflicting
concurrent change. Update operation failed: key: 0:/ update
{_collisions.r1411315f24a-0-1=CONTAINS_MAP_ENTRY false,
_collisions.r1411315f251-0-1=CONTAINS_MAP_ENTRY false,
_collisions.r1411315f3f6-0-1=CONTAINS_MAP_ENTRY false,
_collisions.r1411315f402-0-1=CONTAINS_MAP_ENTRY false, _modified=SET
275800941, _revisions.r1411315f24a-0-1=SET_MAP_ENTRY c-r1411315f404-0-1,
_revisions.r1411315f251-0-1=SET_MAP_ENTRY c-r1411315f404-0-1,
_revisions.r1411315f3f6-0-1=SET_MAP_ENTRY c-r1411315f404-0-1,
_revisions.r1411315f402-0-1=SET_MAP_ENTRY c-r1411315f404-0-1}
        at
org.apache.jackrabbit.oak.plugins.mongomk.MongoMK.merge(MongoMK.java:1130)
        at
org.apache.jackrabbit.oak.kernel.KernelNodeStore.merge(KernelNodeStore.java
:208)
        at
org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch$Persisted.merge(Kern
elNodeStoreBranch.java:378)
        ... 14 more






On 9/12/13 2:45 PM, "mreutegg@apache.org" <mr...@apache.org> wrote:

>Author: mreutegg
>Date: Thu Sep 12 12:45:19 2013
>New Revision: 1522553
>
>URL: http://svn.apache.org/r1522553
>Log:
>OAK-926: MongoMK: split documents when they are too large
>- Generalize access to previous documents (WIP)
>
>Added:
>
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/Range.java   (with props)
>
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/ValueMap.java   (with props)
>Modified:
>
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/NodeDocument.java
>
>jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/DocumentSplitTest.java
>
>Modified:
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/NodeDocument.java
>URL:
>http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/o
>rg/apache/jackrabbit/oak/plugins/mongomk/NodeDocument.java?rev=1522553&r1=
>1522552&r2=1522553&view=diff
>==========================================================================
>====
>---
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/NodeDocument.java (original)
>+++
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/NodeDocument.java Thu Sep 12 12:45:19 2013
>@@ -16,8 +16,6 @@
>  */
> package org.apache.jackrabbit.oak.plugins.mongomk;
>
>-import java.util.AbstractMap;
>-import java.util.AbstractSet;
> import java.util.ArrayList;
> import java.util.Collections;
> import java.util.Comparator;
>@@ -44,10 +42,8 @@ import org.slf4j.LoggerFactory;
> import com.google.common.base.Function;
> import com.google.common.base.Predicate;
> import com.google.common.collect.Iterables;
>-import com.google.common.collect.Iterators;
> import com.google.common.collect.Maps;
>
>-import static com.google.common.base.Preconditions.checkArgument;
> import static com.google.common.base.Preconditions.checkNotNull;
>
> /**
>@@ -186,7 +182,7 @@ public class NodeDocument extends Docume
>      */
>     public boolean containsRevision(@Nonnull Revision revision) {
>         String rev = checkNotNull(revision).toString();
>-        if (getRevisionsMap().containsKey(rev)) {
>+        if (getLocalRevisions().containsKey(rev)) {
>             return true;
>         }
>         for (NodeDocument prev : getPreviousDocs(revision)) {
>@@ -219,7 +215,7 @@ public class NodeDocument extends Docume
>     public SortedMap<Revision, Revision>
>getUncommittedRevisions(RevisionContext context) {
>         // only look at revisions in this document.
>         // uncommitted revisions are not split off
>-        Map<String, String> valueMap = getRevisionsMap();
>+        Map<String, String> valueMap = getLocalRevisions();
>         SortedMap<Revision, Revision> revisions =
>                 new TreeMap<Revision,
>Revision>(context.getRevisionComparator());
>         for (Map.Entry<String, String> commit : valueMap.entrySet()) {
>@@ -244,11 +240,7 @@ public class NodeDocument extends Docume
>      */
>     @CheckForNull
>     public String getCommitRootPath(String revision) {
>-        @SuppressWarnings("unchecked")
>-        Map<String, Integer> valueMap = (Map<String, Integer>)
>get(COMMIT_ROOT);
>-        if (valueMap == null) {
>-            return null;
>-        }
>+        Map<String, Integer> valueMap = getCommitRoot();
>         Integer depth = valueMap.get(revision);
>         if (depth != null) {
>             String p = Utils.getPathFromId(getId());
>@@ -274,13 +266,9 @@ public class NodeDocument extends Docume
>                                       CollisionHandler handler) {
>         SortedSet<String> revisions = new
>TreeSet<String>(Collections.reverseOrder());
>         revisions.addAll(getRevisions().keySet());
>-        if (data.containsKey(COMMIT_ROOT)) {
>-            revisions.addAll(((Map<String, Integer>)
>get(COMMIT_ROOT)).keySet());
>-        }
>-        Map<String, String> deletedMap = (Map<String, String>)
>get(DELETED);
>-        if (deletedMap != null) {
>-            revisions.addAll(deletedMap.keySet());
>-        }
>+        revisions.addAll(getCommitRoot().keySet());
>+        Map<String, String> deletedMap = getDeleted();
>+        revisions.addAll(deletedMap.keySet());
>         Revision newestRev = null;
>         for (String r : revisions) {
>             Revision propRev = Revision.fromString(r);
>@@ -304,12 +292,10 @@ public class NodeDocument extends Docume
>         if (newestRev == null) {
>             return null;
>         }
>-        if (deletedMap != null) {
>-            String value = deletedMap.get(newestRev.toString());
>-            if ("true".equals(value)) {
>-                // deleted in the newest revision
>-                return null;
>-            }
>+        String value = deletedMap.get(newestRev.toString());
>+        if ("true".equals(value)) {
>+            // deleted in the newest revision
>+            return null;
>         }
>         return newestRev;
>     }
>@@ -444,9 +430,8 @@ public class NodeDocument extends Docume
>     public boolean isDeleted(RevisionContext context,
>                              Revision readRevision,
>                              Set<Revision> validRevisions) {
>-        @SuppressWarnings("unchecked")
>-        Map<String, String> valueMap = (Map<String, String>)
>get(NodeDocument.DELETED);
>-        if (valueMap == null) {
>+        Map<String, String> valueMap = getDeleted();
>+        if (valueMap.isEmpty()) {
>             return false;
>         }
>         if (valueMap instanceof NavigableMap) {
>@@ -487,9 +472,8 @@ public class NodeDocument extends Docume
>     @CheckForNull
>     public Revision getLiveRevision(RevisionContext context, Revision
>maxRev,
>                                     Set<Revision> validRevisions) {
>-        @SuppressWarnings("unchecked")
>-        Map<String, String> valueMap = (Map<String, String>)
>get(NodeDocument.DELETED);
>-        if (valueMap == null) {
>+        Map<String, String> valueMap = getDeleted();
>+        if (valueMap.isEmpty()) {
>             return null;
>         }
>         // first, search the newest deleted revision
>@@ -552,13 +536,10 @@ public class NodeDocument extends Docume
>                                  @Nonnull Revision baseRevision,
>                                  @Nonnull RevisionContext context) {
>         // did existence of node change after baseRevision?
>-        @SuppressWarnings("unchecked")
>-        Map<String, String> deleted = (Map<String, String>) get(DELETED);
>-        if (deleted != null) {
>-            for (Map.Entry<String, String> entry : deleted.entrySet()) {
>-                if (isRevisionNewer(context,
>Revision.fromString(entry.getKey()), baseRevision)) {
>-                    return true;
>-                }
>+        Map<String, String> deleted = getDeleted();
>+        for (Map.Entry<String, String> entry : deleted.entrySet()) {
>+            if (isRevisionNewer(context,
>Revision.fromString(entry.getKey()), baseRevision)) {
>+                return true;
>             }
>         }
>
>@@ -615,7 +596,7 @@ public class NodeDocument extends Docume
>         }
>         NavigableMap<Revision, String> splitRevs
>                 = new TreeMap<Revision,
>String>(context.getRevisionComparator());
>-        Map<String, String> revisions = getRevisionsMap();
>+        Map<String, String> revisions = getLocalRevisions();
>         // only consider if there are enough revisions
>         if (revisions.size() > REVISIONS_SPLIT_OFF_SIZE) {
>             // collect commits of this cluster node after the
>@@ -690,6 +671,88 @@ public class NodeDocument extends Docume
>         return super.transformAndSeal(map, key, level);
>     }
>
>+    /**
>+     * Returns previous revision ranges for this document. The revision
>keys are
>+     * sorted descending, newest first!
>+     *
>+     * @return the previous ranges for this document.
>+     */
>+    @Nonnull
>+    SortedMap<Revision, Range> getPreviousRanges() {
>+        @SuppressWarnings("unchecked")
>+        SortedMap<Revision, Range> previous = (SortedMap<Revision,
>Range>) get(PREVIOUS);
>+        if (previous == null) {
>+            previous = EMPTY_RANGE_MAP;
>+        }
>+        return previous;
>+    }
>+
>+    /**
>+     * Returns previous {@link NodeDocument}, which include the given
>revision.
>+     * If the <code>revision</code> is <code>null</code>, then all
>previous
>+     * documents are returned.
>+     *
>+     * @param revision the revision to match or <code>null</code>.
>+     * @return previous documents.
>+     */
>+    Iterable<NodeDocument> getPreviousDocs(final @Nullable Revision
>revision) {
>+        Iterable<NodeDocument> docs = Iterables.transform(
>+                Iterables.filter(getPreviousRanges().entrySet(),
>+                        new Predicate<Map.Entry<Revision, Range>>() {
>+                            @Override
>+                            public boolean apply(Map.Entry<Revision,
>Range> input) {
>+                                return revision == null ||
>input.getValue().includes(revision);
>+                            }
>+                        }), new Function<Map.Entry<Revision, Range>,
>NodeDocument>() {
>+            @Nullable
>+            @Override
>+            public NodeDocument apply(Map.Entry<Revision, Range> input) {
>+                Revision r = input.getKey();
>+                String prevId = Utils.getPreviousIdFor(getId(), r);
>+                NodeDocument prev = store.find(Collection.NODES, prevId);
>+                if (prev == null) {
>+                    LOG.warn("Document with previous revisions not
>found: " + prevId);
>+                }
>+                return prev;
>+            }
>+        });
>+        // filter out null docs and check if the revision is actually in
>there
>+        return Iterables.filter(docs, new Predicate<NodeDocument>() {
>+            @Override
>+            public boolean apply(@Nullable NodeDocument input) {
>+                if (input == null) {
>+                    return false;
>+                }
>+                return revision == null ||
>input.containsRevision(revision.toString());
>+            }
>+        });
>+    }
>+
>+    /**
>+     * Returns the local value map for the given key. Returns
><code>null</code>
>+     * if no such value map exists.
>+     *
>+     * @param key the key.
>+     * @return local value map.
>+     */
>+    @Nonnull
>+    Map<String, String> getLocalMap(String key) {
>+        @SuppressWarnings("unchecked")
>+        Map<String, String> map = (Map<String, String>) get(key);
>+        if (map == null) {
>+            map = Collections.emptyMap();
>+        }
>+        return map;
>+    }
>+
>+    /**
>+     * @return the {@link #REVISIONS} stored on this document.
>+     */
>+    @Nonnull
>+    Map<String, String> getLocalRevisions() {
>+        return getLocalMap(REVISIONS);
>+    }
>+
>     //-------------------------< UpdateOp modifiers
>>---------------------------
>
>     public static void setModified(@Nonnull UpdateOp op,
>@@ -745,15 +808,12 @@ public class NodeDocument extends Docume
>             return this;
>         }
>         // check commit root
>-        @SuppressWarnings("unchecked")
>-        Map<String, Integer> commitRoot = (Map<String, Integer>)
>get(COMMIT_ROOT);
>+        Map<String, Integer> commitRoot = getCommitRoot();
>         String commitRootPath = null;
>-        if (commitRoot != null) {
>-            Integer depth = commitRoot.get(rev.toString());
>-            if (depth != null) {
>-                String p = Utils.getPathFromId(getId());
>-                commitRootPath = PathUtils.getAncestorPath(p,
>PathUtils.getDepth(p) - depth);
>-            }
>+        Integer depth = commitRoot.get(rev.toString());
>+        if (depth != null) {
>+            String p = Utils.getPathFromId(getId());
>+            commitRootPath = PathUtils.getAncestorPath(p,
>PathUtils.getDepth(p) - depth);
>         }
>         if (commitRootPath == null) {
>             // shouldn't happen, either node is commit root for a
>revision
>@@ -835,11 +895,11 @@ public class NodeDocument extends Docume
>     @CheckForNull
>     private String getCommitValue(Revision revision) {
>         String r = revision.toString();
>-        String value = getRevisionsMap().get(r);
>+        String value = getLocalRevisions().get(r);
>         if (value == null) {
>             // check previous
>             for (NodeDocument prev : getPreviousDocs(revision)) {
>-                value = prev.getRevisionsMap().get(r);
>+                value = prev.getLocalRevisions().get(r);
>                 if (value != null) {
>                     break;
>                 }
>@@ -907,193 +967,23 @@ public class NodeDocument extends Docume
>         return value;
>     }
>
>-    Map<String, String> getRevisions() {
>-        final Map<String, String> map = getRevisionsMap();
>-        if (!data.containsKey(PREVIOUS)) {
>-            return map;
>-        }
>-        final Set<Map.Entry<String, String>> revisions
>-                = new AbstractSet<Map.Entry<String, String>>() {
>-
>-            @Override
>-            @Nonnull
>-            public Iterator<Map.Entry<String, String>> iterator() {
>-                return Iterators.concat(map.entrySet().iterator(),
>-                        Iterators.concat(new
>Iterator<Iterator<Map.Entry<String, String>>>() {
>-                            private final Iterator<NodeDocument> previous
>-                                    = getPreviousDocs(null).iterator();
>-
>-                            @Override
>-                            public boolean hasNext() {
>-                                return previous.hasNext();
>-                            }
>-
>-                            @Override
>-                            public Iterator<Map.Entry<String, String>>
>next() {
>-                                return
>previous.next().getRevisions().entrySet().iterator();
>-                            }
>-
>-                            @Override
>-                            public void remove() {
>-                                throw new
>UnsupportedOperationException();
>-                            }
>-                        }));
>-            }
>-
>-            @Override
>-            public int size() {
>-                int size = map.size();
>-                for (NodeDocument prev : getPreviousDocs(null)) {
>-                    size += prev.getRevisions().size();
>-                }
>-                return size;
>-            }
>-        };
>-        return new AbstractMap<String, String>() {
>-
>-            private final Map<String, String> map = getRevisionsMap();
>-
>-            @Override
>-            @Nonnull
>-            public Set<Entry<String, String>> entrySet() {
>-                return revisions;
>-            }
>-
>-            @Override
>-            public String get(Object key) {
>-                // first check revisions map of this document
>-                String value = map.get(key);
>-                if (value != null) {
>-                    return value;
>-                }
>-                Revision r = Revision.fromString(key.toString());
>-                for (NodeDocument prev : getPreviousDocs(r)) {
>-                    value = prev.getRevisions().get(key);
>-                    if (value != null) {
>-                        return value;
>-                    }
>-                }
>-                // not found
>-                return null;
>-            }
>-
>-            @Override
>-            public boolean containsKey(Object key) {
>-                // can use get()
>-                // the revisions map does not have null values
>-                return get(key) != null;
>-            }
>-
>-        };
>-    }
>-
>     @Nonnull
>-    Map<String, String> getRevisionsMap() {
>-        @SuppressWarnings("unchecked")
>-        Map<String, String> map = (Map<String, String>) get(REVISIONS);
>-        if (map == null) {
>-            map = Collections.emptyMap();
>-        }
>-        return map;
>+    private Map<String, String> getRevisions() {
>+        return ValueMap.create(this, REVISIONS);
>     }
>
>-    /**
>-     * Returns previous {@link NodeDocument}, which include the given
>revision.
>-     * If the <code>revision</code> is <code>null</code>, then all
>previous
>-     * documents are returned.
>-     *
>-     * @param revision the revision to match or <code>null</code>.
>-     * @return previous documents.
>-     */
>-    Iterable<NodeDocument> getPreviousDocs(@Nullable final Revision
>revision) {
>-        Iterable<NodeDocument> docs = Iterables.transform(
>-                Iterables.filter(getPreviousRanges().entrySet(),
>-                new Predicate<Map.Entry<Revision, Range>>() {
>-            @Override
>-            public boolean apply(Map.Entry<Revision, Range> input) {
>-                return revision == null ||
>input.getValue().includes(revision);
>-            }
>-        }), new Function<Map.Entry<Revision, Range>, NodeDocument>() {
>-            @Nullable
>-            @Override
>-            public NodeDocument apply(Map.Entry<Revision, Range> input) {
>-                Revision r = input.getKey();
>-                String prevId = Utils.getPreviousIdFor(getId(), r);
>-                NodeDocument prev = store.find(Collection.NODES, prevId);
>-                if (prev == null) {
>-                    LOG.warn("Document with previous revisions not
>found: " + prevId);
>-                }
>-                return prev;
>-            }
>-        });
>-        // filter out null docs and check if the revision is actually in
>there
>-        return Iterables.filter(docs, new Predicate<NodeDocument>() {
>-            @Override
>-            public boolean apply(@Nullable NodeDocument input) {
>-                if (input == null) {
>-                    return false;
>-                }
>-                return revision == null ||
>input.containsRevision(revision.toString());
>-            }
>-        });
>+    @Nonnull
>+    private Map<String, String> getDeleted() {
>+        return ValueMap.create(this, DELETED);
>     }
>
>-    /**
>-     * Returns previous revision ranges for this document. The revision
>keys are
>-     * sorted descending, newest first!
>-     *
>-     * @return the previous ranges for this document.
>-     */
>     @Nonnull
>-    private SortedMap<Revision, Range> getPreviousRanges() {
>+    private Map<String, Integer> getCommitRoot() {
>         @SuppressWarnings("unchecked")
>-        SortedMap<Revision, Range> previous = (SortedMap<Revision,
>Range>) get(PREVIOUS);
>-        if (previous == null) {
>-            previous = EMPTY_RANGE_MAP;
>-        }
>-        return previous;
>-    }
>-
>-    /**
>-     * A range of revisions.
>-     */
>-    private static final class Range {
>-
>-        final Revision high;
>-        final Revision low;
>-
>-        /**
>-         * A range of revisions, with both inclusive bounds.
>-         *
>-         * @param high the high bound.
>-         * @param low the low bound.
>-         */
>-        Range(@Nonnull Revision high, @Nonnull Revision low) {
>-            this.high = checkNotNull(high);
>-            this.low = checkNotNull(low);
>-            checkArgument(high.getClusterId() == low.getClusterId(),
>-                    "Revisions from have the same clusterId");
>-            checkArgument(high.compareRevisionTime(low) > 0,
>-                    "High Revision must be later than low Revision");
>-        }
>-
>-        /**
>-         * Returns <code>true</code> if the given revision is within
>this range.
>-         *
>-         * @param r the revision to check.
>-         * @return <code>true</code> if within this range;
><code>false</code>
>-         * otherwise.
>-         */
>-        boolean includes(Revision r) {
>-            return high.compareRevisionTime(r) >= 0
>-                    && low.compareRevisionTime(r) <= 0;
>-        }
>-
>-        @Override
>-        public String toString() {
>-            return low.toString();
>+        Map<String, Integer> commitRoot = (Map<String, Integer>)
>get(COMMIT_ROOT);
>+        if (commitRoot == null) {
>+            commitRoot = Collections.emptyMap();
>         }
>-
>+        return commitRoot;
>     }
>-
> }
>
>Added:
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/Range.java
>URL:
>http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/o
>rg/apache/jackrabbit/oak/plugins/mongomk/Range.java?rev=1522553&view=auto
>==========================================================================
>====
>---
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/Range.java (added)
>+++
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/Range.java Thu Sep 12 12:45:19 2013
>@@ -0,0 +1,63 @@
>+/*
>+ * Licensed to the Apache Software Foundation (ASF) under one or more
>+ * contributor license agreements.  See the NOTICE file distributed with
>+ * this work for additional information regarding copyright ownership.
>+ * The ASF licenses this file to You under the Apache License, Version
>2.0
>+ * (the "License"); you may not use this file except in compliance with
>+ * the License.  You may obtain a copy of the License at
>+ *
>+ *      http://www.apache.org/licenses/LICENSE-2.0
>+ *
>+ * Unless required by applicable law or agreed to in writing, software
>+ * distributed under the License is distributed on an "AS IS" BASIS,
>+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>implied.
>+ * See the License for the specific language governing permissions and
>+ * limitations under the License.
>+ */
>+package org.apache.jackrabbit.oak.plugins.mongomk;
>+
>+import javax.annotation.Nonnull;
>+
>+import static com.google.common.base.Preconditions.checkArgument;
>+import static com.google.common.base.Preconditions.checkNotNull;
>+
>+/**
>+* A revision range for {@link NodeDocument#PREVIOUS} documents.
>+*/
>+final class Range {
>+
>+    final Revision high;
>+    final Revision low;
>+
>+    /**
>+     * A range of revisions, with both inclusive bounds.
>+     *
>+     * @param high the high bound.
>+     * @param low the low bound.
>+     */
>+    Range(@Nonnull Revision high, @Nonnull Revision low) {
>+        this.high = checkNotNull(high);
>+        this.low = checkNotNull(low);
>+        checkArgument(high.getClusterId() == low.getClusterId(),
>+                "Revisions from have the same clusterId");
>+        checkArgument(high.compareRevisionTime(low) > 0,
>+                "High Revision must be later than low Revision");
>+    }
>+
>+    /**
>+     * Returns <code>true</code> if the given revision is within this
>range.
>+     *
>+     * @param r the revision to check.
>+     * @return <code>true</code> if within this range; <code>false</code>
>+     * otherwise.
>+     */
>+    boolean includes(Revision r) {
>+        return high.compareRevisionTime(r) >= 0
>+                && low.compareRevisionTime(r) <= 0;
>+    }
>+
>+    @Override
>+    public String toString() {
>+        return low.toString();
>+    }
>+}
>
>Propchange:
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/Range.java
>--------------------------------------------------------------------------
>----
>    svn:eol-style = native
>
>Propchange:
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/Range.java
>--------------------------------------------------------------------------
>----
>    svn:keywords = Author Date Id Revision Rev URL
>
>Added:
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/ValueMap.java
>URL:
>http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/o
>rg/apache/jackrabbit/oak/plugins/mongomk/ValueMap.java?rev=1522553&view=au
>to
>==========================================================================
>====
>---
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/ValueMap.java (added)
>+++
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/ValueMap.java Thu Sep 12 12:45:19 2013
>@@ -0,0 +1,113 @@
>+/*
>+ * Licensed to the Apache Software Foundation (ASF) under one or more
>+ * contributor license agreements.  See the NOTICE file distributed with
>+ * this work for additional information regarding copyright ownership.
>+ * The ASF licenses this file to You under the Apache License, Version
>2.0
>+ * (the "License"); you may not use this file except in compliance with
>+ * the License.  You may obtain a copy of the License at
>+ *
>+ *      http://www.apache.org/licenses/LICENSE-2.0
>+ *
>+ * Unless required by applicable law or agreed to in writing, software
>+ * distributed under the License is distributed on an "AS IS" BASIS,
>+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>implied.
>+ * See the License for the specific language governing permissions and
>+ * limitations under the License.
>+ */
>+package org.apache.jackrabbit.oak.plugins.mongomk;
>+
>+import java.util.AbstractMap;
>+import java.util.AbstractSet;
>+import java.util.Iterator;
>+import java.util.Map;
>+import java.util.Set;
>+
>+import javax.annotation.Nonnull;
>+
>+import com.google.common.collect.Iterators;
>+
>+/**
>+ * A value map contains the versioned values of a property. The key into
>this
>+ * map is the revision when the value was set.
>+ */
>+class ValueMap {
>+
>+    @Nonnull
>+    static Map<String, String> create(final @Nonnull NodeDocument doc,
>+                                      final @Nonnull String property) {
>+        final Map<String, String> map = doc.getLocalMap(property);
>+        if (doc.getPreviousRanges().isEmpty()) {
>+            return map;
>+        }
>+        final Set<Map.Entry<String, String>> values
>+                = new AbstractSet<Map.Entry<String, String>>() {
>+
>+            @Override
>+            @Nonnull
>+            public Iterator<Map.Entry<String, String>> iterator() {
>+                return Iterators.concat(map.entrySet().iterator(),
>Iterators.concat(new Iterator<Iterator<Map.Entry<String, String>>>() {
>+                    private final Iterator<NodeDocument> previous =
>doc.getPreviousDocs(null).iterator();
>+
>+                    @Override
>+                    public boolean hasNext() {
>+                        return previous.hasNext();
>+                    }
>+
>+                    @Override
>+                    public Iterator<Map.Entry<String, String>> next() {
>+                        return
>previous.next().getLocalMap(property).entrySet().iterator();
>+                    }
>+
>+                    @Override
>+                    public void remove() {
>+                        throw new UnsupportedOperationException();
>+                    }
>+                }));
>+            }
>+
>+            @Override
>+            public int size() {
>+                int size = map.size();
>+                for (NodeDocument prev : doc.getPreviousDocs(null)) {
>+                    size += prev.getLocalMap(property).size();
>+                }
>+                return size;
>+            }
>+        };
>+        return new AbstractMap<String, String>() {
>+
>+            private final Map<String, String> map =
>doc.getLocalMap(property);
>+
>+            @Override
>+            @Nonnull
>+            public Set<Entry<String, String>> entrySet() {
>+                return values;
>+            }
>+
>+            @Override
>+            public String get(Object key) {
>+                // first check values map of this document
>+                String value = map.get(key);
>+                if (value != null) {
>+                    return value;
>+                }
>+                Revision r = Revision.fromString(key.toString());
>+                for (NodeDocument prev : doc.getPreviousDocs(r)) {
>+                    value = prev.getLocalMap(property).get(key);
>+                    if (value != null) {
>+                        return value;
>+                    }
>+                }
>+                // not found
>+                return null;
>+            }
>+
>+            @Override
>+            public boolean containsKey(Object key) {
>+                // can use get()
>+                // the values map does not have null values
>+                return get(key) != null;
>+            }
>+        };
>+    }
>+}
>
>Propchange:
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/ValueMap.java
>--------------------------------------------------------------------------
>----
>    svn:eol-style = native
>
>Propchange:
>jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/ValueMap.java
>--------------------------------------------------------------------------
>----
>    svn:keywords = Author Date Id Revision Rev URL
>
>Modified:
>jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/DocumentSplitTest.java
>URL:
>http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/o
>rg/apache/jackrabbit/oak/plugins/mongomk/DocumentSplitTest.java?rev=152255
>3&r1=1522552&r2=1522553&view=diff
>==========================================================================
>====
>---
>jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/DocumentSplitTest.java (original)
>+++
>jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plug
>ins/mongomk/DocumentSplitTest.java Thu Sep 12 12:45:19 2013
>@@ -39,7 +39,7 @@ public class DocumentSplitTest extends B
>         Set<String> revisions = Sets.newHashSet();
>         NodeDocument doc = store.find(Collection.NODES,
>Utils.getIdFromPath("/"));
>         assertNotNull(doc);
>-        revisions.addAll(doc.getRevisionsMap().keySet());
>+        revisions.addAll(doc.getLocalRevisions().keySet());
>
>         // MongoMK initializes with a root node with a single revision
>         int numRevs = 1;
>@@ -55,7 +55,7 @@ public class DocumentSplitTest extends B
>         String head = mk.getHeadRevision();
>         doc = store.find(Collection.NODES, Utils.getIdFromPath("/"));
>         assertNotNull(doc);
>-        Map<String, String> revs = doc.getRevisionsMap();
>+        Map<String, String> revs = doc.getLocalRevisions();
>         // one remaining in the local revisions map
>         assertEquals(1, revs.size());
>         for (String r : revisions) {
>
>


RE: svn commit: r1522553 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/mongomk/ test/java/org/apache/jackrabbit/oak/plugins/mongomk/

Posted by Marcel Reutegger <mr...@adobe.com>.
hi,

hmm, I didn't notice the failure in my checkout and cannot reproduce,
but it must be related to recent changes.

how often do you see the failure?

regards
 marcel

> -----Original Message-----
> From: Angela Schreiber
> Sent: Donnerstag, 12. September 2013 19:12
> To: oak-dev@jackrabbit.apache.org; Marcel Reutegger
> Subject: Re: svn commit: r1522553 - in /jackrabbit/oak/trunk/oak-core/src:
> main/java/org/apache/jackrabbit/oak/plugins/mongomk/
> test/java/org/apache/jackrabbit/oak/plugins/mongomk/
>
> hi marcel
>
> could it be that this commit is related to the following test failure?
> i experience it in a up to date checkout of the trunk.
>
> regards
> angela
>
> concurrent[1](org.apache.jackrabbit.oak.jcr.ConcurrentFileOperationsTest)
> Time elapsed: 0.473 sec  <<< ERROR!
> javax.jcr.RepositoryException: OakKernel0001: Failed to merge changes to
> the underlying MicroKernel
>       at
> org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryExceptio
> n(C
> ommitFailedException.java:242)
>       at
> org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryExceptio
> n(C
> ommitFailedException.java:207)
>       at
> org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.newRepositoryExce
> pti
> on(SessionDelegate.java:434)
>       at
> org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegat
> e
> .java:297)
>       at
> org.apache.jackrabbit.oak.jcr.session.SessionImpl$8.perform(SessionImpl.ja
> v
> a:399)
>       at
> org.apache.jackrabbit.oak.jcr.session.SessionImpl$8.perform(SessionImpl.ja
> v
> a:396)
>       at
> org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.perform(SessionDel
> eg
> ate.java:124)
>       at
> org.apache.jackrabbit.oak.jcr.session.SessionImpl.perform(SessionImpl.java:
> 117)
>       at
> org.apache.jackrabbit.oak.jcr.session.SessionImpl.save(SessionImpl.java:396
> )
>       at
> org.apache.jackrabbit.oak.jcr.ConcurrentFileOperationsTest$1.run(Concurre
> nt
> FileOperationsTest.java:103)
>       at java.lang.Thread.run(Thread.java:680)
> Caused by: org.apache.jackrabbit.oak.api.CommitFailedException:
> OakKernel0001: Failed to merge changes to the underlying MicroKernel
>       at
> org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch$Persisted.merge(
> Kern
> elNodeStoreBranch.java:384)
>       at
> org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch.merge(KernelNod
> eStor
> eBranch.java:136)
>       at
> org.apache.jackrabbit.oak.core.AbstractRoot$2.run(AbstractRoot.java:247)
>       at
> org.apache.jackrabbit.oak.core.AbstractRoot$2.run(AbstractRoot.java:243)
>       at java.security.AccessController.doPrivileged(Native Method)
>       at javax.security.auth.Subject.doAs(Subject.java:337)
>       at
> org.apache.jackrabbit.oak.core.AbstractRoot.commit(AbstractRoot.java:242)
>       at
> org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegat
> e
> .java:295)
>       ... 7 more
> Caused by: org.apache.jackrabbit.mk.api.MicroKernelException: Conflicting
> concurrent change. Update operation failed: key: 0:/ update
> {_collisions.r1411315f24a-0-1=CONTAINS_MAP_ENTRY false,
> _collisions.r1411315f251-0-1=CONTAINS_MAP_ENTRY false,
> _collisions.r1411315f3f6-0-1=CONTAINS_MAP_ENTRY false,
> _collisions.r1411315f402-0-1=CONTAINS_MAP_ENTRY false, _modified=SET
> 275800941, _revisions.r1411315f24a-0-1=SET_MAP_ENTRY c-r1411315f404-0-
> 1,
> _revisions.r1411315f251-0-1=SET_MAP_ENTRY c-r1411315f404-0-1,
> _revisions.r1411315f3f6-0-1=SET_MAP_ENTRY c-r1411315f404-0-1,
> _revisions.r1411315f402-0-1=SET_MAP_ENTRY c-r1411315f404-0-1}
>       at
> org.apache.jackrabbit.oak.plugins.mongomk.MongoMK.merge(MongoMK.ja
> va:1130)
>       at
> org.apache.jackrabbit.oak.kernel.KernelNodeStore.merge(KernelNodeStore
> .java
> :208)
>       at
> org.apache.jackrabbit.oak.kernel.KernelNodeStoreBranch$Persisted.merge(
> Kern
> elNodeStoreBranch.java:378)
>       ... 14 more
>
>
>
>
>
>
> On 9/12/13 2:45 PM, "mreutegg@apache.org" <mr...@apache.org>
> wrote:
>
> >Author: mreutegg
> >Date: Thu Sep 12 12:45:19 2013
> >New Revision: 1522553
> >
> >URL: http://svn.apache.org/r1522553
> >Log:
> >OAK-926: MongoMK: split documents when they are too large
> >- Generalize access to previous documents (WIP)
> >
> >Added:
> >
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/Range.java   (with props)
> >
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/ValueMap.java   (with props)
> >Modified:
> >
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/NodeDocument.java
> >
> >jackrabbit/oak/trunk/oak-
> core/src/test/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/DocumentSplitTest.java
> >
> >Modified:
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/NodeDocument.java
> >URL:
> >http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-
> core/src/main/java/o
> >rg/apache/jackrabbit/oak/plugins/mongomk/NodeDocument.java?rev=152
> 2553&r1=
> >1522552&r2=1522553&view=diff
> >=========================================================
> =================
> >====
> >---
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/NodeDocument.java (original)
> >+++
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/NodeDocument.java Thu Sep 12 12:45:19 2013
> >@@ -16,8 +16,6 @@
> >  */
> > package org.apache.jackrabbit.oak.plugins.mongomk;
> >
> >-import java.util.AbstractMap;
> >-import java.util.AbstractSet;
> > import java.util.ArrayList;
> > import java.util.Collections;
> > import java.util.Comparator;
> >@@ -44,10 +42,8 @@ import org.slf4j.LoggerFactory;
> > import com.google.common.base.Function;
> > import com.google.common.base.Predicate;
> > import com.google.common.collect.Iterables;
> >-import com.google.common.collect.Iterators;
> > import com.google.common.collect.Maps;
> >
> >-import static com.google.common.base.Preconditions.checkArgument;
> > import static com.google.common.base.Preconditions.checkNotNull;
> >
> > /**
> >@@ -186,7 +182,7 @@ public class NodeDocument extends Docume
> >      */
> >     public boolean containsRevision(@Nonnull Revision revision) {
> >         String rev = checkNotNull(revision).toString();
> >-        if (getRevisionsMap().containsKey(rev)) {
> >+        if (getLocalRevisions().containsKey(rev)) {
> >             return true;
> >         }
> >         for (NodeDocument prev : getPreviousDocs(revision)) {
> >@@ -219,7 +215,7 @@ public class NodeDocument extends Docume
> >     public SortedMap<Revision, Revision>
> >getUncommittedRevisions(RevisionContext context) {
> >         // only look at revisions in this document.
> >         // uncommitted revisions are not split off
> >-        Map<String, String> valueMap = getRevisionsMap();
> >+        Map<String, String> valueMap = getLocalRevisions();
> >         SortedMap<Revision, Revision> revisions =
> >                 new TreeMap<Revision,
> >Revision>(context.getRevisionComparator());
> >         for (Map.Entry<String, String> commit : valueMap.entrySet()) {
> >@@ -244,11 +240,7 @@ public class NodeDocument extends Docume
> >      */
> >     @CheckForNull
> >     public String getCommitRootPath(String revision) {
> >-        @SuppressWarnings("unchecked")
> >-        Map<String, Integer> valueMap = (Map<String, Integer>)
> >get(COMMIT_ROOT);
> >-        if (valueMap == null) {
> >-            return null;
> >-        }
> >+        Map<String, Integer> valueMap = getCommitRoot();
> >         Integer depth = valueMap.get(revision);
> >         if (depth != null) {
> >             String p = Utils.getPathFromId(getId());
> >@@ -274,13 +266,9 @@ public class NodeDocument extends Docume
> >                                       CollisionHandler handler) {
> >         SortedSet<String> revisions = new
> >TreeSet<String>(Collections.reverseOrder());
> >         revisions.addAll(getRevisions().keySet());
> >-        if (data.containsKey(COMMIT_ROOT)) {
> >-            revisions.addAll(((Map<String, Integer>)
> >get(COMMIT_ROOT)).keySet());
> >-        }
> >-        Map<String, String> deletedMap = (Map<String, String>)
> >get(DELETED);
> >-        if (deletedMap != null) {
> >-            revisions.addAll(deletedMap.keySet());
> >-        }
> >+        revisions.addAll(getCommitRoot().keySet());
> >+        Map<String, String> deletedMap = getDeleted();
> >+        revisions.addAll(deletedMap.keySet());
> >         Revision newestRev = null;
> >         for (String r : revisions) {
> >             Revision propRev = Revision.fromString(r);
> >@@ -304,12 +292,10 @@ public class NodeDocument extends Docume
> >         if (newestRev == null) {
> >             return null;
> >         }
> >-        if (deletedMap != null) {
> >-            String value = deletedMap.get(newestRev.toString());
> >-            if ("true".equals(value)) {
> >-                // deleted in the newest revision
> >-                return null;
> >-            }
> >+        String value = deletedMap.get(newestRev.toString());
> >+        if ("true".equals(value)) {
> >+            // deleted in the newest revision
> >+            return null;
> >         }
> >         return newestRev;
> >     }
> >@@ -444,9 +430,8 @@ public class NodeDocument extends Docume
> >     public boolean isDeleted(RevisionContext context,
> >                              Revision readRevision,
> >                              Set<Revision> validRevisions) {
> >-        @SuppressWarnings("unchecked")
> >-        Map<String, String> valueMap = (Map<String, String>)
> >get(NodeDocument.DELETED);
> >-        if (valueMap == null) {
> >+        Map<String, String> valueMap = getDeleted();
> >+        if (valueMap.isEmpty()) {
> >             return false;
> >         }
> >         if (valueMap instanceof NavigableMap) {
> >@@ -487,9 +472,8 @@ public class NodeDocument extends Docume
> >     @CheckForNull
> >     public Revision getLiveRevision(RevisionContext context, Revision
> >maxRev,
> >                                     Set<Revision> validRevisions) {
> >-        @SuppressWarnings("unchecked")
> >-        Map<String, String> valueMap = (Map<String, String>)
> >get(NodeDocument.DELETED);
> >-        if (valueMap == null) {
> >+        Map<String, String> valueMap = getDeleted();
> >+        if (valueMap.isEmpty()) {
> >             return null;
> >         }
> >         // first, search the newest deleted revision
> >@@ -552,13 +536,10 @@ public class NodeDocument extends Docume
> >                                  @Nonnull Revision baseRevision,
> >                                  @Nonnull RevisionContext context) {
> >         // did existence of node change after baseRevision?
> >-        @SuppressWarnings("unchecked")
> >-        Map<String, String> deleted = (Map<String, String>) get(DELETED);
> >-        if (deleted != null) {
> >-            for (Map.Entry<String, String> entry : deleted.entrySet()) {
> >-                if (isRevisionNewer(context,
> >Revision.fromString(entry.getKey()), baseRevision)) {
> >-                    return true;
> >-                }
> >+        Map<String, String> deleted = getDeleted();
> >+        for (Map.Entry<String, String> entry : deleted.entrySet()) {
> >+            if (isRevisionNewer(context,
> >Revision.fromString(entry.getKey()), baseRevision)) {
> >+                return true;
> >             }
> >         }
> >
> >@@ -615,7 +596,7 @@ public class NodeDocument extends Docume
> >         }
> >         NavigableMap<Revision, String> splitRevs
> >                 = new TreeMap<Revision,
> >String>(context.getRevisionComparator());
> >-        Map<String, String> revisions = getRevisionsMap();
> >+        Map<String, String> revisions = getLocalRevisions();
> >         // only consider if there are enough revisions
> >         if (revisions.size() > REVISIONS_SPLIT_OFF_SIZE) {
> >             // collect commits of this cluster node after the
> >@@ -690,6 +671,88 @@ public class NodeDocument extends Docume
> >         return super.transformAndSeal(map, key, level);
> >     }
> >
> >+    /**
> >+     * Returns previous revision ranges for this document. The revision
> >keys are
> >+     * sorted descending, newest first!
> >+     *
> >+     * @return the previous ranges for this document.
> >+     */
> >+    @Nonnull
> >+    SortedMap<Revision, Range> getPreviousRanges() {
> >+        @SuppressWarnings("unchecked")
> >+        SortedMap<Revision, Range> previous = (SortedMap<Revision,
> >Range>) get(PREVIOUS);
> >+        if (previous == null) {
> >+            previous = EMPTY_RANGE_MAP;
> >+        }
> >+        return previous;
> >+    }
> >+
> >+    /**
> >+     * Returns previous {@link NodeDocument}, which include the given
> >revision.
> >+     * If the <code>revision</code> is <code>null</code>, then all
> >previous
> >+     * documents are returned.
> >+     *
> >+     * @param revision the revision to match or <code>null</code>.
> >+     * @return previous documents.
> >+     */
> >+    Iterable<NodeDocument> getPreviousDocs(final @Nullable Revision
> >revision) {
> >+        Iterable<NodeDocument> docs = Iterables.transform(
> >+                Iterables.filter(getPreviousRanges().entrySet(),
> >+                        new Predicate<Map.Entry<Revision, Range>>() {
> >+                            @Override
> >+                            public boolean apply(Map.Entry<Revision,
> >Range> input) {
> >+                                return revision == null ||
> >input.getValue().includes(revision);
> >+                            }
> >+                        }), new Function<Map.Entry<Revision, Range>,
> >NodeDocument>() {
> >+            @Nullable
> >+            @Override
> >+            public NodeDocument apply(Map.Entry<Revision, Range> input) {
> >+                Revision r = input.getKey();
> >+                String prevId = Utils.getPreviousIdFor(getId(), r);
> >+                NodeDocument prev = store.find(Collection.NODES, prevId);
> >+                if (prev == null) {
> >+                    LOG.warn("Document with previous revisions not
> >found: " + prevId);
> >+                }
> >+                return prev;
> >+            }
> >+        });
> >+        // filter out null docs and check if the revision is actually in
> >there
> >+        return Iterables.filter(docs, new Predicate<NodeDocument>() {
> >+            @Override
> >+            public boolean apply(@Nullable NodeDocument input) {
> >+                if (input == null) {
> >+                    return false;
> >+                }
> >+                return revision == null ||
> >input.containsRevision(revision.toString());
> >+            }
> >+        });
> >+    }
> >+
> >+    /**
> >+     * Returns the local value map for the given key. Returns
> ><code>null</code>
> >+     * if no such value map exists.
> >+     *
> >+     * @param key the key.
> >+     * @return local value map.
> >+     */
> >+    @Nonnull
> >+    Map<String, String> getLocalMap(String key) {
> >+        @SuppressWarnings("unchecked")
> >+        Map<String, String> map = (Map<String, String>) get(key);
> >+        if (map == null) {
> >+            map = Collections.emptyMap();
> >+        }
> >+        return map;
> >+    }
> >+
> >+    /**
> >+     * @return the {@link #REVISIONS} stored on this document.
> >+     */
> >+    @Nonnull
> >+    Map<String, String> getLocalRevisions() {
> >+        return getLocalMap(REVISIONS);
> >+    }
> >+
> >     //-------------------------< UpdateOp modifiers
> >>---------------------------
> >
> >     public static void setModified(@Nonnull UpdateOp op,
> >@@ -745,15 +808,12 @@ public class NodeDocument extends Docume
> >             return this;
> >         }
> >         // check commit root
> >-        @SuppressWarnings("unchecked")
> >-        Map<String, Integer> commitRoot = (Map<String, Integer>)
> >get(COMMIT_ROOT);
> >+        Map<String, Integer> commitRoot = getCommitRoot();
> >         String commitRootPath = null;
> >-        if (commitRoot != null) {
> >-            Integer depth = commitRoot.get(rev.toString());
> >-            if (depth != null) {
> >-                String p = Utils.getPathFromId(getId());
> >-                commitRootPath = PathUtils.getAncestorPath(p,
> >PathUtils.getDepth(p) - depth);
> >-            }
> >+        Integer depth = commitRoot.get(rev.toString());
> >+        if (depth != null) {
> >+            String p = Utils.getPathFromId(getId());
> >+            commitRootPath = PathUtils.getAncestorPath(p,
> >PathUtils.getDepth(p) - depth);
> >         }
> >         if (commitRootPath == null) {
> >             // shouldn't happen, either node is commit root for a
> >revision
> >@@ -835,11 +895,11 @@ public class NodeDocument extends Docume
> >     @CheckForNull
> >     private String getCommitValue(Revision revision) {
> >         String r = revision.toString();
> >-        String value = getRevisionsMap().get(r);
> >+        String value = getLocalRevisions().get(r);
> >         if (value == null) {
> >             // check previous
> >             for (NodeDocument prev : getPreviousDocs(revision)) {
> >-                value = prev.getRevisionsMap().get(r);
> >+                value = prev.getLocalRevisions().get(r);
> >                 if (value != null) {
> >                     break;
> >                 }
> >@@ -907,193 +967,23 @@ public class NodeDocument extends Docume
> >         return value;
> >     }
> >
> >-    Map<String, String> getRevisions() {
> >-        final Map<String, String> map = getRevisionsMap();
> >-        if (!data.containsKey(PREVIOUS)) {
> >-            return map;
> >-        }
> >-        final Set<Map.Entry<String, String>> revisions
> >-                = new AbstractSet<Map.Entry<String, String>>() {
> >-
> >-            @Override
> >-            @Nonnull
> >-            public Iterator<Map.Entry<String, String>> iterator() {
> >-                return Iterators.concat(map.entrySet().iterator(),
> >-                        Iterators.concat(new
> >Iterator<Iterator<Map.Entry<String, String>>>() {
> >-                            private final Iterator<NodeDocument> previous
> >-                                    = getPreviousDocs(null).iterator();
> >-
> >-                            @Override
> >-                            public boolean hasNext() {
> >-                                return previous.hasNext();
> >-                            }
> >-
> >-                            @Override
> >-                            public Iterator<Map.Entry<String, String>>
> >next() {
> >-                                return
> >previous.next().getRevisions().entrySet().iterator();
> >-                            }
> >-
> >-                            @Override
> >-                            public void remove() {
> >-                                throw new
> >UnsupportedOperationException();
> >-                            }
> >-                        }));
> >-            }
> >-
> >-            @Override
> >-            public int size() {
> >-                int size = map.size();
> >-                for (NodeDocument prev : getPreviousDocs(null)) {
> >-                    size += prev.getRevisions().size();
> >-                }
> >-                return size;
> >-            }
> >-        };
> >-        return new AbstractMap<String, String>() {
> >-
> >-            private final Map<String, String> map = getRevisionsMap();
> >-
> >-            @Override
> >-            @Nonnull
> >-            public Set<Entry<String, String>> entrySet() {
> >-                return revisions;
> >-            }
> >-
> >-            @Override
> >-            public String get(Object key) {
> >-                // first check revisions map of this document
> >-                String value = map.get(key);
> >-                if (value != null) {
> >-                    return value;
> >-                }
> >-                Revision r = Revision.fromString(key.toString());
> >-                for (NodeDocument prev : getPreviousDocs(r)) {
> >-                    value = prev.getRevisions().get(key);
> >-                    if (value != null) {
> >-                        return value;
> >-                    }
> >-                }
> >-                // not found
> >-                return null;
> >-            }
> >-
> >-            @Override
> >-            public boolean containsKey(Object key) {
> >-                // can use get()
> >-                // the revisions map does not have null values
> >-                return get(key) != null;
> >-            }
> >-
> >-        };
> >-    }
> >-
> >     @Nonnull
> >-    Map<String, String> getRevisionsMap() {
> >-        @SuppressWarnings("unchecked")
> >-        Map<String, String> map = (Map<String, String>) get(REVISIONS);
> >-        if (map == null) {
> >-            map = Collections.emptyMap();
> >-        }
> >-        return map;
> >+    private Map<String, String> getRevisions() {
> >+        return ValueMap.create(this, REVISIONS);
> >     }
> >
> >-    /**
> >-     * Returns previous {@link NodeDocument}, which include the given
> >revision.
> >-     * If the <code>revision</code> is <code>null</code>, then all
> >previous
> >-     * documents are returned.
> >-     *
> >-     * @param revision the revision to match or <code>null</code>.
> >-     * @return previous documents.
> >-     */
> >-    Iterable<NodeDocument> getPreviousDocs(@Nullable final Revision
> >revision) {
> >-        Iterable<NodeDocument> docs = Iterables.transform(
> >-                Iterables.filter(getPreviousRanges().entrySet(),
> >-                new Predicate<Map.Entry<Revision, Range>>() {
> >-            @Override
> >-            public boolean apply(Map.Entry<Revision, Range> input) {
> >-                return revision == null ||
> >input.getValue().includes(revision);
> >-            }
> >-        }), new Function<Map.Entry<Revision, Range>, NodeDocument>() {
> >-            @Nullable
> >-            @Override
> >-            public NodeDocument apply(Map.Entry<Revision, Range> input) {
> >-                Revision r = input.getKey();
> >-                String prevId = Utils.getPreviousIdFor(getId(), r);
> >-                NodeDocument prev = store.find(Collection.NODES, prevId);
> >-                if (prev == null) {
> >-                    LOG.warn("Document with previous revisions not
> >found: " + prevId);
> >-                }
> >-                return prev;
> >-            }
> >-        });
> >-        // filter out null docs and check if the revision is actually in
> >there
> >-        return Iterables.filter(docs, new Predicate<NodeDocument>() {
> >-            @Override
> >-            public boolean apply(@Nullable NodeDocument input) {
> >-                if (input == null) {
> >-                    return false;
> >-                }
> >-                return revision == null ||
> >input.containsRevision(revision.toString());
> >-            }
> >-        });
> >+    @Nonnull
> >+    private Map<String, String> getDeleted() {
> >+        return ValueMap.create(this, DELETED);
> >     }
> >
> >-    /**
> >-     * Returns previous revision ranges for this document. The revision
> >keys are
> >-     * sorted descending, newest first!
> >-     *
> >-     * @return the previous ranges for this document.
> >-     */
> >     @Nonnull
> >-    private SortedMap<Revision, Range> getPreviousRanges() {
> >+    private Map<String, Integer> getCommitRoot() {
> >         @SuppressWarnings("unchecked")
> >-        SortedMap<Revision, Range> previous = (SortedMap<Revision,
> >Range>) get(PREVIOUS);
> >-        if (previous == null) {
> >-            previous = EMPTY_RANGE_MAP;
> >-        }
> >-        return previous;
> >-    }
> >-
> >-    /**
> >-     * A range of revisions.
> >-     */
> >-    private static final class Range {
> >-
> >-        final Revision high;
> >-        final Revision low;
> >-
> >-        /**
> >-         * A range of revisions, with both inclusive bounds.
> >-         *
> >-         * @param high the high bound.
> >-         * @param low the low bound.
> >-         */
> >-        Range(@Nonnull Revision high, @Nonnull Revision low) {
> >-            this.high = checkNotNull(high);
> >-            this.low = checkNotNull(low);
> >-            checkArgument(high.getClusterId() == low.getClusterId(),
> >-                    "Revisions from have the same clusterId");
> >-            checkArgument(high.compareRevisionTime(low) > 0,
> >-                    "High Revision must be later than low Revision");
> >-        }
> >-
> >-        /**
> >-         * Returns <code>true</code> if the given revision is within
> >this range.
> >-         *
> >-         * @param r the revision to check.
> >-         * @return <code>true</code> if within this range;
> ><code>false</code>
> >-         * otherwise.
> >-         */
> >-        boolean includes(Revision r) {
> >-            return high.compareRevisionTime(r) >= 0
> >-                    && low.compareRevisionTime(r) <= 0;
> >-        }
> >-
> >-        @Override
> >-        public String toString() {
> >-            return low.toString();
> >+        Map<String, Integer> commitRoot = (Map<String, Integer>)
> >get(COMMIT_ROOT);
> >+        if (commitRoot == null) {
> >+            commitRoot = Collections.emptyMap();
> >         }
> >-
> >+        return commitRoot;
> >     }
> >-
> > }
> >
> >Added:
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/Range.java
> >URL:
> >http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-
> core/src/main/java/o
> >rg/apache/jackrabbit/oak/plugins/mongomk/Range.java?rev=1522553&vie
> w=auto
> >=========================================================
> =================
> >====
> >---
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/Range.java (added)
> >+++
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/Range.java Thu Sep 12 12:45:19 2013
> >@@ -0,0 +1,63 @@
> >+/*
> >+ * Licensed to the Apache Software Foundation (ASF) under one or more
> >+ * contributor license agreements.  See the NOTICE file distributed with
> >+ * this work for additional information regarding copyright ownership.
> >+ * The ASF licenses this file to You under the Apache License, Version
> >2.0
> >+ * (the "License"); you may not use this file except in compliance with
> >+ * the License.  You may obtain a copy of the License at
> >+ *
> >+ *      http://www.apache.org/licenses/LICENSE-2.0
> >+ *
> >+ * Unless required by applicable law or agreed to in writing, software
> >+ * distributed under the License is distributed on an "AS IS" BASIS,
> >+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
> or
> >implied.
> >+ * See the License for the specific language governing permissions and
> >+ * limitations under the License.
> >+ */
> >+package org.apache.jackrabbit.oak.plugins.mongomk;
> >+
> >+import javax.annotation.Nonnull;
> >+
> >+import static com.google.common.base.Preconditions.checkArgument;
> >+import static com.google.common.base.Preconditions.checkNotNull;
> >+
> >+/**
> >+* A revision range for {@link NodeDocument#PREVIOUS} documents.
> >+*/
> >+final class Range {
> >+
> >+    final Revision high;
> >+    final Revision low;
> >+
> >+    /**
> >+     * A range of revisions, with both inclusive bounds.
> >+     *
> >+     * @param high the high bound.
> >+     * @param low the low bound.
> >+     */
> >+    Range(@Nonnull Revision high, @Nonnull Revision low) {
> >+        this.high = checkNotNull(high);
> >+        this.low = checkNotNull(low);
> >+        checkArgument(high.getClusterId() == low.getClusterId(),
> >+                "Revisions from have the same clusterId");
> >+        checkArgument(high.compareRevisionTime(low) > 0,
> >+                "High Revision must be later than low Revision");
> >+    }
> >+
> >+    /**
> >+     * Returns <code>true</code> if the given revision is within this
> >range.
> >+     *
> >+     * @param r the revision to check.
> >+     * @return <code>true</code> if within this range; <code>false</code>
> >+     * otherwise.
> >+     */
> >+    boolean includes(Revision r) {
> >+        return high.compareRevisionTime(r) >= 0
> >+                && low.compareRevisionTime(r) <= 0;
> >+    }
> >+
> >+    @Override
> >+    public String toString() {
> >+        return low.toString();
> >+    }
> >+}
> >
> >Propchange:
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/Range.java
> >--------------------------------------------------------------------------
> >----
> >    svn:eol-style = native
> >
> >Propchange:
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/Range.java
> >--------------------------------------------------------------------------
> >----
> >    svn:keywords = Author Date Id Revision Rev URL
> >
> >Added:
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/ValueMap.java
> >URL:
> >http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-
> core/src/main/java/o
> >rg/apache/jackrabbit/oak/plugins/mongomk/ValueMap.java?rev=1522553
> &view=au
> >to
> >=========================================================
> =================
> >====
> >---
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/ValueMap.java (added)
> >+++
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/ValueMap.java Thu Sep 12 12:45:19 2013
> >@@ -0,0 +1,113 @@
> >+/*
> >+ * Licensed to the Apache Software Foundation (ASF) under one or more
> >+ * contributor license agreements.  See the NOTICE file distributed with
> >+ * this work for additional information regarding copyright ownership.
> >+ * The ASF licenses this file to You under the Apache License, Version
> >2.0
> >+ * (the "License"); you may not use this file except in compliance with
> >+ * the License.  You may obtain a copy of the License at
> >+ *
> >+ *      http://www.apache.org/licenses/LICENSE-2.0
> >+ *
> >+ * Unless required by applicable law or agreed to in writing, software
> >+ * distributed under the License is distributed on an "AS IS" BASIS,
> >+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
> or
> >implied.
> >+ * See the License for the specific language governing permissions and
> >+ * limitations under the License.
> >+ */
> >+package org.apache.jackrabbit.oak.plugins.mongomk;
> >+
> >+import java.util.AbstractMap;
> >+import java.util.AbstractSet;
> >+import java.util.Iterator;
> >+import java.util.Map;
> >+import java.util.Set;
> >+
> >+import javax.annotation.Nonnull;
> >+
> >+import com.google.common.collect.Iterators;
> >+
> >+/**
> >+ * A value map contains the versioned values of a property. The key into
> >this
> >+ * map is the revision when the value was set.
> >+ */
> >+class ValueMap {
> >+
> >+    @Nonnull
> >+    static Map<String, String> create(final @Nonnull NodeDocument doc,
> >+                                      final @Nonnull String property) {
> >+        final Map<String, String> map = doc.getLocalMap(property);
> >+        if (doc.getPreviousRanges().isEmpty()) {
> >+            return map;
> >+        }
> >+        final Set<Map.Entry<String, String>> values
> >+                = new AbstractSet<Map.Entry<String, String>>() {
> >+
> >+            @Override
> >+            @Nonnull
> >+            public Iterator<Map.Entry<String, String>> iterator() {
> >+                return Iterators.concat(map.entrySet().iterator(),
> >Iterators.concat(new Iterator<Iterator<Map.Entry<String, String>>>() {
> >+                    private final Iterator<NodeDocument> previous =
> >doc.getPreviousDocs(null).iterator();
> >+
> >+                    @Override
> >+                    public boolean hasNext() {
> >+                        return previous.hasNext();
> >+                    }
> >+
> >+                    @Override
> >+                    public Iterator<Map.Entry<String, String>> next() {
> >+                        return
> >previous.next().getLocalMap(property).entrySet().iterator();
> >+                    }
> >+
> >+                    @Override
> >+                    public void remove() {
> >+                        throw new UnsupportedOperationException();
> >+                    }
> >+                }));
> >+            }
> >+
> >+            @Override
> >+            public int size() {
> >+                int size = map.size();
> >+                for (NodeDocument prev : doc.getPreviousDocs(null)) {
> >+                    size += prev.getLocalMap(property).size();
> >+                }
> >+                return size;
> >+            }
> >+        };
> >+        return new AbstractMap<String, String>() {
> >+
> >+            private final Map<String, String> map =
> >doc.getLocalMap(property);
> >+
> >+            @Override
> >+            @Nonnull
> >+            public Set<Entry<String, String>> entrySet() {
> >+                return values;
> >+            }
> >+
> >+            @Override
> >+            public String get(Object key) {
> >+                // first check values map of this document
> >+                String value = map.get(key);
> >+                if (value != null) {
> >+                    return value;
> >+                }
> >+                Revision r = Revision.fromString(key.toString());
> >+                for (NodeDocument prev : doc.getPreviousDocs(r)) {
> >+                    value = prev.getLocalMap(property).get(key);
> >+                    if (value != null) {
> >+                        return value;
> >+                    }
> >+                }
> >+                // not found
> >+                return null;
> >+            }
> >+
> >+            @Override
> >+            public boolean containsKey(Object key) {
> >+                // can use get()
> >+                // the values map does not have null values
> >+                return get(key) != null;
> >+            }
> >+        };
> >+    }
> >+}
> >
> >Propchange:
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/ValueMap.java
> >--------------------------------------------------------------------------
> >----
> >    svn:eol-style = native
> >
> >Propchange:
> >jackrabbit/oak/trunk/oak-
> core/src/main/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/ValueMap.java
> >--------------------------------------------------------------------------
> >----
> >    svn:keywords = Author Date Id Revision Rev URL
> >
> >Modified:
> >jackrabbit/oak/trunk/oak-
> core/src/test/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/DocumentSplitTest.java
> >URL:
> >http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-
> core/src/test/java/o
> >rg/apache/jackrabbit/oak/plugins/mongomk/DocumentSplitTest.java?rev=
> 152255
> >3&r1=1522552&r2=1522553&view=diff
> >=========================================================
> =================
> >====
> >---
> >jackrabbit/oak/trunk/oak-
> core/src/test/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/DocumentSplitTest.java (original)
> >+++
> >jackrabbit/oak/trunk/oak-
> core/src/test/java/org/apache/jackrabbit/oak/plug
> >ins/mongomk/DocumentSplitTest.java Thu Sep 12 12:45:19 2013
> >@@ -39,7 +39,7 @@ public class DocumentSplitTest extends B
> >         Set<String> revisions = Sets.newHashSet();
> >         NodeDocument doc = store.find(Collection.NODES,
> >Utils.getIdFromPath("/"));
> >         assertNotNull(doc);
> >-        revisions.addAll(doc.getRevisionsMap().keySet());
> >+        revisions.addAll(doc.getLocalRevisions().keySet());
> >
> >         // MongoMK initializes with a root node with a single revision
> >         int numRevs = 1;
> >@@ -55,7 +55,7 @@ public class DocumentSplitTest extends B
> >         String head = mk.getHeadRevision();
> >         doc = store.find(Collection.NODES, Utils.getIdFromPath("/"));
> >         assertNotNull(doc);
> >-        Map<String, String> revs = doc.getRevisionsMap();
> >+        Map<String, String> revs = doc.getLocalRevisions();
> >         // one remaining in the local revisions map
> >         assertEquals(1, revs.size());
> >         for (String r : revisions) {
> >
> >