You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by to...@apache.org on 2016/11/02 12:16:32 UTC
svn commit: r1767652 [1/2] - in /jackrabbit/oak/trunk:
oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/
oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/
oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/ oak-core...
Author: tomekr
Date: Wed Nov 2 12:16:32 2016
New Revision: 1767652
URL: http://svn.apache.org/viewvc?rev=1767652&view=rev
Log:
OAK-4871: Multiplexing NodeStore
Code provided by Robert Munteanu <ro...@apache.org>
Added:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountedNodeStore.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingContext.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java
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
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/package-info.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingChildrenCountTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingCompareTest.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingMemoryFixture.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreBuilderTest.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreTest.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingSegmentFixture.java
Modified:
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountInfo.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/SimpleMountInfoProvider.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mount.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/MountInfoProvider.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mounts.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/package-info.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java
Modified: jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java?rev=1767652&r1=1767651&r2=1767652&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java (original)
+++ jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/FixturesHelper.java Wed Nov 2 12:16:32 2016
@@ -43,7 +43,7 @@ public final class FixturesHelper {
* default fixtures when no {@code nsfixtures} is provided
*/
public enum Fixture {
- DOCUMENT_NS, SEGMENT_MK, DOCUMENT_RDB, MEMORY_NS, DOCUMENT_MEM, SEGMENT_TAR
+ DOCUMENT_NS, SEGMENT_MK, DOCUMENT_RDB, MEMORY_NS, DOCUMENT_MEM, SEGMENT_TAR, MULTIPLEXED_SEGMENT, MULTIPLEXED_MEM
}
private static final Set<Fixture> FIXTURES;
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountInfo.java?rev=1767652&r1=1767651&r2=1767652&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountInfo.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountInfo.java Wed Nov 2 12:16:32 2016
@@ -30,6 +30,7 @@ import com.google.common.collect.Iterabl
import org.apache.jackrabbit.oak.spi.mount.Mount;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
import static org.apache.jackrabbit.oak.commons.PathUtils.isAncestor;
final class MountInfo implements Mount {
@@ -67,6 +68,18 @@ final class MountInfo implements Mount {
return true;
}
}
+
+ return false;
+ }
+
+ @Override
+ public boolean isDirectlyUnder(String path) {
+ path = SANITIZE_PATH.apply(path);
+ for (String includedPath : includedPaths) {
+ if (getParentPath(includedPath).equals(path)) {
+ return true;
+ }
+ }
return false;
}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountedNodeStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountedNodeStore.java?rev=1767652&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountedNodeStore.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MountedNodeStore.java Wed Nov 2 12:16:32 2016
@@ -0,0 +1,54 @@
+/*
+ * 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.spi.mount.Mount;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+class MountedNodeStore {
+
+ private final Mount mount;
+
+ private final NodeStore nodeStore;
+
+ public MountedNodeStore(Mount mount, NodeStore nodeStore) {
+ this.mount = mount;
+ this.nodeStore = nodeStore;
+ }
+
+ public Mount getMount() {
+ return mount;
+ }
+
+ public NodeStore getNodeStore() {
+ return nodeStore;
+ }
+
+ boolean hasChildren(Iterable<String> children) {
+ // since we can't possibly know if a node matching the
+ // 'oak:mount-*' pattern exists below a given path
+ // we are forced to iterate for each node store
+ for (String childNodeName : children) {
+ if (childNodeName.startsWith(getMount().getPathFragmentName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingContext.java?rev=1767652&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingContext.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingContext.java Wed Nov 2 12:16:32 2016
@@ -0,0 +1,149 @@
+/*
+ * 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 com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.mount.Mount;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+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 java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.ImmutableMap.copyOf;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.uniqueIndex;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+
+class MultiplexingContext {
+
+ private final MountInfoProvider mip;
+
+ private final MountedNodeStore globalStore;
+
+ private final List<MountedNodeStore> nonDefaultStores;
+
+ private final Map<Mount, MountedNodeStore> nodeStoresByMount;
+
+ MultiplexingContext(MountInfoProvider mip, NodeStore globalStore, List<MountedNodeStore> nonDefaultStores) {
+ this.mip = mip;
+ this.globalStore = new MountedNodeStore(mip.getDefaultMount(), globalStore);
+ this.nonDefaultStores = nonDefaultStores;
+ this.nodeStoresByMount = copyOf(uniqueIndex(getAllMountedNodeStores(), new Function<MountedNodeStore, Mount>() {
+ @Override
+ public Mount apply(MountedNodeStore input) {
+ return input.getMount();
+ }
+ }));
+ }
+
+ MountedNodeStore getGlobalStore() {
+ return globalStore;
+ }
+
+ List<MountedNodeStore> getNonDefaultStores() {
+ return nonDefaultStores;
+ }
+
+ MountedNodeStore getOwningStore(String path) {
+ Mount mount = mip.getMountByPath(path);
+ if (nodeStoresByMount.containsKey(mount)) {
+ return nodeStoresByMount.get(mount);
+ } else {
+ throw new IllegalArgumentException("Unable to find an owning store for path " + path);
+ }
+ }
+
+ List<MountedNodeStore> getContributingStoresForNodes(String path, final Map<MountedNodeStore, NodeState> nodeStates) {
+ return getContributingStores(path, new Function<MountedNodeStore, Iterable<String>>() {
+ @Override
+ public Iterable<String> apply(MountedNodeStore input) {
+ return nodeStates.get(input).getChildNodeNames();
+ }
+ });
+ }
+
+ List<MountedNodeStore> getContributingStoresForBuilders(String path, final Map<MountedNodeStore, NodeBuilder> nodeBuilders) {
+ return getContributingStores(path, new Function<MountedNodeStore, Iterable<String>>() {
+ @Override
+ public Iterable<String> apply(MountedNodeStore input) {
+ return nodeBuilders.get(input).getChildNodeNames();
+ }
+ });
+ }
+
+ private List<MountedNodeStore> getContributingStores(String path, Function<MountedNodeStore, Iterable<String>> childrenProvider) {
+ Mount owningMount = mip.getMountByPath(path);
+ if (!owningMount.isDefault() && nodeStoresByMount.containsKey(owningMount)) {
+ MountedNodeStore nodeStore = nodeStoresByMount.get(owningMount);
+ if (nodeStore != globalStore) {
+ return singletonList(nodeStore);
+ }
+ }
+
+ // scenario 2 - multiple mounts participate
+ List<MountedNodeStore> mountedStores = newArrayList();
+ mountedStores.add(globalStore);
+
+ // we need mounts placed exactly one level beneath this path
+ Collection<Mount> mounts = mip.getMountsPlacedDirectlyUnder(path);
+
+ // query the mounts next
+ for (MountedNodeStore mountedNodeStore : nonDefaultStores) {
+ if (mounts.contains(mountedNodeStore.getMount())) {
+ mountedStores.add(mountedNodeStore);
+ } else {
+ if (mountedNodeStore.hasChildren(childrenProvider.apply(mountedNodeStore))) {
+ mountedStores.add(mountedNodeStore);
+ }
+ }
+ }
+
+ return mountedStores;
+ }
+
+ Iterable<MountedNodeStore> getAllMountedNodeStores() {
+ return concat(singleton(globalStore), nonDefaultStores);
+ }
+
+ Blob createBlob(InputStream inputStream) throws IOException {
+ return globalStore.getNodeStore().createBlob(inputStream);
+ }
+
+ int getStoresCount() {
+ return nonDefaultStores.size() + 1;
+ }
+
+ Predicate<String> belongsToStore(final MountedNodeStore mountedNodeStore, final String parentPath) {
+ return new Predicate<String>() {
+ @Override
+ public boolean apply(String childName) {
+ return getOwningStore(PathUtils.concat(parentPath, childName)) == mountedNodeStore;
+ }
+ };
+ }
+}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java?rev=1767652&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeBuilder.java Wed Nov 2 12:16:32 2016
@@ -0,0 +1,400 @@
+/*
+ * 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 com.google.common.base.Function;
+import com.google.common.base.Objects;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.spi.state.MoveDetector;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableMap.copyOf;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Maps.newHashMap;
+import static com.google.common.collect.Maps.transformValues;
+import static java.lang.Long.MAX_VALUE;
+import static java.util.Collections.singleton;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE;
+import static org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingNodeState.STOP_COUNTING_CHILDREN;
+import static org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingNodeState.accumulateChildSizes;
+import static org.apache.jackrabbit.oak.spi.state.AbstractNodeState.checkValidName;
+
+class MultiplexingNodeBuilder implements NodeBuilder {
+
+ private final String path;
+
+ private final MultiplexingContext ctx;
+
+ private final Map<MountedNodeStore, NodeBuilder> nodeBuilders;
+
+ private final Map<MountedNodeStore, NodeBuilder> rootBuilders;
+
+ private final MountedNodeStore owningStore;
+
+ MultiplexingNodeBuilder(String path, Map<MountedNodeStore, NodeBuilder> nodeBuilders, Map<MountedNodeStore, NodeBuilder> rootBuilders, MultiplexingContext ctx) {
+ checkArgument(nodeBuilders.size() == ctx.getStoresCount(), "Got %s builders but the context manages %s stores", nodeBuilders.size(), ctx.getStoresCount());
+ checkArgument(rootBuilders.size() == ctx.getStoresCount(), "Got %s builders but the context manages %s stores", rootBuilders.size(), ctx.getStoresCount());
+
+ this.path = path;
+ this.ctx = ctx;
+ this.nodeBuilders = newHashMap(nodeBuilders);
+ this.rootBuilders = copyOf(rootBuilders);
+ this.owningStore = ctx.getOwningStore(path);
+ }
+
+ Map<MountedNodeStore, NodeBuilder> getBuilders() {
+ return nodeBuilders;
+ }
+
+ @Override
+ public NodeState getNodeState() {
+ Map<MountedNodeStore, NodeState> rootNodes = buildersToNodeStates(rootBuilders);
+ return new MultiplexingNodeState(path, rootNodes, ctx);
+ }
+
+ @Override
+ public NodeState getBaseState() {
+ Map<MountedNodeStore, NodeState> rootNodes = buildersToBaseStates(rootBuilders);
+ return new MultiplexingNodeState(path, rootNodes, ctx);
+ }
+
+ private static Map<MountedNodeStore, NodeState> buildersToNodeStates(Map<MountedNodeStore, NodeBuilder> builders) {
+ return copyOf(transformValues(builders, new Function<NodeBuilder, NodeState>() {
+ @Override
+ public NodeState apply(NodeBuilder input) {
+ return input.getNodeState();
+ }
+ }));
+ }
+
+ private static Map<MountedNodeStore, NodeState> buildersToBaseStates(Map<MountedNodeStore, NodeBuilder> builders) {
+ return copyOf(transformValues(builders, new Function<NodeBuilder, NodeState>() {
+ @Override
+ public NodeState apply(NodeBuilder input) {
+ return input.getBaseState();
+ }
+ }));
+ }
+
+ // node or property-related methods ; directly delegate to wrapped builder
+ @Override
+ public boolean exists() {
+ return getWrappedNodeBuilder().exists();
+ }
+
+ @Override
+ public boolean isNew() {
+ return getWrappedNodeBuilder().isNew();
+ }
+
+ @Override
+ public boolean isNew(String name) {
+ return getWrappedNodeBuilder().isNew(name);
+ }
+
+ @Override
+ public boolean isModified() {
+ return getWrappedNodeBuilder().isModified();
+ }
+
+ @Override
+ public boolean isReplaced() {
+ return getWrappedNodeBuilder().isReplaced();
+ }
+
+ @Override
+ public boolean isReplaced(String name) {
+ return getWrappedNodeBuilder().isReplaced(name);
+ }
+
+ @Override
+ public long getPropertyCount() {
+ return getWrappedNodeBuilder().getPropertyCount();
+ }
+
+ @Override
+ public Iterable<? extends PropertyState> getProperties() {
+ return getWrappedNodeBuilder().getProperties();
+ }
+
+ @Override
+ public boolean hasProperty(String name) {
+ return getWrappedNodeBuilder().hasProperty(name);
+ }
+
+ @Override
+ public PropertyState getProperty(String name) {
+ return getWrappedNodeBuilder().getProperty(name);
+ }
+
+ @Override
+ public boolean getBoolean(String name) {
+ return getWrappedNodeBuilder().getBoolean(name);
+ }
+
+ @Override
+ public String getString(String name) {
+ return getWrappedNodeBuilder().getString(name);
+ }
+
+ @Override
+ public String getName(String name) {
+ return getWrappedNodeBuilder().getName(name);
+ }
+
+ @Override
+ public Iterable<String> getNames(String name) {
+ return getWrappedNodeBuilder().getNames(name);
+ }
+
+ @Override
+ public NodeBuilder setProperty(PropertyState property) throws IllegalArgumentException {
+ getWrappedNodeBuilder().setProperty(property);
+ return this;
+ }
+
+ @Override
+ public <T> NodeBuilder setProperty(String name, T value) throws IllegalArgumentException {
+ getWrappedNodeBuilder().setProperty(name, value);
+ return this;
+ }
+
+ @Override
+ public <T> NodeBuilder setProperty(String name, T value, Type<T> type) throws IllegalArgumentException {
+ getWrappedNodeBuilder().setProperty(name, value, type);
+ return this;
+ }
+
+ @Override
+ public NodeBuilder removeProperty(String name) {
+ getWrappedNodeBuilder().removeProperty(name);
+ return this;
+ }
+
+ // child-related methods, require multiplexing
+ @Override
+ public long getChildNodeCount(final long max) {
+ List<MountedNodeStore> contributingStores = ctx.getContributingStoresForBuilders(path, nodeBuilders);
+ if (contributingStores.isEmpty()) {
+ return 0; // this shouldn't happen
+ } else if (contributingStores.size() == 1) {
+ return getWrappedNodeBuilder().getChildNodeCount(max);
+ } else {
+ // Count the children in each contributing store.
+ return accumulateChildSizes(concat(transform(contributingStores, new Function<MountedNodeStore, Iterable<String>>() {
+ @Override
+ public Iterable<String> apply(MountedNodeStore input) {
+ NodeBuilder contributing = nodeBuilders.get(input);
+ if (contributing.getChildNodeCount(max) == MAX_VALUE) {
+ return singleton(STOP_COUNTING_CHILDREN);
+ } else {
+ return filter(contributing.getChildNodeNames(), ctx.belongsToStore(input, path));
+ }
+ }
+ })), max);
+ }
+ }
+
+ @Override
+ public Iterable<String> getChildNodeNames() {
+ return concat(transform(ctx.getContributingStoresForBuilders(path, nodeBuilders), new Function<MountedNodeStore, Iterable<String>>() {
+ @Override
+ public Iterable<String> apply(final MountedNodeStore mountedNodeStore) {
+ return filter(nodeBuilders.get(mountedNodeStore).getChildNodeNames(), ctx.belongsToStore(mountedNodeStore, path));
+ }
+ }));
+ }
+
+ @Override
+ public boolean hasChildNode(String name) {
+ String childPath = PathUtils.concat(path, name);
+ MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
+ return nodeBuilders.get(mountedStore).hasChildNode(name);
+ }
+
+ @Override
+ public NodeBuilder child(String name) {
+ String childPath = PathUtils.concat(path, name);
+ MountedNodeStore mountedNodeStore = ctx.getOwningStore(childPath);
+ createAncestors(mountedNodeStore);
+ nodeBuilders.get(mountedNodeStore).child(name);
+ return getChildNode(name);
+ }
+
+ private void createAncestors(MountedNodeStore mountedNodeStore) {
+ if (mountedNodeStore == owningStore) {
+ return;
+ }
+ if (nodeBuilders.get(mountedNodeStore).exists()) {
+ return;
+ }
+ NodeBuilder builder = rootBuilders.get(mountedNodeStore);
+ for (String element : PathUtils.elements(path)) {
+ builder = builder.child(element);
+ }
+ nodeBuilders.put(mountedNodeStore, builder);
+ }
+
+ @Override
+ public NodeBuilder getChildNode(String name) {
+ String childPath = PathUtils.concat(path, name);
+ MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
+ if (!nodeBuilders.get(mountedStore).hasChildNode(name)) {
+ return MISSING_NODE.builder();
+ }
+
+ Map<MountedNodeStore, NodeBuilder> newNodeBuilders = newHashMap();
+ for (MountedNodeStore mns : ctx.getAllMountedNodeStores()) {
+ newNodeBuilders.put(mns, nodeBuilders.get(mns).getChildNode(name));
+ }
+ return new MultiplexingNodeBuilder(childPath, newNodeBuilders, rootBuilders, ctx);
+ }
+
+ @Override
+ public NodeBuilder setChildNode(String name) throws IllegalArgumentException {
+ return setChildNode(name, EmptyNodeState.EMPTY_NODE);
+ }
+
+ @Override
+ public NodeBuilder setChildNode(String name, NodeState nodeState) {
+ String childPath = PathUtils.concat(path, name);
+ MountedNodeStore mountedNodeStore = ctx.getOwningStore(childPath);
+ createAncestors(mountedNodeStore);
+ nodeBuilders.get(mountedNodeStore).setChildNode(name, nodeState);
+ return getChildNode(name);
+ }
+
+ @Override
+ public boolean remove() {
+ return getWrappedNodeBuilder().remove();
+ }
+
+ @Override
+ public boolean moveTo(NodeBuilder newParent, String newName) {
+ checkNotNull(newParent);
+ checkValidName(newName);
+ if ("/".equals(path) || !exists() || newParent.hasChildNode(newName)) {
+ return false;
+ } else {
+ if (newParent.exists()) {
+ annotateSourcePath();
+ NodeState nodeState = getNodeState();
+ newParent.setChildNode(newName, nodeState);
+ remove();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public Blob createBlob(InputStream stream) throws IOException {
+ return ctx.createBlob(stream);
+ }
+
+ private NodeBuilder getWrappedNodeBuilder() {
+ return nodeBuilders.get(owningStore);
+ }
+
+ private void annotateSourcePath() {
+ String sourcePath = getSourcePath();
+ if (!isTransientlyAdded(sourcePath)) {
+ setProperty(MoveDetector.SOURCE_PATH, sourcePath);
+ }
+ }
+
+ private final String getSourcePath() {
+ // Traverse up the hierarchy until we encounter the first builder
+ // having a source path annotation or until we hit the root
+ MultiplexingNodeBuilder builder = this;
+ String sourcePath = getSourcePathAnnotation(builder);
+ while (sourcePath == null && !"/".equals(builder.path)) {
+ String parentPath = PathUtils.getParentPath(builder.path);
+ builder = getBuilderByPath(parentPath);
+ sourcePath = getSourcePathAnnotation(builder);
+ }
+
+ if (sourcePath == null) {
+ // Neither self nor any parent has a source path annotation. The source
+ // path is just the path of this builder
+ return path;
+ } else {
+ // The source path is the source path of the first parent having a source
+ // path annotation with the relative path from this builder up to that
+ // parent appended.
+ return PathUtils.concat(sourcePath,
+ PathUtils.relativize(builder.path, path));
+ }
+ }
+
+ private static String getSourcePathAnnotation(MultiplexingNodeBuilder builder) {
+ PropertyState base = builder.getBaseState().getProperty(MoveDetector.SOURCE_PATH);
+ PropertyState head = builder.getNodeState().getProperty(MoveDetector.SOURCE_PATH);
+ if (Objects.equal(base, head)) {
+ // Both null: no source path annotation
+ // Both non null but equals: source path annotation is from a previous commit
+ return null;
+ } else {
+ return head.getValue(Type.STRING);
+ }
+ }
+
+ private boolean isTransientlyAdded(String sourcePath) {
+ NodeState node = rootBuilders.get(owningStore).getBaseState();
+ for (String name : PathUtils.elements(sourcePath)) {
+ node = node.getChildNode(name);
+ }
+ return !node.exists();
+ }
+
+ private MultiplexingNodeBuilder getBuilderByPath(String path) {
+ return new MultiplexingNodeBuilder(path, getBuildersByPath(rootBuilders, path), rootBuilders, ctx);
+ }
+
+ private static Map<MountedNodeStore, NodeBuilder> getBuildersByPath(Map<MountedNodeStore, NodeBuilder> rootNodes, final String path) {
+ return copyOf(transformValues(rootNodes, new Function<NodeBuilder, NodeBuilder>() {
+ @Override
+ public NodeBuilder apply(NodeBuilder input) {
+ NodeBuilder result = input;
+ for (String element : PathUtils.elements(path)) {
+ if (result.hasChildNode(element)) {
+ result = result.getChildNode(element);
+ } else {
+ result = MISSING_NODE.builder();
+ break;
+ }
+ }
+ return result;
+ }
+ }));
+ }
+}
\ No newline at end of file
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java?rev=1767652&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeState.java Wed Nov 2 12:16:32 2016
@@ -0,0 +1,388 @@
+/*
+ * 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 com.google.common.base.Function;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Predicates.compose;
+import static com.google.common.collect.ImmutableMap.copyOf;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Maps.newHashMap;
+import static com.google.common.collect.Maps.transformValues;
+import static java.lang.Long.MAX_VALUE;
+import static java.util.Collections.singleton;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE;
+import static org.apache.jackrabbit.oak.spi.state.ChildNodeEntry.GET_NAME;
+
+class MultiplexingNodeState extends AbstractNodeState {
+
+ // A note on content held by node stores which is outside the mount boundaries
+ //
+ // As a matter of design, mounted stores will definitely hold information _above_
+ // their mounted, path, e.g. a store mounted at /a/b/c will definitely have nodes
+ // /a and /a/b, which will not be visible through the multiplexing node store.
+ //
+ // If a node store holds information _below_ a path which belongs to another
+ // repository, the multiplexing node store will not consider that information.
+ //
+ // For instance, with a node store mounted at /libs and the root store
+ // having a node at /libs/food, both the /libs and /libs/foo nodes from
+ // the root store will be ignored
+
+ static final String STOP_COUNTING_CHILDREN = new String(MultiplexingNodeState.class.getName() + ".stopCountingChildren");
+
+ private final String path;
+
+ private final MultiplexingContext ctx;
+
+ private final MountedNodeStore owningStore;
+
+ private final Map<MountedNodeStore, NodeState> rootStates;
+
+ private final Map<MountedNodeStore, NodeState> nodeStates;
+
+ MultiplexingNodeState(String path, Map<MountedNodeStore, NodeState> rootStates, MultiplexingContext ctx) {
+ checkArgument(rootStates.size() == ctx.getStoresCount(), "Got %s node states but the context manages %s stores", rootStates.size(), ctx.getStoresCount());
+
+ this.path = path;
+ this.ctx = ctx;
+ this.rootStates = copyOf(rootStates);
+ this.nodeStates = copyOf(getNodesByPath(this.rootStates, path));
+ this.owningStore = ctx.getOwningStore(path);
+ }
+
+ MultiplexingNodeState(String path, Map<MountedNodeStore, NodeState> nodeStates, Map<MountedNodeStore, NodeState> rootStates, MultiplexingContext ctx) {
+ checkArgument(nodeStates.size() == ctx.getStoresCount(), "Got %s node states but the context manages %s stores", nodeStates.size(), ctx.getStoresCount());
+ checkArgument(rootStates.size() == ctx.getStoresCount(), "Got %s node states but the context manages %s stores", rootStates.size(), ctx.getStoresCount());
+
+ this.path = path;
+ this.ctx = ctx;
+ this.rootStates = copyOf(rootStates);
+ this.nodeStates = copyOf(nodeStates);
+ this.owningStore = ctx.getOwningStore(path);
+ }
+
+ @Override
+ public boolean exists() {
+ return getWrappedNodeState().exists();
+ }
+
+ // delegate all property access to wrapped node
+ @Override
+ public boolean hasProperty(String name) {
+ return getWrappedNodeState().hasProperty(name);
+ }
+
+ @Override
+ public PropertyState getProperty(String name) {
+ return getWrappedNodeState().getProperty(name);
+ }
+
+ @Override
+ public long getPropertyCount() {
+ return getWrappedNodeState().getPropertyCount();
+ }
+
+ @Override
+ public Iterable<? extends PropertyState> getProperties() {
+ return getWrappedNodeState().getProperties();
+ }
+
+ // child node operations
+ @Override
+ public boolean hasChildNode(String name) {
+ String childPath = PathUtils.concat(path, name);
+ MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
+ return nodeStates.get(mountedStore).hasChildNode(name);
+ }
+
+ @Override
+ public NodeState getChildNode(String name) {
+ String childPath = PathUtils.concat(path, name);
+ MountedNodeStore mountedStore = ctx.getOwningStore(childPath);
+ if (!nodeStates.get(mountedStore).hasChildNode(name)) {
+ return MISSING_NODE;
+ }
+
+ Map<MountedNodeStore, NodeState> newNodeStates = newHashMap();
+ for (MountedNodeStore mns : ctx.getAllMountedNodeStores()) {
+ newNodeStates.put(mns, nodeStates.get(mns).getChildNode(name));
+ }
+ return new MultiplexingNodeState(childPath, newNodeStates, rootStates, ctx);
+ }
+
+ @Override
+ public long getChildNodeCount(final long max) {
+ List<MountedNodeStore> contributingStores = ctx.getContributingStoresForNodes(path, nodeStates);
+ if (contributingStores.isEmpty()) {
+ return 0; // this shouldn't happen
+ } else if (contributingStores.size() == 1) {
+ return getWrappedNodeState().getChildNodeCount(max);
+ } else {
+ // Count the children in each contributing store.
+ return accumulateChildSizes(concat(transform(contributingStores, new Function<MountedNodeStore, Iterable<String>>() {
+ @Override
+ public Iterable<String> apply(MountedNodeStore mns) {
+ NodeState contributing = nodeStates.get(mns);
+ if (contributing.getChildNodeCount(max) == MAX_VALUE) {
+ return singleton(STOP_COUNTING_CHILDREN);
+ } else {
+ return filter(contributing.getChildNodeNames(), ctx.belongsToStore(mns, path));
+ }
+ }
+ })), max);
+ }
+ }
+
+ static long accumulateChildSizes(Iterable<String> nodeNames, long max) {
+ long totalCount = 0;
+ for (String name : nodeNames) {
+ totalCount++;
+ if (name == STOP_COUNTING_CHILDREN || totalCount >= max) {
+ return MAX_VALUE;
+ }
+ }
+ return totalCount;
+ }
+
+ @Override
+ public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
+ Iterable<? extends ChildNodeEntry> nativeChildren = concat(transform(ctx.getContributingStoresForNodes(path, nodeStates), new Function<MountedNodeStore, Iterable<? extends ChildNodeEntry>>() {
+ @Override
+ public Iterable<? extends ChildNodeEntry> apply(final MountedNodeStore mountedNodeStore) {
+ return filter(nodeStates.get(mountedNodeStore).getChildNodeEntries(), compose(ctx.belongsToStore(mountedNodeStore, path), GET_NAME));
+ }
+ }));
+ return transform(nativeChildren, new Function<ChildNodeEntry, ChildNodeEntry>() {
+ @Override
+ public ChildNodeEntry apply(ChildNodeEntry input) {
+ NodeState wrapped = getChildNode(input.getName());
+ return new MemoryChildNodeEntry(input.getName(), wrapped);
+ }
+ });
+ }
+
+ @Override
+ public boolean compareAgainstBaseState(NodeState base, NodeStateDiff diff) {
+ if (base instanceof MultiplexingNodeState) {
+ MultiplexingNodeState multiBase = (MultiplexingNodeState) base;
+ NodeStateDiff wrappingDiff = new WrappingDiff(diff, multiBase);
+ boolean full = getWrappedNodeState().compareAgainstBaseState(multiBase.getWrappedNodeState(), new ChildrenDiffFilter(wrappingDiff, owningStore, true));
+ for (MountedNodeStore mns : ctx.getContributingStoresForNodes(path, nodeStates)) {
+ if (owningStore == mns) {
+ continue;
+ }
+ NodeStateDiff childrenDiffFilter = new ChildrenDiffFilter(wrappingDiff, mns, false);
+ NodeState contributing = nodeStates.get(mns);
+ NodeState contributingBase = multiBase.nodeStates.get(mns);
+ full = full && contributing.compareAgainstBaseState(contributingBase, childrenDiffFilter);
+ }
+ return full;
+ } else {
+ return super.compareAgainstBaseState(base, diff);
+ }
+ }
+
+ // write operations
+ @Override
+ public NodeBuilder builder() {
+ Map<MountedNodeStore, NodeBuilder> rootBuilders = copyOf(transformValues(rootStates, new Function<NodeState, NodeBuilder>() {
+ @Override
+ public NodeBuilder apply(NodeState input) {
+ return input.builder();
+ }
+ }));
+ Map<MountedNodeStore, NodeBuilder> nodeBuilders = copyOf(transformValues(rootBuilders, new Function<NodeBuilder, NodeBuilder>() {
+ @Override
+ public NodeBuilder apply(NodeBuilder input) {
+ NodeBuilder result = input;
+ for (String element : PathUtils.elements(path)) {
+ if (result.hasChildNode(element)) {
+ result = result.getChildNode(element);
+ } else {
+ result = MISSING_NODE.builder();
+ break;
+ }
+ }
+ return result;
+ }
+ }));
+ return new MultiplexingNodeBuilder(path, nodeBuilders, rootBuilders, ctx);
+ }
+
+ private NodeState getWrappedNodeState() {
+ return nodeStates.get(owningStore);
+ }
+
+ private static Map<MountedNodeStore, NodeState> getNodesByPath(Map<MountedNodeStore, NodeState> rootNodes, final String path) {
+ return copyOf(transformValues(rootNodes, new Function<NodeState, NodeState>() {
+ @Override
+ public NodeState apply(NodeState input) {
+ NodeState result = input;
+ for (String element : PathUtils.elements(path)) {
+ if (result.hasChildNode(element)) {
+ result = result.getChildNode(element);
+ } else {
+ result = MISSING_NODE;
+ break;
+ }
+ }
+ return result;
+ }
+ }));
+ }
+
+ private class ChildrenDiffFilter implements NodeStateDiff {
+
+ private final NodeStateDiff diff;
+
+ private final MountedNodeStore mns;
+
+ private final boolean includeProperties;
+
+ public ChildrenDiffFilter(NodeStateDiff diff, MountedNodeStore mns, boolean includeProperties) {
+ this.diff = diff;
+ this.mns = mns;
+ this.includeProperties = includeProperties;
+ }
+
+ @Override
+ public boolean propertyAdded(PropertyState after) {
+ if (includeProperties) {
+ return diff.propertyAdded(after);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean propertyChanged(PropertyState before, PropertyState after) {
+ if (includeProperties) {
+ return diff.propertyChanged(before, after);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean propertyDeleted(PropertyState before) {
+ if (includeProperties) {
+ return diff.propertyDeleted(before);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean childNodeAdded(String name, NodeState after) {
+ if (belongsToNodeStore(name)) {
+ return diff.childNodeAdded(name, after);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean childNodeChanged(String name, NodeState before, NodeState after) {
+ if (belongsToNodeStore(name)) {
+ return diff.childNodeChanged(name, before, after);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean childNodeDeleted(String name, NodeState before) {
+ if (belongsToNodeStore(name)) {
+ return diff.childNodeDeleted(name, before);
+ } else {
+ return true;
+ }
+ }
+
+ private boolean belongsToNodeStore(String name) {
+ return ctx.getOwningStore(PathUtils.concat(path, name)) == mns;
+ }
+ }
+
+ private class WrappingDiff implements NodeStateDiff {
+
+ private final NodeStateDiff diff;
+
+ private final MultiplexingNodeState base;
+
+ public WrappingDiff(NodeStateDiff diff, MultiplexingNodeState base) {
+ this.diff = diff;
+ this.base = base;
+ }
+
+ @Override
+ public boolean propertyAdded(PropertyState after) {
+ return diff.propertyAdded(after);
+ }
+
+ @Override
+ public boolean propertyChanged(PropertyState before, PropertyState after) {
+ return diff.propertyChanged(before, after);
+ }
+
+ @Override
+ public boolean propertyDeleted(PropertyState before) {
+ return diff.propertyDeleted(before);
+ }
+
+ @Override
+ public boolean childNodeAdded(String name, NodeState after) {
+ return diff.childNodeAdded(name, wrapAfter(name));
+ }
+
+ @Override
+ public boolean childNodeChanged(String name, NodeState before, NodeState after) {
+ return diff.childNodeChanged(name, wrapBefore(name), wrapAfter(name));
+ }
+
+ @Override
+ public boolean childNodeDeleted(String name, NodeState before) {
+ return diff.childNodeDeleted(name, wrapBefore(name));
+ }
+
+ private NodeState wrapBefore(String name) {
+ return base.getChildNode(name);
+ }
+
+ private NodeState wrapAfter(String name) {
+ return MultiplexingNodeState.this.getChildNode(name);
+ }
+ }
+
+}
Added: 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=1767652&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStore.java Wed Nov 2 12:16:32 2016
@@ -0,0 +1,340 @@
+/*
+ * 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 com.google.common.base.Predicate;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.api.Blob;
+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.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.commit.Observable;
+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;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.isNull;
+import static com.google.common.collect.ImmutableMap.copyOf;
+import static com.google.common.collect.Iterables.any;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Maps.filterKeys;
+import static com.google.common.collect.Maps.newHashMap;
+
+/**
+ * A {@link NodeStore} implementation that multiplexes other {@link NodeStore} instances
+ * mounted under paths defined by {@link MountInfo}.
+ *
+ * <p>The main objective of this implementation is to multiplex operations working on
+ * at most single read-write store with any number of read-only stores. While the
+ * multiplexing would technically work at the NodeStore level there are several
+ * less-than-obvious issues which prevent it:
+ * <ol>
+ * <li>Thread safety of the write operation can be quite costly, and will come on top
+ * of the thread safety measures already put in place by the multiplexed node stores.</li>
+ * <li>Many JCR subsystems require global state, e.g. the versioning store. This global state
+ * can become corrupt if multiple mounts operate on it or if mounts are added and removed.</li>
+ * </ol>
+ *
+ * As such, the only supported configuration is at most a single write-enabled store.</p>
+ *
+ * <p>Because of the limitation described above, right now the only correct way to use
+ * MultiplexingNodeStore is to create a normal repository, split it into parts
+ * using oak-upgrade {@code --{include,exclude}-paths} and then configure this
+ * node store implementation to multiplex split parts together.</p>
+ */
+public class MultiplexingNodeStore implements NodeStore, Observable {
+
+ private static final String CHECKPOINT_ID_PREFIX = "multiplexing.checkpoint.";
+
+ final MultiplexingContext ctx;
+
+ private final List<Observer> observers = new CopyOnWriteArrayList<>();
+
+ // visible for testing only
+ MultiplexingNodeStore(MountInfoProvider mip, NodeStore globalStore, List<MountedNodeStore> nonDefaultStore) {
+ this.ctx = new MultiplexingContext(mip, globalStore, nonDefaultStore);
+ }
+
+ @Override
+ public NodeState getRoot() {
+ // the multiplexed 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();
+ for (MountedNodeStore nodeStore : ctx.getAllMountedNodeStores()) {
+ nodeStates.put(nodeStore, nodeStore.getNodeStore().getRoot());
+ }
+ return createRootNodeState(nodeStates);
+ }
+
+ @Override
+ public NodeState merge(NodeBuilder builder, CommitHook commitHook, CommitInfo info) throws CommitFailedException {
+ checkArgument(builder instanceof MultiplexingNodeBuilder);
+ MultiplexingNodeBuilder nodeBuilder = (MultiplexingNodeBuilder) builder;
+
+ // run commit hooks and apply the changes to the builder instance
+ NodeState processed = commitHook.processCommit(getRoot(), rebase(nodeBuilder), info);
+ processed.compareAgainstBaseState(builder.getNodeState(), new ApplyDiff(nodeBuilder));
+
+ assertNoChangesOnReadOnlyMounts(nodeBuilder);
+
+ // apply the accumulated changes on individual NodeStore instances
+ Map<MountedNodeStore, NodeState> resultStates = newHashMap();
+ 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);
+ }
+ MultiplexingNodeState newRoot = createRootNodeState(resultStates);
+
+ for (Observer observer : observers) {
+ observer.contentChanged(newRoot, info);
+ }
+ return newRoot;
+ }
+
+ private void assertNoChangesOnReadOnlyMounts(MultiplexingNodeBuilder nodeBuilder) throws CommitFailedException {
+ for (MountedNodeStore mountedNodeStore : ctx.getAllMountedNodeStores()) {
+ if (!mountedNodeStore.getMount().isReadOnly()) {
+ 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());
+ }
+ }
+ }
+
+ @Override
+ public NodeState rebase(NodeBuilder builder) {
+ checkArgument(builder instanceof MultiplexingNodeBuilder);
+
+ MultiplexingNodeBuilder nodeBuilder = (MultiplexingNodeBuilder) builder;
+ Map<MountedNodeStore, NodeState> resultStates = newHashMap();
+ 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);
+ }
+
+ @Override
+ public NodeState reset(NodeBuilder builder) {
+ checkArgument(builder instanceof MultiplexingNodeBuilder);
+
+ MultiplexingNodeBuilder nodeBuilder = (MultiplexingNodeBuilder) builder;
+ Map<MountedNodeStore, NodeState> resultStates = newHashMap();
+ 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 MultiplexingNodeState createRootNodeState(Map<MountedNodeStore, NodeState> rootStates) {
+ return new MultiplexingNodeState("/", rootStates, ctx);
+ }
+
+ @Override
+ public Blob createBlob(InputStream inputStream) throws IOException {
+ // since there is no way to infer a path for a blob, we create all blobs in the root store
+ return ctx.createBlob(inputStream);
+ }
+
+ @Override
+ public Blob getBlob(String reference) {
+ for (MountedNodeStore nodeStore : ctx.getAllMountedNodeStores()) {
+ Blob found = nodeStore.getNodeStore().getBlob(reference);
+ if (found != null) {
+ return found;
+ }
+ }
+ return null;
+ }
+
+ public Iterable<String> checkpoints() {
+ final NodeStore globalNodeStore = ctx.getGlobalStore().getNodeStore();
+ return filter(globalNodeStore.checkpoints(), new Predicate<String>() {
+ @Override
+ public boolean apply(String checkpoint) {
+ return isMultiplexingCheckpoint(checkpoint);
+ }
+ });
+ }
+
+ private boolean isMultiplexingCheckpoint(String checkpoint) {
+ Map<String, String> props = ctx.getGlobalStore().getNodeStore().checkpointInfo(checkpoint);
+ if (props == null) {
+ return false;
+ }
+ for (MountedNodeStore mns : ctx.getNonDefaultStores()) {
+ if (!props.containsKey(CHECKPOINT_ID_PREFIX + mns.getMount().getName())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String checkpoint(long lifetime, Map<String, String> properties) {
+ Map<String, String> globalProperties = newHashMap(properties);
+ for (MountedNodeStore mns : ctx.getNonDefaultStores()) {
+ String checkpoint = mns.getNodeStore().checkpoint(lifetime, properties);
+ globalProperties.put(CHECKPOINT_ID_PREFIX + mns.getMount().getName(), checkpoint);
+ }
+ return ctx.getGlobalStore().getNodeStore().checkpoint(lifetime, globalProperties);
+ }
+
+
+ @Override
+ public String checkpoint(long lifetime) {
+ return checkpoint(lifetime, Collections. <String, String> emptyMap());
+ }
+
+ @Override
+ public Map<String, String> checkpointInfo(String checkpoint) {
+ return copyOf(filterKeys(ctx.getGlobalStore().getNodeStore().checkpointInfo(checkpoint), new Predicate<String>() {
+ @Override
+ public boolean apply(String input) {
+ return !input.startsWith(CHECKPOINT_ID_PREFIX);
+ }
+ }));
+ }
+
+ @Override
+ public NodeState retrieve(String checkpoint) {
+ Map<String, String> props = ctx.getGlobalStore().getNodeStore().checkpointInfo(checkpoint);
+ if (props == null) {
+ return null;
+ }
+ Map<MountedNodeStore, NodeState> nodeStates = newHashMap();
+ nodeStates.put(ctx.getGlobalStore(), ctx.getGlobalStore().getNodeStore().retrieve(checkpoint));
+ for (MountedNodeStore nodeStore : ctx.getNonDefaultStores()) {
+ String partialCheckpoint = props.get(CHECKPOINT_ID_PREFIX + nodeStore.getMount().getName());
+ if (partialCheckpoint == null) {
+ return null;
+ } else {
+ nodeStates.put(nodeStore, nodeStore.getNodeStore().retrieve(partialCheckpoint));
+ }
+ }
+ if (any(nodeStates.values(), isNull())) {
+ return null;
+ }
+ return new MultiplexingNodeState("/", nodeStates, ctx);
+ }
+
+ @Override
+ public boolean release(String checkpoint) {
+ Map<String, String> props = ctx.getGlobalStore().getNodeStore().checkpointInfo(checkpoint);
+ if (props == null) {
+ return false;
+ }
+ boolean result = ctx.getGlobalStore().getNodeStore().release(checkpoint);
+ for (MountedNodeStore nodeStore : ctx.getNonDefaultStores()) {
+ String partialCheckpoint = props.get(CHECKPOINT_ID_PREFIX + nodeStore.getMount().getName());
+ if (partialCheckpoint == null) {
+ result = false;
+ } else {
+ result = nodeStore.getNodeStore().release(partialCheckpoint) && result;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Closeable addObserver(final Observer observer) {
+ observer.contentChanged(getRoot(), null);
+ observers.add(observer);
+ return new Closeable() {
+ @Override
+ public void close() throws IOException {
+ observers.remove(observer);
+ }
+ };
+ }
+
+ public static class Builder {
+
+ private final MountInfoProvider mip;
+
+ private final NodeStore globalStore;
+
+ private final List<MountedNodeStore> nonDefaultStores = Lists.newArrayList();
+
+ public Builder(MountInfoProvider mip, NodeStore globalStore) {
+ this.mip = checkNotNull(mip, "mountInfoProvider");
+ this.globalStore = checkNotNull(globalStore, "globalStore");
+ }
+
+ public Builder addMount(String mountName, NodeStore store) {
+ checkNotNull(store, "store");
+ checkNotNull(mountName, "mountName");
+
+ Mount mount = checkNotNull(mip.getMountByName(mountName), "No mount with name %s found in %s", mountName, mip);
+ nonDefaultStores.add(new MountedNodeStore(mount, store));
+ return this;
+ }
+
+ public MultiplexingNodeStore build() {
+ checkReadWriteMountsNumber();
+ checkMountsAreConsistentWithMounts();
+ return new MultiplexingNodeStore(mip, globalStore, nonDefaultStores);
+ }
+
+ private void checkReadWriteMountsNumber() {
+ List<String> readWriteMountNames = Lists.newArrayList();
+ if (!mip.getDefaultMount().isReadOnly()) {
+ readWriteMountNames.add(mip.getDefaultMount().getName());
+ }
+ for (Mount mount : mip.getNonDefaultMounts()) {
+ if (!mount.isReadOnly()) {
+ readWriteMountNames.add(mount.getName());
+ }
+ }
+ checkArgument(readWriteMountNames.size() <= 1,
+ "Expected at most 1 write-enabled mount, but got %s: %s.", readWriteMountNames.size(), readWriteMountNames);
+ }
+
+ private void checkMountsAreConsistentWithMounts() {
+ int buildMountCount = nonDefaultStores.size();
+ int mipMountCount = mip.getNonDefaultMounts().size();
+ checkArgument(buildMountCount == mipMountCount,
+ "Inconsistent mount configuration. Builder received %s mounts, but MountInfoProvider knows about %s.",
+ buildMountCount, mipMountCount);
+ }
+ }
+}
Added: 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=1767652&view=auto
==============================================================================
--- 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/MultiplexingNodeStoreService.java Wed Nov 2 12:16:32 2016
@@ -0,0 +1,205 @@
+/*
+ * 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.felix.scr.annotations.Activate;
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.spi.mount.Mount;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStoreProvider;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Component(policy = ConfigurationPolicy.REQUIRE,
+ metatype = true,
+ label = "Apache Jackrabbit Oak Multiplexing NodeStore Service",
+ description = "NodeStore implementation proxying all the operations " +
+ "to other nodestores configured in OSGi"
+)
+public class MultiplexingNodeStoreService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MultiplexingNodeStoreService.class);
+
+ private static final String GLOBAL_ROLE = "multiplexing:global";
+
+ private static final String MOUNT_ROLE_PREFIX = "multiplexing:mount:";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.STATIC)
+ private MountInfoProvider mountInfoProvider;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC, bind = "bindNodeStore", unbind = "unbindNodeStore", referenceInterface = NodeStoreProvider.class)
+ private List<NodeStoreWithProps> nodeStores = new ArrayList<>();
+
+ private ComponentContext context;
+
+ private ServiceRegistration nsReg;
+
+ @Activate
+ protected void activate(ComponentContext context) {
+ this.context = context;
+ registerMultiplexingNodeStore();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ unregisterMultiplexingNodeStore();
+ }
+
+ private void registerMultiplexingNodeStore() {
+ if (nsReg != null) {
+ return; // already registered
+ }
+
+ NodeStoreWithProps globalNs = null;
+ Set<String> availableMounts = new HashSet<>();
+ for (NodeStoreWithProps ns : nodeStores) {
+ if (isGlobalNodeStore(ns)) {
+ globalNs = ns;
+ } else {
+ availableMounts.add(getMountName(ns));
+ }
+ }
+
+ if (globalNs == null) {
+ LOG.info("Multiplexing node store registration is deferred until there's a global node store registered in OSGi");
+ return;
+ } else {
+ LOG.info("Found global node store: {}", getDescription(globalNs));
+ }
+
+ for (Mount m : mountInfoProvider.getNonDefaultMounts()) {
+ if (!availableMounts.contains(m.getName())) {
+ LOG.info("Multiplexing node store registration is deferred until there's mount {} registered in OSGi", m.getName());
+ return;
+ }
+ }
+ LOG.info("Node stores for all configured mounts are available");
+
+ MultiplexingNodeStore.Builder builder = new MultiplexingNodeStore.Builder(mountInfoProvider, globalNs.getNodeStoreProvider().getNodeStore());
+
+ for (NodeStoreWithProps ns : nodeStores) {
+ if (isGlobalNodeStore(ns)) {
+ continue;
+ }
+ String mountName = getMountName(ns);
+ if (mountName != null) {
+ builder.addMount(mountName, ns.getNodeStoreProvider().getNodeStore());
+ LOG.info("Mounting {} as {}", getDescription(ns), mountName);
+ }
+ }
+
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(Constants.SERVICE_PID, MultiplexingNodeStore.class.getName());
+ props.put("oak.nodestore.description", new String[] { "nodeStoreType=multiplexing" } );
+
+ LOG.info("Registering the multiplexing node store");
+
+ nsReg = context.getBundleContext().registerService(
+ new String[]{
+ NodeStore.class.getName()
+ },
+ builder.build(),
+ props);
+ }
+
+ private boolean isGlobalNodeStore(NodeStoreWithProps ns) {
+ return GLOBAL_ROLE.equals(ns.getRole());
+ }
+
+ private String getMountName(NodeStoreWithProps ns) {
+ String role = ns.getRole();
+ if (!role.startsWith(MOUNT_ROLE_PREFIX)) {
+ return null;
+ }
+ return role.substring(MOUNT_ROLE_PREFIX.length());
+ }
+
+ private String getDescription(NodeStoreWithProps ns) {
+ return PropertiesUtil.toString(ns.getProps().get("oak.nodestore.description"), ns.getNodeStoreProvider().getClass().toString());
+ }
+
+ private void unregisterMultiplexingNodeStore() {
+ if (nsReg != null) {
+ LOG.info("Unregistering the multiplexing node store");
+ nsReg.unregister();
+ nsReg = null;
+ }
+ }
+
+ protected void bindNodeStore(NodeStoreProvider ns, Map<String, ?> config) {
+ NodeStoreWithProps newNs = new NodeStoreWithProps(ns, config);
+ nodeStores.add(newNs);
+
+ unregisterMultiplexingNodeStore();
+ registerMultiplexingNodeStore();
+ }
+
+ protected void unbindNodeStore(NodeStoreProvider ns) {
+ Iterator<NodeStoreWithProps> it = nodeStores.iterator();
+ while (it.hasNext()) {
+ if (it.next().getNodeStoreProvider() == ns) {
+ it.remove();
+ }
+ }
+
+ unregisterMultiplexingNodeStore();
+ registerMultiplexingNodeStore();
+ }
+
+ private static class NodeStoreWithProps {
+
+ private final NodeStoreProvider nodeStore;
+
+ private final Map<String, ?> props;
+
+ public NodeStoreWithProps(NodeStoreProvider nodeStore, Map<String, ?> props) {
+ this.nodeStore = nodeStore;
+ this.props = props;
+ }
+
+ public NodeStoreProvider getNodeStoreProvider() {
+ return nodeStore;
+ }
+
+ public Map<String, ?> getProps() {
+ return props;
+ }
+
+ public String getRole() {
+ return PropertiesUtil.toString(props.get(NodeStoreProvider.ROLE), null);
+ }
+ }
+}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/SimpleMountInfoProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/SimpleMountInfoProvider.java?rev=1767652&r1=1767651&r2=1767652&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/SimpleMountInfoProvider.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/SimpleMountInfoProvider.java Wed Nov 2 12:16:32 2016
@@ -85,6 +85,17 @@ public class SimpleMountInfoProvider imp
}
@Override
+ public Collection<Mount> getMountsPlacedDirectlyUnder(String path) {
+ Collection<Mount> mounts = Lists.newArrayList();
+ for ( Mount mount : this.mounts.values()) {
+ if ( mount.isDirectlyUnder(path) ) {
+ mounts.add(mount);
+ }
+ }
+ return mounts;
+ }
+
+ @Override
public Mount getDefaultMount() {
return defMount;
}
Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/package-info.java?rev=1767652&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/package-info.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/multiplex/package-info.java Wed Nov 2 12:16:32 2016
@@ -0,0 +1,45 @@
+/**
+ * <h1>Multiplexing support</h1>
+ *
+ * <p>This package contains support classes for implementing a multiplexed persistence at the {@linkplain org.apache.jackrabbit.oak.spi.state.NodeStore} level.</p>
+ *
+ * <h2>Design goals</h2>
+ * <p>
+ * <ol>
+ * <li>Transparency of the multiplexing setup. Neither the NodeStores nor the code using a multiplexed
+ * NodeStore should be aware of the specific implementation being used.</li>
+ * <li>Persistence-agnosticity. The multiplexing support should be applicable to any conformant
+ * NodeStore implementation.</li>
+ * <li>Negligible performance impact. Multiplexing should not add a significat performance overhead.</li>
+ * </ol>
+ * </p>
+ *
+ * <h2>Implementation</h2>
+ *
+ * <p>The main entry point is the {@link org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingNodeStore},
+ * which wraps one or more NodeStore instances. Also of interest are the {@link org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingNodeState} and {@link org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingNodeBuilder}.</p>
+ *
+ * <p> These classes maintain internal mappings of the 'native' objects. For instance, if the
+ * multiplexing NodeStore holds two MemoryNodeStore instances, then a call to {@linkplain org.apache.jackrabbit.oak.spi.state.NodeStore#getRoot()}
+ * will return a multiplexing NodeState backed by two MemoryNodeState instances. Similarly, a call to {@linkplain org.apache.jackrabbit.oak.spi.state.NodeState#builder()} will return a multiplexing
+ * NodeBuilder backed by two MemoryNodeState instances.</p>
+ *
+ * <p>Using this approach allows us to always keep related NodeStore, NodeState and NodeBuilder
+ * instances isolated from other instances.</p>
+ *
+ * <h2>Open items</h2>
+ *
+ * <p>1. Brute-force support for oak:mount nodes.</p>
+ *
+ * <p>The {@link org.apache.jackrabbit.oak.spi.mount.Mount#getPathFragmentName()} method defines
+ * a name pattern that can be used by mounted stores to contribute to a patch which is not
+ * owned by them. For instance, a mount named <em>apps</em> which owns <tt>/libs,/apps</tt>
+ * can own another subtree anywhere in the repository given that a node named <tt>:oak-mount-apps</tt>
+ * is found.</p>
+ *
+ * <p>The current implementation naively queries all stores whenever the child node list is prepared.
+ * This is obviously correct but may be slow.</p>
+ * {@link org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingContext#getContributingStores(java.lang.String, com.google.common.base.Function)}</p>
+ */
+package org.apache.jackrabbit.oak.plugins.multiplex;
+
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mount.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mount.java?rev=1767652&r1=1767651&r2=1767652&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mount.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mount.java Wed Nov 2 12:16:32 2016
@@ -19,12 +19,15 @@
package org.apache.jackrabbit.oak.spi.mount;
+import aQute.bnd.annotation.ProviderType;
+
/**
* A ContentRepository represents one big tree. A Mount
* refers to a set of paths in that tree which are possibly
* stored in a separate physical persistent stores. In a
* default setup all paths belong to a default Mount.
*/
+@ProviderType
public interface Mount {
/**
* Name of the mount. If this <code>Mount</code>
@@ -92,4 +95,6 @@ public interface Mount {
* @return true if this Mount is rooted under given path
*/
boolean isUnder(String path);
+
+ boolean isDirectlyUnder(String path);
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/MountInfoProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/MountInfoProvider.java?rev=1767652&r1=1767651&r2=1767652&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/MountInfoProvider.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/MountInfoProvider.java Wed Nov 2 12:16:32 2016
@@ -24,6 +24,9 @@ import java.util.Collection;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
+import aQute.bnd.annotation.ProviderType;
+
+@ProviderType
public interface MountInfoProvider {
/**
@@ -59,13 +62,16 @@ public interface MountInfoProvider {
/**
* Returns all mounts placed under the specified path
- *
+ *
* @param path the path under which mounts are to be found
* @return a collection of mounts, possibly empty
*/
@Nonnull
Collection<Mount> getMountsPlacedUnder(String path);
+ @Nonnull
+ Collection<Mount> getMountsPlacedDirectlyUnder(String path);
+
/**
* Returns the default mount
*/
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mounts.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mounts.java?rev=1767652&r1=1767651&r2=1767652&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mounts.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/Mounts.java Wed Nov 2 12:16:32 2016
@@ -53,6 +53,10 @@ public final class Mounts {
return Collections.emptySet();
}
+ public Collection<Mount> getMountsPlacedDirectlyUnder(String path) {
+ return Collections.emptySet();
+ }
+
@Override
public Mount getDefaultMount() {
return DEFAULT_MOUNT;
@@ -114,6 +118,16 @@ public final class Mounts {
return false;
}
}
+ return true;
+ }
+
+ @Override
+ public boolean isDirectlyUnder(String path) {
+ for (Mount m : mounts) {
+ if (m.isDirectlyUnder(path)) {
+ return false;
+ }
+ }
return true;
}
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/package-info.java?rev=1767652&r1=1767651&r2=1767652&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/mount/package-info.java Wed Nov 2 12:16:32 2016
@@ -16,9 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-@Version("2.0.0")
-@Export(optional = "provide:=true")
+@Version("2.1.0")
package org.apache.jackrabbit.oak.spi.mount;
-import aQute.bnd.annotation.Export;
import aQute.bnd.annotation.Version;
\ No newline at end of file