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);
+ }
+}