blob: 0397d3479b69b764726b70f52884fc64029b04fe [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 java.io.File;
import java.io.IOException;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.json.JsopDiff;
import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfo;
import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfoService;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import org.apache.jackrabbit.oak.plugins.index.IndexInfo;
import org.apache.jackrabbit.oak.plugins.index.IndexInfoProvider;
import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
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.directory.DirectoryUtils;
import org.apache.jackrabbit.oak.plugins.index.lucene.directory.IndexConsistencyChecker;
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.search.IndexDefinition;
import org.apache.jackrabbit.oak.plugins.index.search.util.NodeStateCloner;
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.apache.jackrabbit.oak.spi.state.NodeStore;
import org.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder;
import org.apache.jackrabbit.util.ISO8601;
import org.apache.lucene.store.Directory;
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.search.IndexDefinition.INDEX_DEFINITION_NODE;
public class LuceneIndexInfoProvider implements IndexInfoProvider {
private final Logger log = LoggerFactory.getLogger(getClass());
private final NodeStore nodeStore;
private final AsyncIndexInfoService asyncInfoService;
private final File workDir;
public LuceneIndexInfoProvider(NodeStore nodeStore, AsyncIndexInfoService asyncInfoService, File workDir) {
this.nodeStore = checkNotNull(nodeStore);
this.asyncInfoService = checkNotNull(asyncInfoService);
this.workDir = checkNotNull(workDir);
}
@Override
public String getType() {
return LuceneIndexConstants.TYPE_LUCENE;
}
@Override
public IndexInfo getInfo(String indexPath) throws IOException {
NodeState idxState = NodeStateUtils.getNode(nodeStore.getRoot(), indexPath);
checkArgument(LuceneIndexConstants.TYPE_LUCENE.equals(idxState.getString(IndexConstants.TYPE_PROPERTY_NAME)),
"Index definition at [%s] is not of type 'lucene'", indexPath);
LuceneIndexInfo info = new LuceneIndexInfo(indexPath);
computeSize(idxState, info);
computeIndexDefinitionChange(idxState, info);
computeLastUpdatedTime(idxState, info);
computeAsyncIndexInfo(idxState, indexPath, info);
return info;
}
@Override
public boolean isValid(String indexPath) throws IOException {
IndexConsistencyChecker checker = new IndexConsistencyChecker(nodeStore.getRoot(), indexPath, workDir);
boolean result = false;
try{
result = checker.check(IndexConsistencyChecker.Level.BLOBS_ONLY).clean;
} catch (Exception e) {
log.warn("Error occurred while performing consistency check for {}", indexPath, e);
}
return result;
}
private void computeAsyncIndexInfo(NodeState idxState, String indexPath, LuceneIndexInfo info) {
String asyncName = IndexUtils.getAsyncLaneName(idxState, indexPath);
if (asyncName == null) {
log.warn("No 'async' value for index definition at [{}]. Definition {}", indexPath, idxState);
return;
}
AsyncIndexInfo asyncInfo = asyncInfoService.getInfo(asyncName);
checkNotNull(asyncInfo, "No async info found for name [%s] " +
"for index at [%s]", asyncName, indexPath);
info.indexedUptoTime = asyncInfo.getLastIndexedTo();
info.asyncName = asyncName;
}
private void computeSize(NodeState idxState, LuceneIndexInfo info) throws IOException {
LuceneIndexDefinition defn = LuceneIndexDefinition.newBuilder(nodeStore.getRoot(), idxState, info.indexPath).build();
for (String dirName : idxState.getChildNodeNames()) {
if (NodeStateUtils.isHidden(dirName) && MultiplexersLucene.isIndexDirName(dirName)) {
try (Directory dir = new OakDirectory(new ReadOnlyBuilder(idxState), dirName, defn, true)) {
info.numEntries += DirectoryUtils.getNumDocs(dir);
info.size = DirectoryUtils.dirSize(dir);
}
}
}
}
private static void computeLastUpdatedTime(NodeState idxState, LuceneIndexInfo info) {
NodeState status = idxState.getChildNode(IndexDefinition.STATUS_NODE);
if (status.exists()){
PropertyState updatedTime = status.getProperty(IndexDefinition.STATUS_LAST_UPDATED);
if (updatedTime != null) {
info.lastUpdatedTime = ISO8601.parse(updatedTime.getValue(Type.DATE)).getTimeInMillis();
}
}
}
private static void computeIndexDefinitionChange(NodeState idxState, LuceneIndexInfo info) {
NodeState storedDefn = idxState.getChildNode(INDEX_DEFINITION_NODE);
if (storedDefn.exists()) {
NodeState currentDefn = NodeStateCloner.cloneVisibleState(idxState);
if (!FilteringEqualsDiff.equals(storedDefn, currentDefn)){
info.indexDefinitionChanged = true;
info.indexDiff = JsopDiff.diffToJsop(storedDefn, currentDefn);
}
}
}
private static class LuceneIndexInfo implements IndexInfo {
String indexPath;
String asyncName;
long numEntries;
long size;
long indexedUptoTime;
long lastUpdatedTime;
boolean indexDefinitionChanged;
String indexDiff;
public LuceneIndexInfo(String indexPath) {
this.indexPath = indexPath;
}
@Override
public String getIndexPath() {
return indexPath;
}
@Override
public String getType() {
return LuceneIndexConstants.TYPE_LUCENE;
}
@Override
public String getAsyncLaneName() {
return asyncName;
}
@Override
public long getLastUpdatedTime() {
return lastUpdatedTime;
}
@Override
public long getIndexedUpToTime() {
return indexedUptoTime;
}
@Override
public long getEstimatedEntryCount() {
return numEntries;
}
@Override
public long getSizeInBytes() {
return size;
}
@Override
public boolean hasIndexDefinitionChangedWithoutReindexing() {
return indexDefinitionChanged;
}
@Override
public String getIndexDefinitionDiff() {
return indexDiff;
}
}
static class FilteringEqualsDiff extends EqualsDiff {
private static final Set<String> IGNORED_PROP_NAMES = ImmutableSet.of(
IndexConstants.REINDEX_COUNT,
IndexConstants.REINDEX_PROPERTY_NAME
);
public static boolean equals(NodeState before, NodeState after) {
return before.exists() == after.exists()
&& after.compareAgainstBaseState(before, new FilteringEqualsDiff());
}
@Override
public boolean propertyChanged(PropertyState before, PropertyState after) {
if (ignoredProp(before.getName())){
return true;
}
return false;
}
@Override
public boolean propertyAdded(PropertyState after) {
if (ignoredProp(after.getName())){
return true;
}
return super.propertyAdded(after);
}
@Override
public boolean propertyDeleted(PropertyState before) {
if (ignoredProp(before.getName())){
return true;
}
return super.propertyDeleted(before);
}
private boolean ignoredProp(String name) {
return IGNORED_PROP_NAMES.contains(name) || NodeStateUtils.isHidden(name);
}
}
}