| /* |
| * 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.composite; |
| |
| 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.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; |
| import static org.apache.jackrabbit.oak.plugins.index.IndexUtils.createIndexDefinition; |
| 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.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.sql.DataSource; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| 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.DocumentNodeStoreBuilder; |
| import org.apache.jackrabbit.oak.plugins.document.LeaseCheckMode; |
| import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory; |
| import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions; |
| import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; |
| import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; |
| import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; |
| import org.apache.jackrabbit.oak.segment.SegmentNodeStore; |
| import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders; |
| import org.apache.jackrabbit.oak.segment.SegmentTestConstants; |
| import org.apache.jackrabbit.oak.segment.file.FileStore; |
| import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder; |
| 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.EditorHook; |
| import org.apache.jackrabbit.oak.spi.commit.EmptyHook; |
| import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; |
| import org.apache.jackrabbit.oak.spi.mount.Mounts; |
| 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.jetbrains.annotations.Nullable; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class CompositeNodeStoreTest { |
| |
| private final NodeStoreKind root; |
| private final NodeStoreKind mounts; |
| |
| private final List<NodeStoreRegistration> registrations = newArrayList(); |
| |
| private CompositeNodeStore store; |
| private NodeStore globalStore; |
| private NodeStore mountedStore; |
| private NodeStore deepMountedStore; |
| private NodeStore readOnlyStore; |
| private MountInfoProvider mip; |
| |
| @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 CompositeNodeStoreTest(NodeStoreKind root, NodeStoreKind mounts) { |
| this.root = root; |
| this.mounts = mounts; |
| } |
| |
| @Before |
| public void initStore() throws Exception { |
| mip = Mounts.newBuilder() |
| .readOnlyMount("temp", "/tmp") |
| .readOnlyMount("deep", "/libs/mount") |
| .readOnlyMount("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 CompositeNodeStore(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 childNodeEntryForMountIsComposite() { |
| 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); |
| |
| assertFalse("store incorrectly exposes child at /new", store.retrieve(checkpoint).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 composite 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 composite 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 composite store", store.getRoot().hasChildNode("newNode")); |
| assertTrue("Node must be added to owning (root) store", globalStore.getRoot().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 composite store", store.getRoot().hasChildNode("apps")); |
| assertFalse("Node must be removed from the owning (root) store", globalStore.getRoot().hasChildNode("apps")); |
| } |
| |
| @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 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 (composite 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 (composite store)", store.getRoot().getChildNode("apps").hasChildNode("child1")); |
| assertTrue("Node /apps/child1 must exist (root store)", globalStore.getRoot().getChildNode("apps").hasChildNode("child1")); |
| } |
| |
| @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 composite 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 (composite store)", store.getRoot().getChildNode("dst").hasChildNode("src")); |
| } |
| |
| @Test |
| @Ignore("Test ignored, since only the default store is writeable") |
| 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 (composite 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 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")); |
| } |
| |
| // 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 (expected = UnsupportedOperationException.class) |
| public void readOnlyMountRejectsChanges() throws Exception { |
| NodeBuilder builder = store.getRoot().builder(); |
| builder.getChildNode("readOnly").child("newChild"); |
| } |
| |
| @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); |
| |
| NodeBuilder rootCheckpointBuilder = store.retrieve(checkpoint).builder(); |
| assertFalse("store incorrectly exposes child at /new", rootCheckpointBuilder.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")); |
| } |
| |
| @Test |
| public void propertyIndex() throws Exception{ |
| NodeBuilder globalBuilder = globalStore.getRoot().builder(); |
| createIndexDefinition(globalBuilder.child(INDEX_DEFINITIONS_NAME), "foo", |
| true, false, ImmutableSet.of("foo"), null); |
| EditorHook hook = new EditorHook( |
| new IndexUpdateProvider(new PropertyIndexEditorProvider().with(mip))); |
| |
| globalStore.merge(globalBuilder, hook, CommitInfo.EMPTY); |
| |
| NodeBuilder builder = store.getRoot().builder(); |
| builder.child("content").setProperty("foo", "bar"); |
| store.merge(builder, hook, CommitInfo.EMPTY); |
| |
| builder = store.getRoot().builder(); |
| builder.child("content").removeProperty("foo"); |
| store.merge(builder, hook, CommitInfo.EMPTY); |
| } |
| |
| @Test |
| public void bigPropertyIndexUpdate() throws Exception{ |
| NodeBuilder globalBuilder = globalStore.getRoot().builder(); |
| createIndexDefinition(globalBuilder.child(INDEX_DEFINITIONS_NAME), "foo", |
| true, false, ImmutableSet.of("foo"), null); |
| EditorHook hook = new EditorHook( |
| new IndexUpdateProvider(new PropertyIndexEditorProvider().with(mip))); |
| |
| globalStore.merge(globalBuilder, hook, CommitInfo.EMPTY); |
| |
| int updateLimit = new DocumentNodeStoreBuilder().getUpdateLimit(); |
| NodeBuilder builder = store.getRoot().builder(); |
| for (int i = 0; i < updateLimit; i++) { |
| builder.child("content").child("node-" + i).setProperty("foo", "bar"); |
| } |
| store.merge(builder, hook, CommitInfo.EMPTY); |
| |
| builder = store.getRoot().builder(); |
| builder.child("content").remove(); |
| store.merge(builder, hook, CommitInfo.EMPTY); |
| } |
| |
| 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 = FileStoreBuilder.fileStoreBuilder(storePath).withBlobStore(blobStore).build(); |
| instance = SegmentNodeStoreBuilders.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).build(); |
| instance.setMaxBackOffMillis(0); |
| |
| 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 <= SegmentTestConstants.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)); |
| } |
| } |