diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java index f69e950..89c4e64 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java @@ -404,9 +404,16 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach @CheckForNull private AbstractDocumentNodeState getChildNodeDoc(String childNodeName){ - AbstractDocumentNodeState secondaryState = getSecondaryNodeState(); - if (secondaryState != null){ - NodeState result = secondaryState.getChildNode(childNodeName); + if (cachedSecondaryState == null) { + String childPath = PathUtils.concat(getPath(), childNodeName); + AbstractDocumentNodeState secondaryNodeState = getSecondaryNodeState(childPath); + if (secondaryNodeState == null) { + return store.getNode(childPath, lastRevision); + } else { + return secondaryNodeState; + } + } else { + NodeState result = cachedSecondaryState.getChildNode(childNodeName); //If given child node exist then cast it and return //else return null if (result.exists()){ @@ -414,7 +421,11 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach } return null; } - return store.getNode(PathUtils.concat(getPath(), childNodeName), lastRevision); + } + + @CheckForNull + private AbstractDocumentNodeState getSecondaryNodeState(String path) { + return store.getSecondaryNodeState(path, rootRevision, lastRevision); } @CheckForNull diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedDocumentNodeState.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedDocumentNodeState.java new file mode 100644 index 0000000..56c45c6 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedDocumentNodeState.java @@ -0,0 +1,204 @@ +/* + * 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.document.mount; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState; +import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer; +import org.apache.jackrabbit.oak.plugins.document.RevisionVector; +import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder; +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 javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class MountedDocumentNodeState extends AbstractDocumentNodeState { + + private final NodeState nodeState; + + private final String path; + + private final RevisionVector lastRev; + + private final RevisionVector rootRev; + + private final boolean fromExternalChange; + + private final NodeStateDiffer differ; + + private MountedDocumentNodeState(NodeState nodeState, String path, RevisionVector lastRev, RevisionVector rootRev, boolean fromExternalChange, NodeStateDiffer differ) { + this.nodeState = nodeState; + this.path = path; + this.lastRev = lastRev; + this.rootRev = rootRev; + this.fromExternalChange = fromExternalChange; + this.differ = differ; + } + + public static AbstractDocumentNodeState wrap(NodeState nodeState, String path, RevisionVector lastRev, RevisionVector rootRev, NodeStateDiffer differ) { + return new MountedDocumentNodeState(nodeState, path, lastRev, rootRev, false, differ); + } + + @Override + public String getPath() { + return path; + } + + @Override + public RevisionVector getLastRevision() { + return lastRev; + } + + @Override + public RevisionVector getRootRevision() { + return rootRev; + } + + @Override + public boolean isFromExternalChange() { + return fromExternalChange; + } + + @Override + public AbstractDocumentNodeState withRootRevision(@Nonnull RevisionVector root, boolean externalChange) { + return new MountedDocumentNodeState(nodeState, path, lastRev, root, externalChange, differ); + } + + @Override + public boolean hasNoChildren() { + //Passing max as 1 so as to minimize any overhead. + return nodeState.getChildNodeCount(1) == 0; + } + + @Override + protected NodeStateDiffer getNodeStateDiffer() { + return differ; + } + + @Override + public boolean exists() { + return nodeState.exists(); + } + + @Nonnull + @Override + public Iterable getProperties() { + return nodeState.getProperties(); + } + + @Override + public boolean hasChildNode(@Nonnull String name) { + return nodeState.hasChildNode(name); + } + + @Nonnull + @Override + public NodeState getChildNode(@Nonnull String name) throws IllegalArgumentException { + return decorateChild(nodeState.getChildNode(name), name); + } + + @Nonnull + @Override + public Iterable getChildNodeEntries() { + return Iterables.transform(nodeState.getChildNodeEntries(), new Function() { + @Nullable + @Override + public ChildNodeEntry apply(@Nullable ChildNodeEntry input) { + return new MemoryChildNodeEntry(input.getName(), decorateChild(input.getNodeState(), input.getName())); + } + }); + } + + @Nonnull + @Override + public NodeBuilder builder() { + return new MemoryNodeBuilder(this); + } + + //Following method should be overridden as default implementation in AbstractNodeState + //is not optimized + + @Override + public PropertyState getProperty(@Nonnull String name) { + return nodeState.getProperty(name); + } + + @Override + public long getPropertyCount() { + return nodeState.getPropertyCount(); + } + + @Override + public boolean hasProperty(@Nonnull String name) { + return nodeState.hasProperty(name); + } + + @Override + public boolean getBoolean(@Nonnull String name) { + return nodeState.getBoolean(name); + } + + @Override + public long getLong(String name) { + return nodeState.getLong(name); + } + + @Override + public String getString(String name) { + return nodeState.getString(name); + } + + @Nonnull + @Override + public Iterable getStrings(@Nonnull String name) { + return nodeState.getStrings(name); + } + + @Override + public String getName(@Nonnull String name) { + return nodeState.getName(name); + } + + @Nonnull + @Override + public Iterable getNames(@Nonnull String name) { + return nodeState.getNames(name); + } + + @Override + public long getChildNodeCount(long max) { + return nodeState.getChildNodeCount(max); + } + + @Override + public Iterable getChildNodeNames() { + return nodeState.getChildNodeNames(); + } + + private NodeState decorateChild(NodeState child, String name) { + String childPath = PathUtils.concat(path, name); + return new MountedDocumentNodeState(child, childPath, lastRev, rootRev, fromExternalChange, differ); + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStore.java new file mode 100644 index 0000000..02d416c --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStore.java @@ -0,0 +1,76 @@ +/* + * 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.document.mount; + +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache; +import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer; +import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.apache.jackrabbit.oak.plugins.document.RevisionVector; +import org.apache.jackrabbit.oak.spi.mount.Mount; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStore; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class MountedNodeStore implements DocumentNodeStateCache { + + private static RevisionVector LAST_REV = new RevisionVector(new Revision(0, 0, 1)); + + private static RevisionVector ROOT_REV = new RevisionVector(new Revision(0, 0, 1)); + + private final MountInfoProvider mountInfoProvider; + + private final NodeStore nodeStore; + + private final String mountName; + + private final NodeStateDiffer differ; + + public MountedNodeStore(MountInfoProvider mountInfoProvider, NodeStore nodeStore, String mountName, NodeStateDiffer differ) { + this.mountInfoProvider = checkNotNull(mountInfoProvider); + this.nodeStore = checkNotNull(nodeStore); + this.mountName = checkNotNull(mountName); + this.differ = differ; + } + + @Override + public AbstractDocumentNodeState getDocumentNodeState(String path, RevisionVector rootRevision, RevisionVector lastRev) { + Mount mount = mountInfoProvider.getMountByPath(path); + if (mountName.equals(mount.getName())) { + return getMounted(path); + } else { + return null; + } + } + + public AbstractDocumentNodeState getMounted(String path) { + NodeState state = nodeStore.getRoot(); + for(String element : PathUtils.elements(path)) { + state = state.getChildNode(element); + } + if (state instanceof AbstractDocumentNodeState) { + return (AbstractDocumentNodeState) state; + } else { + return MountedDocumentNodeState.wrap(state, path, LAST_REV, ROOT_REV, differ); + } + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStoreService.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStoreService.java new file mode 100644 index 0000000..f79ec56 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStoreService.java @@ -0,0 +1,118 @@ +/* + * 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.document.mount; + +import com.google.common.collect.Lists; +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.Property; +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.plugins.document.AbstractDocumentNodeState; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer; +import org.apache.jackrabbit.oak.plugins.document.secondary.SecondaryStoreCacheService; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.state.NodeStateDiff; +import org.apache.jackrabbit.oak.spi.state.NodeStoreProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Map; + +@Component(label = "Apache Jackrabbit Oak MountedNodeState Provider", + metatype = true, + policy = ConfigurationPolicy.REQUIRE, + description = "Configures the MountedNodeStore" +) +public class MountedNodeStoreService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + /* + * Have an optional dependency on DocumentNodeStore such that we do not have hard dependency + * on it and DocumentNodeStore can make use of this service even after it has unregistered + */ + @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, + policy = ReferencePolicy.DYNAMIC) + private DocumentNodeStore documentNodeStore; + + @Reference(target = "(role=secondary)") + private NodeStoreProvider mountedStoreProvider; + + @Reference + private MountInfoProvider mountInfoProvider; + + private static final String PROP_MOUNT_NAME_DEFAULT = "private"; + @Property( + value = PROP_MOUNT_NAME_DEFAULT, + label = "Mount name", + description = "Name of the mount configured in MountInfoProvider" + ) + private static final String PROP_MOUNT_NAME = "mountName"; + + private final List regs = Lists.newArrayList(); + + private final MountedNodeStoreService.MultiplexingNodeStateDiffer differ = new MountedNodeStoreService.MultiplexingNodeStateDiffer(); + + @Activate + private void activate(BundleContext context, Map config) { + String mountName = PropertiesUtil.toString(config.get(PROP_MOUNT_NAME), PROP_MOUNT_NAME_DEFAULT); + MountedNodeStore mountedNodeStore = new MountedNodeStore(mountInfoProvider, mountedStoreProvider.getNodeStore(), mountName, differ); + regs.add(context.registerService(DocumentNodeStateCache.class.getName(), mountedNodeStore, null)); + } + + @Deactivate + private void deactivate(){ + for (ServiceRegistration r : regs){ + r.unregister(); + } + } + + protected void bindDocumentNodeStore(DocumentNodeStore documentNodeStore){ + log.info("Registering DocumentNodeStore as the differ"); + differ.setDelegate(documentNodeStore); + } + + protected void unbindDocumentNodeStore(DocumentNodeStore documentNodeStore){ + differ.setDelegate(NodeStateDiffer.DEFAULT_DIFFER); + } + + private static class MultiplexingNodeStateDiffer implements NodeStateDiffer { + private volatile NodeStateDiffer delegate = NodeStateDiffer.DEFAULT_DIFFER; + @Override + public boolean compare(@Nonnull AbstractDocumentNodeState node, + @Nonnull AbstractDocumentNodeState base, @Nonnull NodeStateDiff diff) { + return delegate.compare(node, base, diff); + } + + public void setDelegate(NodeStateDiffer delegate) { + this.delegate = delegate; + } + } +} \ No newline at end of file diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStoreTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStoreTest.java new file mode 100644 index 0000000..d99c0a6 --- /dev/null +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/mount/MountedNodeStoreTest.java @@ -0,0 +1,121 @@ +/* + * 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.document.mount; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.plugins.multiplex.SimpleMountInfoProvider; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.EmptyHook; +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 org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; + +import static org.apache.jackrabbit.oak.spi.state.NodeStateUtils.getNode; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class MountedNodeStoreTest { + + @Rule + public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider(); + + private DocumentNodeStore main; + + private NodeStore priv; + + @Before + public void setUp() throws IOException { + priv = new MemoryNodeStore(); + + MountInfoProvider provider = SimpleMountInfoProvider.newBuilder().readOnlyMount("private", "/libs").build(); + MountedNodeStore mounted = new MountedNodeStore(provider, priv, "private", NodeStateDiffer.DEFAULT_DIFFER); + + main = builderProvider.newBuilder().getNodeStore(); + main.setNodeStateCache(mounted); + } + + @Test + public void testReadingFromPrivate() throws CommitFailedException { + NodeBuilder nb = priv.getRoot().builder(); + create(nb, "/libs/a", "/libs/b", "/libs/c", "/libs/a/a1", "/libs/a/a1/a2"); + nb.getChildNode("libs").setProperty("storage-type", "private", Type.STRING); + merge(priv, nb); + + nb = main.getRoot().builder(); + create(nb, "/x", "/y", "/z", "/libs"); + nb.setProperty("storage-type", "main", Type.STRING); + merge(main, nb); + + assertExists("/x", "/y", "/z"); + assertEquals("main", main.getRoot().getString("storage-type")); + assertExists("/libs"); + assertEquals("private", main.getRoot().getChildNode("libs").getString("storage-type")); + assertExists("/libs/a", "/libs/b", "/libs/c"); + assertExists("/libs/a/a1", "/libs/a/a1/a2"); + } + + @Test + public void testPathFragment() throws CommitFailedException { + NodeBuilder nb = priv.getRoot().builder(); + create(nb, "/content/xyz/oak:mount-private/xyz"); + merge(priv, nb); + + nb = main.getRoot().builder(); + create(nb, "/content/xyz/oak:mount-private"); + nb.getChildNode("content").getChildNode("xyz").setProperty("storage-type", "main", Type.STRING); + merge(main, nb); + + assertExists("/content/xyz"); + assertEquals("main", getNode(main.getRoot(), "/content/xyz").getString("storage-type")); + assertExists("/content/xyz/oak:mount-private"); + assertExists("/content/xyz/oak:mount-private/xyz"); + } + + private static NodeState create(NodeBuilder b, String... paths) { + for (String path : paths) { + NodeBuilder cb = b; + for (String pathElement : PathUtils.elements(path)) { + cb = cb.child(pathElement); + } + } + return b.getNodeState(); + } + + private static NodeState merge(NodeStore ns, NodeBuilder nb) throws CommitFailedException { + return ns.merge(nb, EmptyHook.INSTANCE, CommitInfo.EMPTY); + } + + private void assertExists(String... paths) { + for (String p : paths) { + assertTrue("Path [" + p + "] doesn't exist", getNode(main.getRoot(), p).exists()); + } + } +}