Index: src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java (revision 1850224) +++ src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexTracker.java (working copy) @@ -40,6 +40,7 @@ import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.state.EqualsDiff; import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -207,13 +208,26 @@ @Nullable public LuceneIndexDefinition getIndexDefinition(String indexPath){ - LuceneIndexNodeManager node = indices.get(indexPath); - if (node != null){ - //Accessing the definition should not require - //locking as its immutable state - return node.getDefinition(); + LuceneIndexNodeManager indexNodeManager = indices.get(indexPath); + if (indexNodeManager != null) { + // Accessing the definition should not require + // locking as its immutable state + return indexNodeManager.getDefinition(); } - return null; + // fallback - create definition from scratch + NodeState node = NodeStateUtils.getNode(root, indexPath); + if (!node.exists()) { + return null; + } + // only if there exists a stored index definition + if (!node.hasChildNode(INDEX_DEFINITION_NODE)) { + return null; + } + if (!isLuceneIndexNode(node)) { + return null; + } + // this will internally use the stored index definition + return new LuceneIndexDefinition(root, node, indexPath); } public Set getIndexNodePaths(){ Index: src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (revision 1850224) +++ src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (working copy) @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; @@ -46,11 +47,13 @@ import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.commons.PerfLogger; import org.apache.jackrabbit.oak.plugins.index.lucene.util.fv.SimSearchUtils; +import org.apache.jackrabbit.oak.plugins.index.lucene.writer.LuceneIndexWriter; 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.IndexDefinition.IndexingRule; import org.apache.jackrabbit.oak.plugins.index.lucene.property.HybridPropertyIndexLookup; +import org.apache.jackrabbit.oak.plugins.index.lucene.reader.LuceneIndexReader; import org.apache.jackrabbit.oak.plugins.index.lucene.score.ScorerProviderFactory; import org.apache.jackrabbit.oak.plugins.index.lucene.spi.FulltextQueryTermsProvider; import org.apache.jackrabbit.oak.plugins.index.lucene.util.FacetHelper; @@ -120,6 +123,8 @@ import org.apache.lucene.search.postingshighlight.PostingsHighlighter; import org.apache.lucene.search.spell.SuggestWord; import org.apache.lucene.search.suggest.Lookup; +import org.apache.lucene.search.suggest.analyzing.AnalyzingInfixSuggester; +import org.apache.lucene.store.Directory; import org.apache.lucene.util.Version; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -688,8 +693,8 @@ } @Override - protected LuceneIndexNode acquireIndexNode(String indexPath) { - return tracker.acquireIndexNode(indexPath); + protected LazyLuceneIndexNode acquireIndexNode(String indexPath) { + return new LazyLuceneIndexNode(tracker, indexPath); } @Override @@ -1552,4 +1557,117 @@ return null; } } + + /** + * A index node implementation that acquires the underlying index only if + * actually needed. This is to avoid downloading the index for the planning + * phase, if there is no chance that the index is actually used. + */ + static class LazyLuceneIndexNode implements LuceneIndexNode { + + private static boolean NON_LAZY = Boolean.getBoolean("oak.lucene.nonLazyIndex"); + private AtomicBoolean released = new AtomicBoolean(); + private IndexTracker tracker; + private String indexPath; + private volatile LuceneIndexNode indexNode; + + LazyLuceneIndexNode(IndexTracker tracker, String indexPath) { + this.tracker = tracker; + this.indexPath = indexPath; + if (NON_LAZY) { + getIndexNode(); + } + } + + @Override + public void release() { + if (released.getAndSet(true)) { + // already released + return; + } + if (indexNode != null) { + indexNode.release(); + } + // to ensure it is not used after releasing + indexNode = null; + tracker = null; + indexPath = null; + } + + private void checkNotReleased() { + if (released.get()) { + throw new IllegalStateException("Already released"); + } + } + + @Override + public LuceneIndexDefinition getDefinition() { + checkNotReleased(); + return tracker.getIndexDefinition(indexPath); + } + + private LuceneIndexNode getIndexNode() { + checkNotReleased(); + LuceneIndexNode n = indexNode; + // double checked locking implemented in the correct way for Java 5 + // and newer (actually I don't think this is ever called + // concurrently right now, but better be save) + if (n == null) { + synchronized (this) { + n = indexNode; + if (n == null) { + n = indexNode = tracker.acquireIndexNode(indexPath); + } + } + } + return n; + } + + @Override + public int getIndexNodeId() { + return getIndexNode().getIndexNodeId(); + } + + @Override + public LuceneIndexStatistics getIndexStatistics() { + return getIndexNode().getIndexStatistics(); + } + + @Override + public IndexSearcher getSearcher() { + return getIndexNode().getSearcher(); + } + + @Override + public List getPrimaryReaders() { + return getIndexNode().getPrimaryReaders(); + } + + @Override + public @Nullable Directory getSuggestDirectory() { + return getIndexNode().getSuggestDirectory(); + } + + @Override + public List getNRTReaders() { + return getIndexNode().getNRTReaders(); + } + + @Override + public @Nullable AnalyzingInfixSuggester getLookup() { + return getIndexNode().getLookup(); + } + + @Override + public @Nullable LuceneIndexWriter getLocalWriter() throws IOException { + return getIndexNode().getLocalWriter(); + } + + @Override + public void refreshReadersOnWriteIfRequired() { + getIndexNode().refreshReadersOnWriteIfRequired(); + } + + } + } Index: src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java (revision 1850224) +++ src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java (working copy) @@ -2491,13 +2491,13 @@ prop1.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); root.commit(); - //force CoR - executeQuery("SELECT * FROM [mix:title]", SQL2); + // force CoR + executeQuery("select * from [mix:title] where [jcr:title] = 'x'", SQL2); assertNotNull(corDir); String localPathBeforeReindex = corDir; - //CoW with re-indexing + // CoW with re-indexing idx.setProperty("reindex", true); root.commit(); Index: src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java (revision 1850224) +++ src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/property/SynchronousPropertyIndexTest.java (working copy) @@ -264,6 +264,7 @@ addIndex(indexPath, defnb); root.commit(); + runAsyncIndex(); createPath("/a").setProperty("foo", "bar"); root.commit(); @@ -277,8 +278,8 @@ assertQuery("select * from [nt:base] where [foo] = 'bar'", asList("/a", "/b")); - //Do multiple runs which lead to path being returned from both property and lucene - //index. But the actual result should only contain unique paths + // Do multiple runs which lead to path being returned from both property and lucene + // index. But the actual result should only contain unique paths runAsyncIndex(); runAsyncIndex(); @@ -326,6 +327,7 @@ addIndex(indexPath, defnb); root.commit(); + runAsyncIndex(); assertThat(explain("select * from [nt:base] where [jcr:content/foo] = 'bar'"), containsString("sync:(foo[jcr:content/foo] bar)")); @@ -333,7 +335,6 @@ containsString("sync:(foo bar)")); } - @Test public void relativePropertyTransform() throws Exception{ defnb.async("async", "nrt"); @@ -341,6 +342,7 @@ addIndex(indexPath, defnb); root.commit(); + runAsyncIndex(); createPath("/a/jcr:content").setProperty("foo", "bar"); createPath("/b").setProperty("foo", "bar"); @@ -360,6 +362,7 @@ indexPath = "/content/oak:index/fooIndex"; addIndex(indexPath, defnb); root.commit(); + runAsyncIndex(); createPath("/a").setProperty("foo", "bar"); createPath("/content/a").setProperty("foo", "bar"); @@ -427,7 +430,7 @@ @Test public void nodeTypeIndexing() throws Exception{ - registerTestNodTypes(); + registerTestNodeTypes(); defnb.async("async", "nrt"); defnb.nodeTypeIndex(); @@ -435,6 +438,7 @@ addIndex(indexPath, defnb); root.commit(); + runAsyncIndex(); createPath("/a", "oak:TestSuperType"); createPath("/b", "oak:TestTypeB"); @@ -448,7 +452,7 @@ @Test public void nodeType_mixins() throws Exception{ - registerTestNodTypes(); + registerTestNodeTypes(); defnb.async("async", "nrt"); defnb.nodeTypeIndex(); @@ -456,6 +460,7 @@ addIndex(indexPath, defnb); root.commit(); + runAsyncIndex(); createPath("/a", "oak:Unstructured", singletonList("oak:TestMixA")); createPath("/b", "oak:TestTypeB"); @@ -466,10 +471,10 @@ assertQuery("select * from [oak:TestMixA]", asList("/a", "/b")); } - private void registerTestNodTypes() throws IOException, CommitFailedException { + private void registerTestNodeTypes() throws IOException, CommitFailedException { optionalEditorProvider.delegate = new TypeEditorProvider(); NodeTypeRegistry.register(root, IOUtils.toInputStream(testNodeTypes, "utf-8"), "test nodeType"); - //Flush the changes to nodetypes + // Flush the changes to nodetypes root.commit(); }