Added:
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java?rev=1832166&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
(added)
+++
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
Thu May 24 13:07:54 2018
@@ -0,0 +1,1061 @@
+/*
+ * 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.search.spi.query;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.annotation.CheckForNull;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.property.ValuePatternUtil;
+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.search.IndexFormatVersion;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexNode;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexStatistics;
+import org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction;
+import org.apache.jackrabbit.oak.spi.query.QueryConstants;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextContains;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextExpression;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextTerm;
+import org.apache.jackrabbit.oak.spi.query.fulltext.FullTextVisitor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Lists.newArrayListWithCapacity;
+import static com.google.common.collect.Maps.newHashMap;
+import static org.apache.jackrabbit.JcrConstants.JCR_SCORE;
+import static org.apache.jackrabbit.JcrConstants.NT_BASE;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getAncestorPath;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getDepth;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getName;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+import static org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan;
+import static org.apache.jackrabbit.oak.spi.query.QueryIndex.OrderEntry;
+
+public class FulltextIndexPlanner {
+
+ /**
+ * IndexPaln Attribute name which refers to the name of the fields that
should be used for facets.
+ */
+ public static final String ATTR_FACET_FIELDS = "oak.facet.fields";
+
+ private static final String FLAG_ENTRY_COUNT =
"oak.fulltext.useActualEntryCount";
+ private static final Logger log =
LoggerFactory.getLogger(FulltextIndexPlanner.class);
+ private final IndexDefinition definition;
+ private final Filter filter;
+ private final String indexPath;
+ private final List<OrderEntry> sortOrder;
+ private IndexNode indexNode;
+ private PlanResult result;
+ private static boolean useActualEntryCount = false;
+
+ static {
+ useActualEntryCount =
Boolean.parseBoolean(System.getProperty(FLAG_ENTRY_COUNT, "true"));
+ if (!useActualEntryCount) {
+ log.info("System property {} found to be false. IndexPlanner would
use a default entryCount of 1000 instead" +
+ " of using the actual entry count", FLAG_ENTRY_COUNT);
+ }
+ }
+
+ public FulltextIndexPlanner(IndexNode indexNode,
+ String indexPath,
+ Filter filter, List<OrderEntry> sortOrder) {
+ this.indexNode = indexNode;
+ this.indexPath = indexPath;
+ this.definition = indexNode.getDefinition();
+ this.filter = filter;
+ this.sortOrder = sortOrder;
+ }
+
+ IndexPlan getPlan() {
+ IndexPlan.Builder builder = getPlanBuilder();
+
+ if (definition.isTestMode()){
+ if (builder == null) {
+ if (notSupportedFeature()) {
+ return null;
+ }
+ String msg = String.format("No plan found for filter [%s] " +
+ "while using definition [%s] and testMode is found to
be enabled", filter, definition);
+ throw new IllegalStateException(msg);
+ } else {
+ builder.setEstimatedEntryCount(1)
+ .setCostPerExecution(1e-3)
+ .setCostPerEntry(1e-3);
+ }
+ }
+
+ return builder != null ? builder.build() : null;
+ }
+
+ @Override
+ public String toString() {
+ return "IndexPlanner{" +
+ "indexPath='" + indexPath + '\'' +
+ ", filter=" + filter +
+ ", sortOrder=" + sortOrder +
+ '}';
+ }
+
+ //For tests
+ static void setUseActualEntryCount(boolean useActualEntryCount) {
+ FulltextIndexPlanner.useActualEntryCount = useActualEntryCount;
+ }
+
+ private IndexPlan.Builder getPlanBuilder() {
+ log.trace("Evaluating plan with index definition {}", definition);
+
+ if (wrongIndex()) {
+ return null;
+ }
+
+ FullTextExpression ft = filter.getFullTextConstraint();
+
+ if (!definition.getVersion().isAtLeast(IndexFormatVersion.V2)){
+ log.trace("Index is old format. Not supported");
+ return null;
+ }
+
+ //Query Fulltext and Index does not support fulltext
+ if (ft != null && !definition.isFullTextEnabled()) {
+ return null;
+ }
+
+ IndexDefinition.IndexingRule indexingRule = getApplicableRule();
+ if (indexingRule == null){
+ return null;
+ }
+
+ //Query Fulltext and indexing rule does not support fulltext
+ if (ft != null && !indexingRule.isFulltextEnabled()){
+ return null;
+ }
+
+ if (!checkForQueryPaths()) {
+ log.trace("Opting out due mismatch between path restriction {} and
query paths {}",
+ filter.getPath(), definition.getQueryPaths());
+ return null;
+ }
+
+ result = new PlanResult(indexPath, definition, indexingRule);
+
+ if (definition.hasFunctionDefined()
+ && filter.getPropertyRestriction(definition.getFunctionName())
!= null) {
+ return
getNativeFunctionPlanBuilder(indexingRule.getBaseNodeType());
+ }
+
+ List<String> indexedProps =
newArrayListWithCapacity(filter.getPropertyRestrictions().size());
+
+ for (PropertyDefinition functionIndex :
indexingRule.getFunctionRestrictions()) {
+ for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
+ String f = functionIndex.function;
+ if (pr.propertyName.equals(f)) {
+ indexedProps.add(f);
+ result.propDefns.put(f, functionIndex);
+ }
+ }
+ }
+ //Optimization - Go further only if any of the property is configured
+ //for property index
+ List<String> facetFields = new LinkedList<String>();
+ boolean ntBaseRule = NT_BASE.equals(indexingRule.getNodeTypeName());
+ Map<String, PropertyDefinition> relativePropDefns = new HashMap<>();
+ if (indexingRule.propertyIndexEnabled) {
+ for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
+ String name = pr.propertyName;
+ if (QueryConstants.RESTRICTION_LOCAL_NAME.equals(name)) {
+ continue;
+ }
+ if
(name.startsWith(QueryConstants.FUNCTION_RESTRICTION_PREFIX)) {
+ // function-based indexes were handled before
+ continue;
+ }
+ if (QueryConstants.REP_FACET.equals(pr.propertyName)) {
+ String value = pr.first.getValue(Type.STRING);
+ facetFields.add(FulltextIndex.parseFacetField(value));
+ }
+
+ PropertyDefinition pd =
indexingRule.getConfig(pr.propertyName);
+
+ boolean relativeProps = false;
+ if (pd == null && ntBaseRule) {
+ //Direct match not possible. Check for relative property
definition
+ //i.e. if no match found for jcr:content/@keyword then
check if
+ //property definition exists for 'keyword'
+ pd = getSimpleProperty(indexingRule, pr.propertyName);
+ relativeProps = pd != null;
+ }
+
+ if (pd != null && pd.propertyIndexEnabled()) {
+ if (pr.isNullRestriction() && !pd.nullCheckEnabled){
+ continue;
+ }
+
+ if (!matchesValuePattern(pr, pd)) {
+ continue;
+ }
+
+ //A property definition with weight == 0 is only meant to
be used
+ //with some other definitions
+ if (pd.weight != 0 && !relativeProps) {
+ indexedProps.add(name);
+ }
+
+ if (relativeProps) {
+ relativePropDefns.put(name, pd);
+ } else {
+ result.propDefns.put(name, pd);
+ }
+ }
+ }
+ }
+
+ boolean evalNodeTypeRestrictions =
canEvalNodeTypeRestrictions(indexingRule);
+ boolean evalPathRestrictions = canEvalPathRestrictions(indexingRule);
+ boolean canEvalAlFullText = canEvalAllFullText(indexingRule, ft);
+ boolean canEvalNodeNameRestriction =
canEvalNodeNameRestriction(indexingRule);
+
+ if (ft != null && !canEvalAlFullText){
+ return null;
+ }
+
+ if (indexedProps.isEmpty() && !relativePropDefns.isEmpty() &&
!canEvalAlFullText) {
+ indexedProps = planForRelativeProperties(relativePropDefns);
+ }
+
+ //Fulltext expression can also be like
jcr:contains(jcr:content/metadata/@format, 'image')
+
+ List<OrderEntry> sortOrder = createSortOrder(indexingRule);
+ boolean canSort = canSortByProperty(sortOrder);
+ if (!indexedProps.isEmpty() || canSort || ft != null
+ || evalPathRestrictions || evalNodeTypeRestrictions ||
canEvalNodeNameRestriction) {
+ int costPerEntryFactor = 1;
+ costPerEntryFactor += sortOrder.size();
+
+ IndexPlan.Builder plan = defaultPlan();
+ if (!sortOrder.isEmpty()) {
+ plan.setSortOrder(sortOrder);
+ }
+
+ if (facetFields.size() > 0) {
+ plan.setAttribute(ATTR_FACET_FIELDS, facetFields);
+ }
+
+ if (ft == null){
+ result.enableNonFullTextConstraints();
+ }
+
+ if (evalNodeTypeRestrictions){
+ result.enableNodeTypeEvaluation();
+ }
+
+ if (canEvalNodeNameRestriction){
+ result.enableNodeNameRestriction();
+ }
+
+ // Set a index based guess here. Unique would set its own value
below
+ if (useActualEntryCount && !definition.isEntryCountDefined()) {
+ int maxPossibleNumDocs =
getMaxPossibleNumDocs(result.propDefns, filter);
+ if (maxPossibleNumDocs >= 0) {
+ plan.setEstimatedEntryCount(maxPossibleNumDocs);
+ }
+ }
+
+ if (sortOrder.isEmpty() && ft == null) {
+ boolean uniqueIndexFound = planForSyncIndexes(indexingRule);
+ if (uniqueIndexFound) {
+ //For unique index there would be at max 1 entry
+ plan.setEstimatedEntryCount(1);
+ }
+ }
+
+ return plan.setCostPerEntry(definition.getCostPerEntry() /
costPerEntryFactor);
+ }
+
+ //TODO Support for property existence queries
+
+ return null;
+ }
+
+ private boolean matchesValuePattern(PropertyRestriction pr,
PropertyDefinition pd) {
+ if (!pd.valuePattern.matchesAll()){
+ //So we have a valuePattern defined. So determine if
+ //this index can return a plan based on values
+ Set<String> values = ValuePatternUtil.getAllValues(pr);
+ if (values == null) {
+ // "is not null" condition, but we have a value pattern
+ // that doesn't match everything
+ // case of like search
+ String prefix = ValuePatternUtil.getLongestPrefix(filter,
pr.propertyName);
+ if (!pd.valuePattern.matchesPrefix(prefix)) {
+ // region match which is not fully in the pattern
+ return false;
+ }
+ } else {
+ // we have a value pattern, for example (a|b),
+ // but we search (also) for 'c': can't match
+ if (!pd.valuePattern.matchesAll(values)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean wrongIndex() {
+ // REMARK: similar code is used in oak-core, PropertyIndex
+ // skip index if "option(index ...)" doesn't match
+ PropertyRestriction indexName =
filter.getPropertyRestriction(IndexConstants.INDEX_NAME_OPTION);
+ boolean wrong = false;
+ if (indexName != null && indexName.first != null) {
+ String name = indexName.first.getValue(Type.STRING);
+ String thisName = definition.getIndexName();
+ if (thisName != null) {
+ thisName = PathUtils.getName(thisName);
+ if (thisName.equals(name)) {
+ // index name specified, and matches
+ return false;
+ }
+ }
+ wrong = true;
+ }
+ PropertyRestriction indexTag =
filter.getPropertyRestriction(IndexConstants.INDEX_TAG_OPTION);
+ if (indexTag != null && indexTag.first != null) {
+ // index tag specified
+ String[] tags = definition.getIndexTags();
+ if (tags == null) {
+ // no tag
+ return true;
+ }
+ String tag = indexTag.first.getValue(Type.STRING);
+ for(String t : tags) {
+ if (t.equals(tag)) {
+ // tag matches
+ return false;
+ }
+ }
+ // no tag matches
+ return true;
+ }
+ // no tag specified
+ return wrong;
+ }
+
+ private IndexPlan.Builder getNativeFunctionPlanBuilder(String
indexingRuleBaseNodeType) {
+ boolean canHandleNativeFunction = true;
+
+ PropertyValue pv =
filter.getPropertyRestriction(definition.getFunctionName()).first;
+ String query = pv.getValue(Type.STRING);
+
+ if (query.startsWith("suggest?term=")) {
+ if (definition.isSuggestEnabled()) {
+ canHandleNativeFunction =
indexingRuleBaseNodeType.equals(filter.getNodeType());
+ } else {
+ canHandleNativeFunction = false;
+ }
+ } else if (query.startsWith("spellcheck?term=")) {
+ if (definition.isSpellcheckEnabled()) {
+ canHandleNativeFunction =
indexingRuleBaseNodeType.equals(filter.getNodeType());
+ } else {
+ canHandleNativeFunction = false;
+ }
+ }
+
+ //Suggestion and SpellCheck use virtual paths which is same for all
results
+ if (canHandleNativeFunction) {
+ result.disableUniquePaths();
+ }
+
+ //If native function can be handled by this index then ensure
+ // that lowest cost if returned
+ return canHandleNativeFunction ?
defaultPlan().setEstimatedEntryCount(1) : null;
+ }
+
+ /**
+ * Check if there is a mismatch between QueryPaths associated with index
+ * and path restriction specified in query
+
+ * @return true if QueryPaths and path restrictions do not have any
conflict
+ */
+ private boolean checkForQueryPaths() {
+ String[] queryPaths = definition.getQueryPaths();
+ if (queryPaths == null){
+ //No explicit value specified. Assume '/' which results in true
+ return true;
+ }
+
+ String pathRestriction = filter.getPath();
+ for (String queryPath : queryPaths){
+ if (queryPath.equals(pathRestriction) ||
PathUtils.isAncestor(queryPath, pathRestriction)){
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean canEvalNodeNameRestriction(IndexingRule indexingRule) {
+ PropertyRestriction pr =
filter.getPropertyRestriction(QueryConstants.RESTRICTION_LOCAL_NAME);
+ if (pr == null){
+ return false;
+ }
+ return indexingRule.isNodeNameIndexed();
+ }
+
+ private static boolean canSortByProperty(List<OrderEntry> sortOrder) {
+ if (sortOrder.isEmpty()) {
+ return false;
+ }
+
+ // If jcr:score is the only sort order then opt out
+ if (sortOrder.size() == 1 &&
+ JCR_SCORE.equals(sortOrder.get(0).getPropertyName())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean canEvalAllFullText(final IndexingRule indexingRule,
FullTextExpression ft) {
+ if (ft == null){
+ return false;
+ }
+
+ final HashSet<String> relPaths = new HashSet<String>();
+ final HashSet<String> nonIndexedPaths = new HashSet<String>();
+ final AtomicBoolean relativeParentsFound = new AtomicBoolean();
+ final AtomicBoolean nodeScopedCondition = new AtomicBoolean();
+ ft.accept(new FullTextVisitor.FullTextVisitorBase() {
+ @Override
+ public boolean visit(FullTextContains contains) {
+ visitTerm(contains.getPropertyName());
+ return true;
+ }
+
+ @Override
+ public boolean visit(FullTextTerm term) {
+ visitTerm(term.getPropertyName());
+ return true;
+ }
+
+ private void visitTerm(String propertyName) {
+ String p = propertyName;
+ String propertyPath = null;
+ String nodePath = null;
+ if (p == null) {
+ relPaths.add("");
+ } else if (p.startsWith("../") || p.startsWith("./")) {
+ relPaths.add(p);
+ relativeParentsFound.set(true);
+ } else if (getDepth(p) > 1) {
+ String parent = getParentPath(p);
+ if (FulltextIndex.isNodePath(p)){
+ nodePath = parent;
+ } else {
+ propertyPath = p;
+ }
+ relPaths.add(parent);
+ } else {
+ propertyPath = p;
+ relPaths.add("");
+ }
+
+ if (nodePath != null
+ && !indexingRule.isAggregated(nodePath)){
+ nonIndexedPaths.add(p);
+ } else if (propertyPath != null) {
+ PropertyDefinition pd =
indexingRule.getConfig(propertyPath);
+ //If given prop is not analyzed then its
+ //not indexed
+ if (pd == null){
+ nonIndexedPaths.add(p);
+ } else if (!pd.analyzed){
+ nonIndexedPaths.add(p);
+ }
+ }
+
+ if (nodeScopedTerm(propertyName)){
+ nodeScopedCondition.set(true);
+ }
+ }
+ });
+
+ if (nodeScopedCondition.get() &&
!indexingRule.isNodeFullTextIndexed()){
+ return false;
+ }
+
+ if (relativeParentsFound.get()){
+ log.debug("Relative parents found {} which are not supported",
relPaths);
+ return false;
+ }
+
+ //where contains('jcr:content/bar', 'mountain OR valley') and
contains('jcr:content/foo', 'mountain OR valley')
+ //above query can be evaluated by index which indexes foo and bar with
restriction that both belong to same node
+ //by displacing the query path to evaluate on contains('bar', ...) and
filter out those parents which do not
+ //have jcr:content as parent. So ensure that relPaths size is 1 or 0
+ if (!nonIndexedPaths.isEmpty()){
+ if (relPaths.size() > 1){
+ log.debug("Following relative property paths are not index",
relPaths);
+ return false;
+ }
+ result.setParentPath(Iterables.getOnlyElement(relPaths, ""));
+
+ //Such non indexed path can possibly be evaluated via any rule on
nt:base
+ //which can possibly index everything
+ IndexingRule rule = definition.getApplicableIndexingRule(NT_BASE);
+ if (rule == null){
+ return false;
+ }
+
+ for (String p : nonIndexedPaths){
+ //Index can only evaluate a node search jcr:content/*
+ //if it indexes node scope indexing is enabled
+ if (FulltextIndex.isNodePath(p)){
+ if (!rule.isNodeFullTextIndexed()) {
+ return false;
+ }
+ } else {
+ //Index can only evaluate a property like jcr:content/type
+ //if it indexes 'type' and that too analyzed
+ String propertyName = PathUtils.getName(p);
+ PropertyDefinition pd = rule.getConfig(propertyName);
+ if (pd == null){
+ return false;
+ }
+ if (!pd.analyzed){
+ return false;
+ }
+ }
+ }
+ } else {
+ result.setParentPath("");
+ }
+
+ return true;
+ }
+
+ /**
+ * Computes the indexedProps which can be part of query by virtue of
relativizing i.e.
+ * if query is on jcr:content/keyword then perform search on keyword and
change parent
+ * path to jcr:content
+ * @param relativePropDefns property definitions for such relative
properties. The key
+ * would be actual property name as in query i.e.
jcr:content/keyword
+ * while property definition would be for
'keyword'
+ * @return list of properties which are included in query issued to Lucene
+ */
+ private List<String> planForRelativeProperties(Map<String,
PropertyDefinition> relativePropDefns) {
+ Multimap<String, Map.Entry<String, PropertyDefinition>> relpaths =
ArrayListMultimap.create();
+ int maxSize = 0;
+ String maxCountedParent = null;
+
+ //Collect the relative properties grouped by parent path
+ //and track the parent having maximum properties
+ for (Map.Entry<String, PropertyDefinition> e :
relativePropDefns.entrySet()) {
+ String relativePropertyPath = e.getKey();
+ String parent = getParentPath(relativePropertyPath);
+
+ relpaths.put(parent, e);
+ int count = relpaths.get(parent).size();
+ if (count > maxSize) {
+ maxSize = count;
+ maxCountedParent = parent;
+ }
+ }
+
+ //Set the parent path to one which is present in most prop. In case of
tie any one
+ //such path would be picked
+ result.setParentPath(maxCountedParent);
+
+ //Now add only those properties to plan which have the maxCountedParent
+ List<String> indexedProps = new ArrayList<>(maxSize);
+ for (Map.Entry<String, PropertyDefinition> e :
relpaths.get(maxCountedParent)) {
+ String relativePropertyPath = e.getKey();
+ result.propDefns.put(relativePropertyPath, e.getValue());
+ result.relPropMapping.put(relativePropertyPath,
PathUtils.getName(relativePropertyPath));
+ if (e.getValue().weight != 0) {
+ indexedProps.add(relativePropertyPath);
+ }
+ }
+
+ return indexedProps;
+ }
+
+ @CheckForNull
+ private static PropertyDefinition getSimpleProperty(IndexingRule
indexingRule, String relativePropertyName) {
+ String name = PathUtils.getName(relativePropertyName);
+ if (name.equals(relativePropertyName)){
+ //Not a relative property
+ return null;
+ }
+
+ //Properties using ../ or ./ notation not support. The relative
property path
+ //must be fixed
+ if (relativePropertyName.startsWith("../") ||
relativePropertyName.startsWith("./")) {
+ return null;
+ }
+ return indexingRule.getConfig(name);
+ }
+
+ private boolean planForSyncIndexes(IndexDefinition.IndexingRule
indexingRule) {
+ //If no sync index involved then return right away
+ if (!definition.hasSyncPropertyDefinitions()) {
+ return false;
+ }
+
+ if (result.propDefns.isEmpty() &&
!result.evaluateNodeTypeRestriction()) {
+ return false;
+ }
+
+ List<PropertyIndexResult> unique = newArrayList();
+ List<PropertyIndexResult> nonUnique = newArrayList();
+
+ for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
+ String propertyName = result.getPropertyName(pr);
+ PropertyDefinition pd = result.propDefns.get(pr.propertyName);
+
+ if (pd != null) {
+ PropertyIndexResult e = new PropertyIndexResult(propertyName,
pr);
+ if (pd.unique) {
+ unique.add(e);
+ } else {
+ nonUnique.add(e);
+ }
+ }
+ }
+
+ //Pick the first index (if multiple). For unique its fine
+ //For non unique we can probably later add support for cost
+ //based selection
+ boolean uniqueIndexFound = false;
+ if (!unique.isEmpty()) {
+ result.propertyIndexResult = unique.get(0);
+ uniqueIndexFound = true;
+ } else if (!nonUnique.isEmpty()) {
+ result.propertyIndexResult = nonUnique.get(0);
+ }
+
+ if (result.propertyIndexResult == null &&
result.evaluateNodeTypeRestriction()) {
+ PropertyDefinition pd =
indexingRule.getConfig(JcrConstants.JCR_PRIMARYTYPE);
+ if (pd != null && pd.sync) {
+ result.syncNodeTypeRestrictions = true;
+ }
+ }
+
+ return uniqueIndexFound;
+ }
+
+ private boolean canEvalPathRestrictions(IndexingRule rule) {
+ //Opt out if one is looking for all children for '/' as its equivalent
to
+ //NO_RESTRICTION
+ if (filter.getPathRestriction() ==
Filter.PathRestriction.NO_RESTRICTION
+ || (filter.getPathRestriction() ==
Filter.PathRestriction.ALL_CHILDREN
+ && PathUtils.denotesRoot(filter.getPath()))
+ ){
+ return false;
+ }
+ //If no other restrictions is provided and query is pure
+ //path restriction based then need to be sure that index definition at
least
+ //allows indexing all the path for given nodeType
+ return definition.evaluatePathRestrictions() &&
rule.indexesAllNodesOfMatchingType();
+ }
+
+
+ private boolean canEvalNodeTypeRestrictions(IndexingRule rule) {
+ //No need to handle nt:base
+ if (filter.matchesAllTypes()){
+ return false;
+ }
+
+ //Only opt in if rule is not derived from nt:base otherwise it would
+ //get used when there a full text index on all nodes
+ return rule.indexesAllNodesOfMatchingType() && !rule.isBasedOnNtBase();
+ }
+
+ private IndexPlan.Builder defaultPlan() {
+ return new IndexPlan.Builder()
+ .setCostPerExecution(definition.getCostPerExecution())
+ .setCostPerEntry(definition.getCostPerEntry())
+ .setFulltextIndex(definition.isFullTextEnabled())
+ .setIncludesNodeData(false) // we should not include node data
+ .setFilter(filter)
+ .setPathPrefix(getPathPrefix())
+
.setSupportsPathRestriction(definition.evaluatePathRestrictions())
+ .setDelayed(true) //Lucene is always async
+ .setAttribute(FulltextIndex.ATTR_PLAN_RESULT, result)
+ .setEstimatedEntryCount(estimatedEntryCount())
+ .setPlanName(indexPath);
+ }
+
+ private long estimatedEntryCount() {
+ int numOfDocs = getNumDocs();
+ if (useActualEntryCount) {
+ return definition.isEntryCountDefined() ?
definition.getEntryCount() : numOfDocs;
+ } else {
+ return estimatedEntryCount_Compat(numOfDocs);
+ }
+ }
+
+ private long estimatedEntryCount_Compat(int numOfDocs) {
+ //Other index only compete in case of property indexes. For fulltext
+ //index return true count so as to allow multiple property indexes
+ //to be compared fairly
+ FullTextExpression ft = filter.getFullTextConstraint();
+ if (ft != null && definition.isFullTextEnabled()){
+ return definition.getFulltextEntryCount(numOfDocs);
+ }
+ return Math.min(definition.getEntryCount(), numOfDocs);
+ }
+
+ private String getPathPrefix() {
+ // 2 = /oak:index/<index name>
+ String parentPath = PathUtils.getAncestorPath(indexPath, 2);
+ return PathUtils.denotesRoot(parentPath) ? "" : parentPath;
+ }
+
+ private int getNumDocs() {
+ return indexNode.getIndexStatistics().numDocs();
+ }
+
+ private int getMaxPossibleNumDocs(Map<String, PropertyDefinition>
propDefns, Filter filter) {
+ IndexStatistics indexStatistics = indexNode.getIndexStatistics();
+ int minNumDocs = indexStatistics.numDocs();
+ for (Map.Entry<String, PropertyDefinition> propDef :
propDefns.entrySet()) {
+ String key = propDef.getKey();
+ if (result.relPropMapping.containsKey(key)) {
+ key = getName(key);
+ }
+ int docCntForField = indexStatistics.getDocCountFor(key);
+ if (docCntForField == -1) {
+ continue;
+ }
+
+ int weight = propDef.getValue().weight;
+
+ PropertyRestriction pr = filter.getPropertyRestriction(key);
+ if (pr != null) {
+ if (pr.isNotNullRestriction()) {
+ // don't use weight for "is not null" restrictions
+ weight = 1;
+ } else {
+ if (weight > 1) {
+ // for non-equality conditions such as
+ // where x > 1, x < 2, x like y,...:
+ // use a maximum weight of 3,
+ // so assume we read at least 30%
+ if (!isEqualityRestriction(pr)) {
+ weight = Math.min(3, weight);
+ }
+ }
+ }
+ }
+
+ if (weight > 1) {
+ // use it to scale down the doc count - in broad strokes, we
can think of weight
+ // as number of terms for the field with all terms getting
equal share of
+ // the documents in this field
+ double scaledDocCnt = Math.ceil((double) docCntForField /
weight);
+ if (minNumDocs < scaledDocCnt) {
+ continue;
+ }
+ // since, we've already taken care that scaled cost is lower
than minCost,
+ // we can safely cast without risking overflow
+ minNumDocs = (int)scaledDocCnt;
+ } else if (docCntForField < minNumDocs) {
+ minNumDocs = docCntForField;
+ }
+ }
+ return minNumDocs;
+ }
+
+ private static boolean isEqualityRestriction(PropertyRestriction pr) {
+ return pr.first != null && pr.first == pr.last;
+ }
+
+ private List<OrderEntry> createSortOrder(IndexDefinition.IndexingRule
rule) {
+ if (sortOrder == null) {
+ return Collections.emptyList();
+ }
+
+ List<OrderEntry> orderEntries =
newArrayListWithCapacity(sortOrder.size());
+ for (OrderEntry o : sortOrder) {
+ PropertyDefinition pd = rule.getConfig(o.getPropertyName());
+ if (pd != null
+ && pd.ordered
+ && o.getPropertyType() != null
+ && !o.getPropertyType().isArray()) {
+ orderEntries.add(o); // can manage any order desc/asc
+ result.sortedProperties.add(pd);
+ } else if
(o.getPropertyName().equals(IndexDefinition.NATIVE_SORT_ORDER.getPropertyName()))
{
+ // Supports jcr:score descending natively
+ orderEntries.add(IndexDefinition.NATIVE_SORT_ORDER);
+ }
+ for (PropertyDefinition functionIndex :
rule.getFunctionRestrictions()) {
+ if (o.getPropertyName().equals(functionIndex.function)) {
+ // can manage any order desc/asc
+ orderEntries.add(o);
+ result.sortedProperties.add(functionIndex);
+ }
+ }
+ }
+
+ //TODO Should we return order entries only when all order clauses are
satisfied
+ return orderEntries;
+ }
+
+ @CheckForNull
+ private IndexDefinition.IndexingRule getApplicableRule() {
+ if (filter.matchesAllTypes()){
+ return definition.getApplicableIndexingRule(JcrConstants.NT_BASE);
+ } else {
+ //TODO May be better if filter.getSuperTypes returned a list which
maintains
+ //inheritance order and then we iterate over that
+ for (IndexDefinition.IndexingRule rule :
definition.getDefinedRules()){
+ if (filter.getSupertypes().contains(rule.getNodeTypeName())){
+ //Theoretically there may be multiple rules for same
nodeType with
+ //some condition defined. So again find a rule which
applies
+ IndexDefinition.IndexingRule matchingRule =
definition.getApplicableIndexingRule(rule.getNodeTypeName());
+
+ if (matchingRule == null &&
rule.getNodeTypeName().equals(filter.getNodeType())){
+ //In case nodetype registry in IndexDefinition is
stale then it would not populate
+ //rules for new nodetype even though at indexing time
it was able to index (due to
+ //use of latest nodetype reg nodestate)
+ //In such a case if the rule name and nodetype name
for query matches then it is
+ //considered a match.
+ //This would though not work for the case where rule
is related to nodetype as used
+ //in query matched via some inheritance chain
+ //TODO Need a way to check if nodetype reg as seen by
IndexDefinition is old then
+ //IndexNode is reopened
+ matchingRule = rule;
+ }
+ if (matchingRule != null){
+ log.debug("Applicable IndexingRule found {}",
matchingRule);
+ return rule;
+ }
+ }
+ //nt:base is applicable for all. This specific condition is
+ //required to support mixin case as filter.getSupertypes() for
mixin based
+ //query only includes the mixin type and not nt:base
+ if (rule.getNodeTypeName().equals(JcrConstants.NT_BASE)){
+ return rule;
+ }
+ }
+ log.trace("No applicable IndexingRule found for any of the
superTypes {}",
+ filter.getSupertypes());
+ }
+ return null;
+ }
+
+ private boolean notSupportedFeature() {
+ if(filter.getPathRestriction() == Filter.PathRestriction.NO_RESTRICTION
+ && filter.matchesAllTypes()
+ && filter.getPropertyRestrictions().isEmpty()) {
+ //This mode includes name(), localname() queries
+ //OrImpl [a/name] = 'Hello' or [b/name] = 'World'
+ //Relative parent properties where [../foo1] is not null
+ return true;
+ }
+ boolean failTestOnMissingFunctionIndex = true;
+ if (failTestOnMissingFunctionIndex) {
+ // this means even just function restrictions fail the test
+ // (for example "where upper(name) = 'X'",
+ // if a matching function-based index is missing
+ return false;
+ }
+ // the following would ensure the test doesn't fail in that case:
+ for (PropertyRestriction r : filter.getPropertyRestrictions()) {
+ if
(!r.propertyName.startsWith(QueryConstants.FUNCTION_RESTRICTION_PREFIX)) {
+ // not a function restriction
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Determine if the propertyName of a fulltext term indicates current node
+ * @param propertyName property name in the full text term clause
+ */
+ private static boolean nodeScopedTerm(String propertyName) {
+ return propertyName == null || ".".equals(propertyName) ||
"*".equals(propertyName);
+ }
+
+ //~--------------------------------------------------------< PlanResult >
+
+ public static class PlanResult {
+ final String indexPath;
+ final IndexDefinition indexDefinition;
+ final IndexDefinition.IndexingRule indexingRule;
+ private final List<PropertyDefinition> sortedProperties =
newArrayList();
+
+ //Map of actual property name as present in our property definitions
+ private final Map<String, PropertyDefinition> propDefns = newHashMap();
+
+ //Map of property restriction name -> property definition name
+ //like 'jcr:content/status' -> 'status'
+ private final Map<String, String> relPropMapping = newHashMap();
+
+ private boolean nonFullTextConstraints;
+ private int parentDepth;
+ private String parentPathSegment;
+ private boolean relativize;
+ private boolean nodeTypeRestrictions;
+ private boolean nodeNameRestriction;
+ private boolean uniquePathsRequired = true;
+ private PropertyIndexResult propertyIndexResult;
+ private boolean syncNodeTypeRestrictions;
+
+ public PlanResult(String indexPath, IndexDefinition defn,
IndexDefinition.IndexingRule indexingRule) {
+ this.indexPath = indexPath;
+ this.indexDefinition = defn;
+ this.indexingRule = indexingRule;
+ }
+
+ public PropertyDefinition getPropDefn(PropertyRestriction pr){
+ return propDefns.get(pr.propertyName);
+ }
+
+ /**
+ * Returns the property name to be used for query for given
PropertyRestriction
+ * The name can be same as one for property restriction or it can be a
mapped one
+ */
+ public String getPropertyName(PropertyRestriction pr) {
+ return relPropMapping.getOrDefault(pr.propertyName,
pr.propertyName);
+ }
+
+ public boolean hasProperty(String propName){
+ return propDefns.containsKey(propName);
+ }
+
+ public PropertyDefinition getOrderedProperty(int index){
+ return sortedProperties.get(index);
+ }
+
+ public boolean isPathTransformed(){
+ return relativize;
+ }
+
+ public boolean isUniquePathsRequired() {
+ return uniquePathsRequired;
+ }
+
+ /**
+ * Transforms the given path if the query involved relative properties
and index
+ * is not making use of aggregated properties. If the path
+ *
+ * @param path path to transform
+ * @return transformed path. Returns null if the path does not confirm
to relative
+ * path requirements
+ */
+ @CheckForNull
+ public String transformPath(String path){
+ if (isPathTransformed()){
+ // get the base path
+ // ensure the path ends with the given
+ // relative path
+ if (!path.endsWith(parentPathSegment)) {
+ return null;
+ }
+ return getAncestorPath(path, parentDepth);
+ }
+ return path;
+ }
+
+ public boolean evaluateNonFullTextConstraints(){
+ return nonFullTextConstraints;
+ }
+
+ public boolean evaluateNodeTypeRestriction() {
+ return nodeTypeRestrictions;
+ }
+
+ public boolean evaluateSyncNodeTypeRestriction() {
+ return syncNodeTypeRestrictions;
+ }
+
+ public boolean evaluateNodeNameRestriction() {return
nodeNameRestriction;}
+
+ @CheckForNull
+ public PropertyIndexResult getPropertyIndexResult() {
+ return propertyIndexResult;
+ }
+
+ public boolean hasPropertyIndexResult(){
+ return propertyIndexResult != null;
+ }
+
+ private void setParentPath(String relativePath){
+ parentPathSegment = "/" + relativePath;
+ if (relativePath.isEmpty()){
+ // we only restrict non-full-text conditions if there is
+ // no relative property in the full-text constraint
+ enableNonFullTextConstraints();
+ } else {
+ relativize = true;
+ parentDepth = getDepth(relativePath);
+ }
+ }
+
+ private void enableNonFullTextConstraints(){
+ nonFullTextConstraints = true;
+ }
+
+ private void enableNodeTypeEvaluation() {
+ nodeTypeRestrictions = true;
+ }
+
+ private void enableNodeNameRestriction(){
+ nodeNameRestriction = true;
+ }
+
+ private void disableUniquePaths(){
+ uniquePathsRequired = false;
+ }
+ }
+
+ public static class PropertyIndexResult {
+ final String propertyName;
+ final PropertyRestriction pr;
+
+ public PropertyIndexResult(String propertyName, PropertyRestriction
pr) {
+ this.propertyName = propertyName;
+ this.pr = pr;
+ }
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexPlanner.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTracker.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTracker.java?rev=1832166&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTracker.java
(added)
+++
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTracker.java
Thu May 24 13:07:54 2018
@@ -0,0 +1,259 @@
+/*
+ * 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.search.spi.query;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.CheckForNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.commons.PerfLogger;
+import org.apache.jackrabbit.oak.plugins.index.AsyncIndexInfoService;
+import org.apache.jackrabbit.oak.plugins.index.search.BadIndexTracker;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexNode;
+import org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper;
+import org.apache.jackrabbit.oak.spi.commit.CompositeEditor;
+import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorDiff;
+import org.apache.jackrabbit.oak.spi.commit.SubtreeEditor;
+import org.apache.jackrabbit.oak.spi.state.EqualsDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.in;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Lists.newArrayListWithCapacity;
+import static com.google.common.collect.Maps.filterKeys;
+import static com.google.common.collect.Maps.filterValues;
+import static com.google.common.collect.Maps.newHashMap;
+import static java.util.Collections.emptyMap;
+import static
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.INDEX_DEFINITION_NODE;
+import static
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.STATUS_NODE;
+import static
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+
+public abstract class FulltextIndexTracker {
+
+ /** Logger instance. */
+ private static final Logger log =
LoggerFactory.getLogger(FulltextIndexTracker.class);
+ private static final PerfLogger PERF_LOGGER =
+ new
PerfLogger(LoggerFactory.getLogger(FulltextIndexTracker.class.getName() +
".perf"));
+
+ private final BadIndexTracker badIndexTracker = new BadIndexTracker();
+
+ private NodeState root = EMPTY_NODE;
+
+ private AsyncIndexInfoService asyncIndexInfoService;
+
+ private volatile Map<String, IndexNodeManager> indices = emptyMap();
+
+ private volatile boolean refresh;
+
+ protected abstract IndexNodeManager openIndex(String path, NodeState root,
NodeState node);
+
+ synchronized void close() {
+ Map<String, IndexNodeManager> indices = this.indices;
+ this.indices = emptyMap();
+
+ for (Map.Entry<String, IndexNodeManager> entry : indices.entrySet()) {
+ try {
+ entry.getValue().close();
+ } catch (IOException e) {
+ log.error("Failed to close the Lucene index at " +
entry.getKey(), e);
+ }
+ }
+ }
+
+ public synchronized void update(final NodeState root) {
+ if (refresh) {
+ this.root = root;
+ close();
+ refresh = false;
+ log.info("Refreshed the opened indexes");
+ } else {
+ diffAndUpdate(root);
+ }
+ }
+
+ public void setAsyncIndexInfoService(AsyncIndexInfoService
asyncIndexInfoService) {
+ this.asyncIndexInfoService = asyncIndexInfoService;
+ }
+
+ AsyncIndexInfoService getAsyncIndexInfoService() {
+ return asyncIndexInfoService;
+ }
+
+ private synchronized void diffAndUpdate(final NodeState root) {
+ if (asyncIndexInfoService != null &&
!asyncIndexInfoService.hasIndexerUpdatedForAnyLane(this.root, root)) {
+ log.trace("No changed detected in async indexer state. Skipping
further diff");
+ this.root = root;
+ return;
+ }
+
+ Map<String, IndexNodeManager> original = indices;
+ final Map<String, IndexNodeManager> updates = newHashMap();
+
+ Set<String> indexPaths = Sets.newHashSet();
+ indexPaths.addAll(original.keySet());
+ indexPaths.addAll(badIndexTracker.getIndexPaths());
+
+ List<Editor> editors = newArrayListWithCapacity(indexPaths.size());
+ for (final String path : indexPaths) {
+ editors.add(new SubtreeEditor(new DefaultEditor() {
+ @Override
+ public void leave(NodeState before, NodeState after) {
+ try {
+ if (isStatusChanged(before, after) ||
isIndexDefinitionChanged(before, after)) {
+ long start = PERF_LOGGER.start();
+ IndexNodeManager index = openIndex(path, root,
after);
+ PERF_LOGGER.end(start, -1, "[{}] Index found to be
updated. Reopening the IndexNode", path);
+ updates.put(path, index); // index can be null
+ }
+ } catch (Exception e) {
+ badIndexTracker.markBadPersistedIndex(path, e);
+ }
+ }
+ }, Iterables.toArray(PathUtils.elements(path), String.class)));
+ }
+
+ EditorDiff.process(CompositeEditor.compose(editors), this.root, root);
+ this.root = root;
+
+ if (!updates.isEmpty()) {
+ indices = ImmutableMap.<String, IndexNodeManager>builder()
+ .putAll(filterKeys(original, not(in(updates.keySet()))))
+ .putAll(filterValues(updates, notNull()))
+ .build();
+
+ badIndexTracker.markGoodIndexes(updates.keySet());
+
+ //This might take some time as close need to acquire the
+ //write lock which might be held by current running searches
+ //Given that Tracker is now invoked from a BackgroundObserver
+ //not a high concern
+ for (String path : updates.keySet()) {
+ IndexNodeManager index = original.get(path);
+ try {
+ if (index != null) {
+ index.close();
+ }
+ } catch (IOException e) {
+ log.error("Failed to close Lucene index at " + path, e);
+ }
+ }
+ }
+ }
+
+ void refresh() {
+ log.info("Marked tracker to refresh upon next cycle");
+ refresh = true;
+ }
+
+ public IndexNode acquireIndexNode(String path, String type) {
+ IndexNodeManager index = indices.get(path);
+ IndexNode indexNode = index != null ? index.acquire() : null;
+ if (indexNode != null) {
+ return indexNode;
+ } else {
+ return findIndexNode(path, type);
+ }
+ }
+
+ @CheckForNull
+ public IndexDefinition getIndexDefinition(String indexPath){
+ IndexNodeManager node = indices.get(indexPath);
+ if (node != null){
+ //Accessing the definition should not require
+ //locking as its immutable state
+ return node.getDefinition();
+ }
+ return null;
+ }
+
+ Set<String> getIndexNodePaths(){
+ return indices.keySet();
+ }
+
+ BadIndexTracker getBadIndexTracker() {
+ return badIndexTracker;
+ }
+
+ NodeState getRoot() {
+ return root;
+ }
+
+ private synchronized IndexNode findIndexNode(String path, String type) {
+ // Retry the lookup from acquireIndexNode now that we're
+ // synchronized. The acquire() call is guaranteed to succeed
+ // since the close() method is also synchronized.
+ IndexNodeManager index = indices.get(path);
+ if (index != null) {
+ IndexNode indexNode = index.acquire();
+ return checkNotNull(indexNode);
+ }
+
+ if (badIndexTracker.isIgnoredBadIndex(path)){
+ return null;
+ }
+
+ NodeState node = root;
+ for (String name : PathUtils.elements(path)) {
+ node = node.getChildNode(name);
+ }
+
+ try {
+ if (IndexHelper.isIndexNodeOfType(node, type)) {
+ index = openIndex(path, root, node);
+ if (index != null) {
+ IndexNode indexNode = index.acquire();
+ checkNotNull(indexNode);
+ indices = ImmutableMap.<String, IndexNodeManager>builder()
+ .putAll(indices)
+ .put(path, index)
+ .build();
+ badIndexTracker.markGoodIndex(path);
+ return indexNode;
+ }
+ } else if (node.exists()) {
+ log.warn("Cannot open Index at path {} as the index is not of
type {}", path, type);
+ }
+ } catch (Throwable e) {
+ badIndexTracker.markBadIndexForRead(path, e);
+ }
+
+ return null;
+ }
+
+
+ private static boolean isStatusChanged(NodeState before, NodeState after) {
+ return !EqualsDiff.equals(before.getChildNode(STATUS_NODE),
after.getChildNode(STATUS_NODE));
+ }
+
+ private static boolean isIndexDefinitionChanged(NodeState before,
NodeState after) {
+ return !EqualsDiff.equals(before.getChildNode(INDEX_DEFINITION_NODE),
after.getChildNode(INDEX_DEFINITION_NODE));
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/FulltextIndexTracker.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNodeManager.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNodeManager.java?rev=1832166&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNodeManager.java
(added)
+++
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNodeManager.java
Thu May 24 13:07:54 2018
@@ -0,0 +1,139 @@
+/*
+ * 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.search.spi.query;
+
+import java.io.IOException;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.annotation.CheckForNull;
+
+import org.apache.jackrabbit.oak.commons.PerfLogger;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexNode;
+import org.apache.jackrabbit.oak.plugins.index.search.ReaderRefreshPolicy;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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;
+
+public abstract class IndexNodeManager {
+ /**
+ * Name of the hidden node under which information about the checkpoints
+ * seen and indexed by each async indexer is kept.
+ */
+ private static final String ASYNC = ":async";
+
+ private static final AtomicInteger SEARCHER_ID_COUNTER = new
AtomicInteger();
+
+ private static final PerfLogger PERF_LOGGER =
+ new
PerfLogger(LoggerFactory.getLogger(IndexNodeManager.class.getName() + ".perf"));
+
+ protected abstract IndexNodeManager open(String indexPath, NodeState root,
NodeState defnNodeState);
+
+ protected abstract void releaseResources();
+
+ protected abstract IndexNode getIndexNode();
+
+ protected abstract ReaderRefreshPolicy getReaderRefreshPolicy();
+
+ protected abstract void refreshReaders();
+
+ protected abstract String getName();
+
+ protected abstract IndexDefinition getDefinition();
+
+ 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(IndexNodeManager.class);
+
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+ 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;
+
+
+ @CheckForNull
+ IndexNode acquire() {
+ lock.readLock().lock();
+ if (closed) {
+ lock.readLock().unlock();
+ return null;
+ } else {
+ boolean success = false;
+ try {
+
getReaderRefreshPolicy().refreshOnReadIfRequired(refreshCallback);
+ IndexNode indexNode = getIndexNode();
+ 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();
+ }
+
+ releaseResources();
+ }
+
+}
Propchange:
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/query/IndexNodeManager.java
------------------------------------------------------------------------------
svn:eol-style = native