Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor2Test.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor2Test.java?rev=1841926&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor2Test.java (added) +++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor2Test.java Tue Sep 25 12:24:15 2018 @@ -0,0 +1,353 @@ +/* + * 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.index.lucene; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.index.IndexCommitCallback; +import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexingContext; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditor; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorContext; +import org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil; +import org.apache.jackrabbit.oak.plugins.index.lucene.util.IndexDefinitionBuilder; +import org.apache.jackrabbit.oak.plugins.index.lucene.writer.LuceneIndexWriter; +import org.apache.jackrabbit.oak.plugins.index.search.ExtractedTextCache; +import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition; +import org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition; +import org.apache.jackrabbit.oak.plugins.index.search.PropertyUpdateCallback; +import org.apache.jackrabbit.oak.plugins.index.search.spi.editor.FulltextIndexWriterFactory; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.commit.EditorHook; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.lucene.index.IndexableField; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; + +import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +public class LuceneIndexEditor2Test { + + private NodeState root = INITIAL_CONTENT; + private NodeState before = root; + private IndexUpdateCallback updateCallback = mock(IndexUpdateCallback.class); + private ExtractedTextCache extractedTextCache = new ExtractedTextCache(0, 0); + private TestIndexingContext indexingContext = new TestIndexingContext(); + private TestWriterFactory writerFactory = new TestWriterFactory(); + private TestPropertyUpdateCallback propCallback = new TestPropertyUpdateCallback(); + private TestWriter writer = new TestWriter(); + private String indexPath = "/oak:index/fooIndex"; + + @Test + public void basics() throws Exception{ + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("foo").propertyIndex(); + + NodeState defnState = defnb.build(); + IndexDefinition defn = new IndexDefinition(root, defnState, indexPath); + LuceneIndexEditorContext ctx = newContext(defnState.builder(), defn, true); + EditorHook hook = createHook(ctx); + + updateBefore(defnb); + NodeBuilder builder = before.builder(); + builder.child("a").setProperty("foo", "bar"); + + hook.processCommit(root, builder.getNodeState(), CommitInfo.EMPTY); + + assertThat(writer.docs.keySet(), containsInAnyOrder("/a")); + } + + @Test + public void simplePropertyUpdateCallback() throws Exception{ + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("foo").propertyIndex(); + + NodeState defnState = defnb.build(); + IndexDefinition defn = new IndexDefinition(root, defnState, indexPath); + LuceneIndexEditorContext ctx = newContext(defnState.builder(), defn, true); + ctx.setPropertyUpdateCallback(propCallback); + + EditorHook hook = createHook(ctx); + + updateBefore(defnb); + + //Property added + NodeBuilder builder = before.builder(); + builder.child("a").setProperty("foo", "bar"); + builder.child("a").setProperty("foo2", "bar"); + builder.child("a").child("b"); + + before = hook.processCommit(root, builder.getNodeState(), CommitInfo.EMPTY); + propCallback.state.assertState("/a", "foo", UpdateState.ADDED); + assertEquals(1, propCallback.invocationCount); + assertEquals(1, propCallback.doneInvocationCount); + propCallback.reset(); + + //Property updated + builder = before.builder(); + builder.child("a").setProperty("foo", "bar2"); + builder.child("a").setProperty("foo2", "bar2"); + before = hook.processCommit(before, builder.getNodeState(), CommitInfo.EMPTY); + + propCallback.state.assertState("/a", "foo", UpdateState.UPDATED); + + assertEquals(1, propCallback.invocationCount); + propCallback.reset(); + + //Property deleted + builder = before.builder(); + builder.child("a").removeProperty("foo"); + builder.child("a").removeProperty("foo2"); + before = hook.processCommit(before, builder.getNodeState(), CommitInfo.EMPTY); + + propCallback.state.assertState("/a", "foo", UpdateState.DELETED); + assertEquals(1, propCallback.invocationCount); + propCallback.reset(); + } + + @Test + public void relativeProperties() throws Exception{ + IndexDefinitionBuilder defnb = new IndexDefinitionBuilder(); + defnb.indexRule("nt:base").property("jcr:content/metadata/foo").propertyIndex(); + defnb.aggregateRule("nt:base").include("*"); + + NodeState defnState = defnb.build(); + IndexDefinition defn = new IndexDefinition(root, defnState, indexPath); + LuceneIndexEditorContext ctx = newContext(defnState.builder(), defn, true); + ctx.setPropertyUpdateCallback(propCallback); + + EditorHook hook = createHook(ctx); + + updateBefore(defnb); + + //Property added + NodeBuilder builder = before.builder(); + builder.child("a").child("jcr:content").child("metadata").setProperty("foo", "bar"); + builder.child("a").setProperty("foo2", "bar"); + + before = hook.processCommit(root, builder.getNodeState(), CommitInfo.EMPTY); + propCallback.state.assertState("/a", "jcr:content/metadata/foo", UpdateState.ADDED); + assertEquals(1, propCallback.invocationCount); + propCallback.reset(); + + //Property updated + builder = before.builder(); + builder.child("a").child("jcr:content").child("metadata").setProperty("foo", "bar2"); + builder.child("a").setProperty("foo2", "bar2"); + before = hook.processCommit(before, builder.getNodeState(), CommitInfo.EMPTY); + + propCallback.state.assertState("/a", "jcr:content/metadata/foo", UpdateState.UPDATED); + + assertEquals(1, propCallback.invocationCount); + propCallback.reset(); + + //Property deleted + builder = before.builder(); + builder.child("a").child("jcr:content").child("metadata").removeProperty("foo"); + builder.child("a").removeProperty("foo2"); + before = hook.processCommit(before, builder.getNodeState(), CommitInfo.EMPTY); + + propCallback.state.assertState("/a", "jcr:content/metadata/foo", UpdateState.DELETED); + assertEquals(1, propCallback.invocationCount); + propCallback.reset(); + } + + private void updateBefore(IndexDefinitionBuilder defnb) { + NodeBuilder builder = before.builder(); + NodeBuilder cb = TestUtil.child(builder, PathUtils.getParentPath(indexPath)); + cb.setChildNode(PathUtils.getName(indexPath), defnb.build()); + before = builder.getNodeState(); + } + + private EditorHook createHook(LuceneIndexEditorContext context) { + IndexEditorProvider provider = new IndexEditorProvider() { + @Nullable + @Override + public Editor getIndexEditor(@NotNull String type, @NotNull NodeBuilder definition, + @NotNull NodeState root, @NotNull IndexUpdateCallback callback) + throws CommitFailedException { + if (TYPE_LUCENE.equals(type)) { + return new LuceneIndexEditor(context); + } + return null; + } + }; + + String async = context.isAsyncIndexing() ? "async" : null; + IndexUpdateProvider updateProvider = new IndexUpdateProvider(provider, async, false); + return new EditorHook(updateProvider); + } + + private LuceneIndexEditorContext newContext(NodeBuilder defnBuilder, IndexDefinition defn, boolean asyncIndex) { + return new LuceneIndexEditorContext(root, defnBuilder, defn, updateCallback, writerFactory, + extractedTextCache, null, indexingContext, asyncIndex); + } + + + private static class TestPropertyUpdateCallback implements PropertyUpdateCallback { + int invocationCount; + CallbackState state; + int doneInvocationCount; + + @Override + public void propertyUpdated(String nodePath, String propertyRelativePath, PropertyDefinition pd, + PropertyState before, PropertyState after) { + assertNotNull(nodePath); + assertNotNull(propertyRelativePath); + assertNotNull(pd); + + if (before == null && after == null) { + fail("Both states cannot be null at same time"); + } + + state = new CallbackState(nodePath, propertyRelativePath, pd, before, after); + invocationCount++; + } + + @Override + public void done() throws CommitFailedException { + doneInvocationCount++; + } + + void reset(){ + state = null; + invocationCount = 0; + doneInvocationCount = 0; + } + } + + enum UpdateState {ADDED, UPDATED, DELETED} + + private static class CallbackState { + final String nodePath; + final String propertyPath; + final PropertyDefinition pd; + final PropertyState before; + final PropertyState after; + + + public CallbackState(String nodePath, String propertyPath, PropertyDefinition pd, + PropertyState before, PropertyState after) { + this.nodePath = nodePath; + this.propertyPath = propertyPath; + this.pd = pd; + this.before = before; + this.after = after; + } + + public void assertState(String expectedPath, String expectedName, UpdateState us) { + assertEquals(expectedPath, nodePath); + assertEquals(expectedName, propertyPath); + + switch (us) { + case ADDED: assertNotNull(after); assertNull(before); break; + case UPDATED: assertNotNull(after); assertNotNull(before); break; + case DELETED: assertNull(after); assertNotNull(before); break; + } + } + } + + + private class TestWriterFactory implements FulltextIndexWriterFactory { + @Override + public LuceneIndexWriter newInstance(IndexDefinition definition, + NodeBuilder definitionBuilder, boolean reindex) { + return writer; + } + } + + private static class TestWriter implements LuceneIndexWriter { + Set<String> deletedPaths = new HashSet<>(); + Map<String, Iterable<? extends IndexableField>> docs = new HashMap<>(); + boolean closed; + + @Override + public void updateDocument(String path, Iterable<? extends IndexableField> doc) throws IOException { + docs.put(path, doc); + } + + @Override + public void deleteDocuments(String path) throws IOException { + deletedPaths.add(path); + } + + @Override + public boolean close(long timestamp) throws IOException { + closed = true; + return true; + } + } + + private class TestIndexingContext implements IndexingContext { + CommitInfo info = CommitInfo.EMPTY; + boolean reindexing; + boolean async; + + @Override + public String getIndexPath() { + return indexPath; + } + + @Override + public CommitInfo getCommitInfo() { + return info; + } + + @Override + public boolean isReindexing() { + return reindexing; + } + + @Override + public boolean isAsync() { + return async; + } + + @Override + public void indexUpdateFailed(Exception e) { + + } + + @Override + public void registerIndexCommitCallback(IndexCommitCallback callback) { + + } + } +}
Propchange: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor2Test.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProviderTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProviderTest.java?rev=1841926&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProviderTest.java (added) +++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProviderTest.java Tue Sep 25 12:24:15 2018 @@ -0,0 +1,194 @@ +/* + * 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.index.lucene; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.plugins.index.ContextAwareCallback; +import org.apache.jackrabbit.oak.plugins.index.IndexCommitCallback; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.plugins.index.IndexingContext; +import org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexDefinition; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditor; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorContext; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil; +import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.DocumentQueue; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.spi.commit.CommitContext; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.commit.SimpleCommitContext; +import org.apache.jackrabbit.oak.spi.mount.Mounts; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.junit.Test; + +import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE; +import static org.apache.jackrabbit.oak.plugins.index.lucene.util.LuceneIndexHelper.newLucenePropertyIndexDefinition; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class LuceneIndexEditorProviderTest { + private NodeState root = INITIAL_CONTENT; + private NodeBuilder builder = root.builder(); + + @Test + public void readOnlyBuilderUsedForSync() throws Exception { + LuceneIndexEditorProvider editorProvider = new LuceneIndexEditorProvider(null, + null, + null, + null, + Mounts.defaultMountInfoProvider()); + editorProvider.setIndexingQueue(mock(DocumentQueue.class)); + + IndexUpdateCallback callback = new TestCallback("/oak:index/fooIndex", newCommitInfo(), false, false); + NodeBuilder defnBuilder = createIndexDefinition("fooIndex").builder(); + Editor editor = editorProvider.getIndexEditor(TYPE_LUCENE, defnBuilder, root, callback); + LuceneIndexEditor luceneEditor = (LuceneIndexEditor) editor; + + NodeBuilder builderFromContext = + (NodeBuilder) FieldUtils.readField(luceneEditor.getContext(), "definitionBuilder", true); + + try { + builderFromContext.setProperty("foo", "bar"); + fail("Should have been read only builder"); + } catch (UnsupportedOperationException ignore) { + + } + } + + @Test + public void reuseOldIndexDefinition() throws Exception{ + IndexTracker tracker = mock(IndexTracker.class); + LuceneIndexEditorProvider editorProvider = new LuceneIndexEditorProvider(null, + tracker, + null, + null, + Mounts.defaultMountInfoProvider()); + editorProvider.setIndexingQueue(mock(DocumentQueue.class)); + //Set up a different IndexDefinition which needs to be returned + //from tracker with a marker property + NodeBuilder testBuilder = createIndexDefinition("fooIndex").builder(); + testBuilder.setProperty("foo", "bar"); + LuceneIndexDefinition defn = new LuceneIndexDefinition(root, testBuilder.getNodeState(), "/foo"); + when(tracker.getIndexDefinition("/oak:index/fooIndex")).thenReturn(defn); + + IndexUpdateCallback callback = new TestCallback("/oak:index/fooIndex", newCommitInfo(), false, false); + NodeBuilder defnBuilder = createIndexDefinition("fooIndex").builder(); + Editor editor = editorProvider.getIndexEditor(TYPE_LUCENE, defnBuilder, root, callback); + LuceneIndexEditor luceneEditor = (LuceneIndexEditor) editor; + LuceneIndexEditorContext context = luceneEditor.getContext(); + + //Definition should reflect the marker property + assertEquals("bar", context.getDefinition().getDefinitionNodeState().getString("foo")); + } + + @Test + public void editorNullInCaseOfReindex() throws Exception{ + LuceneIndexEditorProvider editorProvider = new LuceneIndexEditorProvider(null, + null, + null, + null, + Mounts.defaultMountInfoProvider()); + editorProvider.setIndexingQueue(mock(DocumentQueue.class)); + IndexUpdateCallback callback = new TestCallback("/oak:index/fooIndex", newCommitInfo(), true, false); + NodeBuilder defnBuilder = createIndexDefinition("fooIndex").builder(); + Editor editor = editorProvider.getIndexEditor(TYPE_LUCENE, defnBuilder, root, callback); + assertNull(editor); + } + + private NodeState createIndexDefinition(String idxName) { + NodeBuilder idx = newLucenePropertyIndexDefinition(builder.child("oak:index"), + idxName, ImmutableSet.of("foo"), "async"); + TestUtil.enableIndexingMode(idx, FulltextIndexConstants.IndexingMode.NRT); + LuceneIndexEditorContext.configureUniqueId(idx); + LuceneIndexDefinition.updateDefinition(idx); + return idx.getNodeState(); + } + + private CommitInfo newCommitInfo() { + CommitInfo info = new CommitInfo("admin", "s1", + ImmutableMap.<String, Object>of(CommitContext.NAME, new SimpleCommitContext())); + return info; + } + + private static class TestCallback implements IndexUpdateCallback, IndexingContext, ContextAwareCallback { + private final String indexPath; + private final CommitInfo commitInfo; + private final boolean reindexing; + private final boolean async; + + private TestCallback(String indexPath, CommitInfo commitInfo, boolean reindexing, boolean async) { + this.indexPath = indexPath; + this.commitInfo = commitInfo; + this.reindexing = reindexing; + this.async = async; + } + + @Override + public String getIndexPath() { + return indexPath; + } + + @Override + public CommitInfo getCommitInfo() { + return commitInfo; + } + + @Override + public boolean isReindexing() { + return reindexing; + } + + @Override + public boolean isAsync() { + return async; + } + + @Override + public void indexUpdateFailed(Exception e) { + + } + + @Override + public void indexUpdate() throws CommitFailedException { + + } + + @Override + public IndexingContext getIndexingContext() { + return this; + } + + @Override + public void registerIndexCommitCallback(IndexCommitCallback callback) { + + } + } + +} \ No newline at end of file Propchange: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProviderTest.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java?rev=1841926&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java (added) +++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java Tue Sep 25 12:24:15 2018 @@ -0,0 +1,631 @@ +/* + * 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.index.lucene; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.google.common.collect.ImmutableList; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.commons.CIHelper; +import org.apache.jackrabbit.oak.plugins.blob.datastore.CachingFileDataStore; +import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore; +import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreUtils; +import org.apache.jackrabbit.oak.plugins.index.CompositeIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.IndexEditor; +import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexUtils; +import org.apache.jackrabbit.oak.plugins.index.lucene.FieldFactory; +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.LuceneIndexConstants; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexDefinition; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexNode; +import org.apache.jackrabbit.oak.plugins.index.lucene.directory.OakDirectory; +import org.apache.jackrabbit.oak.plugins.index.lucene.writer.MultiplexersLucene; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.search.ExtractedTextCache; +import org.apache.jackrabbit.oak.plugins.index.search.FieldNames; +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition; +import org.apache.jackrabbit.oak.plugins.index.search.IndexFormatVersion; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.DefaultEditor; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.commit.EditorHook; +import org.apache.jackrabbit.oak.spi.mount.Mount; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; +import org.apache.jackrabbit.test.ISO8601; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.queryparser.classic.QueryParser; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.NumericRangeQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.Directory; +import org.jetbrains.annotations.NotNull; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static com.google.common.collect.ImmutableSet.of; +import static javax.jcr.PropertyType.TYPENAME_STRING; +import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.api.Type.STRINGS; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; +import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION; +import static org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.newLuceneIndexDefinitionV2; +import static org.apache.jackrabbit.oak.plugins.index.lucene.util.LuceneIndexHelper.newLuceneIndexDefinition; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_NAMES; +import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; +import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; + +@RunWith(Parameterized.class) +public class LuceneIndexEditorTest { + private EditorHook HOOK; + + private NodeState root = INITIAL_CONTENT; + + private NodeBuilder builder = root.builder(); + + private IndexTracker tracker = new IndexTracker(); + + private LuceneIndexNode indexNode; + + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(new File("target")); + + @Parameterized.Parameter + public boolean useBlobStore; + + @Parameterized.Parameters(name = "{index}: useBlobStore ({0})") + public static List<Boolean[]> fixtures() { + return ImmutableList.of(new Boolean[] {true}, new Boolean[] {false}); + } + + @Before + public void setup() throws Exception { + if (useBlobStore) { + LuceneIndexEditorProvider provider = new LuceneIndexEditorProvider(); + CachingFileDataStore ds = DataStoreUtils + .createCachingFDS(temporaryFolder.newFolder().getAbsolutePath(), + temporaryFolder.newFolder().getAbsolutePath()); + provider.setBlobStore(new DataStoreBlobStore(ds)); + HOOK = new EditorHook(new IndexUpdateProvider(provider)); + } else { + HOOK = new EditorHook(new IndexUpdateProvider(new LuceneIndexEditorProvider())); + } + } + + @Test + public void testLuceneWithFullText() throws Exception { + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder idxnb = newLuceneIndexDefinitionV2(index, "lucene", + of(TYPENAME_STRING)); + LuceneIndexDefinition defn = new LuceneIndexDefinition(root, idxnb.getNodeState(), "/foo"); + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + builder.child("test").setProperty("price", 100); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + + //system fields starts with ':' so need to be escaped + assertEquals("/test", query(escape(FieldNames.createAnalyzedFieldName("foo"))+":fox", defn)); + assertNull("Non string properties not indexed by default", + getPath(NumericRangeQuery.newLongRange("price", 100L, 100L, true, true))); + } + + @Test + public void noChangeIfNonIndexedDelete() throws Exception{ + NodeState before = builder.getNodeState(); + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", of(TYPENAME_STRING)); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo"), STRINGS)); + + + builder.child("test").setProperty("foo", "bar"); + builder.child("test").child("a"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + assertEquals("/test", getPath(new TermQuery(new Term("foo", "bar")))); + + NodeState luceneIdxState1 = NodeStateUtils.getNode(indexed, "/oak:index/lucene"); + + before = indexed; + builder = indexed.builder(); + builder.getChildNode("test").getChildNode("a").remove(); + after = builder.getNodeState(); + indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + + NodeState luceneIdxState2 = NodeStateUtils.getNode(indexed, "/oak:index/lucene"); + assertEquals(luceneIdxState1, luceneIdxState2); + } + + private String escape(String name) { + return name.replace(":", "\\:"); + } + + @Test + public void testLuceneWithNonFullText() throws Exception { + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", + of(TYPENAME_STRING)); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo", "price", "weight", "bool", "creationTime"), STRINGS)); + LuceneIndexDefinition defn = new LuceneIndexDefinition(root, nb.getNodeState(), "/foo"); + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + builder.child("test").setProperty("bar", "kite is flying"); + builder.child("test").setProperty("price", 100); + builder.child("test").setProperty("weight", 10.0); + builder.child("test").setProperty("bool", true); + builder.child("test").setProperty("truth", true); + builder.child("test").setProperty("creationTime", createCal("05/06/2014")); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + + assertNull("Fulltext search should not work", query("foo:fox",defn)); + assertEquals("/test", getPath(new TermQuery(new Term("foo", "fox is jumping")))); + assertNull("bar must NOT be indexed", getPath(new TermQuery(new Term("bar", "kite is flying")))); + + //Long + assertEquals("/test", getPath(NumericRangeQuery.newDoubleRange("weight", 8D, 12D, true, true))); + + //Double + assertEquals("/test", getPath(NumericRangeQuery.newLongRange("price", 100L, 100L, true, true))); + + //Boolean + assertEquals("/test", getPath(new TermQuery(new Term("bool", "true")))); + assertNull("truth must NOT be indexed", getPath(new TermQuery(new Term("truth", "true")))); + + //Date + assertEquals("/test", getPath(NumericRangeQuery.newLongRange("creationTime", + dateToTime("05/05/2014"), dateToTime("05/07/2014"), true, true))); + } + + @Test + public void noOfDocsIndexedNonFullText() throws Exception { + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", + of(TYPENAME_STRING)); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo"), STRINGS)); + + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + builder.child("test2").setProperty("bar", "kite is flying"); + builder.child("test3").setProperty("foo", "wind is blowing"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + + assertEquals(2, getSearcher().getIndexReader().numDocs()); + } + + @Test + public void saveDirectoryListing() throws Exception { + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", + of(TYPENAME_STRING)); + nb.setProperty(LuceneIndexConstants.SAVE_DIR_LISTING, true); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo"), STRINGS)); + + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + NodeState dir = indexed.getChildNode("oak:index").getChildNode("lucene").getChildNode(":data"); + assertTrue(dir.hasProperty(OakDirectory.PROP_DIR_LISTING)); + } + + /** + * 1. Index property foo in /test + * 2. Then modify some other property in /test + * + * This should not cause the index to be updated + */ + @Test + public void nonIncludedPropertyChange() throws Exception { + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", + of(TYPENAME_STRING)); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo"), + STRINGS)); + + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + builder.child("test2").setProperty("foo", "bird is chirping"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + + assertEquals(2, getSearcher().getIndexReader().numDocs()); + + assertEquals("/test", getPath(new TermQuery(new Term("foo", "fox is jumping")))); + + releaseIndexNode(); + before = indexed; + builder = before.builder(); + builder.child("test").setProperty("bar", "kite is flying"); + after = builder.getNodeState(); + indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + + assertEquals(2, getSearcher().getIndexReader().numDocs()); + assertEquals("change in non included property should not cause " + + "index update",0, getSearcher().getIndexReader().numDeletedDocs()); + } + + @Test + public void testLuceneWithRelativeProperty() throws Exception { + // OAK-6833 + assumeFalse(CIHelper.windows()); + + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", + of(TYPENAME_STRING)); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo", "jcr:content/mime", + "jcr:content/metadata/type"), STRINGS)); + + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + builder.child("test").child("jcr:content").setProperty("mime", "text"); + builder.child("test").child("jcr:content").child("metadata").setProperty("type", "image"); + builder.child("jcr:content").setProperty("count", "text"); + builder.child("jcr:content").child("boom").child("metadata").setProperty("type", "image"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + + assertEquals(1, getSearcher().getIndexReader().numDocs()); + + assertEquals("/test", getPath(new TermQuery(new Term("foo", "fox is jumping")))); + assertEquals("/test", getPath(new TermQuery(new Term("jcr:content/mime", "text")))); + assertEquals("/test", getPath(new TermQuery(new Term("jcr:content/metadata/type", "image")))); + assertNull("bar must NOT be indexed", getPath(new TermQuery(new Term("count", "text")))); + + releaseIndexNode(); + before = indexed; + builder = before.builder(); + builder.child("test").child("jcr:content").setProperty("mime", "pdf"); + after = builder.getNodeState(); + indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + + assertEquals("/test", getPath(new TermQuery(new Term("jcr:content/mime", "pdf")))); + + releaseIndexNode(); + before = indexed; + builder = before.builder(); + builder.child("test").child("jcr:content").remove(); + after = builder.getNodeState(); + indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + assertNull("removes must be persisted too, 1st level", + getPath(new TermQuery(new Term("jcr:content/mime", "pdf")))); + assertNull("removes must be persisted too, 2nd level", + getPath(new TermQuery(new Term("jcr:content/metadata/type", + "image")))); + } + + @Test + public void indexVersionSwitchOnReindex() throws Exception{ + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinition(index, "lucene", + of(TYPENAME_STRING)); + + //1. Trigger a index so that next index step does not see it as a fresh index + NodeState indexed = HOOK.processCommit(EMPTY_NODE, builder.getNodeState(), CommitInfo.EMPTY); + builder = indexed.builder(); + + //By default logic would use current version. To simulate upgrade we forcefully set + //version to V1 + builder.child(INDEX_DEFINITIONS_NAME).child("lucene").setProperty(IndexDefinition.INDEX_VERSION, + IndexFormatVersion.V1.getVersion()); + + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + NodeState after = builder.getNodeState(); + + indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + assertEquals(IndexFormatVersion.V1, new IndexDefinition(root, + indexed.getChildNode(INDEX_DEFINITIONS_NAME).getChildNode("lucene"), "/foo").getVersion()); + + //3. Trigger a reindex and version should switch to current + builder = indexed.builder(); + before = indexed; + builder.child(INDEX_DEFINITIONS_NAME).child("lucene").setProperty(IndexConstants.REINDEX_PROPERTY_NAME, true); + after = builder.getNodeState(); + indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + assertEquals(IndexFormatVersion.getDefault(), new IndexDefinition(root, + indexed.getChildNode(INDEX_DEFINITIONS_NAME).getChildNode("lucene"), "/foo").getVersion()); + + } + + @Test + public void autoFormatUpdate() throws Exception{ + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", + of(TYPENAME_STRING)); + + //1. Trigger a index so that next index step does not see it as a fresh index + NodeState indexed = HOOK.processCommit(EMPTY_NODE, builder.getNodeState(), CommitInfo.EMPTY); + + IndexDefinition defn = new IndexDefinition(root, indexed.getChildNode(INDEX_DEFINITIONS_NAME).getChildNode("lucene"), "/foo"); + assertFalse(defn.isOfOldFormat()); + } + + @Test + public void copyOnWriteAndLocks() throws Exception { + assumeFalse(CIHelper.windows()); + + ExecutorService executorService = Executors.newFixedThreadPool(2); + IndexCopier copier = new IndexCopier(executorService, temporaryFolder.getRoot()); + + FailOnDemandEditorProvider failingProvider = new FailOnDemandEditorProvider(); + EditorHook hook = new EditorHook( + new IndexUpdateProvider( + new CompositeIndexEditorProvider( + failingProvider, + new LuceneIndexEditorProvider(copier)))); + + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, "lucene", of(TYPENAME_STRING)); + IndexUtils.createIndexDefinition(index, "failingIndex", false, false, of("foo"), null); + + + //1. Get initial set indexed. So that next cycle is normal indexing + NodeState indexed = hook.processCommit(EMPTY_NODE, builder.getNodeState(), CommitInfo.EMPTY); + builder = indexed.builder(); + + NodeState before = indexed; + builder.child("test").setProperty("a", "fox is jumping"); + NodeState after = builder.getNodeState(); + + //2. Ensure that Lucene gets triggered but close is not called + failingProvider.setShouldFail(true); + try { + hook.processCommit(before, after, CommitInfo.EMPTY); + fail(); + } catch (CommitFailedException ignore){ + + } + + //3. Disable the troubling editor + failingProvider.setShouldFail(false); + + //4. Now commit should process fine + hook.processCommit(before, after, CommitInfo.EMPTY); + + executorService.shutdown(); + } + + + @Test + public void multiplexingWriter() throws Exception{ + newLucenePropertyIndex("lucene", "foo"); + MountInfoProvider mip = Mounts.newBuilder() + .mount("foo", "/libs", "/apps").build(); + EditorHook hook = new EditorHook( + new IndexUpdateProvider( + new LuceneIndexEditorProvider(null, new ExtractedTextCache(0, 0), null, mip))); + + NodeState indexed = hook.processCommit(EMPTY_NODE, builder.getNodeState(), CommitInfo.EMPTY); + builder = indexed.builder(); + NodeState before = indexed; + builder.child("content").child("en").setProperty("foo", "bar"); + builder.child("libs").child("install").setProperty("foo", "bar"); + NodeState after = builder.getNodeState(); + + indexed = hook.processCommit(before, after, CommitInfo.EMPTY); + builder = indexed.builder(); + + assertEquals(1, numDocs(mip.getMountByName("foo"))); + assertEquals(1, numDocs(mip.getDefaultMount())); + } + + private int numDocs(Mount m) throws IOException { + String indexDirName = MultiplexersLucene.getIndexDirName(m); + NodeBuilder defnBuilder = builder.child(INDEX_DEFINITIONS_NAME).child("lucene"); + Directory d = new OakDirectory(defnBuilder, indexDirName, new LuceneIndexDefinition(root, defnBuilder.getNodeState(), "/foo"), true); + IndexReader r = DirectoryReader.open(d); + return r.numDocs(); + } + + + //@Test + public void checkLuceneIndexFileUpdates() throws Exception{ + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinition(index, "lucene", + of(TYPENAME_STRING)); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of("foo" , "bar", "baz"), STRINGS)); + //nb.removeProperty(REINDEX_PROPERTY_NAME); + + NodeState before = builder.getNodeState(); + builder.child("test").setProperty("foo", "fox is jumping"); + + //InfoStream.setDefault(new PrintStreamInfoStream(System.out)); + before = commitAndDump(before, builder.getNodeState()); + + builder = before.builder(); + builder.child("test2").setProperty("bar", "ship is sinking"); + before = commitAndDump(before, builder.getNodeState()); + + builder = before.builder(); + builder.child("test3").setProperty("baz", "horn is blowing"); + before = commitAndDump(before, builder.getNodeState()); + + builder = before.builder(); + builder.child("test2").remove(); + before = commitAndDump(before, builder.getNodeState()); + + builder = before.builder(); + builder.child("test2").setProperty("bar", "ship is back again"); + before = commitAndDump(before, builder.getNodeState()); + } + + @After + public void releaseIndexNode(){ + if(indexNode != null){ + indexNode.release(); + } + indexNode = null; + } + + private NodeState newLucenePropertyIndex(String indexName, String propName){ + NodeBuilder index = builder.child(INDEX_DEFINITIONS_NAME); + NodeBuilder nb = newLuceneIndexDefinitionV2(index, indexName, + of(TYPENAME_STRING)); + nb.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + nb.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, of(propName), STRINGS)); + return builder.getNodeState(); + } + + private String query(String query, LuceneIndexDefinition defn) throws IOException, ParseException { + QueryParser queryParser = new QueryParser(VERSION, "", defn.getAnalyzer()); + return getPath(queryParser.parse(query)); + } + + private String getPath(Query query) throws IOException { + TopDocs td = getSearcher().search(query, 100); + if (td.totalHits > 0){ + if(td.totalHits > 1){ + fail("More than 1 result found for query " + query); + } + return getSearcher().getIndexReader().document(td.scoreDocs[0].doc).get(FieldNames.PATH); + } + return null; + } + + private IndexSearcher getSearcher(){ + if(indexNode == null){ + indexNode = tracker.acquireIndexNode("/oak:index/lucene"); + } + return indexNode.getSearcher(); + } + + private NodeState commitAndDump(NodeState before, NodeState after) throws CommitFailedException, IOException { + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + tracker.update(indexed); + dumpIndexDir(); + return indexed; + } + + private void dumpIndexDir() throws IOException { + Directory dir = ((DirectoryReader)getSearcher().getIndexReader()).directory(); + + System.out.println("================"); + String[] fileNames = dir.listAll(); + Arrays.sort(fileNames); + for (String file : fileNames){ + System.out.printf("%s - %d %n", file, dir.fileLength(file)); + } + releaseIndexNode(); + } + + public static Calendar createCal(String dt) throws java.text.ParseException { + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); + Calendar cal = Calendar.getInstance(); + cal.setTime(sdf.parse(dt)); + return cal; + } + + static long dateToTime(String dt) throws java.text.ParseException { + return FieldFactory.dateToLong(ISO8601.format(createCal(dt))); + } + + private static class FailOnDemandEditorProvider implements IndexEditorProvider { + + private boolean shouldFail; + + @Override + public Editor getIndexEditor(@NotNull String type, @NotNull NodeBuilder definition, + @NotNull NodeState root, + @NotNull IndexUpdateCallback callback) throws CommitFailedException { + if (PropertyIndexEditorProvider.TYPE.equals(type)) { + return new FailOnDemandEditor(); + } + return null; + } + + public void setShouldFail(boolean shouldFail) { + this.shouldFail = shouldFail; + } + + private class FailOnDemandEditor extends DefaultEditor implements IndexEditor { + @Override + public void leave(NodeState before, NodeState after) + throws CommitFailedException { + throwExceptionIfTold(); + super.leave(before, after); + } + + void throwExceptionIfTold() throws CommitFailedException { + if (shouldFail) { + throw new CommitFailedException("commit",1 , null); + } + } + } + } + +} Propchange: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexExclusionQueryTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexExclusionQueryTest.java?rev=1841926&r1=1841925&r2=1841926&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexExclusionQueryTest.java (original) +++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexExclusionQueryTest.java Tue Sep 25 12:24:15 2018 @@ -24,8 +24,8 @@ import static org.apache.jackrabbit.JcrC import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED; import static org.apache.jackrabbit.oak.api.Type.DATE; import static org.apache.jackrabbit.oak.api.Type.STRINGS; -import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.EXCLUDE_PROPERTY_NAMES; -import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INCLUDE_PROPERTY_TYPES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EXCLUDE_PROPERTY_NAMES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_TYPES; import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE; import static org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.useV2; Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexInfoProviderTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexInfoProviderTest.java?rev=1841926&r1=1841925&r2=1841926&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexInfoProviderTest.java (original) +++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexInfoProviderTest.java Tue Sep 25 12:24:15 2018 @@ -21,7 +21,6 @@ package org.apache.jackrabbit.oak.plugin import java.io.File; -import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfo; import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfoService; import org.apache.jackrabbit.oak.plugins.index.IndexInfo; @@ -36,11 +35,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import static com.google.common.collect.Lists.newArrayList; -import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexLookupTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexLookupTest.java?rev=1841926&r1=1841925&r2=1841926&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexLookupTest.java (original) +++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexLookupTest.java Tue Sep 25 12:24:15 2018 @@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.plugins.index.lucene; +import org.apache.jackrabbit.oak.plugins.index.search.IndexLookup; import org.apache.jackrabbit.oak.query.index.FilterImpl; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; @@ -43,11 +44,11 @@ public class LuceneIndexLookupTest { newLuceneIndexDefinition(index, "l1", of(TYPENAME_STRING)); newLuceneIndexDefinition(index, "l2", of(TYPENAME_STRING)); - LuceneIndexLookup lookup = new LuceneIndexLookup(builder.getNodeState()); + IndexLookup lookup = LuceneIndexLookupUtil.getLuceneIndexLookup(builder.getNodeState()); FilterImpl f = FilterImpl.newTestInstance(); f.restrictPath("/", Filter.PathRestriction.EXACT); assertEquals(of("/oak:index/l1", "/oak:index/l2"), - lookup.collectIndexNodePaths(f)); + lookup.collectIndexNodePaths(f)); } @Test @@ -61,14 +62,14 @@ public class LuceneIndexLookupTest { index = builder.child("a").child("b").child(INDEX_DEFINITIONS_NAME); newLuceneIndexDefinition(index, "l3", of(TYPENAME_STRING)); - LuceneIndexLookup lookup = new LuceneIndexLookup(builder.getNodeState()); + IndexLookup lookup = LuceneIndexLookupUtil.getLuceneIndexLookup(builder.getNodeState()); FilterImpl f = FilterImpl.newTestInstance(); f.restrictPath("/a", Filter.PathRestriction.EXACT); assertEquals(of("/oak:index/l1", "/a/oak:index/l2"), - lookup.collectIndexNodePaths(f)); + lookup.collectIndexNodePaths(f)); f.restrictPath("/a/b", Filter.PathRestriction.EXACT); assertEquals(of("/oak:index/l1", "/a/oak:index/l2", "/a/b/oak:index/l3"), - lookup.collectIndexNodePaths(f)); + lookup.collectIndexNodePaths(f)); } } Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java?rev=1841926&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java (added) +++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java Tue Sep 25 12:24:15 2018 @@ -0,0 +1,448 @@ +/* + * 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.index.lucene; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean; +import org.apache.jackrabbit.oak.api.jmx.CheckpointMBean; +import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard; +import org.apache.jackrabbit.oak.plugins.blob.datastore.CachingFileDataStore; +import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore; +import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreUtils; +import org.apache.jackrabbit.oak.plugins.document.spi.JournalPropertyService; +import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfoService; +import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexPathService; +import org.apache.jackrabbit.oak.plugins.index.fulltext.PreExtractedTextProvider; +import org.apache.jackrabbit.oak.plugins.index.importer.IndexImporterProvider; +import org.apache.jackrabbit.oak.plugins.index.lucene.CopyOnReadStatsMBean; +import org.apache.jackrabbit.oak.plugins.index.lucene.IndexAugmentorFactory; +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.LoggingInfoStream; +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.LuceneIndexProviderService; +import org.apache.jackrabbit.oak.plugins.index.lucene.directory.BufferedOakDirectory; +import org.apache.jackrabbit.oak.plugins.index.lucene.property.PropertyIndexCleaner; +import org.apache.jackrabbit.oak.plugins.index.lucene.reader.DefaultIndexReaderFactory; +import org.apache.jackrabbit.oak.plugins.index.lucene.score.ScorerProviderFactory; +import org.apache.jackrabbit.oak.plugins.index.search.ExtractedTextCache; +import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore; +import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver; +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.QueryIndexProvider; +import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; +import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils; +import org.apache.jackrabbit.oak.stats.StatisticsProvider; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.util.InfoStream; +import org.apache.sling.testing.mock.osgi.MockOsgi; +import org.apache.sling.testing.mock.osgi.junit.OsgiContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.osgi.framework.ServiceReference; + +import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class LuceneIndexProviderServiceTest { + /* + The test case uses raw config name and not access it via + constants in LuceneIndexProviderService to ensure that change + in names are detected + */ + + @Rule + public final TemporaryFolder folder = new TemporaryFolder(new File("target")); + + @Rule + public final OsgiContext context = new OsgiContext(); + + private LuceneIndexProviderService service = new LuceneIndexProviderService(); + + private Whiteboard wb; + + private MountInfoProvider mip; + + @Before + public void setUp(){ + mip = Mounts.newBuilder().build(); + context.registerService(MountInfoProvider.class, mip); + context.registerService(StatisticsProvider.class, StatisticsProvider.NOOP); + context.registerService(ScorerProviderFactory.class, ScorerProviderFactory.DEFAULT); + context.registerService(IndexAugmentorFactory.class, new IndexAugmentorFactory()); + context.registerService(NodeStore.class, new MemoryNodeStore()); + context.registerService(IndexPathService.class, mock(IndexPathService.class)); + context.registerService(AsyncIndexInfoService.class, mock(AsyncIndexInfoService.class)); + context.registerService(CheckpointMBean.class, mock(CheckpointMBean.class)); + + wb = new OsgiWhiteboard(context.bundleContext()); + MockOsgi.injectServices(service, context.bundleContext()); + } + + @After + public void after(){ + IndexDefinition.setDisableStoredIndexDefinition(false); + } + + @Test + public void defaultSetup() throws Exception{ + MockOsgi.activate(service, context.bundleContext(), getDefaultConfig()); + + assertNotNull(context.getService(QueryIndexProvider.class)); + assertNotNull(context.getService(Observer.class)); + assertNotNull(context.getService(IndexEditorProvider.class)); + + LuceneIndexEditorProvider editorProvider = + (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class); + assertNotNull(editorProvider.getIndexCopier()); + assertNotNull(editorProvider.getIndexingQueue()); + + IndexCopier indexCopier = service.getIndexCopier(); + assertNotNull("IndexCopier should be initialized as CopyOnRead is enabled by default", indexCopier); + assertTrue(indexCopier.isPrefetchEnabled()); + assertFalse(IndexDefinition.isDisableStoredIndexDefinition()); + + assertNotNull("CopyOnRead should be enabled by default", context.getService(CopyOnReadStatsMBean.class)); + assertNotNull(context.getService(CacheStatsMBean.class)); + + assertTrue(context.getService(Observer.class) instanceof BackgroundObserver); + assertEquals(InfoStream.NO_OUTPUT, InfoStream.getDefault()); + + assertEquals(1024, BooleanQuery.getMaxClauseCount()); + + assertNotNull(FieldUtils.readDeclaredField(service, "documentQueue", true)); + + assertNotNull(context.getService(JournalPropertyService.class)); + assertNotNull(context.getService(IndexImporterProvider.class)); + + assertNotNull(WhiteboardUtils.getServices(wb, Runnable.class, r -> r instanceof PropertyIndexCleaner)); + + MockOsgi.deactivate(service, context.bundleContext()); + + IndexTracker tracker = (IndexTracker) FieldUtils.readDeclaredField(service, "tracker", true); + assertNotNull(tracker.getAsyncIndexInfoService()); + } + + @Test + public void typeProperty() throws Exception{ + MockOsgi.activate(service, context.bundleContext(), getDefaultConfig()); + ServiceReference sr = context.bundleContext().getServiceReference(IndexEditorProvider.class.getName()); + assertEquals(TYPE_LUCENE, sr.getProperty("type")); + } + + @Test + public void disableOpenIndexAsync() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("enableOpenIndexAsync", false); + MockOsgi.activate(service, context.bundleContext(), config); + + assertTrue(context.getService(Observer.class) instanceof LuceneIndexProvider); + + MockOsgi.deactivate(service, context.bundleContext()); + } + + @Test + public void enableCopyOnWrite() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("enableCopyOnWriteSupport", true); + MockOsgi.activate(service, context.bundleContext(), config); + + LuceneIndexEditorProvider editorProvider = + (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class); + + assertNotNull(editorProvider); + assertNotNull(editorProvider.getIndexCopier()); + + MockOsgi.deactivate(service, context.bundleContext()); + } + + // OAK-7357 + @Test + public void disableCoRCoW() throws Exception { + // inject ds as OAK-7357 revealed ABD bean had a bug - which comes into play only with blob stores + CachingFileDataStore ds = DataStoreUtils + .createCachingFDS(folder.newFolder().getAbsolutePath(), + folder.newFolder().getAbsolutePath()); + + context.registerService(GarbageCollectableBlobStore.class, new DataStoreBlobStore(ds)); + + // re-init service and inject references + service = new LuceneIndexProviderService(); + MockOsgi.injectServices(service, context.bundleContext()); + + Map<String,Object> config = getDefaultConfig(); + config.put("enableCopyOnReadSupport", false); + config.put("enableCopyOnWriteSupport", false); + + // activation should work + MockOsgi.activate(service, context.bundleContext(), config); + + // get lucene index provider + LuceneIndexProvider lip = null; + for (QueryIndexProvider qip : context.getServices(QueryIndexProvider.class, null)) { + if (qip instanceof LuceneIndexProvider) { + lip = (LuceneIndexProvider)qip; + break; + } + } + assertNotNull(lip); + IndexTracker tracker = lip.getTracker(); + + // access reader factory with reflection and implicitly assert that it's DefaultIndexReaderFactory + Field readerFactorFld = IndexTracker.class.getDeclaredField("readerFactory"); + readerFactorFld.setAccessible(true); + DefaultIndexReaderFactory readerFactory = (DefaultIndexReaderFactory)readerFactorFld.get(tracker); + + Field mipFld = DefaultIndexReaderFactory.class.getDeclaredField("mountInfoProvider"); + mipFld.setAccessible(true); + // OAK-7408: LIPS was using default tracker ctor and hence reader factor used default mounts + assertEquals("Reader factory not using configured MountInfoProvider", mip, mipFld.get(readerFactory)); + + // de-activation should work + MockOsgi.deactivate(service, context.bundleContext()); + } + + @Test + public void enablePrefetchIndexFiles() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("prefetchIndexFiles", true); + MockOsgi.activate(service, context.bundleContext(), config); + + IndexCopier indexCopier = service.getIndexCopier(); + assertTrue(indexCopier.isPrefetchEnabled()); + + MockOsgi.deactivate(service, context.bundleContext()); + } + + @Test + public void debugLogging() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("debug", true); + MockOsgi.activate(service, context.bundleContext(), config); + + assertEquals(LoggingInfoStream.INSTANCE, InfoStream.getDefault()); + MockOsgi.deactivate(service, context.bundleContext()); + } + + @Test + public void enableExtractedTextCaching() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("extractedTextCacheSizeInMB", 11); + MockOsgi.activate(service, context.bundleContext(), config); + + ExtractedTextCache textCache = service.getExtractedTextCache(); + assertNotNull(textCache.getCacheStats()); + assertNotNull(context.getService(CacheStatsMBean.class)); + + assertEquals(11 * FileUtils.ONE_MB, textCache.getCacheStats().getMaxTotalWeight()); + + MockOsgi.deactivate(service, context.bundleContext()); + + assertNull(context.getService(CacheStatsMBean.class)); + } + + @Test + public void preExtractedTextProvider() throws Exception{ + MockOsgi.activate(service, context.bundleContext(), getDefaultConfig()); + LuceneIndexEditorProvider editorProvider = + (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class); + assertNull(editorProvider.getExtractedTextCache().getExtractedTextProvider()); + assertFalse(editorProvider.getExtractedTextCache().isAlwaysUsePreExtractedCache()); + + //Mock OSGi does not support components + //context.registerService(PreExtractedTextProvider.class, new DummyProvider()); + service.bindExtractedTextProvider(mock(PreExtractedTextProvider.class)); + + assertNotNull(editorProvider.getExtractedTextCache().getExtractedTextProvider()); + } + + @Test + public void preExtractedProviderBindBeforeActivate() throws Exception{ + service.bindExtractedTextProvider(mock(PreExtractedTextProvider.class)); + MockOsgi.activate(service, context.bundleContext(), getDefaultConfig()); + LuceneIndexEditorProvider editorProvider = + (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class); + assertNotNull(editorProvider.getExtractedTextCache().getExtractedTextProvider()); + } + + @Test + public void alwaysUsePreExtractedCache() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("alwaysUsePreExtractedCache", "true"); + MockOsgi.activate(service, context.bundleContext(), config); + LuceneIndexEditorProvider editorProvider = + (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class); + assertTrue(editorProvider.getExtractedTextCache().isAlwaysUsePreExtractedCache()); + } + + @Test + public void booleanQuerySize() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("booleanClauseLimit", 4000); + MockOsgi.activate(service, context.bundleContext(), config); + + assertEquals(4000, BooleanQuery.getMaxClauseCount()); + } + + @Test + public void indexDefnStorafe() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("disableStoredIndexDefinition", true); + MockOsgi.activate(service, context.bundleContext(), config); + + assertTrue(IndexDefinition.isDisableStoredIndexDefinition()); + } + + + @Test + public void blobStoreRegistered() throws Exception{ + MockOsgi.activate(service, context.bundleContext(), getDefaultConfig()); + LuceneIndexEditorProvider editorProvider = + (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class); + assertNull(editorProvider.getBlobStore()); + + /* Register a blob store */ + CachingFileDataStore ds = DataStoreUtils + .createCachingFDS(folder.newFolder().getAbsolutePath(), + folder.newFolder().getAbsolutePath()); + + context.registerService(GarbageCollectableBlobStore.class, new DataStoreBlobStore(ds)); + reactivate(); + + editorProvider = + (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class); + assertNotNull(editorProvider.getBlobStore()); + } + + @Test + public void executorPoolBehaviour() throws Exception{ + MockOsgi.activate(service, context.bundleContext(), getDefaultConfig()); + ExecutorService executor = service.getExecutorService(); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + Callable cb1 = new Callable() { + @Override + public Object call() throws Exception { + latch1.await(); + return null; + } + }; + + Callable cb2 = new Callable() { + @Override + public Object call() throws Exception { + latch2.countDown(); + return null; + } + }; + + executor.submit(cb1); + executor.submit(cb2); + + //Even if one task gets stuck the other task must get completed + assertTrue("Second task not executed", latch2.await(1, TimeUnit.MINUTES)); + latch1.countDown(); + + MockOsgi.deactivate(service, context.bundleContext()); + } + + + @Test + public void singleBlobPerIndexFileConfig() throws Exception { + Map<String, Object> config = getDefaultConfig(); + config.put("enableSingleBlobIndexFiles", "true"); + MockOsgi.activate(service, context.bundleContext(), config); + assertTrue("Enabling property must reflect in BufferedOakDirectory state", + BufferedOakDirectory.isEnableWritingSingleBlobIndexFile()); + MockOsgi.deactivate(service, context.bundleContext()); + + config = getDefaultConfig(); + config.put("enableSingleBlobIndexFiles", "false"); + MockOsgi.activate(service, context.bundleContext(), config); + assertFalse("Enabling property must reflect in BufferedOakDirectory state", + BufferedOakDirectory.isEnableWritingSingleBlobIndexFile()); + MockOsgi.deactivate(service, context.bundleContext()); + } + + @Test + public void cleanerRegistration() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("propIndexCleanerIntervalInSecs", 142); + + MockOsgi.activate(service, context.bundleContext(), config); + ServiceReference[] sr = context.bundleContext().getAllServiceReferences(Runnable.class.getName(), + "(scheduler.name="+ PropertyIndexCleaner.class.getName()+")"); + assertEquals(sr.length, 1); + + assertEquals(142L, sr[0].getProperty("scheduler.period")); + } + + @Test + public void cleanerRegistrationDisabled() throws Exception{ + Map<String,Object> config = getDefaultConfig(); + config.put("propIndexCleanerIntervalInSecs", 0); + + MockOsgi.activate(service, context.bundleContext(), config); + ServiceReference[] sr = context.bundleContext().getAllServiceReferences(Runnable.class.getName(), + "(scheduler.name="+ PropertyIndexCleaner.class.getName()+")"); + assertNull(sr); + } + + private void reactivate() { + MockOsgi.deactivate(service, context.bundleContext()); + service = new LuceneIndexProviderService(); + + MockOsgi.injectServices(service, context.bundleContext()); + MockOsgi.activate(service, context.bundleContext(), getDefaultConfig()); + } + + private Map<String,Object> getDefaultConfig(){ + Map<String,Object> config = new HashMap<String, Object>(); + config.put("localIndexDir", folder.getRoot().getAbsolutePath()); + return config; + } +} Propchange: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java ------------------------------------------------------------------------------ svn:eol-style = native