Author: thomasm
Date: Wed Mar 20 13:59:39 2019
New Revision: 1855907
URL: http://svn.apache.org/viewvc?rev=1855907&view=rev
Log:
GRANITE-24239 Composite node store: tests with queries
Added:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTest.java
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTestBase.java
Modified:
jackrabbit/oak/trunk/oak-lucene/pom.xml
Modified: jackrabbit/oak/trunk/oak-lucene/pom.xml
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/pom.xml?rev=1855907&r1=1855906&r2=1855907&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-lucene/pom.xml Wed Mar 20 13:59:39 2019
@@ -337,6 +337,12 @@
</dependency>
<dependency>
<groupId>org.apache.jackrabbit</groupId>
+ <artifactId>oak-store-composite</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
<artifactId>oak-jcr</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
Added:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTest.java?rev=1855907&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTest.java
(added)
+++
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTest.java
Wed Mar 20 13:59:39 2019
@@ -0,0 +1,269 @@
+/*
+ * 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 org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.api.Type.NAME;
+import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static
org.apache.jackrabbit.oak.plugins.index.IndexUtils.createIndexDefinition;
+import static
org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import javax.jcr.query.Query;
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider;
+import
org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import
org.apache.jackrabbit.oak.plugins.index.reference.NodeReferenceConstants;
+import
org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexFormatVersion;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import com.google.common.collect.ImmutableSet;
+
+import jersey.repackaged.com.google.common.collect.Lists;
+
+/**
+ * Tests indexing and queries when using the composite node store.
+ */
+@RunWith(Parameterized.class)
+public class CompositeNodeStoreQueryTest extends
CompositeNodeStoreQueryTestBase {
+
+ public CompositeNodeStoreQueryTest(NodeStoreKind root, NodeStoreKind
mounts) {
+ super(root, mounts);
+ }
+
+ @Test
+ public void propertyIndex() throws Exception {
+ // create an index in both the read-only and the read-write store
+ NodeBuilder b;
+ NodeBuilder readOnlyBuilder = readOnlyStore.getRoot().builder();
+ b =
createIndexDefinition(readOnlyBuilder.child(INDEX_DEFINITIONS_NAME), "foo",
+ true, false, ImmutableSet.of("foo"), null);
+ b.setProperty("excludedPaths", "/jcr:system");
+ NodeBuilder globalBuilder = globalStore.getRoot().builder();
+ b = createIndexDefinition(globalBuilder.child(INDEX_DEFINITIONS_NAME),
"foo",
+ true, false, ImmutableSet.of("foo"), null);
+ b.setProperty("excludedPaths", "/jcr:system");
+ EditorHook hook = new EditorHook(
+ new IndexUpdateProvider(new
PropertyIndexEditorProvider().with(mip)));
+ readOnlyStore.merge(readOnlyBuilder, hook, CommitInfo.EMPTY);
+ globalStore.merge(globalBuilder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ // add nodes in the read-only area
+ NodeBuilder builder;
+ builder = readOnlyStore.getRoot().builder();
+ for (int i = 0; i < 3; i++) {
+ builder.child("readOnly").child("node-" + i).setProperty("foo",
"bar");
+ }
+
+ readOnlyStore.merge(builder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ // run a query
+ // need to login again to see changes in the read-only area
+ session = createRepository(store).login(null, null);
+ root = session.getLatestRoot();
+ qe = root.getQueryEngine();
+ assertThat(
+ executeQuery("explain /jcr:root//*[@foo = 'bar']", "xpath",
false).toString(),
+ containsString("/* property foo = bar"));
+ assertEquals("[/readOnly/node-0, /readOnly/node-1, /readOnly/node-2]",
+ executeQuery("/jcr:root//*[@foo = 'bar']",
"xpath").toString());
+
+ // add nodes in the read-write area
+ builder = store.getRoot().builder();
+ for (int i = 0; i < 3; i++) {
+ builder.child("content").child("node-" + i).setProperty("foo",
"bar");
+ }
+ store.merge(builder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ // run a query
+ assertEquals("[/content/node-0, /content/node-1, /content/node-2, " +
+ "/readOnly/node-0, /readOnly/node-1, /readOnly/node-2]",
+ executeQuery("/jcr:root//*[@foo = 'bar']",
"xpath").toString());
+ assertThat(executeQuery("explain /jcr:root/content//*[@foo = 'bar']",
"xpath", false).toString(),
+ containsString("/* property foo = bar"));
+
+ // remove all data
+ builder = store.getRoot().builder();
+ builder.child("content").remove();
+ store.merge(builder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ // run a query
+ assertEquals("[]",
+ executeQuery("/jcr:root/content//*[@foo = 'bar']",
"xpath").toString());
+ }
+
+ @Test
+ @Ignore("OAK-7995")
+ public void referenceIndex() throws Exception {
+ // create an index in both the read-only and the read-write store
+ NodeBuilder b;
+ NodeBuilder readOnlyBuilder = readOnlyStore.getRoot().builder();
+ b = readOnlyBuilder.child(INDEX_DEFINITIONS_NAME).child("reference");
+ b.setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME);
+ b.setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE);
+
+ NodeBuilder globalBuilder = globalStore.getRoot().builder();
+ b = globalBuilder.child(INDEX_DEFINITIONS_NAME).child("reference");
+ b.setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME);
+ b.setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE);
+
+ EditorHook hook = new EditorHook(
+ new IndexUpdateProvider(new
ReferenceEditorProvider().with(mip)));
+ readOnlyStore.merge(readOnlyBuilder, hook, CommitInfo.EMPTY);
+ globalStore.merge(globalBuilder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ NodeBuilder builder;
+ builder = readOnlyStore.getRoot().builder();
+ for (int i = 0; i < 3; i++) {
+ builder.child("readOnly").child("node-" +
i).setProperty(createProperty("foo", "u1", Type.REFERENCE));
+ }
+ readOnlyStore.merge(builder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ builder = store.getRoot().builder();
+ builder.child("a").child("x").setProperty(createProperty("foo", "u1",
Type.REFERENCE));
+ store.merge(builder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ // run a query
+ // need to login again to see changes in the read-only area
+ session = createRepository(store).login(null, null);
+ root = session.getLatestRoot();
+ qe = root.getQueryEngine();
+ assertThat(executeQuery("explain select * from [nt:base] " +
+ "where property([*], 'Reference') = cast('u1' as reference)",
Query.JCR_SQL2, false).toString(),
+ containsString("/* reference "));
+ // expected: also /readOnly/node-0 .. 2
+ assertEquals("[/a/x, /readOnly/node-0, /readOnly/node-1,
/readOnly/node-2]",
+ executeQuery("select [jcr:path] from [nt:base] " +
+ "where property([*], 'Reference') = cast('u1' as
reference)", Query.JCR_SQL2).toString());
+
+ }
+
+ public void createLuceneIndex(NodeBuilder b) {
+ b = b.child(INDEX_DEFINITIONS_NAME).child("lucene");
+ b.setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME);
+ b.setProperty(TYPE_PROPERTY_NAME, LuceneIndexConstants.TYPE_LUCENE);
+ b.setProperty(FulltextIndexConstants.COMPAT_MODE,
IndexFormatVersion.V2.getVersion());
+ b.setProperty(IndexConstants.ASYNC_PROPERTY_NAME,
+ Lists.newArrayList("async", "nrt"), Type.STRINGS);
+ b.setProperty("excludedPaths", "/jcr:system");
+ NodeBuilder foo = b.child(FulltextIndexConstants.INDEX_RULES)
+ .child("nt:base")
+ .child(FulltextIndexConstants.PROP_NODE)
+ .child("asyncFoo");
+ foo.setProperty(FulltextIndexConstants.PROP_NAME, "asyncFoo");
+ foo.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true);
+ }
+
+ @Test
+ public void luceneIndex() throws Exception {
+ // create an index in both the read-only and the read-write store
+ NodeBuilder readOnlyBuilder = readOnlyStore.getRoot().builder();
+ // add nodes in the read-only area
+ for (int i = 0; i < 3; i++) {
+ NodeBuilder b = readOnlyBuilder.child("readOnly").child("node-" +
i);
+ b.setProperty("asyncFoo", "bar");
+ b.setProperty("jcr:primaryType", "nt:base", Type.NAME);
+ }
+ createLuceneIndex(readOnlyBuilder);
+
+ NodeBuilder globalBuilder = globalStore.getRoot().builder();
+ createLuceneIndex(globalBuilder);
+
+ LuceneIndexEditorProvider iep = new
LuceneIndexEditorProvider(indexCopier, indexTracker, null, null, mip);
+ EditorHook hook = new EditorHook(
+ new IndexUpdateProvider(iep, "async", false));
+ readOnlyStore.merge(readOnlyBuilder, hook, CommitInfo.EMPTY);
+ globalStore.merge(globalBuilder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ readOnlyStore.merge(readOnlyBuilder, hook, CommitInfo.EMPTY);
+ globalStore.merge(globalBuilder, hook, CommitInfo.EMPTY);
+ root.commit();
+ indexTracker.update(readOnlyStore.getRoot());
+ indexTracker.update(globalStore.getRoot());
+
+ // add nodes in the read-only area
+
+ // run a query
+ // need to login again to see changes in the read-only area
+ session = createRepository(store).login(null, null);
+ root = session.getLatestRoot();
+ qe = root.getQueryEngine();
+
+ indexTracker.update(store.getRoot());
+
+ assertThat(
+ executeQuery("explain /jcr:root//*[@asyncFoo = 'bar']",
"xpath", false).toString(),
+ containsString("/* lucene:lucene(/oak:index/lucene)
asyncFoo:bar"));
+ assertEquals("[/readOnly/node-0, /readOnly/node-1, /readOnly/node-2]",
+ executeQuery("/jcr:root//*[@asyncFoo = 'bar']",
"xpath").toString());
+
+ // add nodes in the read-write area
+ NodeBuilder builder;
+ builder = store.getRoot().builder();
+ for (int i = 0; i < 3; i++) {
+ builder.child("content").child("node-" +
i).setProperty("asyncFoo", "bar");
+ }
+ store.merge(builder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ // run a query
+ assertThat(
+ executeQuery("explain /jcr:root//*[@asyncFoo = 'bar']",
"xpath", false).toString(),
+ containsString("/* lucene:lucene(/oak:index/lucene)
asyncFoo:bar"));
+ assertEquals("[/content/node-0, /content/node-1, /content/node-2, " +
+ "/readOnly/node-0, /readOnly/node-1, /readOnly/node-2]",
+ executeQuery("/jcr:root//*[@asyncFoo = 'bar']",
"xpath").toString());
+
+ // remove all data
+ builder = store.getRoot().builder();
+ builder.child("content").remove();
+ store.merge(builder, hook, CommitInfo.EMPTY);
+ root.commit();
+
+ // run a query
+ assertEquals("[]",
+ executeQuery("/jcr:root/content//*[@asyncFoo = 'bar']",
"xpath").toString());
+
+ }
+
+}
\ No newline at end of file
Added:
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTestBase.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTestBase.java?rev=1855907&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTestBase.java
(added)
+++
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreQueryTestBase.java
Wed Mar 20 13:59:39 2019
@@ -0,0 +1,501 @@
+/*
+ * 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.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.oak.api.QueryEngine.NO_BINDINGS;
+import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.oak.InitialContent;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.QueryEngine;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Root;
+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.index.counter.NodeCounterEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexCopier;
+import org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexProvider;
+import
org.apache.jackrabbit.oak.plugins.index.lucene.reader.DefaultIndexReaderFactory;
+import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider;
+import
org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider;
+import
org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider;
+import
org.apache.jackrabbit.oak.plugins.index.reference.ReferenceIndexProvider;
+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.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.EmptyHook;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.mount.Mounts;
+import org.apache.jackrabbit.oak.spi.query.QueryConstants;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.jetbrains.annotations.Nullable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Base class for testing indexing and queries when using the composite node
+ * store.
+ */
+@RunWith(Parameterized.class)
+public class CompositeNodeStoreQueryTestBase {
+
+ private final NodeStoreKind nodeStoreRoot;
+ private final NodeStoreKind mounts;
+
+ private final List<NodeStoreRegistration> registrations = newArrayList();
+
+ private NodeStore mountedStore;
+ private NodeStore deepMountedStore;
+
+ protected NodeStore readOnlyStore;
+
+ // the composite store (containing read-write and read-only stores)
+ protected CompositeNodeStore store;
+
+ // the global store (read-write)
+ protected NodeStore globalStore;
+
+ protected NodeStore emptyStore;
+
+ protected MountInfoProvider mip;
+
+ protected QueryEngine qe;
+ protected ContentSession session;
+ protected Root root;
+
+ protected ExecutorService executorService =
Executors.newFixedThreadPool(2);
+
+ protected IndexTracker indexTracker;
+ protected IndexCopier indexCopier;
+ protected Oak oak;
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder(new
File("target"));
+
+ @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 CompositeNodeStoreQueryTestBase(NodeStoreKind root, NodeStoreKind
mounts) {
+ this.nodeStoreRoot = root;
+ this.mounts = mounts;
+ }
+
+ @Before
+ public void initStore() throws Exception {
+ globalStore = register(nodeStoreRoot.create(null));
+ mountedStore = register(mounts.create("temp"));
+ deepMountedStore = register(mounts.create("deep"));
+ readOnlyStore = register(mounts.create("readOnly"));
+ 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();
+ new InitialContent().initialize(builder);
+ builder.child("readOnly");
+
+ readOnlyStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+ initMounts();
+ }
+
+ void initMounts() throws Exception {
+ mip = Mounts.newBuilder()
+ .readOnlyMount("temp", "/tmp")
+ .readOnlyMount("deep", "/libs/mount")
+ .readOnlyMount("empty", "/nowhere")
+ .readOnlyMount("readOnly", "/readOnly")
+ .build();
+
+ // 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);
+
+ session = createRepository(store).login(null, null);
+ root = session.getLatestRoot();
+ qe = root.getQueryEngine();
+ }
+
+ protected ContentRepository createRepository(NodeStore store) {
+ return getOakRepo(store).createContentRepository();
+ }
+
+ Oak getOakRepo(NodeStore store) {
+ if (oak != null) {
+ return oak;
+ }
+ try {
+ indexCopier = new IndexCopier(executorService,
temporaryFolder.getRoot());
+ } catch (IOException e) {
+ throw new RuntimeException();
+ }
+ DefaultIndexReaderFactory indexReaderFactory = new
DefaultIndexReaderFactory(mip, indexCopier);
+ indexTracker = new IndexTracker(indexReaderFactory);
+
+ LuceneIndexProvider luceneIndexProvider =
+ new LuceneIndexProvider(indexTracker);
+ LuceneIndexEditorProvider luceneIndexEditor =
+ new LuceneIndexEditorProvider(indexCopier, indexTracker, null,
null, mip);
+ oak = new Oak(store).with(new InitialContent())
+ .with(new OpenSecurityProvider())
+ .with(new PropertyIndexEditorProvider().with(mip))
+ .with(new NodeCounterEditorProvider().with(mip))
+ .with(new PropertyIndexProvider().with(mip))
+ .with(luceneIndexEditor)
+ .with((QueryIndexProvider) luceneIndexProvider)
+ .with((Observer) luceneIndexProvider)
+ .with(new NodeTypeIndexProvider().with(mip))
+ .with(new ReferenceEditorProvider().with(mip))
+ .with(new ReferenceIndexProvider().with(mip));
+ return oak;
+ }
+
+ protected List<String> executeQuery(String query, String language) {
+ boolean pathsOnly = false;
+ if (language.equals("xpath")) {
+ pathsOnly = true;
+ }
+ return executeQuery(query, language, pathsOnly);
+ }
+
+ protected List<String> executeQuery(String query, String language, boolean
pathsOnly) {
+ return executeQuery(query, language, pathsOnly, false);
+ }
+
+ protected List<String> executeQuery(String query, String language, boolean
pathsOnly, boolean skipSort) {
+ long time = System.currentTimeMillis();
+ List<String> lines = new ArrayList<String>();
+ try {
+ Result result = executeQuery(query, language, NO_BINDINGS);
+ for (ResultRow row : result.getRows()) {
+ String r = readRow(row, pathsOnly);
+ if (query.startsWith("explain ")) {
+ r = formatPlan(r);
+ }
+ lines.add(r);
+ }
+ if (!query.contains("order by") && !skipSort) {
+ Collections.sort(lines);
+ }
+ } catch (ParseException e) {
+ lines.add(e.toString());
+ } catch (IllegalArgumentException e) {
+ lines.add(e.toString());
+ }
+ time = System.currentTimeMillis() - time;
+ if (time > 5 * 60 * 1000 && !isDebugModeEnabled()) {
+ // more than 5 minutes
+ fail("Query took too long: " + query + " took " + time + " ms");
+ }
+ return lines;
+ }
+
+ /**
+ * Check whether the test is running in debug mode.
+ *
+ * @return true if debug most is (most likely) enabled
+ */
+ protected static boolean isDebugModeEnabled() {
+ return java.lang.management.ManagementFactory.getRuntimeMXBean()
+ .getInputArguments().toString().indexOf("-agentlib:jdwp") > 0;
+ }
+
+ static String formatPlan(String plan) {
+ plan = plan.replaceAll(" where ", "\n where ");
+ plan = plan.replaceAll(" inner join ", "\n inner join ");
+ plan = plan.replaceAll(" on ", "\n on ");
+ plan = plan.replaceAll(" and ", "\n and ");
+ return plan;
+ }
+
+ protected static String readRow(ResultRow row, boolean pathOnly) {
+ if (pathOnly) {
+ return row.getValue(QueryConstants.JCR_PATH).getValue(Type.STRING);
+ }
+ StringBuilder buff = new StringBuilder();
+ PropertyValue[] values = row.getValues();
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ buff.append(", ");
+ }
+ PropertyValue v = values[i];
+ if (v == null) {
+ buff.append("null");
+ } else if (v.isArray()) {
+ buff.append('[');
+ for (int j = 0; j < v.count(); j++) {
+ buff.append(v.getValue(Type.STRING, j));
+ if (j > 0) {
+ buff.append(", ");
+ }
+ }
+ buff.append(']');
+ } else {
+ buff.append(v.getValue(Type.STRING));
+ }
+ }
+ return buff.toString();
+ }
+
+ protected Result executeQuery(String statement, String language,
+ Map<String, PropertyValue> sv) throws ParseException {
+ return qe.executeQuery(statement, language, sv, NO_MAPPINGS);
+ }
+
+ @After
+ public void closeRepositories() throws Exception {
+ for ( NodeStoreRegistration reg : registrations ) {
+ reg.close();
+ }
+ }
+
+ 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();
+ }
+
+}
\ No newline at end of file