Author: tomekr
Date: Wed Jan 25 12:32:52 2017
New Revision: 1780177

URL: http://svn.apache.org/viewvc?rev=1780177&view=rev
Log:
OAK-5515: Allow to ignore writes for some of the read-only paths

Added:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiff.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiffTest.java
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreService.java

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiff.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiff.java?rev=1780177&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiff.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiff.java
 Wed Jan 25 12:32:52 2017
@@ -0,0 +1,91 @@
+/*
+ * 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.multiplex;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+
+public class ModifiedPathDiff implements NodeStateDiff {
+
+    private final Set<String> paths;
+
+    private final String currentPath;
+
+    public static Set<String> getModifiedPaths(NodeState before, NodeState 
after) {
+        ModifiedPathDiff diff = new ModifiedPathDiff();
+        after.compareAgainstBaseState(before, diff);
+        return diff.getPaths();
+    }
+
+    private ModifiedPathDiff() {
+        this.paths = new HashSet<>();
+        this.currentPath = "/";
+    }
+
+    private ModifiedPathDiff(ModifiedPathDiff parent, String name) {
+        this.paths = parent.paths;
+        this.currentPath = concat(parent.currentPath, name);
+    }
+
+    @Override
+    public boolean propertyAdded(PropertyState after) {
+        paths.add(currentPath);
+        return true;
+    }
+
+    @Override
+    public boolean propertyChanged(PropertyState before, PropertyState after) {
+        paths.add(currentPath);
+        return true;
+    }
+
+    @Override
+    public boolean propertyDeleted(PropertyState before) {
+        paths.add(currentPath);
+        return true;
+    }
+
+    @Override
+    public boolean childNodeAdded(String name, NodeState after) {
+        paths.add(concat(currentPath, name));
+        return true;
+    }
+
+    @Override
+    public boolean childNodeChanged(String name, NodeState before, NodeState 
after) {
+        return after.compareAgainstBaseState(before, new 
ModifiedPathDiff(this, name));
+    }
+
+    @Override
+    public boolean childNodeDeleted(String name, NodeState before) {
+        paths.add(concat(currentPath, name));
+        return true;
+    }
+
+    private Set<String> getPaths() {
+        return paths;
+    }
+}

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java?rev=1780177&r1=1780176&r2=1780177&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java
 Wed Jan 25 12:32:52 2017
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugin
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.spi.commit.CommitHook;
@@ -31,6 +32,8 @@ import org.apache.jackrabbit.oak.spi.sta
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.Closeable;
 import java.io.IOException;
@@ -38,6 +41,8 @@ import java.io.InputStream;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -48,6 +53,11 @@ import static com.google.common.collect.
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Maps.filterKeys;
 import static com.google.common.collect.Maps.newHashMap;
+import static com.google.common.collect.Sets.difference;
+import static com.google.common.collect.Sets.filter;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.oak.commons.PathUtils.isAncestor;
+import static 
org.apache.jackrabbit.oak.plugins.multiplex.ModifiedPathDiff.getModifiedPaths;
 
 /**
  * A {@link NodeStore} implementation that multiplexes other {@link NodeStore} 
instances
@@ -73,15 +83,24 @@ import static com.google.common.collect.
  */
 public class MultiplexingNodeStore implements NodeStore, Observable {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(MultiplexingNodeStore.class);
+
     private static final String CHECKPOINT_ID_PREFIX = 
"multiplexing.checkpoint.";
 
+    private final TreeSet<String> ignoreReadOnlyWritePaths;
+
     final MultiplexingContext ctx;
 
     private final List<Observer> observers = new CopyOnWriteArrayList<>();
 
     // visible for testing only
     MultiplexingNodeStore(MountInfoProvider mip, NodeStore globalStore, 
List<MountedNodeStore> nonDefaultStore) {
+        this(mip, globalStore, nonDefaultStore, 
Collections.<String>emptyList());
+    }
+
+    MultiplexingNodeStore(MountInfoProvider mip, NodeStore globalStore, 
List<MountedNodeStore> nonDefaultStore, List<String> ignoreReadOnlyWritePaths) {
         this.ctx = new MultiplexingContext(mip, globalStore, nonDefaultStore);
+        this.ignoreReadOnlyWritePaths = new 
TreeSet<>(ignoreReadOnlyWritePaths);
     }
 
     @Override
@@ -115,7 +134,6 @@ public class MultiplexingNodeStore imple
             resultStates.put(mountedNodeStore, result);
         }
         MultiplexingNodeState newRoot = createRootNodeState(resultStates);
-
         for (Observer observer : observers) {
             observer.contentChanged(newRoot, info);
         }
@@ -128,9 +146,18 @@ public class MultiplexingNodeStore imple
                 continue;
             }
             NodeBuilder partialBuilder = 
nodeBuilder.getBuilders().get(mountedNodeStore);
-            if 
(!partialBuilder.getNodeState().equals(partialBuilder.getBaseState())) {
-                // TODO - add proper error code
-                throw new CommitFailedException("Multiplex", 31, "Unable to 
perform changes on read-only mount " + mountedNodeStore.getMount());
+            NodeState baseState = partialBuilder.getBaseState();
+            NodeState nodeState = partialBuilder.getNodeState();
+            if (!nodeState.equals(baseState)) {
+                Set<String> changedPaths = getModifiedPaths(baseState, 
nodeState);
+                Set<String> ignoredChangedPaths = 
getIgnoredPaths(changedPaths);
+                if (!ignoredChangedPaths.isEmpty()) {
+                    LOG.warn("Can't merge following read-only paths (they are 
configured to be ignored): {}.", ignoredChangedPaths);
+                }
+                Set<String> failingChangedPaths = difference(changedPaths, 
ignoredChangedPaths);
+                if (!failingChangedPaths.isEmpty()) {
+                    throw new CommitFailedException("Multiplex", 31, "Unable 
to perform changes on read-only mount " + mountedNodeStore.getMount().getName() 
+ ". Failing paths: " + failingChangedPaths.toString());
+                }
             }
         }
     }
@@ -287,6 +314,16 @@ public class MultiplexingNodeStore imple
         };
     }
 
+    private Set<String> getIgnoredPaths(Set<String> paths) {
+        return newHashSet(filter(paths, new Predicate<String>() {
+            @Override
+            public boolean apply(String path) {
+                String previousPath = ignoreReadOnlyWritePaths.floor(path);
+                return previousPath != null && (previousPath.equals(path) || 
isAncestor(previousPath, path));
+            }
+        }));
+    }
+
     public static class Builder {
 
         private final MountInfoProvider mip;
@@ -295,6 +332,8 @@ public class MultiplexingNodeStore imple
 
         private final List<MountedNodeStore> nonDefaultStores = 
Lists.newArrayList();
 
+        private final List<String> ignoreReadOnlyWritePaths = 
Lists.newArrayList();
+
         public Builder(MountInfoProvider mip, NodeStore globalStore) {
             this.mip = checkNotNull(mip, "mountInfoProvider");
             this.globalStore = checkNotNull(globalStore, "globalStore");
@@ -309,10 +348,15 @@ public class MultiplexingNodeStore imple
             return this;
         }
 
+        public Builder addIgnoredReadOnlyWritePath(String path) {
+            ignoreReadOnlyWritePaths.add(path);
+            return this;
+        }
+
         public MultiplexingNodeStore build() {
             checkReadWriteMountsNumber();
             checkMountsAreConsistentWithMounts();
-            return new MultiplexingNodeStore(mip, globalStore, 
nonDefaultStores);
+            return new MultiplexingNodeStore(mip, globalStore, 
nonDefaultStores, ignoreReadOnlyWritePaths);
         }
 
         private void checkReadWriteMountsNumber() {

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreService.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreService.java?rev=1780177&r1=1780176&r2=1780177&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreService.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreService.java
 Wed Jan 25 12:32:52 2017
@@ -20,6 +20,8 @@ import org.apache.felix.scr.annotations.
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.ConfigurationPolicy;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.ReferencePolicy;
@@ -59,15 +61,24 @@ public class MultiplexingNodeStoreServic
     @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = 
ReferencePolicy.DYNAMIC, bind = "bindNodeStore", unbind = "unbindNodeStore", 
referenceInterface = NodeStoreProvider.class)
     private List<NodeStoreWithProps> nodeStores = new ArrayList<>();
 
+    @Property(label = "Ignore read only writes",
+            unbounded = PropertyUnbounded.ARRAY,
+            description = "Writes to these read-only paths won't fail the 
commit"
+    )
+    private static final String PROP_IGNORE_READ_ONLY_WRITES = 
"ignoreReadOnlyWrites";
+
     private ComponentContext context;
 
     private ServiceRegistration nsReg;
 
     private ObserverTracker observerTracker;
 
+    private String[] ignoreReadOnlyWritePaths;
+
     @Activate
-    protected void activate(ComponentContext context) {
+    protected void activate(ComponentContext context, Map<String, ?> config) {
         this.context = context;
+        ignoreReadOnlyWritePaths = 
PropertiesUtil.toStringArray(config.get(PROP_IGNORE_READ_ONLY_WRITES));
         registerMultiplexingNodeStore();
     }
 
@@ -107,6 +118,9 @@ public class MultiplexingNodeStoreServic
         LOG.info("Node stores for all configured mounts are available");
 
         MultiplexingNodeStore.Builder builder = new 
MultiplexingNodeStore.Builder(mountInfoProvider, 
globalNs.getNodeStoreProvider().getNodeStore());
+        for (String p : ignoreReadOnlyWritePaths) {
+            builder.addIgnoredReadOnlyWritePath(p);
+        }
 
         for (NodeStoreWithProps ns : nodeStores) {
             if (isGlobalNodeStore(ns)) {

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiffTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiffTest.java?rev=1780177&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiffTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/ModifiedPathDiffTest.java
 Wed Jan 25 12:32:52 2017
@@ -0,0 +1,72 @@
+/*
+ * 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.multiplex;
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Test;
+
+import java.util.Set;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static 
org.apache.jackrabbit.oak.plugins.multiplex.ModifiedPathDiff.getModifiedPaths;
+import static org.junit.Assert.assertEquals;
+
+public class ModifiedPathDiffTest {
+
+    private final NodeState base; {
+        NodeBuilder builder = EmptyNodeState.EMPTY_NODE.builder();
+        builder.setChildNode("a");
+        builder.setChildNode("b");
+        builder.setChildNode("c").child("d");
+        builder.setProperty("x", 1);
+        builder.setProperty("y", 1);
+        builder.setProperty("z", 1);
+        base = builder.getNodeState();
+    }
+
+    @Test
+    public void testAddedNodesAreModified() {
+        NodeBuilder builder = base.builder();
+        builder.getChildNode("a").child("xyz");
+        builder.getChildNode("c").getChildNode("d").child("abc");
+        Set<String> paths = getModifiedPaths(base, builder.getNodeState());
+        assertEquals(newHashSet("/a/xyz", "/c/d/abc"), paths);
+    }
+
+    @Test
+    public void testRemovedNodesAreModified() {
+        NodeBuilder builder = base.builder();
+        builder.getChildNode("a").remove();
+        builder.getChildNode("c").getChildNode("d").remove();
+        Set<String> paths = getModifiedPaths(base, builder.getNodeState());
+        assertEquals(newHashSet("/a", "/c/d"), paths);
+    }
+
+    @Test
+    public void testModifiedNodesAreModified() {
+        NodeBuilder builder = base.builder();
+        builder.getChildNode("a").setProperty("x", 1l, Type.LONG);
+        builder.getChildNode("c").getChildNode("d").setProperty("x", 1l, 
Type.LONG);
+        Set<String> paths = getModifiedPaths(base, builder.getNodeState());
+        assertEquals(newHashSet("/a", "/c/d"), paths);
+    }
+}


Reply via email to