blob: cc54420166a4b6cea65759f4a87d370cf06afbbf [file] [log] [blame]
/*
* 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.api.CommitFailedException;
import org.apache.jackrabbit.oak.plugins.index.ContextAwareCallback;
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.IndexingContext;
import org.apache.jackrabbit.oak.plugins.index.lucene.directory.ActiveDeletedBlobCollectorFactory;
import org.apache.jackrabbit.oak.plugins.index.lucene.directory.ActiveDeletedBlobCollectorFactory.ActiveDeletedBlobCollector;
import org.apache.jackrabbit.oak.plugins.index.lucene.directory.ActiveDeletedBlobCollectorFactory.BlobDeletionCallback;
import org.apache.jackrabbit.oak.plugins.index.lucene.directory.DefaultDirectoryFactory;
import org.apache.jackrabbit.oak.plugins.index.lucene.directory.DirectoryFactory;
import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.IndexingQueue;
import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.LocalIndexWriterFactory;
import org.apache.jackrabbit.oak.plugins.index.lucene.hybrid.LuceneDocumentHolder;
import org.apache.jackrabbit.oak.plugins.index.lucene.property.LuceneIndexPropertyQuery;
import org.apache.jackrabbit.oak.plugins.index.lucene.property.PropertyIndexUpdateCallback;
import org.apache.jackrabbit.oak.plugins.index.lucene.property.PropertyQuery;
import org.apache.jackrabbit.oak.plugins.index.lucene.writer.DefaultIndexWriterFactory;
import org.apache.jackrabbit.oak.plugins.index.lucene.writer.LuceneIndexWriterConfig;
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.spi.editor.FulltextIndexWriterFactory;
import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
import org.apache.jackrabbit.oak.spi.commit.CommitContext;
import org.apache.jackrabbit.oak.spi.commit.Editor;
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.ReadOnlyBuilder;
import org.jetbrains.annotations.NotNull;
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.checkNotNull;
import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE;
/**
* Service that provides Lucene based {@link IndexEditor}s
*
* @see LuceneIndexEditor
* @see IndexEditorProvider
*
*/
public class LuceneIndexEditorProvider implements IndexEditorProvider {
private final Logger log = LoggerFactory.getLogger(getClass());
private final IndexCopier indexCopier;
private final ExtractedTextCache extractedTextCache;
private final IndexAugmentorFactory augmentorFactory;
private final IndexTracker indexTracker;
private final MountInfoProvider mountInfoProvider;
private final ActiveDeletedBlobCollector activeDeletedBlobCollector;
private GarbageCollectableBlobStore blobStore;
private IndexingQueue indexingQueue;
private boolean nrtIndexingEnabled;
private LuceneIndexWriterConfig writerConfig = new LuceneIndexWriterConfig();
/**
* Number of indexed Lucene document that can be held in memory
* This ensures that for very large commit memory consumption
* is bounded
*/
private int inMemoryDocsLimit = Integer.getInteger("oak.lucene.inMemoryDocsLimit", 500);
public LuceneIndexEditorProvider() {
this(null);
}
public LuceneIndexEditorProvider(@Nullable IndexCopier indexCopier) {
//Disable the cache by default in ExtractedTextCache
this(indexCopier, new ExtractedTextCache(0, 0));
}
public LuceneIndexEditorProvider(@Nullable IndexCopier indexCopier,
ExtractedTextCache extractedTextCache) {
this(indexCopier, extractedTextCache, null, Mounts.defaultMountInfoProvider());
}
public LuceneIndexEditorProvider(@Nullable IndexCopier indexCopier,
ExtractedTextCache extractedTextCache,
@Nullable IndexAugmentorFactory augmentorFactory,
MountInfoProvider mountInfoProvider) {
this(indexCopier, null, extractedTextCache, augmentorFactory, mountInfoProvider);
}
public LuceneIndexEditorProvider(@Nullable IndexCopier indexCopier,
@Nullable IndexTracker indexTracker,
ExtractedTextCache extractedTextCache,
@Nullable IndexAugmentorFactory augmentorFactory,
MountInfoProvider mountInfoProvider) {
this(indexCopier, indexTracker, extractedTextCache, augmentorFactory, mountInfoProvider,
ActiveDeletedBlobCollectorFactory.NOOP);
}
public LuceneIndexEditorProvider(@Nullable IndexCopier indexCopier,
@Nullable IndexTracker indexTracker,
ExtractedTextCache extractedTextCache,
@Nullable IndexAugmentorFactory augmentorFactory,
MountInfoProvider mountInfoProvider,
@NotNull ActiveDeletedBlobCollectorFactory.ActiveDeletedBlobCollector activeDeletedBlobCollector) {
this.indexCopier = indexCopier;
this.indexTracker = indexTracker;
this.extractedTextCache = extractedTextCache != null ? extractedTextCache : new ExtractedTextCache(0, 0);
this.augmentorFactory = augmentorFactory;
this.mountInfoProvider = checkNotNull(mountInfoProvider);
this.activeDeletedBlobCollector = activeDeletedBlobCollector;
}
@Override
public Editor getIndexEditor(
@NotNull String type, @NotNull NodeBuilder definition, @NotNull NodeState root,
@NotNull IndexUpdateCallback callback)
throws CommitFailedException {
if (TYPE_LUCENE.equals(type)) {
checkArgument(callback instanceof ContextAwareCallback, "callback instance not of type " +
"ContextAwareCallback [%s]", callback);
IndexingContext indexingContext = ((ContextAwareCallback)callback).getIndexingContext();
BlobDeletionCallback blobDeletionCallback = activeDeletedBlobCollector.getBlobDeletionCallback();
indexingContext.registerIndexCommitCallback(blobDeletionCallback);
FulltextIndexWriterFactory writerFactory = null;
LuceneIndexDefinition indexDefinition = null;
boolean asyncIndexing = true;
String indexPath = indexingContext.getIndexPath();
PropertyIndexUpdateCallback propertyUpdateCallback = null;
if (nrtIndexingEnabled() && !indexingContext.isAsync() && IndexDefinition.supportsSyncOrNRTIndexing(definition)) {
//Would not participate in reindexing. Only interested in
//incremental indexing
if (indexingContext.isReindexing()){
return null;
}
CommitContext commitContext = getCommitContext(indexingContext);
if (commitContext == null){
//Logically there should not be any commit without commit context. But
//some initializer code does the commit with out it. So ignore such calls with
//warning now
//TODO Revisit use of warn level once all such cases are analyzed
log.warn("No CommitContext found for commit", new Exception());
return null;
}
//TODO Also check if index has been done once
writerFactory = new LocalIndexWriterFactory(getDocumentHolder(commitContext),
indexPath);
//IndexDefinition from tracker might differ from one passed here for reindexing
//case which should be fine. However reusing existing definition would avoid
//creating definition instance for each commit as this gets executed for each commit
if (indexTracker != null){
indexDefinition = indexTracker.getIndexDefinition(indexPath);
if (indexDefinition != null && !indexDefinition.hasMatchingNodeTypeReg(root)){
log.debug("Detected change in NodeType registry for index {}. Would not use " +
"existing index definition", indexDefinition.getIndexPath());
indexDefinition = null;
}
}
if (indexDefinition == null) {
indexDefinition = LuceneIndexDefinition.newBuilder(root, definition.getNodeState(),
indexPath).build();
}
if (indexDefinition.hasSyncPropertyDefinitions()) {
propertyUpdateCallback = new PropertyIndexUpdateCallback(indexPath, definition, root);
if (indexTracker != null) {
PropertyQuery query = new LuceneIndexPropertyQuery(indexTracker, indexPath);
propertyUpdateCallback.getUniquenessConstraintValidator().setSecondStore(query);
}
}
//Pass on a read only builder to ensure that nothing gets written
//at all to NodeStore for local indexing.
//TODO [hybrid] This would cause issue with Facets as for faceted fields
//some stuff gets written to NodeBuilder. That logic should be refactored
//to be moved to LuceneIndexWriter
definition = new ReadOnlyBuilder(definition.getNodeState());
asyncIndexing = false;
}
if (writerFactory == null) {
writerFactory = new DefaultIndexWriterFactory(mountInfoProvider, newDirectoryFactory(blobDeletionCallback), writerConfig);
}
LuceneIndexEditorContext context = new LuceneIndexEditorContext(root, definition, indexDefinition, callback,
writerFactory, extractedTextCache, augmentorFactory, indexingContext, asyncIndexing);
context.setPropertyUpdateCallback(propertyUpdateCallback);
return new LuceneIndexEditor(context);
}
return null;
}
IndexCopier getIndexCopier() {
return indexCopier;
}
IndexingQueue getIndexingQueue() {
return indexingQueue;
}
public ExtractedTextCache getExtractedTextCache() {
return extractedTextCache;
}
public void setInMemoryDocsLimit(int inMemoryDocsLimit) {
this.inMemoryDocsLimit = inMemoryDocsLimit;
}
protected DirectoryFactory newDirectoryFactory(BlobDeletionCallback blobDeletionCallback) {
return new DefaultDirectoryFactory(indexCopier, blobStore, blobDeletionCallback);
}
private LuceneDocumentHolder getDocumentHolder(CommitContext commitContext){
LuceneDocumentHolder holder = (LuceneDocumentHolder) commitContext.get(LuceneDocumentHolder.NAME);
if (holder == null) {
holder = new LuceneDocumentHolder(indexingQueue, inMemoryDocsLimit);
commitContext.set(LuceneDocumentHolder.NAME, holder);
}
return holder;
}
public void setBlobStore(@Nullable GarbageCollectableBlobStore blobStore) {
this.blobStore = blobStore;
}
public void setIndexingQueue(IndexingQueue indexingQueue) {
this.indexingQueue = indexingQueue;
this.nrtIndexingEnabled = indexingQueue != null;
}
public void setWriterConfig(LuceneIndexWriterConfig writerConfig) {
this.writerConfig = writerConfig;
}
GarbageCollectableBlobStore getBlobStore() {
return blobStore;
}
private boolean nrtIndexingEnabled() {
return nrtIndexingEnabled;
}
private static CommitContext getCommitContext(IndexingContext indexingContext) {
return (CommitContext) indexingContext.getCommitInfo().getInfo().get(CommitContext.NAME);
}
}