Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexNodeManager.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexNodeManager.java?rev=1841926&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexNodeManager.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexNodeManager.java
 Tue Sep 25 12:24:15 2018
@@ -0,0 +1,376 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.commons.PerfLogger;
+import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.NRTIndex;
+import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.NRTIndexFactory;
+import org.apache.jackrabbit.oak.plugins.index.lucene.reader.LuceneIndexReader;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.reader.LuceneIndexReaderFactory;
+import org.apache.jackrabbit.oak.plugins.index.lucene.writer.LuceneIndexWriter;
+import 
org.apache.jackrabbit.oak.plugins.index.search.update.ReaderRefreshPolicy;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.MultiReader;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.suggest.analyzing.AnalyzingInfixSuggester;
+import org.apache.lucene.store.Directory;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.ASYNC_PROPERTY_NAME;
+import static 
org.apache.jackrabbit.oak.plugins.index.IndexUtils.getAsyncLaneName;
+
+/**
+ * Keeps track of the open read sessions for an index.
+ */
+public class LuceneIndexNodeManager {
+    // TODO oak-search: should extend LuceneIndexNodeManager
+    /**
+     * Name of the hidden node under which information about the checkpoints
+     * seen and indexed by each async indexer is kept.
+     */
+    static final String ASYNC = ":async";
+
+    private static final AtomicInteger SEARCHER_ID_COUNTER = new 
AtomicInteger();
+
+    private static final PerfLogger PERF_LOGGER =
+            new 
PerfLogger(LoggerFactory.getLogger(LuceneIndexNodeManager.class.getName() + 
".perf"));
+
+    static LuceneIndexNodeManager open(String indexPath, NodeState root, 
NodeState defnNodeState,
+                                       LuceneIndexReaderFactory readerFactory, 
@Nullable NRTIndexFactory nrtFactory)
+            throws IOException {
+        LuceneIndexDefinition definition = new LuceneIndexDefinition(root, 
defnNodeState, indexPath);
+        List<LuceneIndexReader> readers = 
readerFactory.createReaders(definition, defnNodeState, indexPath);
+        NRTIndex nrtIndex = nrtFactory != null ? 
nrtFactory.createIndex(definition) : null;
+        if (!readers.isEmpty() || (nrtIndex != null && 
!hasAsyncIndexerRun(root, indexPath, defnNodeState))){
+            return new LuceneIndexNodeManager(PathUtils.getName(indexPath), 
definition, readers, nrtIndex);
+        }
+        return null;
+    }
+
+    static boolean hasAsyncIndexerRun(NodeState root, String indexPath, 
NodeState defnNodeState) {
+        boolean hasAsyncNode = root.hasChildNode(ASYNC);
+
+        String asyncLaneName = getAsyncLaneName(defnNodeState, indexPath, 
defnNodeState.getProperty(ASYNC_PROPERTY_NAME));
+
+        if (asyncLaneName != null) {
+            return hasAsyncNode && 
root.getChildNode(ASYNC).hasProperty(asyncLaneName);
+        } else {
+            // useful only for tests - basically non-async index defs which 
don't rely on /:async
+            // hence either readers are there (and this method doesn't come 
into play during open)
+            // OR there is no cycle (where we return false correctly)
+            return  false;
+        }
+    }
+
+    private static final Logger log = 
LoggerFactory.getLogger(LuceneIndexNodeManager.class);
+
+    private final List<LuceneIndexReader> readers;
+
+    private final String name;
+
+    private final LuceneIndexDefinition definition;
+
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+    private volatile SearcherHolder searcherHolder;
+
+    private final NRTIndex nrtIndex;
+
+    private final ReaderRefreshPolicy refreshPolicy;
+
+    private final Semaphore refreshLock = new Semaphore(1);
+
+    private final Runnable refreshCallback = new Runnable() {
+        @Override
+        public void run() {
+            if (refreshLock.tryAcquire()) {
+                try {
+                    refreshReaders();
+                }finally {
+                    refreshLock.release();
+                }
+            }
+        }
+    };
+
+    private boolean closed = false;
+
+    LuceneIndexNodeManager(String name, LuceneIndexDefinition definition, 
List<LuceneIndexReader> readers, @Nullable NRTIndex nrtIndex)
+            throws IOException {
+        checkArgument(!readers.isEmpty() || nrtIndex != null);
+        this.name = name;
+        this.definition = definition;
+        this.readers = readers;
+        this.nrtIndex = nrtIndex;
+        this.searcherHolder = createHolder(getNRTReaders());
+        this.refreshPolicy = nrtIndex != null ? nrtIndex.getRefreshPolicy() : 
ReaderRefreshPolicy.NEVER;
+    }
+
+    private String getName() {
+        return name;
+    }
+
+    LuceneIndexDefinition getDefinition() {
+        return definition;
+    }
+
+    @Nullable
+    private Directory getSuggestDirectory() {
+        return readers.isEmpty() ? null : 
getDefaultReader().getSuggestDirectory();
+    }
+
+    @Nullable
+    private AnalyzingInfixSuggester getLookup() {
+        return readers.isEmpty() ? null : getDefaultReader().getLookup();
+    }
+
+    @Nullable
+    LuceneIndexNode acquire() {
+        lock.readLock().lock();
+        if (closed) {
+            lock.readLock().unlock();
+            return null;
+        } else {
+            boolean success = false;
+            try {
+                refreshPolicy.refreshOnReadIfRequired(refreshCallback);
+                SearcherHolder local = searcherHolder;
+                int tryCount = 0;
+                while (!local.searcher.getIndexReader().tryIncRef()) {
+                    checkState(++tryCount < 10, "Not able to " +
+                            "get open searcher in %s attempts", tryCount);
+                    local = searcherHolder;
+                }
+                LuceneIndexNode indexNode = new IndexNodeImpl(local);
+                success = true;
+                return indexNode;
+            } finally {
+                if (!success) {
+                    lock.readLock().unlock();
+                }
+            }
+        }
+    }
+
+    private void release() {
+        lock.readLock().unlock();
+    }
+
+    void close() throws IOException {
+        lock.writeLock().lock();
+        try {
+            checkState(!closed);
+            closed = true;
+        } finally {
+            lock.writeLock().unlock();
+        }
+
+        releaseHolder(searcherHolder);
+        closeReaders(readers);
+    }
+
+    private List<LuceneIndexReader> getPrimaryReaders() {
+        return readers;
+    }
+
+    @Nullable
+    private LuceneIndexWriter getLocalWriter() throws IOException{
+        return nrtIndex != null ? nrtIndex.getWriter() : null;
+    }
+
+    private void refreshReadersOnWriteIfRequired() {
+        refreshPolicy.refreshOnWriteIfRequired(refreshCallback);
+    }
+
+    private void refreshReaders(){
+        long start = PERF_LOGGER.start();
+        List<LuceneIndexReader> newNRTReaders = getNRTReaders();
+        //The list reference would differ if index got updated
+        //so if they are same no need to reinitialize the searcher
+        if (newNRTReaders != searcherHolder.nrtReaders) {
+            SearcherHolder old = searcherHolder;
+            searcherHolder = createHolder(newNRTReaders);
+            releaseHolder(old);
+            PERF_LOGGER.end(start, 0, "Refreshed reader for index [{}]", 
definition);
+        }
+    }
+
+    private LuceneIndexReader getDefaultReader(){
+        //TODO This is still required to support Suggester, Spellcheck etc 
OAK-4643
+        return readers.get(0);
+    }
+
+    private IndexReader createReader(List<LuceneIndexReader> nrtReaders) {
+        //Increment count by 1. MultiReader does it for all readers
+        //So no need for an explicit increment for MultiReader
+
+        if (readers.size() == 1 && nrtReaders.isEmpty()){
+            IndexReader reader = readers.get(0).getReader();
+            reader.incRef();
+            return reader;
+        }
+        if (nrtReaders.size() == 1 && readers.isEmpty()){
+            IndexReader reader = nrtReaders.get(0).getReader();
+            reader.incRef();
+            return reader;
+        }
+
+        IndexReader[] readerArr = new IndexReader[readers.size() + 
nrtReaders.size()];
+        int i = 0;
+        for (LuceneIndexReader r : Iterables.concat(readers, nrtReaders)){
+            readerArr[i++] = r.getReader();
+        }
+        return new MultiReader(readerArr, false);
+    }
+
+    private List<LuceneIndexReader> getNRTReaders() {
+        return nrtIndex != null ? nrtIndex.getReaders() : 
Collections.<LuceneIndexReader>emptyList();
+    }
+
+    private SearcherHolder createHolder(List<LuceneIndexReader> newNRTReaders) 
{
+        return new SearcherHolder(new 
IndexSearcher(createReader(newNRTReaders)), newNRTReaders);
+    }
+
+    private void closeReaders(Iterable<LuceneIndexReader> readers) {
+        for (LuceneIndexReader r : readers){
+            try {
+                r.close();
+            } catch (IOException e) {
+                log.warn("Error occurred while releasing reader for index 
[{}]", definition.getIndexPath(), e);
+            }
+        }
+    }
+
+    private void releaseHolder(SearcherHolder holder) {
+        decrementSearcherUsageCount(holder.searcher);
+    }
+
+    private void decrementSearcherUsageCount(IndexSearcher searcher) {
+        try {
+            //Decrement the count by 1 as we increased it while creating the 
searcher
+            //in createReader
+            searcher.getIndexReader().decRef();
+        } catch (IOException e) {
+            log.warn("Error occurred while releasing reader for index [{}]", 
definition.getIndexPath(), e);
+        }
+    }
+
+    private static class SearcherHolder {
+        final IndexSearcher searcher;
+        final List<LuceneIndexReader> nrtReaders;
+        final int searcherId = SEARCHER_ID_COUNTER.incrementAndGet();
+        final LuceneIndexStatistics indexStatistics;
+
+        public SearcherHolder(IndexSearcher searcher, List<LuceneIndexReader> 
nrtReaders) {
+            this.searcher = searcher;
+            this.nrtReaders = nrtReaders;
+            this.indexStatistics = new 
LuceneIndexStatistics(searcher.getIndexReader());
+        }
+
+        public LuceneIndexStatistics getIndexStatistics() {
+            return indexStatistics;
+        }
+    }
+
+    private class IndexNodeImpl implements LuceneIndexNode {
+        private final SearcherHolder holder;
+        private final AtomicBoolean released = new AtomicBoolean();
+
+        private IndexNodeImpl(SearcherHolder searcherHolder) {
+            this.holder = searcherHolder;
+        }
+
+        @Override
+        public void release() {
+            if (released.compareAndSet(false, true)) {
+                try {
+                    //Decrement on each release
+                    decrementSearcherUsageCount(holder.searcher);
+                } finally {
+                    LuceneIndexNodeManager.this.release();
+                }
+            }
+        }
+
+        @Override
+        public IndexSearcher getSearcher() {
+            return holder.searcher;
+        }
+
+        @Override
+        public LuceneIndexStatistics getIndexStatistics() {
+            return holder.getIndexStatistics();
+        }
+
+        @Override
+        public LuceneIndexDefinition getDefinition() {
+            return definition;
+        }
+
+        @Override
+        public List<LuceneIndexReader> getPrimaryReaders() {
+            return LuceneIndexNodeManager.this.getPrimaryReaders();
+        }
+
+        @Override
+        public Directory getSuggestDirectory() {
+            return LuceneIndexNodeManager.this.getSuggestDirectory();
+        }
+
+        @Override
+        public List<LuceneIndexReader> getNRTReaders() {
+            return holder.nrtReaders;
+        }
+
+        @Override
+        public AnalyzingInfixSuggester getLookup() {
+            return LuceneIndexNodeManager.this.getLookup();
+        }
+
+        @Override
+        public int getIndexNodeId() {
+            return holder.searcherId;
+        }
+
+        @Override
+        public LuceneIndexWriter getLocalWriter() throws IOException {
+            return LuceneIndexNodeManager.this.getLocalWriter();
+        }
+
+        @Override
+        public void refreshReadersOnWriteIfRequired() {
+            LuceneIndexNodeManager.this.refreshReadersOnWriteIfRequired();
+        }
+    }
+
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexNodeManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java?rev=1841926&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
 Tue Sep 25 12:24:15 2018
@@ -0,0 +1,108 @@
+/*
+ * 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.Closeable;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.jackrabbit.oak.plugins.index.aggregate.AggregateIndex;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.score.ScorerProviderFactory;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A provider for Lucene indexes.
+ * 
+ * @see LuceneIndex
+ */
+public class LuceneIndexProvider implements QueryIndexProvider, Observer, 
Closeable {
+
+    protected final IndexTracker tracker;
+
+    protected volatile QueryIndex.NodeAggregator aggregator = null;
+
+    ScorerProviderFactory scorerFactory;
+
+    IndexAugmentorFactory augmentorFactory;
+
+    public LuceneIndexProvider() {
+        this(new IndexTracker());
+    }
+
+    public LuceneIndexProvider(IndexCopier indexCopier) {
+        this(new IndexTracker(indexCopier));
+    }
+
+    public LuceneIndexProvider(IndexTracker tracker) {
+        this(tracker, ScorerProviderFactory.DEFAULT, null);
+    }
+
+    public LuceneIndexProvider(IndexTracker tracker, ScorerProviderFactory 
scorerFactory, IndexAugmentorFactory augmentorFactory) {
+        this.tracker = tracker;
+        this.scorerFactory = scorerFactory;
+        this.augmentorFactory = augmentorFactory;
+    }
+
+    public void close() {
+        tracker.close();
+    }
+
+    //----------------------------------------------------------< Observer >--
+
+    @Override
+    public void contentChanged(@NotNull NodeState root, @NotNull CommitInfo 
info) {
+        tracker.update(root);
+    }
+
+    //------------------------------------------------< QueryIndexProvider >--
+
+    @Override @NotNull
+    public List<QueryIndex> getQueryIndexes(NodeState nodeState) {
+        return ImmutableList.<QueryIndex> of(new 
AggregateIndex(newLuceneIndex()), newLucenePropertyIndex());
+    }
+
+    protected LuceneIndex newLuceneIndex() {
+        return new LuceneIndex(tracker, aggregator);
+    }
+
+    protected LucenePropertyIndex newLucenePropertyIndex() {
+        return new LucenePropertyIndex(tracker, scorerFactory, 
augmentorFactory);
+    }
+
+    /**
+     * sets the default node aggregator that will be used at query time
+     */
+    public void setAggregator(QueryIndex.NodeAggregator aggregator) {
+        this.aggregator = aggregator;
+    }
+
+    // ----- helper builder method
+
+    public LuceneIndexProvider with(QueryIndex.NodeAggregator analyzer) {
+        this.setAggregator(analyzer);
+        return this;
+    }
+
+    IndexTracker getTracker() {
+        return tracker;
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java?rev=1841926&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java
 Tue Sep 25 12:24:15 2018
@@ -0,0 +1,822 @@
+/*
+ * 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.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.management.NotCompliantMBeanException;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.ReferencePolicyOption;
+import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
+import org.apache.jackrabbit.oak.api.jmx.CheckpointMBean;
+import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
+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.IndexInfoProvider;
+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.directory.ActiveDeletedBlobCollectorFactory;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.BufferedOakDirectory;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.directory.LuceneIndexImporter;
+import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.DocumentQueue;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.ExternalObserverBuilder;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.LocalIndexObserver;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.LuceneJournalPropertyService;
+import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.NRTIndexFactory;
+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.index.search.TextExtractionStatsMBean;
+import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
+import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver;
+import org.apache.jackrabbit.oak.spi.commit.BackgroundObserverMBean;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.gc.GCMonitor;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.state.Clusterable;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.stats.Clock;
+import org.apache.jackrabbit.oak.stats.StatisticsProvider;
+import org.apache.lucene.analysis.util.CharFilterFactory;
+import org.apache.lucene.analysis.util.TokenFilterFactory;
+import org.apache.lucene.analysis.util.TokenizerFactory;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.util.InfoStream;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Collections.emptyMap;
+import static org.apache.commons.io.FileUtils.ONE_MB;
+import static 
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE;
+import static 
org.apache.jackrabbit.oak.spi.blob.osgi.SplitBlobStoreService.ONLY_STANDALONE_TARGET;
+import static 
org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerMBean;
+import static 
org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.scheduleWithFixedDelay;
+
+@SuppressWarnings("UnusedDeclaration")
+@Component(metatype = true, label = "Apache Jackrabbit Oak 
LuceneIndexProvider")
+public class LuceneIndexProviderService {
+    public static final String REPOSITORY_HOME = "repository.home";
+
+    private LuceneIndexProvider indexProvider;
+
+    private final List<ServiceRegistration> regs = Lists.newArrayList();
+    private final List<Registration> oakRegs = Lists.newArrayList();
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY,
+            policyOption = ReferencePolicyOption.GREEDY,
+            policy = ReferencePolicy.DYNAMIC
+    )
+    private QueryIndex.NodeAggregator nodeAggregator;
+
+    private static final boolean PROP_DISABLED_DEFAULT = false;
+
+    @Property(
+            boolValue = PROP_DISABLED_DEFAULT,
+            label = "Disable this component",
+            description = "If true, this component is disabled."
+    )
+    private static final String PROP_DISABLED = "disabled";
+
+    @Property(
+            boolValue = false,
+            label = "Enable Debug Logging",
+            description = "Enables debug logging in Lucene. After enabling 
this actual logging can be " +
+            "controlled via changing log level for category 'oak.lucene' to 
debug")
+    private static final String PROP_DEBUG = "debug";
+
+    @Property(
+            boolValue = true,
+            label = "Enable CopyOnRead",
+            description = "Enable copying of Lucene index to local file system 
to improve query performance",
+            propertyPrivate = true
+    )
+    private static final String PROP_COPY_ON_READ = "enableCopyOnReadSupport";
+
+    @Property(
+            label = "Local index storage path",
+            description = "Local file system path where Lucene indexes would 
be copied when CopyOnRead is enabled. " +
+                    "If not specified then indexes would be stored under 
'index' dir under Repository Home"
+    )
+    private static final String PROP_LOCAL_INDEX_DIR = "localIndexDir";
+
+
+    private static final boolean PROP_COPY_ON_WRITE_DEFAULT = true;
+    @Property(
+            boolValue = PROP_COPY_ON_WRITE_DEFAULT,
+            label = "Enable CopyOnWrite",
+            description = "Enable copying of Lucene index to local file system 
to improve index writer performance",
+            propertyPrivate = true
+    )
+    private static final String PROP_COPY_ON_WRITE = 
"enableCopyOnWriteSupport";
+
+    @Property(
+            boolValue = true,
+            label = "Open index asynchronously",
+            description = "Enable opening of indexes in asynchronous mode"
+    )
+    private static final String PROP_ASYNC_INDEX_OPEN = "enableOpenIndexAsync";
+
+    private static final int PROP_THREAD_POOL_SIZE_DEFAULT = 5;
+    @Property(
+            intValue = PROP_THREAD_POOL_SIZE_DEFAULT,
+            label = "Thread pool size",
+            description = "Thread pool size used to perform various 
asynchronous task in Oak Lucene"
+    )
+    private static final String PROP_THREAD_POOL_SIZE = "threadPoolSize";
+
+    private static final boolean PROP_PREFETCH_INDEX_FILES_DEFAULT = true;
+    @Property(
+            boolValue = PROP_PREFETCH_INDEX_FILES_DEFAULT,
+            label = "Prefetch Index Files",
+            description = "Prefetch the index files when CopyOnRead is 
enabled. When enabled all new Lucene" +
+                    " index files would be copied locally before the index is 
made available to QueryEngine"
+    )
+    private static final String PROP_PREFETCH_INDEX_FILES = 
"prefetchIndexFiles";
+
+    private static final int PROP_EXTRACTED_TEXT_CACHE_SIZE_DEFAULT = 20;
+    @Property(
+            intValue = PROP_EXTRACTED_TEXT_CACHE_SIZE_DEFAULT,
+            label = "Extracted text cache size (MB)",
+            description = "Cache size in MB for caching extracted text for 
some time. When set to 0 then " +
+                    "cache would be disabled"
+    )
+    private static final String PROP_EXTRACTED_TEXT_CACHE_SIZE = 
"extractedTextCacheSizeInMB";
+
+    private static final int PROP_EXTRACTED_TEXT_CACHE_EXPIRY_DEFAULT = 300;
+    @Property(
+            intValue = PROP_EXTRACTED_TEXT_CACHE_EXPIRY_DEFAULT,
+            label = "Extracted text cache expiry (secs)",
+            description = "Time in seconds for which the extracted text would 
be cached in memory"
+    )
+    private static final String PROP_EXTRACTED_TEXT_CACHE_EXPIRY = 
"extractedTextCacheExpiryInSecs";
+
+    private static final boolean PROP_PRE_EXTRACTED_TEXT_ALWAYS_USE_DEFAULT = 
false;
+    @Property(
+            boolValue = PROP_PRE_EXTRACTED_TEXT_ALWAYS_USE_DEFAULT,
+            label = "Always use pre-extracted text cache",
+            description = "By default pre extracted text cache would only be 
used for reindex case. If this setting " +
+                    "is enabled then it would also be used in normal 
incremental indexing"
+    )
+    private static final String PROP_PRE_EXTRACTED_TEXT_ALWAYS_USE = 
"alwaysUsePreExtractedCache";
+
+    private static final int PROP_BOOLEAN_CLAUSE_LIMIT_DEFAULT = 1024;
+    @Property(
+            intValue = PROP_BOOLEAN_CLAUSE_LIMIT_DEFAULT,
+            label = "Boolean Clause Limit",
+            description = "Limit for number of boolean clauses generated for 
handling of OR query"
+    )
+    private static final String PROP_BOOLEAN_CLAUSE_LIMIT = 
"booleanClauseLimit";
+
+    private static final boolean PROP_HYBRID_INDEXING_DEFAULT = true;
+    @Property(
+            boolValue = PROP_HYBRID_INDEXING_DEFAULT,
+            label = "Hybrid Indexing",
+            description = "When enabled Lucene NRT Indexing mode would be 
enabled"
+    )
+    private static final String PROP_HYBRID_INDEXING = "enableHybridIndexing";
+
+    private static final int PROP_HYBRID_QUEUE_SIZE_DEFAULT = 10000;
+    @Property(
+            intValue = PROP_HYBRID_QUEUE_SIZE_DEFAULT,
+            label = "Queue size",
+            description = "Size of in memory queue used for storing Lucene 
Documents which need to be " +
+                    "added to local index"
+    )
+    private static final String PROP_HYBRID_QUEUE_SIZE = "hybridQueueSize";
+
+    private static final boolean PROP_DISABLE_DEFN_STORAGE_DEFAULT = false;
+    @Property(
+            boolValue = PROP_DISABLE_DEFN_STORAGE_DEFAULT,
+            label = "Disable index definition storage",
+            description = "By default index definitions would be stored at 
time of reindexing to ensure that future " +
+                    "modifications to it are not effective untill index is 
reindex. Set this to true would disable " +
+                    "this feature"
+    )
+    private static final String PROP_DISABLE_STORED_INDEX_DEFINITION = 
"disableStoredIndexDefinition";
+
+    private static final boolean PROP_DELETED_BLOB_COLLECTION_DEFAULT_ENABLED 
= true;
+    @Property(
+            boolValue = PROP_DELETED_BLOB_COLLECTION_DEFAULT_ENABLED,
+            label = "Enable actively removing deleted index blobs from blob 
store",
+            description = "Index blobs are explicitly unique and don't require 
mark-sweep type collection." +
+                    "This is used to enable the feature. Cleanup implies 
purging index blobs marked as deleted " +
+                    "earlier during some indexing cycle."
+    )
+    private static final String 
PROP_NAME_DELETED_BLOB_COLLECTION_DEFAULT_ENABLED = 
"deletedBlobsCollectionEnabled";
+
+    private static final int PROP_INDEX_CLEANER_INTERVAL_DEFAULT = 10*60;
+    @Property(
+            intValue = PROP_INDEX_CLEANER_INTERVAL_DEFAULT,
+            label = "Property Index Cleaner Interval (seconds)",
+            description = "Cleaner interval time (in seconds) for synchronous 
property indexes configured as " +
+                    "part of lucene indexes"
+    )
+    private static final String PROP_INDEX_CLEANER_INTERVAL = 
"propIndexCleanerIntervalInSecs";
+
+    private static final boolean 
PROP_ENABLE_SINGLE_BLOB_PER_INDEX_FILE_DEFAULT = true;
+    @Property(
+            boolValue = PROP_ENABLE_SINGLE_BLOB_PER_INDEX_FILE_DEFAULT,
+            label = "With CoW enabled, should index files by written as single 
blobs",
+            description = "Index files can be written as single blobs as 
chunked into smaller blobs. Enable" +
+                    " this to write single blob per index file. This would 
reduce number of blobs in the data store."
+    )
+    private static final String PROP_NAME_ENABLE_SINGLE_BLOB_PER_INDEX_FILE = 
"enableSingleBlobIndexFiles";
+
+    private final Clock clock = Clock.SIMPLE;
+
+    private Whiteboard whiteboard;
+
+    private BackgroundObserver backgroundObserver;
+
+    private BackgroundObserver externalIndexObserver;
+
+    @Reference
+    ScorerProviderFactory scorerFactory;
+
+    @Reference
+    private IndexAugmentorFactory augmentorFactory;
+
+    @Reference
+    private StatisticsProvider statisticsProvider;
+
+    @Reference(policy = ReferencePolicy.DYNAMIC,
+            cardinality = ReferenceCardinality.OPTIONAL_UNARY,
+            policyOption = ReferencePolicyOption.GREEDY
+    )
+    private volatile PreExtractedTextProvider extractedTextProvider;
+
+    @Reference
+    private MountInfoProvider mountInfoProvider;
+
+    @Reference
+    private NodeStore nodeStore;
+
+    @Reference
+    private IndexPathService indexPathService;
+
+    @Reference
+    private AsyncIndexInfoService asyncIndexInfoService;
+
+    @Reference(
+            cardinality = ReferenceCardinality.OPTIONAL_UNARY,
+            policy = ReferencePolicy.STATIC,
+            policyOption = ReferencePolicyOption.GREEDY,
+            target = ONLY_STANDALONE_TARGET
+    )
+    private GarbageCollectableBlobStore blobStore;
+
+    @Reference
+    private CheckpointMBean checkpointMBean;
+
+    private IndexCopier indexCopier;
+
+    private ActiveDeletedBlobCollectorFactory.ActiveDeletedBlobCollector 
activeDeletedBlobCollector;
+
+    private File indexDir;
+
+    private ExecutorService executorService;
+
+    private int threadPoolSize;
+
+    private ExtractedTextCache extractedTextCache;
+
+    private boolean hybridIndex;
+
+    private NRTIndexFactory nrtIndexFactory;
+
+    private DocumentQueue documentQueue;
+
+    private LuceneIndexEditorProvider editorProvider;
+
+    private IndexTracker tracker;
+
+    private PropertyIndexCleaner cleaner;
+
+    @Activate
+    private void activate(BundleContext bundleContext, Map<String, ?> config)
+            throws NotCompliantMBeanException, IOException {
+        boolean disabled = PropertiesUtil.toBoolean(config.get(PROP_DISABLED), 
PROP_DISABLED_DEFAULT);
+        hybridIndex = 
PropertiesUtil.toBoolean(config.get(PROP_HYBRID_INDEXING), 
PROP_DISABLED_DEFAULT);
+
+        if (disabled) {
+            log.info("Component disabled by configuration");
+            return;
+        }
+
+        configureIndexDefinitionStorage(config);
+        configureBooleanClauseLimit(config);
+        initializeFactoryClassLoaders(getClass().getClassLoader());
+        if 
(System.getProperty(BufferedOakDirectory.ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM)
 == null) {
+            
BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(PropertiesUtil.toBoolean(
+                    config.get(PROP_NAME_ENABLE_SINGLE_BLOB_PER_INDEX_FILE),
+                    PROP_ENABLE_SINGLE_BLOB_PER_INDEX_FILE_DEFAULT));
+        } else {
+            log.info("Not setting config for single blob for an index file as 
it's set by command line!");
+        }
+
+        whiteboard = new OsgiWhiteboard(bundleContext);
+        threadPoolSize = 
PropertiesUtil.toInteger(config.get(PROP_THREAD_POOL_SIZE), 
PROP_THREAD_POOL_SIZE_DEFAULT);
+        initializeIndexDir(bundleContext, config);
+        initializeExtractedTextCache(bundleContext, config);
+        tracker = createTracker(bundleContext, config);
+        indexProvider = new LuceneIndexProvider(tracker, scorerFactory, 
augmentorFactory);
+        initializeActiveBlobCollector(whiteboard, config);
+        initializeLogging(config);
+        initialize();
+
+        
regs.add(bundleContext.registerService(QueryIndexProvider.class.getName(), 
indexProvider, null));
+        registerObserver(bundleContext, config);
+        registerLocalIndexObserver(bundleContext, tracker, config);
+        registerIndexEditor(bundleContext, tracker, config);
+        registerIndexInfoProvider(bundleContext);
+        registerIndexImporterProvider(bundleContext);
+        registerPropertyIndexCleaner(config, bundleContext);
+
+        oakRegs.add(registerMBean(whiteboard,
+                LuceneIndexMBean.class,
+                new LuceneIndexMBeanImpl(indexProvider.getTracker(), 
nodeStore, indexPathService, getIndexCheckDir(), cleaner),
+                LuceneIndexMBean.TYPE,
+                "Lucene Index statistics"));
+        registerGCMonitor(whiteboard, indexProvider.getTracker());
+    }
+
+    private File getIndexCheckDir() {
+        return new File(checkNotNull(indexDir), "indexCheckDir");
+    }
+
+    @Deactivate
+    private void deactivate() throws InterruptedException, IOException {
+        for (ServiceRegistration reg : regs) {
+            reg.unregister();
+        }
+
+        for (Registration reg : oakRegs){
+            reg.unregister();
+        }
+
+        if (backgroundObserver != null){
+            backgroundObserver.close();
+        }
+
+        if (externalIndexObserver != null){
+            externalIndexObserver.close();
+        }
+
+        if (indexProvider != null) {
+            indexProvider.close();
+            indexProvider = null;
+        }
+
+        if (documentQueue != null){
+            documentQueue.close();
+        }
+
+        if (nrtIndexFactory != null){
+            nrtIndexFactory.close();
+        }
+
+        //Close the copier first i.e. before executorService
+        if (indexCopier != null){
+            indexCopier.close();
+        }
+
+        if (executorService != null){
+            executorService.shutdown();
+            executorService.awaitTermination(1, TimeUnit.MINUTES);
+        }
+
+        if (extractedTextCache != null) {
+            extractedTextCache.close();
+        }
+
+        InfoStream.setDefault(InfoStream.NO_OUTPUT);
+    }
+
+    void initializeIndexDir(BundleContext bundleContext, Map<String, ?> 
config) {
+        String indexDirPath = 
PropertiesUtil.toString(config.get(PROP_LOCAL_INDEX_DIR), null);
+        if (Strings.isNullOrEmpty(indexDirPath)) {
+            String repoHome = bundleContext.getProperty(REPOSITORY_HOME);
+            if (repoHome != null){
+                indexDirPath = FilenameUtils.concat(repoHome, "index");
+            }
+        }
+
+        checkNotNull(indexDirPath, "Index directory cannot be determined as 
neither index " +
+                "directory path [%s] nor repository home [%s] defined", 
PROP_LOCAL_INDEX_DIR, REPOSITORY_HOME);
+
+        indexDir = new File(indexDirPath);
+    }
+
+    IndexCopier getIndexCopier() {
+        return indexCopier;
+    }
+
+    ExtractedTextCache getExtractedTextCache() {
+        return extractedTextCache;
+    }
+
+    private void initialize(){
+        if(indexProvider == null){
+            return;
+        }
+
+        if(nodeAggregator != null){
+            log.debug("Using NodeAggregator {}", nodeAggregator.getClass());
+        }
+
+        indexProvider.setAggregator(nodeAggregator);
+    }
+
+    private void initializeLogging(Map<String, ?> config) {
+        boolean debug = PropertiesUtil.toBoolean(config.get(PROP_DEBUG), 
false);
+        if (debug) {
+            InfoStream.setDefault(LoggingInfoStream.INSTANCE);
+            log.info("Registered LoggingInfoStream with Lucene. Lucene logs 
can be enabled " +
+                    "now via category [{}]", LoggingInfoStream.PREFIX);
+        }
+    }
+
+    private void registerIndexEditor(BundleContext bundleContext, IndexTracker 
tracker, Map<String, ?> config) throws IOException {
+        boolean enableCopyOnWrite = 
PropertiesUtil.toBoolean(config.get(PROP_COPY_ON_WRITE), 
PROP_COPY_ON_WRITE_DEFAULT);
+        if (enableCopyOnWrite){
+            initializeIndexCopier(bundleContext, config);
+            editorProvider = new LuceneIndexEditorProvider(indexCopier, 
tracker, extractedTextCache,
+                    augmentorFactory,  mountInfoProvider, 
activeDeletedBlobCollector);
+            log.info("Enabling CopyOnWrite support. Index files would be 
copied under {}", indexDir.getAbsolutePath());
+        } else {
+            editorProvider = new LuceneIndexEditorProvider(null, tracker, 
extractedTextCache, augmentorFactory,
+                    mountInfoProvider, activeDeletedBlobCollector);
+        }
+        editorProvider.setBlobStore(blobStore);
+
+        if (hybridIndex){
+            editorProvider.setIndexingQueue(checkNotNull(documentQueue));
+        }
+
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put("type", TYPE_LUCENE);
+        
regs.add(bundleContext.registerService(IndexEditorProvider.class.getName(), 
editorProvider, props));
+        oakRegs.add(registerMBean(whiteboard,
+                TextExtractionStatsMBean.class,
+                editorProvider.getExtractedTextCache().getStatsMBean(),
+                TextExtractionStatsMBean.TYPE,
+                "TextExtraction statistics"));
+    }
+
+    private IndexTracker createTracker(BundleContext bundleContext, 
Map<String, ?> config) throws IOException {
+        boolean enableCopyOnRead = 
PropertiesUtil.toBoolean(config.get(PROP_COPY_ON_READ), true);
+        IndexTracker tracker;
+        if (enableCopyOnRead){
+            initializeIndexCopier(bundleContext, config);
+            log.info("Enabling CopyOnRead support. Index files would be copied 
under {}", indexDir.getAbsolutePath());
+            if (hybridIndex) {
+                nrtIndexFactory = new NRTIndexFactory(indexCopier, 
statisticsProvider);
+            }
+            tracker = new IndexTracker(new 
DefaultIndexReaderFactory(mountInfoProvider, indexCopier), nrtIndexFactory);
+        } else {
+            tracker = new IndexTracker(new 
DefaultIndexReaderFactory(mountInfoProvider, null));
+        }
+
+        tracker.setAsyncIndexInfoService(asyncIndexInfoService);
+        return tracker;
+    }
+
+    private void initializeIndexCopier(BundleContext bundleContext, 
Map<String, ?> config) throws IOException {
+        if(indexCopier != null){
+            return;
+        }
+        boolean prefetchEnabled = 
PropertiesUtil.toBoolean(config.get(PROP_PREFETCH_INDEX_FILES),
+                PROP_PREFETCH_INDEX_FILES_DEFAULT);
+
+        if (prefetchEnabled){
+            log.info("Prefetching of index files enabled. Index would be 
opened after copying all new files locally");
+        }
+
+        indexCopier = new IndexCopier(getExecutorService(), indexDir, 
prefetchEnabled);
+
+        oakRegs.add(registerMBean(whiteboard,
+                CopyOnReadStatsMBean.class,
+                indexCopier,
+                CopyOnReadStatsMBean.TYPE,
+                "IndexCopier support statistics"));
+
+    }
+
+    ExecutorService getExecutorService(){
+        if (executorService == null){
+            executorService = createExecutor();
+        }
+        return executorService;
+    }
+
+    private ExecutorService createExecutor() {
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60L, 
TimeUnit.SECONDS,
+                new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {
+            private final AtomicInteger counter = new AtomicInteger();
+            private final Thread.UncaughtExceptionHandler handler = new 
Thread.UncaughtExceptionHandler() {
+                @Override
+                public void uncaughtException(Thread t, Throwable e) {
+                    log.warn("Error occurred in asynchronous processing ", e);
+                }
+            };
+            @Override
+            public Thread newThread(@NotNull Runnable r) {
+                Thread thread = new Thread(r, createName());
+                thread.setDaemon(true);
+                thread.setPriority(Thread.MIN_PRIORITY);
+                thread.setUncaughtExceptionHandler(handler);
+                return thread;
+            }
+
+            private String createName() {
+                return "oak-lucene-" + counter.getAndIncrement();
+            }
+        });
+        executor.setKeepAliveTime(1, TimeUnit.MINUTES);
+        executor.allowCoreThreadTimeOut(true);
+        return executor;
+    }
+
+    private void registerObserver(BundleContext bundleContext, Map<String, ?> 
config) {
+        boolean enableAsyncIndexOpen = 
PropertiesUtil.toBoolean(config.get(PROP_ASYNC_INDEX_OPEN), true);
+        Observer observer = indexProvider;
+        if (enableAsyncIndexOpen) {
+            backgroundObserver = new BackgroundObserver(indexProvider, 
getExecutorService(), 5);
+            observer = backgroundObserver;
+            oakRegs.add(registerMBean(whiteboard,
+                    BackgroundObserverMBean.class,
+                    backgroundObserver.getMBean(),
+                    BackgroundObserverMBean.TYPE,
+                    "LuceneIndexConfigObserver queue stats"));
+            log.info("Registering the LuceneIndexProvider as a 
BackgroundObserver");
+        }
+        regs.add(bundleContext.registerService(Observer.class.getName(), 
observer, null));
+    }
+
+    private void registerLocalIndexObserver(BundleContext bundleContext, 
IndexTracker tracker, Map<String, ?> config) {
+        if (!hybridIndex){
+            log.info("Hybrid indexing feature disabled");
+            return;
+        }
+
+        int queueSize = 
PropertiesUtil.toInteger(config.get(PROP_HYBRID_QUEUE_SIZE), 
PROP_HYBRID_QUEUE_SIZE_DEFAULT);
+        documentQueue = new DocumentQueue(queueSize, tracker, 
getExecutorService(), statisticsProvider);
+        LocalIndexObserver localIndexObserver = new 
LocalIndexObserver(documentQueue, statisticsProvider);
+        regs.add(bundleContext.registerService(Observer.class.getName(), 
localIndexObserver, null));
+
+        int observerQueueSize = 1000;
+        int builderMaxSize = 5000;
+        
regs.add(bundleContext.registerService(JournalPropertyService.class.getName(),
+                new LuceneJournalPropertyService(builderMaxSize), null));
+        ExternalObserverBuilder builder = new 
ExternalObserverBuilder(documentQueue, tracker, statisticsProvider,
+                getExecutorService(), observerQueueSize);
+        log.info("Configured JournalPropertyBuilder with max size {} and 
backed by BackgroundObserver " +
+                "with queue size {}", builderMaxSize, observerQueueSize);
+
+        Observer observer = builder.build();
+        externalIndexObserver = builder.getBackgroundObserver();
+        regs.add(bundleContext.registerService(Observer.class.getName(), 
observer, null));
+        oakRegs.add(registerMBean(whiteboard,
+                BackgroundObserverMBean.class,
+                externalIndexObserver.getMBean(),
+                BackgroundObserverMBean.TYPE,
+                "LuceneExternalIndexObserver queue stats"));
+        log.info("Hybrid indexing enabled for configured indexes with queue 
size of {}", queueSize);
+    }
+
+    private void initializeFactoryClassLoaders(ClassLoader classLoader) {
+        ClassLoader originalClassLoader = Thread.currentThread()
+                .getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(classLoader);
+            //Access TokenizerFactory etc trigger a static initialization
+            //so switch the TCCL so that static initializer picks up the right
+            //classloader
+            initializeFactoryClassLoaders0(classLoader);
+            initializeClasses();
+        } catch (Throwable t) {
+            log.warn("Error occurred while initializing the Lucene " +
+                    "Factories", t);
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader);
+        }
+    }
+
+    private void initializeFactoryClassLoaders0(ClassLoader classLoader) {
+        //Factories use the Threads context classloader to perform SPI classes
+        //lookup by default which would not work in OSGi world. So reload the
+        //factories by providing the bundle classloader
+        TokenizerFactory.reloadTokenizers(classLoader);
+        CharFilterFactory.reloadCharFilters(classLoader);
+        TokenFilterFactory.reloadTokenFilters(classLoader);
+    }
+
+    private void initializeClasses() {
+        // prevent LUCENE-6482
+        // (also done in IndexDefinition, just to be save)
+        OakCodec ensureLucene46CodecLoaded = new OakCodec();
+        // to ensure the JVM doesn't optimize away object creation
+        // (probably not really needed; just to be save)
+        log.debug("Lucene46Codec is loaded: {}", ensureLucene46CodecLoaded);
+    }
+
+    private void initializeExtractedTextCache(BundleContext bundleContext, 
Map<String, ?> config) {
+        int cacheSizeInMB = 
PropertiesUtil.toInteger(config.get(PROP_EXTRACTED_TEXT_CACHE_SIZE),
+                PROP_EXTRACTED_TEXT_CACHE_SIZE_DEFAULT);
+        int cacheExpiryInSecs = 
PropertiesUtil.toInteger(config.get(PROP_EXTRACTED_TEXT_CACHE_EXPIRY),
+                PROP_EXTRACTED_TEXT_CACHE_EXPIRY_DEFAULT);
+        boolean alwaysUsePreExtractedCache = 
PropertiesUtil.toBoolean(config.get(PROP_PRE_EXTRACTED_TEXT_ALWAYS_USE),
+                PROP_PRE_EXTRACTED_TEXT_ALWAYS_USE_DEFAULT);
+
+        extractedTextCache = new ExtractedTextCache(
+                cacheSizeInMB * ONE_MB,
+                cacheExpiryInSecs,
+                alwaysUsePreExtractedCache,
+                indexDir);
+        if (extractedTextProvider != null){
+            registerExtractedTextProvider(extractedTextProvider);
+        }
+        CacheStats stats = extractedTextCache.getCacheStats();
+        if (stats != null){
+            oakRegs.add(registerMBean(whiteboard,
+                    CacheStatsMBean.class, stats,
+                    CacheStatsMBean.TYPE, stats.getName()));
+            log.info("Extracted text caching enabled with maxSize {} MB, 
expiry time {} secs",
+                    cacheSizeInMB, cacheExpiryInSecs);
+        }
+    }
+
+    private void registerExtractedTextProvider(PreExtractedTextProvider 
provider){
+        if (extractedTextCache != null){
+            if (provider != null){
+                String usage = 
extractedTextCache.isAlwaysUsePreExtractedCache() ?
+                        "always" : "only during reindexing phase";
+                log.info("Registering PreExtractedTextProvider {} with 
extracted text cache. " +
+                        "It would be used {}",  provider, usage);
+            } else {
+                log.info("Unregistering PreExtractedTextProvider with 
extracted text cache");
+            }
+            extractedTextCache.setExtractedTextProvider(provider);
+        }
+    }
+
+    private void configureBooleanClauseLimit(Map<String, ?> config) {
+        int booleanClauseLimit = 
PropertiesUtil.toInteger(config.get(PROP_BOOLEAN_CLAUSE_LIMIT),
+                PROP_BOOLEAN_CLAUSE_LIMIT_DEFAULT);
+        if (booleanClauseLimit != BooleanQuery.getMaxClauseCount()){
+            BooleanQuery.setMaxClauseCount(booleanClauseLimit);
+            log.info("Changed the Max boolean clause limit to {}", 
booleanClauseLimit);
+        }
+    }
+
+    private void configureIndexDefinitionStorage(Map<String, ?> config) {
+        boolean disableStorage = 
PropertiesUtil.toBoolean(config.get(PROP_DISABLE_STORED_INDEX_DEFINITION),
+                PROP_DISABLE_DEFN_STORAGE_DEFAULT);
+        if (disableStorage){
+            log.info("Feature to ensure that index definition matches the 
index state is disabled. Change in " +
+                    "index definition would now affect query plans and might 
lead to inconsistent results.");
+            IndexDefinition.setDisableStoredIndexDefinition(disableStorage);
+        }
+    }
+
+    private void registerGCMonitor(Whiteboard whiteboard,
+                                   final IndexTracker tracker) {
+        GCMonitor gcMonitor = new GCMonitor.Empty() {
+            @Override
+            public void compacted() {
+                tracker.refresh();
+            }
+        };
+        oakRegs.add(whiteboard.register(GCMonitor.class, gcMonitor, 
emptyMap()));
+    }
+
+    private void registerIndexInfoProvider(BundleContext bundleContext) {
+        IndexInfoProvider infoProvider = new 
LuceneIndexInfoProvider(nodeStore, asyncIndexInfoService, getIndexCheckDir());
+        
regs.add(bundleContext.registerService(IndexInfoProvider.class.getName(), 
infoProvider, null));
+    }
+
+    private void registerIndexImporterProvider(BundleContext bundleContext) {
+        LuceneIndexImporter importer = new LuceneIndexImporter(blobStore);
+        
regs.add(bundleContext.registerService(IndexImporterProvider.class.getName(), 
importer, null));
+    }
+
+    private void initializeActiveBlobCollector(Whiteboard whiteboard, 
Map<String, ?> config) {
+        boolean activeDeletionEnabled = PropertiesUtil.toBoolean(
+                config.get(PROP_NAME_DELETED_BLOB_COLLECTION_DEFAULT_ENABLED),
+                PROP_DELETED_BLOB_COLLECTION_DEFAULT_ENABLED);
+        if (activeDeletionEnabled && blobStore!= null) {
+            File blobCollectorWorkingDir = new File(indexDir, "deleted-blobs");
+            activeDeletedBlobCollector = 
ActiveDeletedBlobCollectorFactory.newInstance(blobCollectorWorkingDir,
+                    getExecutorService());
+            ActiveDeletedBlobCollectorMBean bean =
+                    new 
ActiveDeletedBlobCollectorMBeanImpl(activeDeletedBlobCollector, whiteboard, 
nodeStore,
+                            indexPathService, asyncIndexInfoService, 
blobStore, getExecutorService());
+
+            oakRegs.add(registerMBean(whiteboard, 
ActiveDeletedBlobCollectorMBean.class, bean,
+                    ActiveDeletedBlobCollectorMBean.TYPE, "Active lucene files 
collection"));
+            log.info("Active blob collector initialized at working dir: {}", 
blobCollectorWorkingDir);
+        } else {
+            activeDeletedBlobCollector = 
ActiveDeletedBlobCollectorFactory.NOOP;
+            log.info("Active blob collector set to NOOP. enabled: {} seconds; 
blobStore: {}",
+                    activeDeletionEnabled, blobStore);
+        }
+    }
+
+    private void registerPropertyIndexCleaner(Map<String, ?> config, 
BundleContext bundleContext) {
+        int cleanerInterval = 
PropertiesUtil.toInteger(config.get(PROP_INDEX_CLEANER_INTERVAL),
+                PROP_INDEX_CLEANER_INTERVAL_DEFAULT);
+
+        if (cleanerInterval <= 0) {
+            log.info("Property index cleaner would not be registered");
+            return;
+        }
+
+        cleaner = new PropertyIndexCleaner(nodeStore, indexPathService, 
asyncIndexInfoService, statisticsProvider);
+
+        //Proxy check for DocumentNodeStore
+        if (nodeStore instanceof Clusterable) {
+            cleaner.setRecursiveDelete(true);
+            log.info("PropertyIndexCleaner configured to perform recursive 
delete");
+        }
+        oakRegs.add(scheduleWithFixedDelay(whiteboard, cleaner,
+                ImmutableMap.of("scheduler.name", 
PropertyIndexCleaner.class.getName()),
+                cleanerInterval, true, true));
+        log.info("Property index cleaner configured to run every [{}] 
seconds", cleanerInterval);
+    }
+
+
+    protected void bindNodeAggregator(QueryIndex.NodeAggregator aggregator) {
+        this.nodeAggregator = aggregator;
+        initialize();
+    }
+
+    protected void unbindNodeAggregator(QueryIndex.NodeAggregator aggregator) {
+        this.nodeAggregator = null;
+        initialize();
+    }
+
+    protected void bindExtractedTextProvider(PreExtractedTextProvider 
preExtractedTextProvider){
+        this.extractedTextProvider = preExtractedTextProvider;
+        registerExtractedTextProvider(preExtractedTextProvider);
+    }
+
+    protected void unbindExtractedTextProvider(PreExtractedTextProvider 
preExtractedTextProvider){
+        this.extractedTextProvider = null;
+        registerExtractedTextProvider(null);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderService.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexStatistics.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexStatistics.java?rev=1841926&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexStatistics.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexStatistics.java
 Tue Sep 25 12:24:15 2018
@@ -0,0 +1,126 @@
+/*
+ * 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.Collections;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexStatistics;
+import org.apache.lucene.index.Fields;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.MultiFields;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FieldNames.isPropertyField;
+
+/**
+ * This class would populate some statistics from a reader. We want to be 
careful here such that
+ * we only collect statistics which don't incur reads from the index i.e. we 
would only collect
+ * stats that lucene would already have read into memory when the reader was 
opened.
+ */
+public class LuceneIndexStatistics implements IndexStatistics {
+    private static final Logger LOG = 
LoggerFactory.getLogger(LuceneIndexStatistics.class);
+    private final int numDocs;
+    private final Map<String, Integer> numDocsForField;
+    private final boolean safelyInitialized;
+
+    // For ease of tests as there didn't seem an easy way to make an 
IndexReader delegator
+    // that would fail calls to reader on-demand.
+    static boolean failReadingFields = false;
+    static boolean failReadingSyntheticallyFalliableField = false;
+
+    static final String SYNTHETICALLY_FALLIABLE_FIELD = 
"synthetically-falliable-field";
+
+    /**
+     * @param reader {@link IndexReader} for which statistics need to be 
collected.
+     */
+    LuceneIndexStatistics(IndexReader reader) {
+        numDocs = reader.numDocs();
+
+        Map<String, Integer> numDocsForField = Maps.newHashMap();
+
+        Fields fields = null;
+        try {
+            if (failReadingFields) {
+                throw new IOException("Synthetically fail to read fields");
+            }
+            fields = MultiFields.getFields(reader);
+        } catch (IOException e) {
+            LOG.warn("Couldn't open fields for reader ({}). Won't extract doc 
count per field", reader);
+            numDocsForField = null;
+        }
+
+
+        if (fields != null) {
+            for(String f : fields) {
+                if (isPropertyField(f)) {
+                    int docCntForField = -1;
+                    try {
+                        if (failReadingSyntheticallyFalliableField && 
SYNTHETICALLY_FALLIABLE_FIELD.equals(f)) {
+                            throw new IOException("Synthetically fail to read 
count for field jcr:title");
+                        }
+                        docCntForField = reader.getDocCount(f);
+                    } catch (IOException e) {
+                        LOG.warn("Couldn't read doc count for field {} via 
reader ({}).");
+                    }
+                    numDocsForField.put(f, docCntForField);
+                }
+            }
+        }
+
+        if (numDocsForField != null) {
+            this.numDocsForField = 
Collections.unmodifiableMap(numDocsForField);
+            this.safelyInitialized = true;
+        } else {
+            this.numDocsForField = Collections.emptyMap();
+            this.safelyInitialized = false;
+        }
+    }
+
+    /**
+     * @return number of documents in the index
+     */
+    public int numDocs() {
+        return numDocs;
+    }
+
+    /**
+     * @param field Index field for which number of indexed documents are to 
be return
+     * @return number of indexed documents (without subtracting potentially 
deleted ones)
+     *         for the given {@code field}.<br>
+     *         -1: if index codec doesn't store doc-count-for-field 
statistics, OR <br>
+     *             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reader threw an 
exception while reading fields, OR <br>
+     *             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;exception thrown while 
reading count for the field, OR <br>
+     *             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;doc-count is asked for 
a non-property field.
+     */
+    public int getDocCountFor(String field) {
+        if (!safelyInitialized) {
+            return -1;
+        }
+
+        int docCntForField = isPropertyField(field) ? 0 : -1;
+        if (numDocsForField.containsKey(field)) {
+            docCntForField = numDocsForField.get(field);
+        }
+
+        return docCntForField;
+    }
+
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexStatistics.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexWriterFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexWriterFactory.java?rev=1841926&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexWriterFactory.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexWriterFactory.java
 Tue Sep 25 12:24:15 2018
@@ -0,0 +1,30 @@
+/*
+ * 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 org.apache.jackrabbit.oak.plugins.index.lucene.writer.LuceneIndexWriter;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import 
org.apache.jackrabbit.oak.plugins.index.search.spi.editor.FulltextIndexWriterFactory;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+
+public interface LuceneIndexWriterFactory extends FulltextIndexWriterFactory {
+    @Override
+    LuceneIndexWriter newInstance(IndexDefinition definition, NodeBuilder 
definitionBuilder, boolean reindex);
+}

Propchange: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexWriterFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to