Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingChildrenCountTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingChildrenCountTest.java?rev=1767652&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingChildrenCountTest.java (added) +++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingChildrenCountTest.java Wed Nov 2 12:16:32 2016 @@ -0,0 +1,259 @@ +/* + * 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.collect.Iterables; +import com.google.common.collect.Lists; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState; +import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +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.NodeStore; +import org.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder; +import org.junit.Test; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Iterables.cycle; +import static com.google.common.collect.Iterables.limit; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Maps.newHashMap; +import static java.lang.Long.MAX_VALUE; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; +import static org.junit.Assert.assertEquals; + +public class MultiplexingChildrenCountTest { + + @Test + public void singleContributingStore() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder().build(); + NodeStore globalStore = new MemoryNodeStore(); + MultiplexingNodeStore multiplexingNodeStore = new MultiplexingNodeStore.Builder(mip, globalStore).build(); + + MultiplexingNodeStateBuilder b = new MultiplexingNodeStateBuilder(multiplexingNodeStore.ctx); + b.configureMount("/", MAX_VALUE); + assertEquals(MAX_VALUE, b.getNodeState().getChildNodeCount(123)); + + b.clear().configureMount("/", 10); + assertEquals(10, b.getNodeState().getChildNodeCount(200)); + } + + @Test + public void multipleContributingStores() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder().mount("libs", "/libs", "/libs1", "/libs2", "/libs3", "/libs4").build(); + NodeStore globalStore = new MemoryNodeStore(); + NodeStore libsStore = new MemoryNodeStore(); + + List<MountedNodeStore> mounts = Lists.newArrayList(); + mounts.add(new MountedNodeStore(mip.getMountByName("libs"), libsStore)); + MultiplexingNodeStore multiplexingNodeStore = new MultiplexingNodeStore(mip, globalStore, mounts); + + MultiplexingNodeStateBuilder b = new MultiplexingNodeStateBuilder(multiplexingNodeStore.ctx); + TestingNodeState globalTestingNS = b.configureMount("/", 5); + TestingNodeState libsTestingNS = b.configureMount("/libs", "libs", "libs1", "libs2"); + + MultiplexingNodeState mns = b.getNodeState(); + + assertEquals(8, mns.getChildNodeCount(9)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(3, libsTestingNS.fetchedChildren); + globalTestingNS.fetchedChildren = 0; + libsTestingNS.fetchedChildren = 0; + + assertEquals(MAX_VALUE, mns.getChildNodeCount(8)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(3, libsTestingNS.fetchedChildren); + globalTestingNS.fetchedChildren = 0; + libsTestingNS.fetchedChildren = 0; + + assertEquals(MAX_VALUE, mns.getChildNodeCount(7)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(2, libsTestingNS.fetchedChildren); + globalTestingNS.fetchedChildren = 0; + libsTestingNS.fetchedChildren = 0; + + assertEquals(8, mns.builder().getChildNodeCount(9)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(3, libsTestingNS.fetchedChildren); + globalTestingNS.fetchedChildren = 0; + libsTestingNS.fetchedChildren = 0; + + assertEquals(MAX_VALUE, mns.builder().getChildNodeCount(8)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(3, libsTestingNS.fetchedChildren); + globalTestingNS.fetchedChildren = 0; + libsTestingNS.fetchedChildren = 0; + + assertEquals(MAX_VALUE, mns.builder().getChildNodeCount(7)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(2, libsTestingNS.fetchedChildren); + } + + @Test + public void contributingStoreReturnsInfinity() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder().mount("libs", "/libs", "/libs1", "/libs2", "/libs3", "/libs4").build(); + NodeStore globalStore = new MemoryNodeStore(); + NodeStore libsStore = new MemoryNodeStore(); + + List<MountedNodeStore> mounts = Lists.newArrayList(); + mounts.add(new MountedNodeStore(mip.getMountByName("libs"), libsStore)); + MultiplexingNodeStore multiplexingNodeStore = new MultiplexingNodeStore(mip, globalStore, mounts); + + MultiplexingNodeStateBuilder b = new MultiplexingNodeStateBuilder(multiplexingNodeStore.ctx); + TestingNodeState globalTestingNS = b.configureMount("/", 5); + TestingNodeState libsTestingNS = b.configureMount("/libs", MAX_VALUE); + + MultiplexingNodeState mns = b.getNodeState(); + + assertEquals(MAX_VALUE, mns.getChildNodeCount(100)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(0, libsTestingNS.fetchedChildren); + globalTestingNS.fetchedChildren = 0; + libsTestingNS.fetchedChildren = 0; + + assertEquals(MAX_VALUE, mns.builder().getChildNodeCount(100)); + assertEquals(5, globalTestingNS.fetchedChildren); + assertEquals(0, libsTestingNS.fetchedChildren); + } + + private static class MultiplexingNodeStateBuilder { + + private final Map<MountedNodeStore, NodeState> rootStates = newHashMap(); + + private final MultiplexingContext ctx; + + public MultiplexingNodeStateBuilder(MultiplexingContext ctx) { + this.ctx = ctx; + } + + public TestingNodeState configureMount(String mountPath, long children) { + TestingNodeState nodeState = new TestingNodeState(children); + rootStates.put(ctx.getOwningStore(mountPath), nodeState); + return nodeState; + } + + public TestingNodeState configureMount(String mountPath, String... children) { + TestingNodeState nodeState = new TestingNodeState(children); + rootStates.put(ctx.getOwningStore(mountPath), nodeState); + return nodeState; + } + + public MultiplexingNodeState getNodeState() { + return new MultiplexingNodeState("/", rootStates, ctx); + } + + public MultiplexingNodeStateBuilder clear() { + rootStates.clear(); + return this; + } + } + + private static class TestingNodeState extends AbstractNodeState { + + private final long childrenCount; + + private final String[] children; + + private long fetchedChildren = 0; + + private TestingNodeState(long childrenCount) { + this.children = null; + this.childrenCount = childrenCount; + } + + private TestingNodeState(String... children) { + this.children = children; + this.childrenCount = children.length; + } + + @Override + public boolean exists() { + return true; + } + + @Nonnull + @Override + public Iterable<? extends PropertyState> getProperties() { + return emptyList(); + } + + @Override + public boolean hasChildNode(@Nonnull String name) { + return false; + } + + @Nonnull + @Override + public NodeState getChildNode(@Nonnull String name) throws IllegalArgumentException { + return EmptyNodeState.MISSING_NODE; + } + + @Nonnull + @Override + public Iterable<? extends ChildNodeEntry> getChildNodeEntries() { + if (children == null) { + Iterable<? extends ChildNodeEntry> childrenIterable = cycle(new MemoryChildNodeEntry("child", EMPTY_NODE)); + return asCountingIterable(limit(childrenIterable, childrenCount == MAX_VALUE ? 1000 : (int) childrenCount)); + } else { + return asCountingIterable(transform(asList(children), new Function<String, ChildNodeEntry>() { + @Nullable + @Override + public ChildNodeEntry apply(@Nullable String input) { + return new MemoryChildNodeEntry(input, EMPTY_NODE); + } + })); + } + } + + @Override + public long getChildNodeCount(long max) { + return childrenCount; + } + + @Nonnull + @Override + public NodeBuilder builder() { + return new ReadOnlyBuilder(this); + } + + private <T> Iterable<T> asCountingIterable(Iterable<T> input) { + return Iterables.transform(input, new Function<T, T>() { + @Nullable + @Override + public T apply(@Nullable T input) { + fetchedChildren++; + return input; + } + }); + } + } +}
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingCompareTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingCompareTest.java?rev=1767652&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingCompareTest.java (added) +++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingCompareTest.java Wed Nov 2 12:16:32 2016 @@ -0,0 +1,163 @@ +/* + * 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.collect.ImmutableSet; +import com.google.common.collect.Lists; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +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.DefaultNodeStateDiff; +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.Test; + +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.Sets.newHashSet; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class MultiplexingCompareTest { + + @Test + public void reportedNodesAreWrapped() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder().build(); + NodeStore globalStore = new MemoryNodeStore(); + MultiplexingNodeStore multiplexingNodeStore = new MultiplexingNodeStore.Builder(mip, globalStore).build(); + + NodeBuilder builder = multiplexingNodeStore.getRoot().builder(); + builder.child("changed"); + builder.child("deleted"); + NodeState base = builder.getNodeState(); + + builder.getChildNode("changed").setProperty("newProp", "xyz", Type.STRING); + builder.getChildNode("deleted").remove(); + builder.child("added"); + final NodeState modified = builder.getNodeState(); + + final Set<String> modifiedNodes = newHashSet(); + modified.compareAgainstBaseState(base, new DefaultNodeStateDiff() { + @Override + public boolean childNodeAdded(String name, NodeState after) { + assertTrue(after instanceof MultiplexingNodeState); + assertEquals(name, "added"); + modifiedNodes.add(name); + return true; + } + + @Override + public boolean childNodeChanged(String name, NodeState before, NodeState after) { + assertTrue(before instanceof MultiplexingNodeState); + assertTrue(after instanceof MultiplexingNodeState); + assertEquals(name, "changed"); + modifiedNodes.add(name); + return true; + } + + @Override + public boolean childNodeDeleted(String name, NodeState before) { + assertTrue(before instanceof MultiplexingNodeState); + assertEquals(name, "deleted"); + modifiedNodes.add(name); + return true; + } + }); + assertEquals(ImmutableSet.of("added", "changed", "deleted"), modifiedNodes); + } + + @Test + public void onlyPropertiesOnMainNodesAreCompared() throws CommitFailedException { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder().mount("libs", "/libs").build(); + NodeStore globalStore = new MemoryNodeStore(); + NodeStore libsStore = new MemoryNodeStore(); + + List<MountedNodeStore> mounts = Lists.newArrayList(); + mounts.add(new MountedNodeStore(mip.getMountByName("libs"), libsStore)); + MultiplexingNodeStore multiplexingNodeStore = new MultiplexingNodeStore(mip, globalStore, mounts); + + NodeState empty = multiplexingNodeStore.getRoot(); + + NodeBuilder builder = globalStore.getRoot().builder(); + builder.setProperty("global-prop-1", "val"); + builder.setProperty("global-prop-2", "val"); + globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + NodeBuilder libsBuilder = libsStore.getRoot().builder(); + libsBuilder.setProperty("libs-prop-1", "val"); + libsBuilder.setProperty("libs-prop-2", "val"); + libsStore.merge(libsBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + NodeState modified = multiplexingNodeStore.getRoot(); + + final Set<String> addedProperties = newHashSet(); + modified.compareAgainstBaseState(empty, new DefaultNodeStateDiff() { + @Override + public boolean propertyAdded(PropertyState after) { + addedProperties.add(after.getName()); + return true; + } + }); + assertEquals(ImmutableSet.of("global-prop-1", "global-prop-2"), addedProperties); + } + + @Test + public void nodesOutsideTheMountsAreIgnored() throws CommitFailedException { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder().mount("libs", "/libs").build(); + NodeStore globalStore = new MemoryNodeStore(); + NodeStore libsStore = new MemoryNodeStore(); + + List<MountedNodeStore> mounts = Lists.newArrayList(); + mounts.add(new MountedNodeStore(mip.getMountByName("libs"), libsStore)); + MultiplexingNodeStore multiplexingNodeStore = new MultiplexingNodeStore(mip, globalStore, mounts); + + NodeState empty = multiplexingNodeStore.getRoot(); + + NodeBuilder builder = globalStore.getRoot().builder(); + builder.child("global-child-1"); + builder.child("global-child-2"); + globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + NodeBuilder libsBuilder = libsStore.getRoot().builder(); + libsBuilder.child("libs"); + libsBuilder.child("libs-child-1"); + libsBuilder.child("libs-child-2"); + libsStore.merge(libsBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + NodeState modified = multiplexingNodeStore.getRoot(); + + final Set<String> addedChildren = newHashSet(); + modified.compareAgainstBaseState(empty, new DefaultNodeStateDiff() { + @Override + public boolean childNodeAdded(String name, NodeState after) { + addedChildren.add(name); + return true; + } + }); + assertEquals(ImmutableSet.of("global-child-1", "global-child-2", "libs"), addedChildren); + + } +} Modified: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java?rev=1767652&r1=1767651&r2=1767652&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java (original) +++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/NodeStoreFixtures.java Wed Nov 2 12:16:32 2016 @@ -28,6 +28,8 @@ import org.apache.jackrabbit.oak.fixture import org.apache.jackrabbit.oak.fixture.DocumentRdbFixture; import org.apache.jackrabbit.oak.fixture.MemoryFixture; import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; +import org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingMemoryFixture; +import org.apache.jackrabbit.oak.plugins.multiplex.MultiplexingSegmentFixture; import org.apache.jackrabbit.oak.plugins.segment.fixture.SegmentFixture; import org.apache.jackrabbit.oak.segment.fixture.SegmentTarFixture; @@ -45,6 +47,10 @@ public class NodeStoreFixtures { public static final NodeStoreFixture DOCUMENT_MEM = new DocumentMemoryFixture(); + public static final NodeStoreFixture MULTIPLEXED_SEGMENT = new MultiplexingSegmentFixture(); + + public static final NodeStoreFixture MULTIPLEXED_MEM = new MultiplexingMemoryFixture(); + public static Collection<Object[]> asJunitParameters(Set<FixturesHelper.Fixture> fixtures) { List<NodeStoreFixture> configuredFixtures = new ArrayList<NodeStoreFixture>(); if (fixtures.contains(FixturesHelper.Fixture.DOCUMENT_NS)) { @@ -65,6 +71,12 @@ public class NodeStoreFixtures { if (fixtures.contains(FixturesHelper.Fixture.SEGMENT_TAR)) { configuredFixtures.add(SEGMENT_TAR); } + if (fixtures.contains(FixturesHelper.Fixture.MULTIPLEXED_SEGMENT)) { + configuredFixtures.add(MULTIPLEXED_SEGMENT); + } + if (fixtures.contains(FixturesHelper.Fixture.MULTIPLEXED_MEM)) { + configuredFixtures.add(MULTIPLEXED_MEM); + } Collection<Object[]> result = new ArrayList<Object[]>(); for (NodeStoreFixture f : configuredFixtures) { Added: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingMemoryFixture.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingMemoryFixture.java?rev=1767652&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingMemoryFixture.java (added) +++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingMemoryFixture.java Wed Nov 2 12:16:32 2016 @@ -0,0 +1,47 @@ +/* + * 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.fixture.NodeStoreFixture; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.state.NodeStore; + +public class MultiplexingMemoryFixture extends NodeStoreFixture { + + private static final String MOUNT_PATH = "/tmp"; + + @Override + public NodeStore createNodeStore() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .readOnlyMount("temp", MOUNT_PATH) + .build(); + + NodeStore globalStore = new MemoryNodeStore(); + NodeStore tempMount = new MemoryNodeStore(); + + return new MultiplexingNodeStore.Builder(mip, globalStore).addMount("temp", tempMount).build(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " with a mount under " + MOUNT_PATH; + } + +} \ No newline at end of file Added: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreBuilderTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreBuilderTest.java?rev=1767652&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreBuilderTest.java (added) +++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreBuilderTest.java Wed Nov 2 12:16:32 2016 @@ -0,0 +1,84 @@ +/* + * 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.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.junit.Test; + +public class MultiplexingNodeStoreBuilderTest { + + @Test(expected = IllegalArgumentException.class) + public void builderRejectsTooManyReadWriteStores_oneExtra() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .mount("temp", "/tmp") + .build(); + + new MultiplexingNodeStore.Builder(mip, new MemoryNodeStore()) + .addMount("temp", new MemoryNodeStore()) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void builderRejectsTooManyReadWriteStores_mixed() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .mount("temp", "/tmp") + .readOnlyMount("readOnly", "/readOnly") + .build(); + + new MultiplexingNodeStore.Builder(mip, new MemoryNodeStore()) + .addMount("temp", new MemoryNodeStore()) + .addMount("readOnly", new MemoryNodeStore()) + .build(); + } + + @Test + public void builderAcceptsMultipleReadOnlyStores() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .readOnlyMount("readOnly", "/readOnly") + .readOnlyMount("readOnly2", "/readOnly2") + .build(); + + new MultiplexingNodeStore.Builder(mip, new MemoryNodeStore()) + .addMount("readOnly", new MemoryNodeStore()) + .addMount("readOnly2", new MemoryNodeStore()) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void mismatchBetweenMountsAndStoresIsRejected() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .mount("temp", "/tmp") + .build(); + + new MultiplexingNodeStore.Builder(mip, new MemoryNodeStore()) + .build(); + } + + @Test(expected = NullPointerException.class) + public void mismatchBetweenMountNameAndStoreName() { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .mount("temp", "/tmp") + .build(); + + new MultiplexingNodeStore.Builder(mip, new MemoryNodeStore()) + .addMount("not-temp", new MemoryNodeStore()) + .build(); + } +} \ No newline at end of file Added: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreTest.java?rev=1767652&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreTest.java (added) +++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingNodeStoreTest.java Wed Nov 2 12:16:32 2016 @@ -0,0 +1,948 @@ +/* + * 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 static com.google.common.base.Predicates.compose; +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Lists.newArrayList; +import static org.apache.jackrabbit.oak.spi.state.ChildNodeEntry.GET_NAME; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; +import javax.sql.DataSource; + +import com.google.common.base.Predicates; +import org.apache.commons.io.FileUtils; +import org.apache.jackrabbit.oak.api.Blob; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.document.DocumentMK; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory; +import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions; +import org.apache.jackrabbit.oak.plugins.document.util.CountingDiff; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.plugins.segment.Segment; +import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStore; +import org.apache.jackrabbit.oak.plugins.segment.file.FileStore; +import org.apache.jackrabbit.oak.spi.blob.BlobStore; +import org.apache.jackrabbit.oak.spi.blob.FileBlobStore; +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.ChildNodeEntry; +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.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +@RunWith(Parameterized.class) +public class MultiplexingNodeStoreTest { + + private final NodeStoreKind root; + private final NodeStoreKind mounts; + + private final List<NodeStoreRegistration> registrations = newArrayList(); + + private MultiplexingNodeStore store; + private NodeStore globalStore; + private NodeStore mountedStore; + private NodeStore deepMountedStore; + private NodeStore readOnlyStore; + + @Parameters(name="Root: {0}, Mounts: {1}") + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + { NodeStoreKind.MEMORY, NodeStoreKind.MEMORY }, + { NodeStoreKind.SEGMENT, NodeStoreKind.SEGMENT}, + { NodeStoreKind.DOCUMENT_H2, NodeStoreKind.DOCUMENT_H2}, + { NodeStoreKind.DOCUMENT_H2, NodeStoreKind.SEGMENT} + }); + } + + public MultiplexingNodeStoreTest(NodeStoreKind root, NodeStoreKind mounts) { + this.root = root; + this.mounts = mounts; + } + + @Before + public void initStore() throws Exception { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .mount("temp", "/tmp") + .mount("deep", "/libs/mount") + .mount("empty", "/nowhere") + .readOnlyMount("readOnly", "/readOnly") + .build(); + + globalStore = register(root.create(null)); + mountedStore = register(mounts.create("temp")); + deepMountedStore = register(mounts.create("deep")); + readOnlyStore = register(mounts.create("readOnly")); + NodeStore emptyStore = register(mounts.create("empty")); // this NodeStore will always be empty + + // create a property on the root node + NodeBuilder builder = globalStore.getRoot().builder(); + builder.setProperty("prop", "val"); + globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue(globalStore.getRoot().hasProperty("prop")); + + // create a different sub-tree on the root store + builder = globalStore.getRoot().builder(); + NodeBuilder libsBuilder = builder.child("libs"); + libsBuilder.child("first"); + libsBuilder.child("second"); + + // create an empty /apps node with a property + builder.child("apps").setProperty("prop", "val"); + + globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertThat(globalStore.getRoot().getChildNodeCount(10), equalTo(2l)); + + // create a /tmp child on the mounted store and set a property + builder = mountedStore.getRoot().builder(); + NodeBuilder tmpBuilder = builder.child("tmp"); + tmpBuilder.setProperty("prop1", "val1"); + tmpBuilder.child("child1").setProperty("prop1", "val1"); + tmpBuilder.child("child2"); + + mountedStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue(mountedStore.getRoot().hasChildNode("tmp")); + assertThat(mountedStore.getRoot().getChildNode("tmp").getChildNodeCount(10), equalTo(2l)); + + // populate /libs/mount/third in the deep mount, and include a property + + builder = deepMountedStore.getRoot().builder(); + builder.child("libs").child("mount").child("third").setProperty("mounted", "true"); + + deepMountedStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue(deepMountedStore.getRoot().getChildNode("libs").getChildNode("mount").getChildNode("third").hasProperty("mounted")); + + // populate /readonly with a single node + builder = readOnlyStore.getRoot().builder(); + builder.child("readOnly"); + + readOnlyStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // don't use the builder since it would fail due to too many read-write stores + // but for the purposes of testing the general correctness it's fine + List<MountedNodeStore> nonDefaultStores = Lists.newArrayList(); + nonDefaultStores.add(new MountedNodeStore(mip.getMountByName("temp"), mountedStore)); + nonDefaultStores.add(new MountedNodeStore(mip.getMountByName("deep"), deepMountedStore)); + nonDefaultStores.add(new MountedNodeStore(mip.getMountByName("empty"), emptyStore)); + nonDefaultStores.add(new MountedNodeStore(mip.getMountByName("readOnly"), readOnlyStore)); + store = new MultiplexingNodeStore(mip, globalStore, nonDefaultStores); + } + + @After + public void closeRepositories() throws Exception { + for ( NodeStoreRegistration reg : registrations ) { + reg.close(); + } + } + + @Test + public void rootExists() { + assertThat("root exists", store.getRoot().exists(), equalTo(true)); + } + + @Test + public void rootPropertyIsSet() { + assertThat("root[prop]", store.getRoot().hasProperty("prop"), equalTo(true)); + assertThat("root[prop] = val", store.getRoot().getProperty("prop").getValue(Type.STRING), equalTo("val")); + } + + @Test + public void nonMountedChildIsFound() { + assertThat("root.libs", store.getRoot().hasChildNode("libs"), equalTo(true)); + } + + @Test + public void nestedMountNodeIsVisible() { + assertThat("root.libs(childCount)", store.getRoot().getChildNode("libs").getChildNodeCount(10), equalTo(3l)); + } + + @Test + public void mixedMountsChildNodes() { + assertThat("root(childCount)", store.getRoot().getChildNodeCount(100), equalTo(4l)); + } + + @Test + public void mountedChildIsFound() { + + assertThat("root.tmp", store.getRoot().hasChildNode("tmp"), equalTo(true)); + } + + @Test + public void childrenUnderMountAreFound() { + assertThat("root.tmp(childCount)", store.getRoot().getChildNode("tmp").getChildNodeCount(10), equalTo(2l)); + } + + @Test + public void childNodeEntryForMountIsMultiplexed() { + ChildNodeEntry libsNode = Iterables.find(store.getRoot().getChildNodeEntries(), new Predicate<ChildNodeEntry>() { + + @Override + public boolean apply(ChildNodeEntry input) { + return input.getName().equals("libs"); + } + }); + + assertThat("root.libs(childCount)", libsNode.getNodeState().getChildNodeCount(10), equalTo(3l)); + } + + @Test + public void contentBelongingToAnotherMountIsIgnored() throws Exception { + // create a /tmp/oops child on the root store + // these two nodes must be ignored + NodeBuilder builder = globalStore.getRoot().builder(); + builder.child("tmp").child("oops"); + + globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue(globalStore.getRoot().getChildNode("tmp").hasChildNode("oops")); + + assertFalse(store.getRoot().getChildNode("tmp").hasChildNode("oops")); + } + + @Test + public void checkpoint() throws Exception { + String checkpoint = store.checkpoint(TimeUnit.DAYS.toMillis(1)); + + assertNotNull("checkpoint reference is null", checkpoint); + + // create a new child /new in the root store + NodeBuilder globalBuilder = globalStore.getRoot().builder(); + globalBuilder.child("new"); + globalStore.merge(globalBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // create a new child /tmp/new in the mounted store + NodeBuilder mountedBuilder = mountedStore.getRoot().builder(); + mountedBuilder.getChildNode("tmp").child("new"); + mountedStore.merge(mountedBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // create a new child /libs/mount/new in the deeply mounted store + NodeBuilder deepMountBuilder = deepMountedStore.getRoot().builder(); + deepMountBuilder.getChildNode("libs").getChildNode("mount").child("new"); + deepMountedStore.merge(deepMountBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("store incorrectly exposes child at /new", store.retrieve(checkpoint).hasChildNode("new")); + assertFalse("store incorrectly exposes child at /tmp/new", store.retrieve(checkpoint).getChildNode("tmp").hasChildNode("new")); + assertFalse("store incorrectly exposes child at /libs/mount/new", store.retrieve(checkpoint).getChildNode("libs").getChildNode("mount").hasChildNode("new")); + } + + @Test + public void checkpointInfo() throws Exception { + Map<String, String> info = Collections.singletonMap("key", "value"); + String checkpoint = store.checkpoint(TimeUnit.DAYS.toMillis(1), info); + + assertThat(store.checkpointInfo(checkpoint), equalTo(info)); + } + + @Test + public void release() { + String checkpoint = store.checkpoint(TimeUnit.DAYS.toMillis(1)); + + assertTrue(store.release(checkpoint)); + } + + @Test + public void existingBlobsInRootStoreAreRetrieved() throws Exception { + assumeTrue(root.supportsBlobCreation()); + + Blob createdBlob = globalStore.createBlob(createLargeBlob()); + Blob retrievedBlob = store.getBlob(createdBlob.getReference()); + + assertThat(retrievedBlob.getContentIdentity(), equalTo(createdBlob.getContentIdentity())); + } + + + @Test + public void existingBlobsInMountedStoreAreRetrieved() throws Exception { + + assumeTrue(mounts.supportsBlobCreation()); + + Blob createdBlob = mountedStore.createBlob(createLargeBlob()); + Blob retrievedBlob = store.getBlob(createdBlob.getReference()); + + assertThat(retrievedBlob.getContentIdentity(), equalTo(createdBlob.getContentIdentity())); + } + + @Test + public void blobCreation() throws Exception { + assumeTrue(root.supportsBlobCreation()); + + Blob createdBlob = store.createBlob(createLargeBlob()); + Blob retrievedBlob = store.getBlob(createdBlob.getReference()); + + assertThat(retrievedBlob.getContentIdentity(), equalTo(createdBlob.getContentIdentity())); + } + + @Test + public void setPropertyOnRootStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + builder.setProperty("newProp", "newValue"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertThat("Property must be visible in multiplexed store", + store.getRoot().getProperty("newProp").getValue(Type.STRING), equalTo("newValue")); + + assertThat("Property must be visible in owning (root) store", + globalStore.getRoot().getProperty("newProp").getValue(Type.STRING), equalTo("newValue")); + } + + @Test + public void removePropertyFromRootStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + builder.removeProperty("prop"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("Property must be removed from multiplexed store", store.getRoot().hasProperty("prop")); + assertFalse("Property must be removed from owning (root) store", globalStore.getRoot().hasProperty("prop")); + } + + @Test + public void createNodeInRootStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + builder.child("newNode"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue("Node must be added to multiplexed store", store.getRoot().hasChildNode("newNode")); + assertTrue("Node must be added to owning (root) store", globalStore.getRoot().hasChildNode("newNode")); + } + + @Test + public void createNodeInMountedStore() throws Exception { + + NodeBuilder builder = store.getRoot().builder(); + + builder.getChildNode("tmp").child("newNode"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue("Node must be added to multiplexed store", store.getRoot().getChildNode("tmp").hasChildNode("newNode")); + assertTrue("Node must be added to owning (mounted) store", mountedStore.getRoot().getChildNode("tmp").hasChildNode("newNode")); + } + + @Test + public void removeNodeInRootStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + builder.getChildNode("apps").remove(); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("Node must be removed from the multiplexed store", store.getRoot().hasChildNode("apps")); + assertFalse("Node must be removed from the owning (root) store", globalStore.getRoot().hasChildNode("apps")); + } + + + @Test + public void removeNodeInMountedStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + builder.getChildNode("tmp").child("newNode"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue("Node must be added to multiplexed store", store.getRoot().getChildNode("tmp").hasChildNode("newNode")); + + builder = store.getRoot().builder(); + + builder.getChildNode("tmp").getChildNode("newNode").remove(); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("Node must be removed from the multiplexed store", store.getRoot().getChildNode("tmp").hasChildNode("newNode")); + assertFalse("Node must be removed from the owning (multiplexed) store", globalStore.getRoot().getChildNode("tmp").hasChildNode("newNode")); + } + + @Test + public void builderChildrenCountInRootStore() throws Exception { + assertThat("root(childCount)", store.getRoot().builder().getChildNodeCount(100), equalTo(4l)); + } + + @Test + public void builderChildrenCountInMountedStore() { + assertThat("root.tmp(childCount)", store.getRoot().builder().getChildNode("tmp").getChildNodeCount(10), equalTo(2l)); + } + + @Test + public void builderChildNodeNamesInRootStore() throws Exception { + assertChildNodeNames(store.getRoot().builder(), "libs", "apps", "tmp", "readOnly"); + } + + @Test + public void builderChildNodeNamesInMountedStore() throws Exception { + assertChildNodeNames(store.getRoot().builder().getChildNode("tmp"), "child1", "child2"); + } + + @Test + public void builderStateIsUpdatedBeforeMergeinGlobalStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + builder.child("newChild"); + + assertTrue("Newly created node should be visible in the builder's node state", builder.hasChildNode("newChild")); + } + + @Test + public void builderStateIsUpdatedBeforeMergeinMountedStore() throws Exception { + NodeBuilder builder = store.getRoot().getChildNode("tmp").builder(); + builder.child("newChild"); + + assertTrue("Newly created node should be visible in the builder's node state", builder.hasChildNode("newChild")); + } + + + @Test + public void builderHasPropertyNameInRootStore() { + assertFalse("Node 'nope' does not exist", store.getRoot().builder().hasChildNode("nope")); + assertTrue("Node 'tmp' should exist (contributed by mount)", store.getRoot().builder().hasChildNode("tmp")); + assertTrue("Node 'libs' should exist (contributed by root)", store.getRoot().builder().hasChildNode("libs")); + } + + @Test + public void builderHasPropertyNameInMountedStore() { + assertFalse("Node 'nope' does not exist", store.getRoot().builder().getChildNode("tmp").hasChildNode("nope")); + assertTrue("Node 'child1' should exist", store.getRoot().builder().getChildNode("tmp").hasChildNode("child1")); + } + + @Test + public void setChildNodeInRootStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + builder.setChildNode("apps"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue("Node apps must still exist", store.getRoot().hasChildNode("apps")); + assertThat("Node apps must not have any properties", store.getRoot().getChildNode("apps").getPropertyCount(), equalTo(0l)); + } + + + @Test + public void setChildNodeInMountStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + builder.getChildNode("tmp").setChildNode("child1"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertTrue("Node child1 must still exist", store.getRoot().getChildNode("tmp").hasChildNode("child1")); + assertThat("Node child1 must not have any properties", store.getRoot().getChildNode("tmp").getChildNode("child1").getPropertyCount(), equalTo(0l)); + } + + + @Test + public void builderBasedOnRootStoreChildNode() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + NodeBuilder appsBuilder = builder.getChildNode("apps"); + + appsBuilder.removeProperty("prop"); + appsBuilder.setChildNode("child1"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("Node apps must have no properties (multiplexed store)", store.getRoot().getChildNode("apps").hasProperty("prop")); + assertFalse("Node apps must have no properties (root store)", globalStore.getRoot().getChildNode("apps").hasProperty("prop")); + + assertTrue("Node /apps/child1 must exist (multiplexed store)", store.getRoot().getChildNode("apps").hasChildNode("child1")); + assertTrue("Node /apps/child1 must exist (root store)", globalStore.getRoot().getChildNode("apps").hasChildNode("child1")); + } + + @Test + public void builderBasedOnMountStoreChildNode() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + NodeBuilder tmpBuilder = builder.getChildNode("tmp"); + + tmpBuilder.removeProperty("prop1"); + tmpBuilder.setChildNode("child3"); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("Node tmp must have no properties (multiplexed store)", store.getRoot().getChildNode("tmp").hasProperty("prop1")); + assertFalse("Node tmp must have no properties (mounted store)", mountedStore.getRoot().getChildNode("tmp").hasProperty("prop1")); + + assertTrue("Node /tmp/build3 must exist (multiplexed store)", store.getRoot().getChildNode("tmp").hasChildNode("child3")); + assertTrue("Node /tmp/child3 must exist (mounted store)", mountedStore.getRoot().getChildNode("tmp").hasChildNode("child3")); + + } + + @Test + public void freshBuilderForGlobalStore() { + NodeBuilder builder = store.getRoot().builder(); + + assertFalse("builder.isNew", builder.isNew()); + assertFalse("builder.isModified", builder.isModified()); + assertFalse("builder.isReplaced", builder.isReplaced()); + } + + @Test + public void freshBuilderForMountedStore() { + NodeBuilder builder = store.getRoot().getChildNode("tmp").builder(); + + assertFalse("builder.isNew", builder.isNew()); + assertFalse("builder.isModified", builder.isModified()); + assertFalse("builder.isReplaced", builder.isReplaced()); + } + + @Test + public void newBuilderForGlobalStore() { + NodeBuilder builder = store.getRoot().builder(); + + builder = builder.child("newChild"); + + assertTrue("builder.isNew", builder.isNew()); + assertFalse("builder.isModified", builder.isModified()); + assertFalse("builder.isReplaced", builder.isReplaced()); + } + + @Test + public void newBuilderForMountedStore() { + NodeBuilder builder = store.getRoot().getChildNode("tmp").builder(); + + builder = builder.child("newChild"); + + assertTrue("builder.isNew", builder.isNew()); + assertFalse("builder.isModified", builder.isModified()); + assertFalse("builder.isReplaced", builder.isReplaced()); + } + + @Test + public void replacedBuilderForGlobalStore() { + NodeBuilder builder = store.getRoot().builder(); + + NodeBuilder libsBuilder = builder.setChildNode("libs"); + + assertTrue("libsBuilder.isReplaced", libsBuilder.isReplaced()); + assertTrue("builder.getChild('libs').isReplaced", builder.getChildNode("libs").isReplaced()); + } + + @Test + public void replacedBuilderForMountedStore() { + NodeBuilder builder = store.getRoot().getChildNode("tmp").builder(); + + builder = builder.setChildNode("child1"); + + assertTrue("builder.isReplaced", builder.isReplaced()); + } + + @Test + public void readChildNodeBasedOnPathFragment() throws Exception { + NodeBuilder builder = globalStore.getRoot().builder(); + + builder.child("multi-holder"); + + globalStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + builder = mountedStore.getRoot().builder(); + + builder.child("multi-holder").child("oak:mount-temp").setProperty("prop", "val"); + + mountedStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + NodeState holderNode = store.getRoot().getChildNode("multi-holder"); + assertTrue("/multi-holder/oak:mount-temp should be visible from the multiplexed store", + holderNode.hasChildNode("oak:mount-temp")); + + assertChildNodeNames(holderNode, "oak:mount-temp"); + + assertThat("/multi-holder/ must have 1 child entry", holderNode.getChildNodeCount(10), equalTo(1l)); + } + + @Test + public void moveNodeInSameStore() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + NodeBuilder src = builder.child("src"); + NodeBuilder dst = builder.child("dst"); + + boolean result = src.moveTo(dst, "src"); + assertTrue("move result should be success", result); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("/src must no longer exist", store.getRoot().hasChildNode("src")); + assertTrue("/dst/src must exist (multiplexed store)", store.getRoot().getChildNode("dst").hasChildNode("src")); + } + + @Test + public void moveNodeBetweenStores() throws Exception { + NodeBuilder builder = store.getRoot().builder(); + + NodeBuilder src = builder.child("src"); + NodeBuilder dst = builder.child("tmp"); + + boolean result = src.moveTo(dst, "src"); + assertTrue("move result should be success", result); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("/src must no longer exist", store.getRoot().hasChildNode("src")); + assertTrue("/tmp/src must exist (multiplexed store)", store.getRoot().getChildNode("tmp").hasChildNode("src")); + + } + + @Test + public void resetOnGlobalStore() { + NodeBuilder builder = store.getRoot().builder(); + builder.child("newChild"); + + store.reset(builder); + + assertFalse("Newly added child should no longer be visible after reset", builder.hasChildNode("newChild")); + } + + @Test + public void resetOnMountedStore() { + NodeBuilder rootBuilder = store.getRoot().builder(); + NodeBuilder builder = rootBuilder.getChildNode("tmp"); + builder.child("newChild"); + + store.reset(rootBuilder); + + assertFalse("Newly added child should no longer be visible after reset", builder.getChildNode("tmp").hasChildNode("newChild")); + } + + @Test + public void oldNodeStateDoesNotRefreshOnGlobalStore() throws Exception { + NodeState old = store.getRoot(); + + NodeBuilder builder = store.getRoot().builder(); + builder.child("newNode"); + + assertFalse("old NodeState should not see newly added child node before merge ", old.hasChildNode("newNode")); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("old NodeState should not see newly added child node after merge ", old.hasChildNode("newNode")); + } + + @Test + public void oldNodeStateDoesNotRefreshOnMountedStore() throws Exception { + NodeState old = store.getRoot(); + + NodeBuilder builder = store.getRoot().builder(); + + builder.getChildNode("tmp").child("newNode"); + + assertFalse("old NodeState should not see newly added child node before merge ", old.getChildNode("tmp").hasChildNode("newNode")); + + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + assertFalse("old NodeState should not see newly added child node after merge ", old.getChildNode("tmp").hasChildNode("newNode")); + } + + // this test ensures that when going from State -> Builder -> State -> Builder the state is properly maintained + @Test + public void nestedBuilderFromState() throws Exception { + NodeState rootState = store.getRoot(); + NodeBuilder rootBuilder = rootState.builder(); + rootBuilder.child("newNode"); + + NodeState baseState = rootBuilder.getNodeState(); + NodeBuilder builderFromState = baseState.builder(); + + assertTrue(builderFromState.hasChildNode("newNode")); + } + + @Test + public void nestedBuilderWithNewPropertyFromState() throws Exception { + NodeState rootState = store.getRoot(); + NodeBuilder rootBuilder = rootState.builder(); + rootBuilder.setProperty("newProperty", true, Type.BOOLEAN); + + NodeState baseState = rootBuilder.getNodeState(); + assertTrue(baseState.getBoolean("newProperty")); + + NodeBuilder builderFromState = baseState.builder(); + assertTrue(builderFromState.getBoolean("newProperty")); + assertTrue(builderFromState.getNodeState().getBoolean("newProperty")); + //assertTrue(builderFromState.getBaseState().getBoolean("newProperty")); // FIXME + } + + @Test + public void readOnlyMountRejectsChanges() throws Exception { + NodeState oldState = store.getRoot(); + try { + NodeBuilder builder = store.getRoot().builder(); + builder.getChildNode("readOnly").child("newChild"); + builder.getChildNode("libs").child("otherChild"); + store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + } catch (CommitFailedException e) { + // expected + // validate that changes were not applied + CountingDiff countingDiff = new CountingDiff(); + store.getRoot().compareAgainstBaseState(oldState, countingDiff); + assertThat("Unexpected number of changes", countingDiff.getNumChanges(), equalTo(0)); + } catch ( Exception e ) { + throw e; + } + } + + @Test + public void builderBasedOnCheckpoint() throws CommitFailedException { + String checkpoint = store.checkpoint(TimeUnit.DAYS.toMillis(1)); + + assertNotNull("checkpoint reference is null", checkpoint); + + // create a new child /new in the root store + NodeBuilder globalBuilder = globalStore.getRoot().builder(); + globalBuilder.child("new"); + globalStore.merge(globalBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // create a new child /tmp/new in the mounted store + NodeBuilder mountedBuilder = mountedStore.getRoot().builder(); + mountedBuilder.getChildNode("tmp").child("new"); + mountedStore.merge(mountedBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // create a new child /libs/mount/new in the deeply mounted store + NodeBuilder deepMountBuilder = deepMountedStore.getRoot().builder(); + deepMountBuilder.getChildNode("libs").getChildNode("mount").child("new"); + deepMountedStore.merge(deepMountBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + NodeBuilder rootCheckpointBuilder = store.retrieve(checkpoint).builder(); + assertFalse("store incorrectly exposes child at /new", rootCheckpointBuilder.hasChildNode("new")); + assertFalse("store incorrectly exposes child at /tmp/new", rootCheckpointBuilder.getChildNode("tmp").hasChildNode("new")); + assertFalse("store incorrectly exposes child at /libs/mount/new", rootCheckpointBuilder.getChildNode("libs").getChildNode("mount").hasChildNode("new")); + } + + @Test + public void duplicatedChildren() throws CommitFailedException { + // create a new child /new in the root store + NodeBuilder globalBuilder = globalStore.getRoot().builder(); + globalBuilder.child("new").setProperty("store", "global", Type.STRING); + globalStore.merge(globalBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // create a new child /tmp/new in the mounted store + NodeBuilder mountedBuilder = mountedStore.getRoot().builder(); + mountedBuilder.child("new").setProperty("store", "mounted", Type.STRING); + mountedStore.merge(mountedBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + // create a new child /libs/mount/new in the deeply mounted store + NodeBuilder deepMountBuilder = deepMountedStore.getRoot().builder(); + deepMountBuilder.child("new").setProperty("store", "deepMounted", Type.STRING); + deepMountedStore.merge(deepMountBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY); + + List<ChildNodeEntry> children = newArrayList(filter(store.getRoot().getChildNodeEntries(), compose(Predicates.equalTo("new"), GET_NAME))); + assertEquals(1, children.size()); + assertEquals("global", children.get(0).getNodeState().getString("store")); + + NodeBuilder rootBuilder = store.getRoot().builder(); + List<String> childNames = newArrayList(filter(rootBuilder.getChildNodeNames(), Predicates.equalTo("new"))); + assertEquals(1, childNames.size()); + assertEquals("global", rootBuilder.getChildNode("new").getString("store")); + } + + private static enum NodeStoreKind { + MEMORY { + @Override + public NodeStoreRegistration create(String name) { + return new NodeStoreRegistration() { + + private MemoryNodeStore instance; + + @Override + public NodeStore get() { + + if (instance != null) { + throw new IllegalStateException("instance already created"); + } + + instance = new MemoryNodeStore(); + + return instance; + } + + @Override + public void close() throws Exception { + // does nothing + + } + }; + } + + public boolean supportsBlobCreation() { + return false; + } + }, SEGMENT { + @Override + public NodeStoreRegistration create(final String name) { + return new NodeStoreRegistration() { + + private SegmentNodeStore instance; + private FileStore store; + private File storePath; + private String blobStorePath; + + @Override + public NodeStore get() throws Exception { + + if (instance != null) { + throw new IllegalStateException("instance already created"); + } + + // TODO - don't use Unix directory separators + String directoryName = name != null ? "segment-" + name : "segment"; + storePath = new File("target/classes/" + directoryName); + + String blobStoreDirectoryName = name != null ? "blob-" + name : "blob"; + blobStorePath = "target/classes/" + blobStoreDirectoryName; + + BlobStore blobStore = new FileBlobStore(blobStorePath); + + store = FileStore.builder(storePath).withBlobStore(blobStore).build(); + instance = SegmentNodeStore.builder(store).build(); + + return instance; + } + + @Override + public void close() throws Exception { + store.close(); + + FileUtils.deleteQuietly(storePath); + FileUtils.deleteQuietly(new File(blobStorePath)); + } + }; + } + }, DOCUMENT_H2 { + + // TODO - copied from DocumentRdbFixture + + private DataSource ds; + + @Override + public NodeStoreRegistration create(final String name) { + + return new NodeStoreRegistration() { + + private DocumentNodeStore instance; + + @Override + public NodeStore get() throws Exception { + RDBOptions options = new RDBOptions().dropTablesOnClose(true); + String jdbcUrl = "jdbc:h2:file:./target/classes/document"; + if ( name != null ) { + jdbcUrl += "-" + name; + } + ds = RDBDataSourceFactory.forJdbcUrl(jdbcUrl, "sa", ""); + + instance = new DocumentMK.Builder().setRDBConnection(ds, options).getNodeStore(); + + return instance; + + } + + @Override + public void close() throws Exception { + instance.dispose(); + if ( ds instanceof Closeable ) { + ((Closeable) ds).close(); + } + } + + }; + + } + }; + + public abstract NodeStoreRegistration create(@Nullable String name); + + public boolean supportsBlobCreation() { + return true; + } + } + + private interface NodeStoreRegistration { + NodeStore get() throws Exception; + + void close() throws Exception; + } + + private NodeStore register(NodeStoreRegistration reg) throws Exception { + registrations.add(reg); + + return reg.get(); + } + + // ensure blobs don't get inlined by the SegmentBlobStore + private ByteArrayInputStream createLargeBlob() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + for ( int i = 0 ; i <= Segment.MEDIUM_LIMIT; i++) { + out.write('a'); + } + + return new ByteArrayInputStream(out.toByteArray()); + } + + private void assertChildNodeNames(NodeBuilder builder, String... names) { + Iterable<String> childNodeNames = builder.getChildNodeNames(); + + assertNotNull("childNodeNames must not be empty", childNodeNames); + assertThat("Incorrect number of elements", Iterables.size(childNodeNames), equalTo(names.length)); + assertThat("Mismatched elements", childNodeNames, hasItems(names)); + } + + private void assertChildNodeNames(NodeState state, String... names) { + Iterable<String> childNodeNames = state.getChildNodeNames(); + + assertNotNull("childNodeNames must not be empty", childNodeNames); + assertThat("Incorrect number of elements", Iterables.size(childNodeNames), equalTo(names.length)); + assertThat("Mismatched elements", childNodeNames, hasItems(names)); + } +} \ No newline at end of file Added: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingSegmentFixture.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingSegmentFixture.java?rev=1767652&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingSegmentFixture.java (added) +++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/plugins/multiplex/MultiplexingSegmentFixture.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 java.io.IOException; + +import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; +import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStore; +import org.apache.jackrabbit.oak.plugins.segment.memory.MemoryStore; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.state.NodeStore; + +public class MultiplexingSegmentFixture extends NodeStoreFixture { + + private static final String MOUNT_PATH = "/tmp"; + + @Override + public NodeStore createNodeStore() { + try { + MountInfoProvider mip = new SimpleMountInfoProvider.Builder() + .readOnlyMount("temp", MOUNT_PATH) + .build(); + + NodeStore globalStore = SegmentNodeStore.builder(new MemoryStore()).build(); + NodeStore tempMount = SegmentNodeStore.builder(new MemoryStore()).build(); + + return new MultiplexingNodeStore.Builder(mip, globalStore).addMount("temp", tempMount).build(); + } catch (IOException e) { + throw new RuntimeException(); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + " with a mount under " + MOUNT_PATH; + } + +} \ No newline at end of file Modified: jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java?rev=1767652&r1=1767651&r2=1767652&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java (original) +++ jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/spi/state/NodeStoreTest.java Wed Nov 2 12:16:32 2016 @@ -457,7 +457,8 @@ public class NodeStoreTest extends OakBa public void moveToDescendant() { NodeBuilder test = store.getRoot().builder().getChildNode("test"); NodeBuilder x = test.getChildNode("x"); - if (fixture == NodeStoreFixtures.SEGMENT_TAR || fixture == NodeStoreFixtures.SEGMENT_MK || fixture == NodeStoreFixtures.MEMORY_NS) { + if (fixture == NodeStoreFixtures.SEGMENT_TAR || fixture == NodeStoreFixtures.SEGMENT_MK || fixture == NodeStoreFixtures.MEMORY_NS + || fixture == NodeStoreFixtures.MULTIPLEXED_SEGMENT || fixture == NodeStoreFixtures.MULTIPLEXED_MEM) { assertTrue(x.moveTo(x, "xx")); assertFalse(x.exists()); assertFalse(test.hasChildNode("x"));
