Author: tomekr
Date: Mon Jul 17 10:55:26 2017
New Revision: 1802126

URL: http://svn.apache.org/viewvc?rev=1802126&view=rev
Log:
OAK-6425: Make the CompositeNodeStore thread-safe

New, lock-less approach.

Added:
    
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/AtomicCompositeMergeTest.java
    
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CommitHookEnhancer.java
Modified:
    
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeBuilder.java
    
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeState.java
    
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStore.java
    
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositionContext.java

Added: 
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/AtomicCompositeMergeTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/AtomicCompositeMergeTest.java?rev=1802126&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/AtomicCompositeMergeTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/AtomicCompositeMergeTest.java
 Mon Jul 17 10:55:26 2017
@@ -0,0 +1,213 @@
+/*
+ * 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.composite;
+
+import com.google.common.io.Closer;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.InitialContent;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.QueryEngine;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.commons.IOUtils;
+import org.apache.jackrabbit.oak.plugins.document.DocumentMK;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore;
+import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager;
+import 
org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
+import org.apache.jackrabbit.oak.spi.mount.Mounts;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.query.Query;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS;
+import static org.junit.Assert.assertEquals;
+
+public class AtomicCompositeMergeTest {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AtomicCompositeMergeTest.class);
+
+    private static final int THREADS = 6;
+
+    private static final String TEST_UUID = IdentifierManager.generateUUID();
+
+    private Closer closer;
+
+    @Before
+    public void initNodeStore() {
+        closer = Closer.create();
+    }
+
+    @After
+    public void closeAll() throws IOException {
+        closer.close();
+    }
+
+    @Test
+    @Ignore
+    public void testLocalMerges() throws InterruptedException, IOException, 
ParseException, CommitFailedException {
+        Oak oak = getOak(getCompositeNodeStore(new MemoryNodeStore()));
+        testAtomicMerges(clusterId -> oak);
+    }
+
+    @Test
+    public void testDistributedMerge() throws InterruptedException, 
IOException, ParseException, CommitFailedException {
+        MemoryDocumentStore sharedDocStore = new MemoryDocumentStore();
+        testAtomicMerges(clusterId -> {
+            DocumentNodeStore docNodeStore = new DocumentMK.Builder()
+                    .setDocumentStore(sharedDocStore)
+                    .setClusterId(clusterId)
+                    .getNodeStore();
+            closer.register(() -> docNodeStore.dispose());
+            NodeStore compositeNodeStore = getCompositeNodeStore(docNodeStore);
+            return getOak(compositeNodeStore);
+        });
+    }
+
+    private void testAtomicMerges(Function<Integer, Oak> oakSupplier) throws 
InterruptedException, IOException, ParseException, CommitFailedException {
+        Set<String> failedMerges = Collections.synchronizedSet(newHashSet());
+        List<Thread> threads = newArrayList();
+
+        ContentSession generalSession = 
oakSupplier.apply(100).createContentSession();
+        closer.register(generalSession);
+        waitForReindexing(generalSession);
+        sleep(1000); // sleep for a sec, so the new repository have chance to 
initialize itself
+
+        for (int i = 0; i < THREADS; i++) {
+            String name = "child_" + i;
+            Oak oak = oakSupplier.apply(i + 1);
+            ContentSession session = oak.createContentSession();
+            threads.add(new Thread(() -> {
+                LOG.info("Started thread {}", name);
+                try {
+                    Root root = session.getLatestRoot();
+                    
root.getTree("/").addChild(name).setProperty(JcrConstants.JCR_UUID, TEST_UUID);
+                    root.commit();
+                    LOG.info("Merged successfully the node /{}: {}", name, 
root.getTree("/" + name));
+                } catch (CommitFailedException e) {
+                    LOG.info("Expected failure", e);
+                    failedMerges.add(name);
+                } catch (Exception e) {
+                    LOG.error("Can't commit", e);
+                } finally {
+                    IOUtils.closeQuietly(session);
+                }
+            }));
+        }
+
+        threads.forEach(Thread::start);
+        threads.forEach(AtomicCompositeMergeTest::join);
+
+        List<String> uuidPaths = waitForUuid(generalSession, TEST_UUID);
+
+        assertEquals("There should be just one indexed value for the 
TEST_UUID, but following are given: " + uuidPaths + ". Failed merge list: " + 
failedMerges, 1, uuidPaths.size());
+        assertEquals("There should be " + (THREADS - 1) + " failed merges, but 
following are given: " + failedMerges,THREADS - 1, failedMerges.size());
+    }
+
+    private static List<String> waitForUuid(ContentSession session, String 
uuid) throws ParseException {
+        for (int i = 0; i < 20; i++) {
+            List<String> result = queryUuid(session, uuid);
+            if (result.isEmpty()) {
+                sleep(500);
+            } else {
+                return result;
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    private static List<String> queryUuid(ContentSession session, String uuid) 
throws ParseException {
+        Map<String, PropertyValue> bindings = Collections.singletonMap("id", 
PropertyValues.newString(uuid));
+        Result result = session.getLatestRoot().getQueryEngine().executeQuery(
+                "SELECT * FROM [nt:base] WHERE [jcr:uuid] = $id" + 
QueryEngine.INTERNAL_SQL2_QUERY,
+                Query.JCR_SQL2,
+                bindings, NO_MAPPINGS);
+        return StreamSupport.stream(result.getRows().spliterator(), false)
+                .map(r -> r.getPath())
+                .collect(Collectors.toList());
+    }
+
+    private void waitForReindexing(ContentSession session) throws 
CommitFailedException, ParseException {
+        String tmpUuid = IdentifierManager.generateUUID();
+
+        Root root = session.getLatestRoot();
+        root.getTree("/").addChild("tmp").setProperty(JcrConstants.JCR_UUID, 
tmpUuid);
+        root.commit();
+
+        assertEquals(1, waitForUuid(session, tmpUuid).size());
+
+        root.getTree("/tmp").remove();
+        root.commit();
+    }
+
+    private static void join(Thread t) {
+        try {
+            t.join();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void sleep(long millis) {
+        if (millis <= 0) {
+            return;
+        }
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            LOG.error("Interrupted", e);
+        }
+    }
+
+    private static NodeStore getCompositeNodeStore(NodeStore globalNodeStore) {
+        return new CompositeNodeStore(Mounts.defaultMountInfoProvider(), 
globalNodeStore, Collections.emptyList());
+    }
+
+    private static Oak getOak(NodeStore nodeStore) {
+        return new Oak(nodeStore)
+                .with(new OpenSecurityProvider())
+                .with(new PropertyIndexEditorProvider())
+                .with(new InitialContent());
+
+    }
+}

Added: 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CommitHookEnhancer.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CommitHookEnhancer.java?rev=1802126&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CommitHookEnhancer.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CommitHookEnhancer.java
 Mon Jul 17 10:55:26 2017
@@ -0,0 +1,77 @@
+/*
+ * 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.composite;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import javax.annotation.Nonnull;
+import java.util.Map;
+import java.util.Optional;
+
+import static com.google.common.collect.Maps.newHashMap;
+
+class CommitHookEnhancer implements CommitHook {
+
+    private final CompositionContext ctx;
+
+    private final Map<MountedNodeStore, NodeBuilder> builders;
+
+    private final CommitHook hook;
+
+    private Optional<CompositeNodeBuilder> updatedBuilder = Optional.empty();
+
+    CommitHookEnhancer(CommitHook hook, CompositionContext ctx, 
Map<MountedNodeStore, NodeBuilder> builders) {
+        this.ctx = ctx;
+        this.builders = builders;
+        this.hook = hook;
+    }
+
+    @Nonnull
+    @Override
+    public NodeState processCommit(NodeState before, NodeState after, 
CommitInfo info) throws CommitFailedException {
+        Map<MountedNodeStore, NodeState> beforeStates = newHashMap();
+        Map<MountedNodeStore, NodeState> afterStates = newHashMap();
+        for (Map.Entry<MountedNodeStore, NodeBuilder> e : builders.entrySet()) 
{
+            afterStates.put(e.getKey(), 
e.getKey().getNodeStore().rebase(e.getValue()));
+            beforeStates.put(e.getKey(), e.getValue().getBaseState());
+        }
+        beforeStates.put(ctx.getGlobalStore(), before);
+        afterStates.put(ctx.getGlobalStore(), after);
+
+        CompositeNodeState compositeBefore = 
ctx.createRootNodeState(beforeStates);
+        CompositeNodeState compositeAfter = 
ctx.createRootNodeState(afterStates);
+
+        NodeState result = hook.processCommit(compositeBefore, compositeAfter, 
info);
+        updatedBuilder = Optional.of(toComposite(result, compositeBefore));
+        return 
updatedBuilder.get().getNodeState().getNodeStates().get(ctx.getGlobalStore());
+    }
+
+    Optional<CompositeNodeBuilder> getUpdatedBuilder() {
+        return updatedBuilder;
+    }
+
+    private CompositeNodeBuilder toComposite(NodeState nodeState, 
CompositeNodeState compositeRoot) {
+        CompositeNodeBuilder builder = compositeRoot.builder();
+        nodeState.compareAgainstBaseState(compositeRoot, new 
ApplyDiff(builder));
+        return builder;
+    }
+}

Modified: 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeBuilder.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeBuilder.java?rev=1802126&r1=1802125&r2=1802126&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeBuilder.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeBuilder.java
 Mon Jul 17 10:55:26 2017
@@ -83,12 +83,12 @@ class CompositeNodeBuilder implements No
     }
 
     @Override
-    public NodeState getNodeState() {
+    public CompositeNodeState getNodeState() {
         return new CompositeNodeState(path, new 
IdentityHashMap<>(buildersToNodeStates(nodeBuilders)), ctx);
     }
 
     @Override
-    public NodeState getBaseState() {
+    public CompositeNodeState getBaseState() {
         return new CompositeNodeState(path, 
buildersToBaseStates(nodeBuilders), ctx);
     }
 

Modified: 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeState.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeState.java?rev=1802126&r1=1802125&r2=1802126&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeState.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeState.java
 Mon Jul 17 10:55:26 2017
@@ -75,6 +75,10 @@ class CompositeNodeState extends Abstrac
         this.owningStore = ctx.getOwningStore(path);
     }
 
+    Map<MountedNodeStore, NodeState> getNodeStates() {
+        return nodeStates;
+    }
+
     @Override
     public boolean exists() {
         return getWrappedNodeState().exists();
@@ -198,7 +202,7 @@ class CompositeNodeState extends Abstrac
 
     // write operations
     @Override
-    public NodeBuilder builder() {
+    public CompositeNodeBuilder builder() {
         Map<MountedNodeStore, NodeBuilder> nodeBuilders = 
transformValues(nodeStates, new Function<NodeState, NodeBuilder>() {
             @Override
             public NodeBuilder apply(NodeState input) {

Modified: 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStore.java?rev=1802126&r1=1802125&r2=1802126&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositeNodeStore.java
 Mon Jul 17 10:55:26 2017
@@ -32,7 +32,6 @@ import org.apache.jackrabbit.oak.spi.com
 import org.apache.jackrabbit.oak.spi.commit.Observer;
 import org.apache.jackrabbit.oak.spi.mount.Mount;
 import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
-import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
@@ -49,8 +48,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -103,7 +102,7 @@ public class CompositeNodeStore implemen
 
     private final List<Observer> observers = new CopyOnWriteArrayList<>();
 
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    private final Lock mergeLock;
 
     // visible for testing only
     CompositeNodeStore(MountInfoProvider mip, NodeStore globalStore, 
List<MountedNodeStore> nonDefaultStore) {
@@ -113,6 +112,7 @@ public class CompositeNodeStore implemen
     CompositeNodeStore(MountInfoProvider mip, NodeStore globalStore, 
List<MountedNodeStore> nonDefaultStore, List<String> ignoreReadOnlyWritePaths) {
         this.ctx = new CompositionContext(mip, globalStore, nonDefaultStore);
         this.ignoreReadOnlyWritePaths = new 
TreeSet<>(ignoreReadOnlyWritePaths);
+        this.mergeLock = new ReentrantLock();
     }
 
     @Override
@@ -120,15 +120,10 @@ public class CompositeNodeStore implemen
         // the composite root state exposes the node states as they are
         // at this certain point in time, so we eagerly retrieve them from all 
stores
         Map<MountedNodeStore, NodeState> nodeStates = newHashMap();
-        lock.readLock().lock();
-        try {
-            for (MountedNodeStore nodeStore : ctx.getAllMountedNodeStores()) {
-                nodeStates.put(nodeStore, nodeStore.getNodeStore().getRoot());
-            }
-        } finally {
-            lock.readLock().unlock();
+        for (MountedNodeStore nodeStore : ctx.getAllMountedNodeStores()) {
+            nodeStates.put(nodeStore, nodeStore.getNodeStore().getRoot());
         }
-        return createRootNodeState(nodeStates);
+        return ctx.createRootNodeState(nodeStates);
     }
 
     @Override
@@ -139,27 +134,33 @@ public class CompositeNodeStore implemen
             throw new IllegalArgumentException();
         }
 
+        assertNoChangesOnReadOnlyMounts(nodeBuilder);
+
+        // merge the global builder and apply the commit hooks within
         Map<MountedNodeStore, NodeState> resultStates = newHashMap();
-        lock.writeLock().lock();
-        try {
-            // run commit hooks and apply the changes to the builder instance
-            NodeState rebased = rebase(nodeBuilder);
-            NodeState processed = 
commitHook.processCommit(nodeBuilder.getBaseState(), rebased, info);
-            processed.compareAgainstBaseState(rebased, new 
ApplyDiff(nodeBuilder));
-
-            assertNoChangesOnReadOnlyMounts(nodeBuilder);
-
-            // apply the accumulated changes on individual NodeStore instances
-            for (MountedNodeStore mountedNodeStore : 
ctx.getAllMountedNodeStores()) {
-                NodeStore nodeStore = mountedNodeStore.getNodeStore();
-                NodeBuilder partialBuilder = 
nodeBuilder.getBuilders().get(mountedNodeStore);
-                NodeState result = nodeStore.merge(partialBuilder, 
EmptyHook.INSTANCE, info);
-                resultStates.put(mountedNodeStore, result);
+        MountedNodeStore globalStore = ctx.getGlobalStore();
+        CommitHookEnhancer hookEnhancer = new CommitHookEnhancer(commitHook, 
ctx, nodeBuilder.getBuilders());
+        NodeState globalResult = 
globalStore.getNodeStore().merge(nodeBuilder.getBuilders().get(globalStore), 
hookEnhancer, info);
+        resultStates.put(globalStore, globalResult);
+        CompositeNodeBuilder updatedBuilder = 
hookEnhancer.getUpdatedBuilder().orElse(nodeBuilder);
+
+        // merge the partial builders
+        for (MountedNodeStore mns : ctx.getNonDefaultStores()) {
+            NodeBuilder partialBuilder = updatedBuilder.getBuilders().get(mns);
+
+            if (mns.getMount().isReadOnly()) {
+                if 
(!partialBuilder.getNodeState().equals(partialBuilder.getBaseState())) {
+                    throw new CommitFailedException("CompositeStore", 31, 
"Unable to perform changes on read-only mount " + mns.getMount().getName());
+                }
+                resultStates.put(mns, partialBuilder.getBaseState());
+                continue;
             }
-        } finally {
-            lock.writeLock().unlock();
+
+            NodeState partialState = mns.getNodeStore().merge(partialBuilder, 
EmptyHook.INSTANCE, info);
+            resultStates.put(mns, partialState);
         }
-        CompositeNodeState newRoot = createRootNodeState(resultStates);
+
+        CompositeNodeState newRoot = ctx.createRootNodeState(resultStates);
         for (Observer observer : observers) {
             observer.contentChanged(newRoot, info);
         }
@@ -194,19 +195,13 @@ public class CompositeNodeStore implemen
 
         CompositeNodeBuilder nodeBuilder = (CompositeNodeBuilder) builder;
         Map<MountedNodeStore, NodeState> resultStates = newHashMap();
-
-        lock.readLock().lock();
-        try {
-            for (MountedNodeStore mountedNodeStore : 
ctx.getAllMountedNodeStores()) {
-                NodeStore nodeStore = mountedNodeStore.getNodeStore();
-                NodeBuilder partialBuilder = 
nodeBuilder.getBuilders().get(mountedNodeStore);
-                NodeState result = nodeStore.rebase(partialBuilder);
-                resultStates.put(mountedNodeStore, result);
-            }
-        } finally {
-            lock.readLock().unlock();
+        for (MountedNodeStore mountedNodeStore : 
ctx.getAllMountedNodeStores()) {
+            NodeStore nodeStore = mountedNodeStore.getNodeStore();
+            NodeBuilder partialBuilder = 
nodeBuilder.getBuilders().get(mountedNodeStore);
+            NodeState result = nodeStore.rebase(partialBuilder);
+            resultStates.put(mountedNodeStore, result);
         }
-        return createRootNodeState(resultStates);
+        return ctx.createRootNodeState(resultStates);
     }
 
     @Override
@@ -215,23 +210,13 @@ public class CompositeNodeStore implemen
 
         CompositeNodeBuilder nodeBuilder = (CompositeNodeBuilder) builder;
         Map<MountedNodeStore, NodeState> resultStates = newHashMap();
-
-        lock.readLock().lock();
-        try {
-            for (MountedNodeStore mountedNodeStore : 
ctx.getAllMountedNodeStores()) {
-                NodeStore nodeStore = mountedNodeStore.getNodeStore();
-                NodeBuilder partialBuilder = 
nodeBuilder.getBuilders().get(mountedNodeStore);
-                NodeState result = nodeStore.reset(partialBuilder);
-                resultStates.put(mountedNodeStore, result);
-            }
-        } finally {
-            lock.readLock().unlock();
+        for (MountedNodeStore mountedNodeStore : 
ctx.getAllMountedNodeStores()) {
+            NodeStore nodeStore = mountedNodeStore.getNodeStore();
+            NodeBuilder partialBuilder = 
nodeBuilder.getBuilders().get(mountedNodeStore);
+            NodeState result = nodeStore.reset(partialBuilder);
+            resultStates.put(mountedNodeStore, result);
         }
-        return createRootNodeState(resultStates);
-    }
-
-    private CompositeNodeState createRootNodeState(Map<MountedNodeStore, 
NodeState> rootStates) {
-        return new CompositeNodeState("/", rootStates, ctx);
+        return ctx.createRootNodeState(resultStates);
     }
 
     @Override
@@ -274,24 +259,18 @@ public class CompositeNodeStore implemen
         Map<String, String> globalProperties = newHashMap(properties);
         globalProperties.put(CHECKPOINT_METADATA + "created", 
Long.toString(currentTimeMillis()));
         globalProperties.put(CHECKPOINT_METADATA + "expires", 
Long.toString(currentTimeMillis() + lifetime));
-
-        lock.readLock().lock();
-        try {
-            for (MountedNodeStore mns : ctx.getNonDefaultStores()) {
-                if (mns.getMount().isReadOnly()) {
-                    continue;
-                }
-                String checkpoint = mns.getNodeStore().checkpoint(lifetime, 
properties);
-                globalProperties.put(CHECKPOINT_METADATA_MOUNT + 
mns.getMount().getName(), checkpoint);
-            }
-            String newCheckpoint = 
ctx.getGlobalStore().getNodeStore().checkpoint(lifetime, globalProperties);
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("Created checkpoint {}. Debug info:\n{}", 
newCheckpoint, checkpointDebugInfo());
+        for (MountedNodeStore mns : ctx.getNonDefaultStores()) {
+            if (mns.getMount().isReadOnly()) {
+                continue;
             }
-            return newCheckpoint;
-        } finally {
-            lock.readLock().unlock();
+            String checkpoint = mns.getNodeStore().checkpoint(lifetime, 
properties);
+            globalProperties.put(CHECKPOINT_METADATA_MOUNT + 
mns.getMount().getName(), checkpoint);
+        }
+        String newCheckpoint = 
ctx.getGlobalStore().getNodeStore().checkpoint(lifetime, globalProperties);
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Created checkpoint {}. Debug info:\n{}", newCheckpoint, 
checkpointDebugInfo());
         }
+        return newCheckpoint;
     }
 
     @Override

Modified: 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositionContext.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositionContext.java?rev=1802126&r1=1802125&r2=1802126&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositionContext.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/CompositionContext.java
 Mon Jul 17 10:55:26 2017
@@ -172,4 +172,14 @@ class CompositionContext {
             }
         };
     }
+
+    CompositeNodeState createRootNodeState(Map<MountedNodeStore, NodeState> 
rootStates) {
+        for (NodeState s : rootStates.values()) {
+            if (s instanceof CompositeNodeState) {
+                throw new IllegalArgumentException("Nesting composite node 
states is not supported");
+            }
+        }
+        return new CompositeNodeState("/", rootStates, this);
+    }
+
 }


Reply via email to