| /* |
| * 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; |
| |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.regex.Pattern; |
| import java.util.stream.Stream; |
| |
| import javax.jcr.PropertyType; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.nodetype.NodeTypeIterator; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import com.google.common.primitives.Ints; |
| import org.apache.jackrabbit.JcrConstants; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Root; |
| import org.apache.jackrabbit.oak.api.Tree; |
| import org.apache.jackrabbit.oak.api.Type; |
| import org.apache.jackrabbit.oak.commons.PathUtils; |
| import org.apache.jackrabbit.oak.namepath.NamePathMapper; |
| import org.apache.jackrabbit.oak.plugins.index.IndexConstants; |
| import org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil; |
| import org.apache.jackrabbit.oak.plugins.index.search.util.FunctionIndexProcessor; |
| import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; |
| import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager; |
| import org.apache.jackrabbit.oak.plugins.tree.factories.RootFactory; |
| import org.apache.jackrabbit.oak.plugins.tree.factories.TreeFactory; |
| import org.apache.jackrabbit.oak.spi.filter.PathFilter; |
| import org.apache.jackrabbit.oak.spi.query.QueryIndex.OrderEntry; |
| import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; |
| import org.apache.jackrabbit.oak.spi.state.NodeBuilder; |
| import org.apache.jackrabbit.oak.spi.state.NodeState; |
| import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; |
| 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.checkNotNull; |
| 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 com.google.common.collect.Sets.newHashSet; |
| import static org.apache.jackrabbit.JcrConstants.JCR_SCORE; |
| import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM; |
| import static org.apache.jackrabbit.JcrConstants.NT_BASE; |
| import static org.apache.jackrabbit.oak.api.Type.NAMES; |
| import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath; |
| import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.DECLARING_NODE_TYPES; |
| import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.ENTRY_COUNT_PROPERTY_NAME; |
| import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEXING_MODE_NRT; |
| import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEXING_MODE_SYNC; |
| import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_COUNT; |
| import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.*; |
| import static org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition.DEFAULT_BOOST; |
| import static org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil.getOptionalValue; |
| import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; |
| import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.JCR_NODE_TYPES; |
| import static org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants.NODE_TYPES_PATH; |
| |
| /** |
| * Represents a configuration of an index. |
| */ |
| public class IndexDefinition implements Aggregate.AggregateMapper { |
| /** |
| * Name of the internal property that contains the child order defined in |
| * org.apache.jackrabbit.oak.plugins.tree.TreeConstants |
| */ |
| private static final String OAK_CHILD_ORDER = ":childOrder"; |
| |
| private static final Logger log = LoggerFactory.getLogger(IndexDefinition.class); |
| |
| private static boolean disableStoredIndexDefinition; |
| |
| /** |
| * Blob size to use by default. To avoid issues in OAK-2105 the size should not |
| * be power of 2. |
| */ |
| public static final int DEFAULT_BLOB_SIZE = 1024 * 1024 - 1024; |
| |
| /** |
| * Default entry count to keep estimated entry count low. |
| */ |
| public static final long DEFAULT_ENTRY_COUNT = 1000; |
| |
| /** |
| * Default value for property {@link #maxFieldLength}. |
| */ |
| public static final int DEFAULT_MAX_FIELD_LENGTH = 10000; |
| |
| public static final int DEFAULT_MAX_EXTRACT_LENGTH = -10; |
| |
| /** |
| * System managed hidden property to record the current index version |
| */ |
| public static final String INDEX_VERSION = ":version"; |
| |
| /** |
| * Hidden node under index definition which is used to store the index definition |
| * nodestate as it was at time of reindexing |
| */ |
| public static final String INDEX_DEFINITION_NODE = ":index-definition"; |
| |
| /** |
| * Hidden node under index definition which is used to store meta info |
| */ |
| public static final String STATUS_NODE = ":status"; |
| |
| /** |
| * Property on status node which refers to the date when the index was lastUpdated |
| * This may not be the same time as when index was closed but the time of checkpoint |
| * upto which index is upto date (OAK-6194) |
| */ |
| public static final String STATUS_LAST_UPDATED = "lastUpdated"; |
| |
| public static final String CREATION_TIMESTAMP = "creationTimestamp"; |
| public static final String REINDEX_COMPLETION_TIMESTAMP = "reindexCompletionTimestamp"; |
| /** |
| * Property to store paths for documents failed during index updates. |
| */ |
| public static final String FAILED_DOC_PATHS = "failedDocPaths"; |
| |
| /** |
| * Meta property which provides the unique id |
| */ |
| public static final String PROP_UID = "uid"; |
| |
| private static final String TYPES_ALLOW_ALL_NAME = "all"; |
| |
| static final int TYPES_ALLOW_NONE = PropertyType.UNDEFINED; |
| |
| static final int TYPES_ALLOW_ALL = -1; |
| |
| /** |
| * Default suggesterUpdateFrequencyMinutes |
| */ |
| public static final int DEFAULT_SUGGESTER_UPDATE_FREQUENCY_MINUTES = 10; |
| |
| /** |
| * Default no. of facets retrieved |
| */ |
| static final int DEFAULT_FACET_COUNT = 10; |
| |
| /** |
| * native sort order |
| */ |
| public static final OrderEntry NATIVE_SORT_ORDER = new OrderEntry(JCR_SCORE, Type.UNDEFINED, |
| OrderEntry.Order.DESCENDING); |
| |
| protected final boolean fullTextEnabled; |
| |
| protected final NodeState definition; |
| |
| private final NodeState root; |
| |
| private final IndexFormatVersion version; |
| |
| private final String funcName; |
| |
| private final int blobSize; |
| |
| /** |
| * Defines the maximum estimated entry count configured. |
| * Defaults to {#DEFAULT_ENTRY_COUNT} |
| */ |
| private final long entryCount; |
| |
| private final boolean entryCountDefined; |
| |
| private final double costPerEntry; |
| |
| private final double costPerExecution; |
| |
| /** |
| * The {@link IndexingRule}s inside this configuration. Keys being the NodeType names |
| */ |
| private final Map<String, List<IndexingRule>> indexRules; |
| |
| private final List<IndexingRule> definedRules; |
| |
| private final String indexName; |
| |
| private final boolean evaluatePathRestrictions; |
| |
| private final Map<String, Aggregate> aggregates; |
| |
| private final String scorerProviderName; |
| |
| private final boolean hasCustomTikaConfig; |
| |
| private final Map<String, String> customTikaMimeTypeMappings; |
| |
| private final int maxFieldLength; |
| |
| private final int maxExtractLength; |
| |
| private final int suggesterUpdateFrequencyMinutes; |
| |
| private final long reindexCount; |
| |
| private final PathFilter pathFilter; |
| |
| @Nullable |
| private final String[] queryPaths; |
| |
| private final boolean suggestAnalyzed; |
| |
| private final SecureFacetConfiguration secureFacets; |
| private final long randomSeed; |
| |
| private final int numberOfTopFacets; |
| |
| private final boolean suggestEnabled; |
| |
| private final boolean spellcheckEnabled; |
| |
| private final String indexPath; |
| |
| private final boolean nrtIndexMode; |
| private final boolean syncIndexMode; |
| |
| private final boolean nodeTypeIndex; |
| |
| @Nullable |
| private final String uid; |
| |
| @Nullable |
| private final String[] indexTags; |
| |
| private final boolean syncPropertyIndexes; |
| |
| private final String useIfExists; |
| |
| private final boolean deprecated; |
| |
| private final boolean testMode; |
| |
| /** |
| * See {@link FulltextIndexConstants#PROP_VALUE_REGEX} |
| */ |
| private final Pattern propertyRegex; |
| |
| public boolean isTestMode() { |
| return testMode; |
| } |
| |
| //~--------------------------------------------------------< Builder > |
| |
| // TODO - this method should be removed after tests don't use it anymore |
| public static Builder newBuilder(NodeState root, NodeState defn, String indexPath){ |
| return new Builder().root(root).defn(defn).indexPath(indexPath); |
| } |
| |
| public static class Builder { |
| /** |
| * Default unique id used when no existing uid is defined |
| * and index is not populated |
| */ |
| private static final String DEFAULT_UID = "0"; |
| protected NodeState root; |
| private NodeState defn; |
| protected String indexPath; |
| protected String uid; |
| private boolean reindexMode; |
| protected IndexFormatVersion version; |
| |
| public Builder root(NodeState root) { |
| this.root = checkNotNull(root); |
| return this; |
| } |
| |
| public Builder defn(NodeState defn) { |
| this.defn = checkNotNull(defn); |
| return this; |
| } |
| |
| public Builder indexPath(String indexPath) { |
| this.indexPath = checkNotNull(indexPath); |
| return this; |
| } |
| |
| public Builder uid(String uid){ |
| this.uid = uid; |
| return this; |
| } |
| |
| public Builder version(IndexFormatVersion version){ |
| this.version = version; |
| return this; |
| } |
| |
| |
| public Builder reindex(){ |
| this.reindexMode = true; |
| return this; |
| } |
| |
| public IndexDefinition build(){ |
| if (version == null){ |
| version = determineIndexFormatVersion(defn); |
| } |
| if (uid == null){ |
| uid = determineUniqueId(defn); |
| if (uid == null && !IndexDefinition.hasPersistedIndex(defn)){ |
| uid = DEFAULT_UID; |
| } |
| } |
| |
| NodeState indexDefnStateToUse = defn; |
| if (!reindexMode){ |
| indexDefnStateToUse = getIndexDefinitionState(defn); |
| } |
| return createInstance(indexDefnStateToUse); |
| } |
| |
| // TODO: This method should be abstract... to be done later after tests are updated so that they compile |
| protected IndexDefinition createInstance(NodeState indexDefnStateToUse) { |
| return new IndexDefinition(root, indexDefnStateToUse, version, uid, indexPath); |
| } |
| } |
| |
| public IndexDefinition(NodeState root, NodeState defn, String indexPath) { |
| this(root, getIndexDefinitionState(defn), determineIndexFormatVersion(defn), determineUniqueId(defn), indexPath); |
| } |
| |
| protected IndexDefinition(NodeState root, NodeState defn, IndexFormatVersion version, String uid, String indexPath) { |
| try { |
| this.root = root; |
| this.version = checkNotNull(version); |
| this.uid = uid; |
| this.definition = defn; |
| this.indexPath = checkNotNull(indexPath); |
| this.indexName = indexPath; |
| this.indexTags = getOptionalStrings(defn, IndexConstants.INDEX_TAGS); |
| this.nodeTypeIndex = getOptionalValue(defn, FulltextIndexConstants.PROP_INDEX_NODE_TYPE, false); |
| |
| this.blobSize = getOptionalValue(defn, BLOB_SIZE, DEFAULT_BLOB_SIZE); |
| |
| this.aggregates = nodeTypeIndex ? Collections.emptyMap() : collectAggregates(defn); |
| |
| NodeState rulesState = defn.getChildNode(FulltextIndexConstants.INDEX_RULES); |
| if (!rulesState.exists()){ |
| rulesState = createIndexRules(defn).getNodeState(); |
| } |
| |
| this.testMode = getOptionalValue(defn, FulltextIndexConstants.TEST_MODE, false); |
| List<IndexingRule> definedIndexRules = newArrayList(); |
| this.indexRules = collectIndexRules(rulesState, definedIndexRules); |
| this.definedRules = ImmutableList.copyOf(definedIndexRules); |
| |
| this.fullTextEnabled = hasFulltextEnabledIndexRule(definedIndexRules); |
| this.evaluatePathRestrictions = getOptionalValue(defn, EVALUATE_PATH_RESTRICTION, false); |
| if (defn.hasProperty(PROP_VALUE_REGEX)) { |
| this.propertyRegex = Pattern.compile(getOptionalValue(defn, PROP_VALUE_REGEX, "")); |
| } else { |
| this.propertyRegex = null; |
| } |
| String functionName = getOptionalValue(defn, FulltextIndexConstants.FUNC_NAME, null); |
| if (fullTextEnabled && functionName == null) { |
| functionName = getDefaultFunctionName(); |
| } |
| this.funcName = functionName != null ? "native*" + functionName : null; |
| |
| if (defn.hasProperty(ENTRY_COUNT_PROPERTY_NAME)) { |
| this.entryCountDefined = true; |
| this.entryCount = getOptionalValue(defn, ENTRY_COUNT_PROPERTY_NAME, DEFAULT_ENTRY_COUNT); |
| } else { |
| this.entryCountDefined = false; |
| this.entryCount = DEFAULT_ENTRY_COUNT; |
| } |
| |
| this.maxFieldLength = getOptionalValue(defn, FulltextIndexConstants.MAX_FIELD_LENGTH, DEFAULT_MAX_FIELD_LENGTH); |
| this.costPerEntry = getOptionalValue(defn, FulltextIndexConstants.COST_PER_ENTRY, getDefaultCostPerEntry(version)); |
| this.costPerExecution = getOptionalValue(defn, FulltextIndexConstants.COST_PER_EXECUTION, 1.0); |
| this.hasCustomTikaConfig = getTikaConfigNode().exists(); |
| this.customTikaMimeTypeMappings = buildMimeTypeMap(definition.getChildNode(TIKA).getChildNode(TIKA_MIME_TYPES)); |
| this.maxExtractLength = determineMaxExtractLength(); |
| this.suggesterUpdateFrequencyMinutes = evaluateSuggesterUpdateFrequencyMinutes(defn, |
| DEFAULT_SUGGESTER_UPDATE_FREQUENCY_MINUTES); |
| this.scorerProviderName = getOptionalValue(defn, FulltextIndexConstants.PROP_SCORER_PROVIDER, null); |
| this.reindexCount = getOptionalValue(defn, REINDEX_COUNT, 0); |
| this.pathFilter = PathFilter.from(new ReadOnlyBuilder(defn)); |
| this.queryPaths = getOptionalStrings(defn, IndexConstants.QUERY_PATHS); |
| this.suggestAnalyzed = evaluateSuggestAnalyzed(defn, false); |
| |
| { |
| PropertyState randomPS = defn.getProperty(PROP_RANDOM_SEED); |
| if (randomPS != null && randomPS.getType() == Type.LONG) { |
| randomSeed = randomPS.getValue(Type.LONG); |
| } else { |
| // create a random number |
| randomSeed = UUID.randomUUID().getMostSignificantBits(); |
| } |
| } |
| if (defn.hasChildNode(FACETS)) { |
| NodeState facetsConfig = defn.getChildNode(FACETS); |
| this.secureFacets = SecureFacetConfiguration.getInstance(randomSeed, facetsConfig); |
| this.numberOfTopFacets = getOptionalValue(facetsConfig, PROP_FACETS_TOP_CHILDREN, DEFAULT_FACET_COUNT); |
| } else { |
| this.secureFacets = SecureFacetConfiguration.getInstance(randomSeed, null); |
| this.numberOfTopFacets = DEFAULT_FACET_COUNT; |
| } |
| |
| this.suggestEnabled = evaluateSuggestionEnabled(); |
| this.spellcheckEnabled = evaluateSpellcheckEnabled(); |
| this.nrtIndexMode = supportsNRTIndexing(defn); |
| this.syncIndexMode = supportsSyncIndexing(defn); |
| this.syncPropertyIndexes = definedRules.stream().anyMatch(ir -> !ir.syncProps.isEmpty()); |
| this.useIfExists = getOptionalValue(defn, IndexConstants.USE_IF_EXISTS, null); |
| this.deprecated = getOptionalValue(defn, IndexConstants.INDEX_DEPRECATED, false); |
| } catch (IllegalStateException e) { |
| log.error("Config error for index definition at {} . Please correct the index definition " |
| + "and reindex after correction. Additional Info : {}", indexPath, e.getMessage(), e); |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| public NodeState getDefinitionNodeState() { |
| return definition; |
| } |
| |
| public boolean isEnabled() { |
| if (useIfExists == null) { |
| return true; |
| } |
| if (!PathUtils.isValid(useIfExists)) { |
| return false; |
| } |
| NodeState nodeState = root; |
| for (String element : PathUtils.elements(useIfExists)) { |
| if (element.startsWith("@")) { |
| return nodeState.hasProperty(element.substring(1)); |
| } |
| nodeState = nodeState.getChildNode(element); |
| if (!nodeState.exists()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public boolean isFullTextEnabled() { |
| return fullTextEnabled; |
| } |
| |
| public String getFunctionName(){ |
| return funcName; |
| } |
| |
| //TODO Should this method be abstract? |
| protected String getDefaultFunctionName() { |
| return "fulltext";//TODO Should this be FulltextIndexConstants.FUNC_NAME |
| } |
| |
| public boolean hasFunctionDefined(){ |
| return funcName != null; |
| } |
| |
| /** |
| * Size in bytes for the blobs created while storing the index content |
| * @return size in bytes |
| */ |
| public int getBlobSize() { |
| return blobSize; |
| } |
| |
| public long getReindexCount(){ |
| return reindexCount; |
| } |
| |
| public long getEntryCount() { |
| return entryCount; |
| } |
| |
| private int evaluateSuggesterUpdateFrequencyMinutes(NodeState defn, int defaultValue) { |
| NodeState suggestionConfig = defn.getChildNode(FulltextIndexConstants.SUGGESTION_CONFIG); |
| |
| if (!suggestionConfig.exists()) { |
| //handle backward compatibility |
| return getOptionalValue(defn, FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, defaultValue); |
| } |
| |
| return getOptionalValue(suggestionConfig, FulltextIndexConstants.SUGGEST_UPDATE_FREQUENCY_MINUTES, defaultValue); |
| } |
| |
| public int getSuggesterUpdateFrequencyMinutes() { |
| return suggesterUpdateFrequencyMinutes; |
| } |
| |
| public boolean isEntryCountDefined() { |
| return entryCountDefined; |
| } |
| |
| public double getCostPerEntry() { |
| return costPerEntry; |
| } |
| |
| protected double getDefaultCostPerEntry(IndexFormatVersion version) { |
| return 1.0; |
| } |
| |
| public double getCostPerExecution() { |
| return costPerExecution; |
| } |
| |
| public long getFulltextEntryCount(long numOfDocs){ |
| if (isEntryCountDefined()){ |
| return Math.min(getEntryCount(), numOfDocs); |
| } |
| return numOfDocs; |
| } |
| |
| public boolean isDeprecated() { |
| return deprecated; |
| } |
| |
| public IndexFormatVersion getVersion() { |
| return version; |
| } |
| |
| |
| public boolean isOfOldFormat(){ |
| return !hasIndexingRules(definition); |
| } |
| |
| public boolean evaluatePathRestrictions() { |
| return evaluatePathRestrictions; |
| } |
| |
| public boolean hasCustomTikaConfig(){ |
| return hasCustomTikaConfig; |
| } |
| |
| public InputStream getTikaConfig(){ |
| return ConfigUtil.getBlob(getTikaConfigNode(), TIKA_CONFIG).getNewStream(); |
| } |
| |
| public String getTikaMappedMimeType(String type) { |
| return customTikaMimeTypeMappings.getOrDefault(type, type); |
| } |
| |
| public String getIndexName() { |
| return indexName; |
| } |
| |
| public String[] getIndexTags() { |
| return indexTags; |
| } |
| |
| public int getMaxExtractLength() { |
| return maxExtractLength; |
| } |
| |
| public String getScorerProviderName() { |
| return scorerProviderName; |
| } |
| |
| public PathFilter getPathFilter() { |
| return pathFilter; |
| } |
| |
| @Nullable |
| public String[] getQueryPaths() { |
| return queryPaths; |
| } |
| |
| @Nullable |
| public String getUniqueId() { |
| return uid; |
| } |
| |
| public boolean isNRTIndexingEnabled() { |
| return nrtIndexMode; |
| } |
| |
| public boolean isSyncIndexingEnabled() { |
| return syncIndexMode; |
| } |
| |
| public boolean hasSyncPropertyDefinitions() { |
| return syncPropertyIndexes; |
| } |
| |
| public boolean isPureNodeTypeIndex() { |
| return nodeTypeIndex; |
| } |
| |
| /** |
| * Check if the index definition is fresh, or (some) indexing has occurred. |
| * |
| * WARNING: If there is _any_ hidden node, then it is assumed that |
| * no reindex is needed. Even if the hidden node is completely unrelated |
| * and doesn't contain index data (for example the node ":status"). |
| * See also OAK-7991. |
| * |
| * @param definition nodestate for Index Definition |
| * @return true if index has some indexed content |
| */ |
| public static boolean hasPersistedIndex(NodeState definition){ |
| for (String rm : definition.getChildNodeNames()) { |
| if (NodeStateUtils.isHidden(rm)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isDisableStoredIndexDefinition() { |
| return disableStoredIndexDefinition; |
| } |
| |
| public static void setDisableStoredIndexDefinition(boolean disableStoredIndexDefinitionDefault) { |
| IndexDefinition.disableStoredIndexDefinition = disableStoredIndexDefinitionDefault; |
| } |
| |
| public Set<String> getRelativeNodeNames(){ |
| //Can be computed lazily as required only for oak-run indexing for now |
| Set<String> names = new HashSet<>(); |
| for (IndexingRule r : definedRules) { |
| for (Aggregate.Include i : r.aggregate.getIncludes()) { |
| for (int d = 0; d < i.maxDepth(); d++) { |
| if (!i.isPattern(d)) { |
| names.add(i.getElementNameIfNotAPattern(d)); |
| } |
| } |
| } |
| } |
| return names; |
| } |
| |
| public boolean indexesRelativeNodes(){ |
| for (IndexingRule r : definedRules) { |
| if (!r.aggregate.getIncludes().isEmpty()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| return "Fulltext Index : " + indexName; |
| } |
| |
| |
| //~---------------------------------------------------< Aggregates > |
| |
| @Nullable |
| public Aggregate getAggregate(String nodeType){ |
| Aggregate agg = aggregates.get(nodeType); |
| return agg; |
| } |
| |
| private Map<String, Aggregate> collectAggregates(NodeState defn) { |
| Map<String, Aggregate> aggregateMap = newHashMap(); |
| |
| for (ChildNodeEntry cne : defn.getChildNode(FulltextIndexConstants.AGGREGATES).getChildNodeEntries()) { |
| String nodeType = cne.getName(); |
| int recursionLimit = getOptionalValue(cne.getNodeState(), FulltextIndexConstants.AGG_RECURSIVE_LIMIT, |
| Aggregate.RECURSIVE_AGGREGATION_LIMIT_DEFAULT); |
| |
| List<Aggregate.Include> includes = newArrayList(); |
| for (ChildNodeEntry include : cne.getNodeState().getChildNodeEntries()) { |
| NodeState is = include.getNodeState(); |
| String primaryType = is.getString(FulltextIndexConstants.AGG_PRIMARY_TYPE); |
| String path = is.getString(FulltextIndexConstants.AGG_PATH); |
| boolean relativeNode = getOptionalValue(is, FulltextIndexConstants.AGG_RELATIVE_NODE, false); |
| if (path == null) { |
| log.warn("Aggregate pattern in {} does not have required property [{}]. {} aggregate rule would " + |
| "be ignored", this, FulltextIndexConstants.AGG_PATH, include.getName()); |
| continue; |
| } |
| includes.add(new Aggregate.NodeInclude(this, primaryType, path, relativeNode)); |
| } |
| aggregateMap.put(nodeType, new Aggregate(nodeType, includes, recursionLimit)); |
| } |
| |
| return ImmutableMap.copyOf(aggregateMap); |
| } |
| |
| //~---------------------------------------------------< IndexRule > |
| |
| public boolean hasMatchingNodeTypeReg(NodeState root){ |
| return this.root.getChildNode(JCR_SYSTEM).getChildNode(JCR_NODE_TYPES) |
| .equals(root.getChildNode(JCR_SYSTEM).getChildNode(JCR_NODE_TYPES)); |
| } |
| |
| |
| public List<IndexingRule> getDefinedRules() { |
| return definedRules; |
| } |
| |
| @Nullable |
| public IndexingRule getApplicableIndexingRule(String primaryNodeType) { |
| //This method would be invoked for every node. So be as |
| //conservative as possible in object creation |
| List<IndexingRule> rules = null; |
| List<IndexingRule> r = indexRules.get(primaryNodeType); |
| if (r != null) { |
| rules = new ArrayList<IndexingRule>(); |
| rules.addAll(r); |
| } |
| |
| if (rules != null) { |
| for (IndexingRule rule : rules) { |
| if (rule.appliesTo(primaryNodeType)) { |
| return rule; |
| } |
| } |
| } |
| |
| // no applicable rule |
| return null; |
| } |
| |
| /** |
| * Returns the first indexing rule that applies to the given node |
| * <code>state</code>. |
| * |
| * @param state a node state. |
| * @return the indexing rule or <code>null</code> if none applies. |
| */ |
| @Nullable |
| public IndexingRule getApplicableIndexingRule(NodeState state) { |
| //This method would be invoked for every node. So be as |
| //conservative as possible in object creation |
| List<IndexingRule> rules = null; |
| List<IndexingRule> r = indexRules.get(getPrimaryTypeName(state)); |
| if (r != null) { |
| rules = new ArrayList<IndexingRule>(); |
| rules.addAll(r); |
| } |
| |
| for (String name : getMixinTypeNames(state)) { |
| r = indexRules.get(name); |
| if (r != null) { |
| if (rules == null) { |
| rules = new ArrayList<IndexingRule>(); |
| } |
| rules.addAll(r); |
| } |
| } |
| |
| if (rules != null) { |
| for (IndexingRule rule : rules) { |
| if (rule.appliesTo(state)) { |
| return rule; |
| } |
| } |
| } |
| |
| // no applicable rule |
| return null; |
| } |
| |
| private Map<String, List<IndexingRule>> collectIndexRules(NodeState indexRules, |
| List<IndexingRule> definedIndexRules){ |
| //TODO if a rule is defined for nt:base then this map would have entry for each |
| //registered nodeType in the system |
| |
| if (!indexRules.exists()) { |
| return Collections.emptyMap(); |
| } |
| |
| if (!hasOrderableChildren(indexRules)){ |
| log.warn("IndexRule node does not have orderable children in [{}]", IndexDefinition.this); |
| } |
| |
| Map<String, List<IndexingRule>> nt2rules = newHashMap(); |
| ReadOnlyNodeTypeManager ntReg = createNodeTypeManager(RootFactory.createReadOnlyRoot(root)); |
| |
| //Use Tree API to read ordered child nodes |
| Tree ruleTree = TreeFactory.createReadOnlyTree(indexRules); |
| final List<String> allNames = getAllNodeTypes(ntReg); |
| for (Tree ruleEntry : ruleTree.getChildren()) { |
| IndexingRule rule = new IndexingRule(ruleEntry.getName(), indexRules.getChildNode(ruleEntry.getName())); |
| definedIndexRules.add(rule); |
| |
| // register under node type and all its sub types |
| log.trace("Found rule '{}' for NodeType '{}'", rule, rule.getNodeTypeName()); |
| |
| List<String> ntNames = allNames; |
| if (!rule.inherited){ |
| //Trim the list to rule's nodeType so that inheritance check |
| //is not performed for other nodeTypes |
| ntNames = Collections.singletonList(rule.getNodeTypeName()); |
| } |
| |
| for (String ntName : ntNames) { |
| if (ntReg.isNodeType(ntName, rule.getNodeTypeName())) { |
| List<IndexingRule> perNtConfig = nt2rules.get(ntName); |
| if (perNtConfig == null) { |
| perNtConfig = new ArrayList<IndexingRule>(); |
| nt2rules.put(ntName, perNtConfig); |
| } |
| log.trace("Registering rule '{}' for name '{}'", rule, ntName); |
| perNtConfig.add(new IndexingRule(rule, ntName)); |
| } |
| } |
| } |
| |
| for (Map.Entry<String, List<IndexingRule>> e : nt2rules.entrySet()){ |
| e.setValue(ImmutableList.copyOf(e.getValue())); |
| } |
| |
| return ImmutableMap.copyOf(nt2rules); |
| } |
| |
| private boolean areAllTypesIndexed() { |
| IndexingRule ntBaseRule = getApplicableIndexingRule(NT_BASE); |
| return ntBaseRule != null; |
| } |
| |
| private boolean evaluateSuggestionEnabled() { |
| for (IndexingRule indexingRule : definedRules) { |
| for (PropertyDefinition propertyDefinition : indexingRule.propConfigs.values()) { |
| if (propertyDefinition.useInSuggest) { |
| return true; |
| } |
| } |
| for (NamePattern np : indexingRule.namePatterns) { |
| if (np.getConfig().useInSuggest) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public Pattern getPropertyRegex() { |
| return propertyRegex; |
| } |
| |
| public boolean isSuggestEnabled() { |
| return suggestEnabled; |
| } |
| |
| private boolean evaluateSpellcheckEnabled() { |
| for (IndexingRule indexingRule : definedRules) { |
| for (PropertyDefinition propertyDefinition : indexingRule.propConfigs.values()) { |
| if (propertyDefinition.useInSpellcheck) { |
| return true; |
| } |
| } |
| for (NamePattern np : indexingRule.namePatterns) { |
| if (np.getConfig().useInSpellcheck) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public boolean isSpellcheckEnabled() { |
| return spellcheckEnabled; |
| } |
| |
| public String getIndexPath() { |
| return indexPath; |
| } |
| |
| private boolean evaluateSuggestAnalyzed(NodeState defn, boolean defaultValue) { |
| NodeState suggestionConfig = defn.getChildNode(FulltextIndexConstants.SUGGESTION_CONFIG); |
| |
| if (!suggestionConfig.exists()) { |
| //handle backward compatibility |
| return getOptionalValue(defn, FulltextIndexConstants.SUGGEST_ANALYZED, defaultValue); |
| } |
| |
| return getOptionalValue(suggestionConfig, FulltextIndexConstants.SUGGEST_ANALYZED, defaultValue); |
| } |
| |
| public boolean isSuggestAnalyzed() { |
| return suggestAnalyzed; |
| } |
| |
| public SecureFacetConfiguration getSecureFacetConfiguration() { |
| return secureFacets; |
| } |
| |
| public int getNumberOfTopFacets() { |
| return numberOfTopFacets; |
| } |
| |
| public class IndexingRule { |
| private final String baseNodeType; |
| private final String nodeTypeName; |
| /** |
| * Case insensitive map of lower cased propertyName to propertyConfigs |
| */ |
| private final Map<String, PropertyDefinition> propConfigs; |
| /** |
| * List of {@code NamePattern}s configured for this rule |
| */ |
| private final List<NamePattern> namePatterns; |
| private final List<PropertyDefinition> nullCheckEnabledProperties; |
| private final List<PropertyDefinition> functionRestrictions; |
| private final List<PropertyDefinition> notNullCheckEnabledProperties; |
| private final List<PropertyDefinition> nodeScopeAnalyzedProps; |
| private final List<PropertyDefinition> syncProps; |
| private final List<PropertyDefinition> similarityProperties; |
| private final boolean indexesAllNodesOfMatchingType; |
| private final boolean nodeNameIndexed; |
| |
| public final float boost; |
| final boolean inherited; |
| public final int propertyTypes; |
| final boolean fulltextEnabled; |
| public final boolean propertyIndexEnabled; |
| final boolean nodeFullTextIndexed; |
| |
| final Aggregate aggregate; |
| final Aggregate propAggregate; |
| |
| IndexingRule(String nodeTypeName, NodeState config) { |
| this.nodeTypeName = nodeTypeName; |
| this.baseNodeType = nodeTypeName; |
| this.boost = getOptionalValue(config, FIELD_BOOST, DEFAULT_BOOST); |
| this.inherited = getOptionalValue(config, FulltextIndexConstants.RULE_INHERITED, true); |
| this.propertyTypes = getSupportedTypes(config, INCLUDE_PROPERTY_TYPES, TYPES_ALLOW_ALL); |
| |
| List<NamePattern> namePatterns = newArrayList(); |
| List<PropertyDefinition> nonExistentProperties = newArrayList(); |
| List<PropertyDefinition> functionRestrictions = newArrayList(); |
| List<PropertyDefinition> existentProperties = newArrayList(); |
| List<PropertyDefinition> nodeScopeAnalyzedProps = newArrayList(); |
| List<PropertyDefinition> syncProps = newArrayList(); |
| List<PropertyDefinition> similarityProperties = newArrayList(); |
| List<Aggregate.Include> propIncludes = newArrayList(); |
| this.propConfigs = collectPropConfigs(config, namePatterns, propIncludes, nonExistentProperties, |
| existentProperties, nodeScopeAnalyzedProps, functionRestrictions, syncProps, similarityProperties); |
| this.propAggregate = new Aggregate(nodeTypeName, propIncludes); |
| this.aggregate = combine(propAggregate, nodeTypeName); |
| |
| this.namePatterns = ImmutableList.copyOf(namePatterns); |
| this.nodeScopeAnalyzedProps = ImmutableList.copyOf(nodeScopeAnalyzedProps); |
| this.nullCheckEnabledProperties = ImmutableList.copyOf(nonExistentProperties); |
| this.functionRestrictions = ImmutableList.copyOf(functionRestrictions); |
| this.notNullCheckEnabledProperties = ImmutableList.copyOf(existentProperties); |
| this.similarityProperties = ImmutableList.copyOf(similarityProperties); |
| this.fulltextEnabled = aggregate.hasNodeAggregates() || hasAnyFullTextEnabledProperty(); |
| this.nodeFullTextIndexed = aggregate.hasNodeAggregates() || anyNodeScopeIndexedProperty(); |
| this.propertyIndexEnabled = hasAnyPropertyIndexConfigured(); |
| this.indexesAllNodesOfMatchingType = areAlMatchingNodeByTypeIndexed(); |
| this.nodeNameIndexed = evaluateNodeNameIndexed(config); |
| this.syncProps = ImmutableList.copyOf(syncProps); |
| validateRuleDefinition(); |
| } |
| |
| /** |
| * Creates a new indexing rule base on an existing one, but for a |
| * different node type name. |
| * |
| * @param original the existing rule. |
| * @param nodeTypeName the node type name for the rule. |
| */ |
| IndexingRule(IndexingRule original, String nodeTypeName) { |
| this.nodeTypeName = nodeTypeName; |
| this.baseNodeType = original.getNodeTypeName(); |
| this.propConfigs = original.propConfigs; |
| this.namePatterns = original.namePatterns; |
| this.boost = original.boost; |
| this.inherited = original.inherited; |
| this.propertyTypes = original.propertyTypes; |
| this.propertyIndexEnabled = original.propertyIndexEnabled; |
| this.propAggregate = original.propAggregate; |
| this.nullCheckEnabledProperties = original.nullCheckEnabledProperties; |
| this.notNullCheckEnabledProperties = original.notNullCheckEnabledProperties; |
| this.functionRestrictions = original.functionRestrictions; |
| this.nodeScopeAnalyzedProps = original.nodeScopeAnalyzedProps; |
| this.aggregate = combine(propAggregate, nodeTypeName); |
| this.fulltextEnabled = aggregate.hasNodeAggregates() || original.fulltextEnabled; |
| this.nodeFullTextIndexed = aggregate.hasNodeAggregates() || original.nodeFullTextIndexed; |
| this.indexesAllNodesOfMatchingType = areAlMatchingNodeByTypeIndexed(); |
| this.nodeNameIndexed = original.nodeNameIndexed; |
| this.syncProps = original.syncProps; |
| this.similarityProperties = original.similarityProperties; |
| } |
| |
| /** |
| * Returns <code>true</code> if the property with the given name is |
| * indexed according to this rule. |
| * |
| * @param propertyName the name of a property. |
| * @return <code>true</code> if the property is indexed; |
| * <code>false</code> otherwise. |
| */ |
| public boolean isIndexed(String propertyName) { |
| return getConfig(propertyName) != null; |
| } |
| |
| |
| /** |
| * Returns the name of the node type where this rule applies to. |
| * |
| * @return name of the node type. |
| */ |
| public String getNodeTypeName() { |
| return nodeTypeName; |
| } |
| |
| /** |
| * @return name of the base node type. |
| */ |
| public String getBaseNodeType() { |
| return baseNodeType; |
| } |
| |
| /** |
| * Returns all the configured {@code PropertyDefinition}s for this {@code IndexRule}. |
| * |
| * In case of a pure nodetype index we just return primaryType and mixins. |
| * |
| * @return an {@code Iterable} of {@code PropertyDefinition}s. |
| * @see IndexDefinition#isPureNodeTypeIndex() |
| */ |
| public Iterable<PropertyDefinition> getProperties() { |
| return propConfigs.values(); |
| } |
| |
| public List<PropertyDefinition> getNullCheckEnabledProperties() { |
| return nullCheckEnabledProperties; |
| } |
| |
| public List<PropertyDefinition> getFunctionRestrictions() { |
| return functionRestrictions; |
| } |
| |
| public List<PropertyDefinition> getNotNullCheckEnabledProperties() { |
| return notNullCheckEnabledProperties; |
| } |
| |
| public List<PropertyDefinition> getNodeScopeAnalyzedProps() { |
| return nodeScopeAnalyzedProps; |
| } |
| |
| public List<PropertyDefinition> getSimilarityProperties() { |
| return similarityProperties; |
| } |
| |
| public Stream<PropertyDefinition> getNamePatternsProperties() { |
| return namePatterns.stream().map(NamePattern::getConfig); |
| } |
| |
| @Override |
| public String toString() { |
| String str = "IndexRule: "+ nodeTypeName; |
| if (!baseNodeType.equals(nodeTypeName)){ |
| str += "(" + baseNodeType + ")"; |
| } |
| return str; |
| } |
| |
| public boolean isAggregated(String nodePath) { |
| return aggregate.hasRelativeNodeInclude(nodePath); |
| } |
| |
| /** |
| * Returns <code>true</code> if this rule applies to the given node |
| * <code>state</code>. |
| * |
| * @param state the state to check. |
| * @return <code>true</code> the rule applies to the given node; |
| * <code>false</code> otherwise. |
| */ |
| public boolean appliesTo(NodeState state) { |
| for (String mixinName : getMixinTypeNames(state)){ |
| if (nodeTypeName.equals(mixinName)){ |
| return true; |
| } |
| } |
| |
| return nodeTypeName.equals(getPrimaryTypeName(state)); |
| } |
| |
| public boolean appliesTo(String nodeTypeName) { |
| return this.nodeTypeName.equals(nodeTypeName); |
| |
| } |
| |
| public boolean isNodeNameIndexed() { |
| return nodeNameIndexed; |
| } |
| |
| /** |
| * Returns true if the rule has any property definition which enables |
| * evaluation of fulltext related clauses |
| */ |
| public boolean isFulltextEnabled() { |
| return fulltextEnabled; |
| } |
| |
| /** |
| * Returns true if a fulltext field for the node would be created |
| * for any node matching the current rule. |
| */ |
| public boolean isNodeFullTextIndexed() { |
| return nodeFullTextIndexed; |
| } |
| |
| /** |
| * @param propertyName name of a property. |
| * @return the property configuration or <code>null</code> if this |
| * indexing rule does not contain a configuration for the given |
| * property. |
| */ |
| @Nullable |
| public PropertyDefinition getConfig(String propertyName) { |
| PropertyDefinition config = propConfigs.get(propertyName.toLowerCase(Locale.ENGLISH)); |
| if (config != null) { |
| return config; |
| } else if (namePatterns.size() > 0) { |
| // check patterns |
| for (NamePattern np : namePatterns) { |
| if (np.matches(propertyName)) { |
| return np.getConfig(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| public boolean includePropertyType(int type){ |
| return IndexDefinition.includePropertyType(propertyTypes, type); |
| } |
| |
| public Aggregate getAggregate() { |
| return aggregate; |
| } |
| |
| /** |
| * Flag to determine weather current index rule definition allows indexing of all |
| * node of type as covered by the current rule. For example if the rule only indexes |
| * certain property 'foo' for node type 'app:Asset' then index would only have |
| * entries for those assets where foo is defined. Such an index cannot claim that |
| * it has entries for all assets. |
| |
| * @return true in case all matching node types are covered by this rule |
| */ |
| public boolean indexesAllNodesOfMatchingType() { |
| return indexesAllNodesOfMatchingType; |
| } |
| |
| public boolean isBasedOnNtBase(){ |
| return JcrConstants.NT_BASE.equals(baseNodeType); |
| } |
| |
| private Map<String, PropertyDefinition> collectPropConfigs(NodeState config, |
| List<NamePattern> patterns, |
| List<Aggregate.Include> propAggregate, |
| List<PropertyDefinition> nonExistentProperties, |
| List<PropertyDefinition> existentProperties, |
| List<PropertyDefinition> nodeScopeAnalyzedProps, |
| List<PropertyDefinition> functionRestrictions, |
| List<PropertyDefinition> syncProps, |
| List<PropertyDefinition> similarityProperties) { |
| Map<String, PropertyDefinition> propDefns = newHashMap(); |
| NodeState propNode = config.getChildNode(FulltextIndexConstants.PROP_NODE); |
| |
| if (propNode.exists() && !hasOrderableChildren(propNode)){ |
| log.warn("Properties node for [{}] does not have orderable " + |
| "children in [{}]", this, IndexDefinition.this); |
| } |
| |
| //In case of a pure nodetype index we just index primaryType and mixins |
| //and ignore any other property definition |
| if (nodeTypeIndex) { |
| boolean sync = getOptionalValue(config, FulltextIndexConstants.PROP_SYNC, false); |
| PropertyDefinition pdpt = createNodeTypeDefinition(this, JcrConstants.JCR_PRIMARYTYPE, sync); |
| PropertyDefinition pdmixin = createNodeTypeDefinition(this, JcrConstants.JCR_MIXINTYPES, sync); |
| |
| propDefns.put(pdpt.name.toLowerCase(Locale.ENGLISH), pdpt); |
| propDefns.put(pdmixin.name.toLowerCase(Locale.ENGLISH), pdmixin); |
| |
| if (sync) { |
| syncProps.add(pdpt); |
| syncProps.add(pdmixin); |
| } |
| |
| if (propNode.getChildNodeCount(1) > 0) { |
| log.warn("Index at [{}] has {} enabled and cannot support other property " + |
| "definitions", indexPath, FulltextIndexConstants.PROP_INDEX_NODE_TYPE); |
| } |
| |
| return ImmutableMap.copyOf(propDefns); |
| } |
| |
| //Include all immediate child nodes to 'properties' node by default |
| Tree propTree = TreeFactory.createReadOnlyTree(propNode); |
| for (Tree prop : propTree.getChildren()) { |
| String propName = prop.getName(); |
| NodeState propDefnNode = propNode.getChildNode(propName); |
| if (propDefnNode.exists() && !propDefns.containsKey(propName)) { |
| PropertyDefinition pd = new PropertyDefinition(this, propName, propDefnNode); |
| if (pd.function != null) { |
| functionRestrictions.add(pd); |
| String[] properties = FunctionIndexProcessor.getProperties(pd.functionCode); |
| for (String p : properties) { |
| if (PathUtils.getDepth(p) > 1) { |
| PropertyDefinition pd2 = new PropertyDefinition(this, p, propDefnNode); |
| propAggregate.add(new Aggregate.FunctionInclude(pd2)); |
| } |
| } |
| // a function index has no other options |
| continue; |
| } |
| if(pd.isRegexp){ |
| patterns.add(new NamePattern(pd.name, pd)); |
| } else { |
| propDefns.put(pd.name.toLowerCase(Locale.ENGLISH), pd); |
| } |
| |
| if (pd.relative){ |
| propAggregate.add(new Aggregate.PropertyInclude(pd)); |
| } |
| |
| if (pd.nullCheckEnabled){ |
| nonExistentProperties.add(pd); |
| } |
| |
| if (pd.notNullCheckEnabled){ |
| existentProperties.add(pd); |
| } |
| |
| //Include props with name, boosted and nodeScopeIndex |
| if (pd.nodeScopeIndex |
| && pd.analyzed |
| && !pd.isRegexp){ |
| nodeScopeAnalyzedProps.add(pd); |
| } |
| |
| if (pd.sync) { |
| syncProps.add(pd); |
| } |
| if (pd.useInSimilarity) { |
| similarityProperties.add(pd); |
| } |
| } |
| } |
| ensureNodeTypeIndexingIsConsistent(propDefns, syncProps); |
| return ImmutableMap.copyOf(propDefns); |
| } |
| |
| /** |
| * If jcr:primaryType is indexed but jcr:mixinTypes is not indexed |
| * then ensure that jcr:mixinTypes is also indexed |
| */ |
| private void ensureNodeTypeIndexingIsConsistent(Map<String, PropertyDefinition> propDefns, |
| List<PropertyDefinition> syncProps) { |
| PropertyDefinition pd_pr = propDefns.get(JcrConstants.JCR_PRIMARYTYPE.toLowerCase(Locale.ENGLISH)); |
| PropertyDefinition pd_mixin = propDefns.get(JcrConstants.JCR_MIXINTYPES.toLowerCase(Locale.ENGLISH)); |
| |
| if (pd_pr != null && pd_pr.propertyIndex && pd_mixin == null) { |
| pd_mixin = createNodeTypeDefinition(this, JcrConstants.JCR_MIXINTYPES, pd_pr.sync); |
| syncProps.add(pd_mixin); |
| propDefns.put(JcrConstants.JCR_MIXINTYPES.toLowerCase(Locale.ENGLISH), pd_mixin); |
| } |
| } |
| |
| private boolean hasAnyFullTextEnabledProperty() { |
| for (PropertyDefinition pd : propConfigs.values()){ |
| if (pd.fulltextEnabled()){ |
| return true; |
| } |
| } |
| |
| for (NamePattern np : namePatterns){ |
| if (np.getConfig().fulltextEnabled()){ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean hasAnyPropertyIndexConfigured() { |
| for (PropertyDefinition pd : propConfigs.values()){ |
| if (pd.propertyIndex){ |
| return true; |
| } |
| } |
| |
| for (NamePattern np : namePatterns){ |
| if (np.getConfig().propertyIndex){ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean anyNodeScopeIndexedProperty(){ |
| //Check if there is any nodeScope indexed property as |
| //for such case a node would always be indexed |
| for (PropertyDefinition pd : propConfigs.values()){ |
| if (pd.nodeScopeIndex){ |
| return true; |
| } |
| } |
| |
| for (NamePattern np : namePatterns){ |
| if (np.getConfig().nodeScopeIndex){ |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private boolean areAlMatchingNodeByTypeIndexed(){ |
| if (nodeTypeIndex) { |
| return true; |
| } |
| |
| if (nodeFullTextIndexed){ |
| return true; |
| } |
| |
| //If there is nullCheckEnabled property which is not relative then |
| //all nodes would be indexed. relativeProperty with nullCheckEnabled might |
| //not ensure that (OAK-1085) |
| for (PropertyDefinition pd : nullCheckEnabledProperties){ |
| if (!pd.relative) { |
| return true; |
| } |
| } |
| |
| //jcr:primaryType is present on all node. So if such a property |
| //is indexed then it would mean all nodes covered by this index rule |
| //are indexed |
| return getConfig(JcrConstants.JCR_PRIMARYTYPE) != null; |
| |
| } |
| |
| private boolean evaluateNodeNameIndexed(NodeState config) { |
| //check global config first |
| if (getOptionalValue(config, FulltextIndexConstants.INDEX_NODE_NAME, false)) { |
| return true; |
| } |
| |
| //iterate over property definitions |
| for (PropertyDefinition pd : propConfigs.values()){ |
| if (FulltextIndexConstants.PROPDEF_PROP_NODE_NAME.equals(pd.name)){ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private Aggregate combine(Aggregate propAggregate, String nodeTypeName){ |
| Aggregate nodeTypeAgg = IndexDefinition.this.getAggregate(nodeTypeName); |
| List<Aggregate.Include> includes = newArrayList(); |
| includes.addAll(propAggregate.getIncludes()); |
| if (nodeTypeAgg != null){ |
| includes.addAll(nodeTypeAgg.getIncludes()); |
| } |
| return new Aggregate(nodeTypeName, includes); |
| } |
| |
| private void validateRuleDefinition() { |
| if (!nullCheckEnabledProperties.isEmpty() && isBasedOnNtBase()){ |
| throw new IllegalStateException("nt:base based rule cannot have a " + |
| "PropertyDefinition with nullCheckEnabled"); |
| } |
| } |
| } |
| |
| /** |
| * A property name pattern. |
| */ |
| private static final class NamePattern { |
| private final String parentPath; |
| /** |
| * The pattern to match. |
| */ |
| private final Pattern pattern; |
| |
| /** |
| * The associated configuration. |
| */ |
| private final PropertyDefinition config; |
| |
| /** |
| * Creates a new name pattern. |
| * |
| * @param pattern the pattern as defined by the property definition |
| * @param config the associated configuration. |
| */ |
| private NamePattern(String pattern, |
| PropertyDefinition config){ |
| |
| //Special handling for all props regex as its already being used |
| //and use of '/' in regex would confuse the parent path calculation |
| //logic |
| if (FulltextIndexConstants.REGEX_ALL_PROPS.equals(pattern)){ |
| this.parentPath = ""; |
| this.pattern = Pattern.compile(pattern); |
| } else { |
| this.parentPath = getParentPath(pattern); |
| this.pattern = Pattern.compile(PathUtils.getName(pattern)); |
| } |
| this.config = config; |
| } |
| |
| /** |
| * @param propertyPath property name to match |
| * @return <code>true</code> if <code>property name</code> matches this name |
| * pattern; <code>false</code> otherwise. |
| */ |
| boolean matches(String propertyPath) { |
| String parentPath = getParentPath(propertyPath); |
| String propertyName = PathUtils.getName(propertyPath); |
| if (!this.parentPath.equals(parentPath)) { |
| return false; |
| } |
| return pattern.matcher(propertyName).matches(); |
| } |
| |
| PropertyDefinition getConfig() { |
| return config; |
| } |
| } |
| |
| //~---------------------------------------------< compatibility > |
| |
| public static NodeBuilder updateDefinition(NodeBuilder indexDefn){ |
| return updateDefinition(indexDefn, "unknown"); |
| } |
| |
| public static NodeBuilder updateDefinition(NodeBuilder indexDefn, String indexPath){ |
| NodeState defn = indexDefn.getBaseState(); |
| if (!hasIndexingRules(defn)){ |
| NodeState rulesState = createIndexRules(defn).getNodeState(); |
| indexDefn.setChildNode(FulltextIndexConstants.INDEX_RULES, rulesState); |
| indexDefn.setProperty(INDEX_VERSION, determineIndexFormatVersion(defn).getVersion()); |
| |
| indexDefn.removeProperty(DECLARING_NODE_TYPES); |
| indexDefn.removeProperty(INCLUDE_PROPERTY_NAMES); |
| indexDefn.removeProperty(EXCLUDE_PROPERTY_NAMES); |
| indexDefn.removeProperty(ORDERED_PROP_NAMES); |
| indexDefn.removeProperty(FULL_TEXT_ENABLED); |
| indexDefn.child(PROP_NODE).remove(); |
| log.info("Updated index definition for {}", indexPath); |
| } |
| return indexDefn; |
| } |
| |
| /** |
| * Constructs IndexingRule based on earlier format of index configuration |
| */ |
| private static NodeBuilder createIndexRules(NodeState defn){ |
| NodeBuilder builder = EMPTY_NODE.builder(); |
| Set<String> declaringNodeTypes = getMultiProperty(defn, DECLARING_NODE_TYPES); |
| Set<String> includes = getMultiProperty(defn, INCLUDE_PROPERTY_NAMES); |
| Set<String> excludes = toLowerCase(getMultiProperty(defn, EXCLUDE_PROPERTY_NAMES)); |
| Set<String> orderedProps = getMultiProperty(defn, ORDERED_PROP_NAMES); |
| boolean fullTextEnabled = getOptionalValue(defn, FULL_TEXT_ENABLED, true); |
| boolean storageEnabled = getOptionalValue(defn, EXPERIMENTAL_STORAGE, true); |
| NodeState propNodeState = defn.getChildNode(FulltextIndexConstants.PROP_NODE); |
| |
| //If no explicit nodeType defined then all config applies for nt:base |
| if (declaringNodeTypes.isEmpty()){ |
| declaringNodeTypes = Collections.singleton(NT_BASE); |
| } |
| |
| Set<String> propNamesSet = Sets.newHashSet(); |
| propNamesSet.addAll(includes); |
| propNamesSet.addAll(excludes); |
| propNamesSet.addAll(orderedProps); |
| |
| //Also include all immediate leaf propNode names |
| for (ChildNodeEntry cne : propNodeState.getChildNodeEntries()){ |
| if (!propNamesSet.contains(cne.getName()) |
| && Iterables.isEmpty(cne.getNodeState().getChildNodeNames())){ |
| propNamesSet.add(cne.getName()); |
| } |
| } |
| |
| List<String> propNames = new ArrayList<String>(propNamesSet); |
| |
| final String includeAllProp = FulltextIndexConstants.REGEX_ALL_PROPS; |
| if (fullTextEnabled |
| && includes.isEmpty()){ |
| //Add the regEx for including all properties at the end |
| //for fulltext index and when no explicit includes are defined |
| propNames.add(includeAllProp); |
| } |
| |
| for (String typeName : declaringNodeTypes){ |
| NodeBuilder rule = builder.child(typeName); |
| markAsNtUnstructured(rule); |
| List<String> propNodeNames = newArrayListWithCapacity(propNamesSet.size()); |
| NodeBuilder propNodes = rule.child(PROP_NODE); |
| int i = 0; |
| for (String propName : propNames){ |
| String propNodeName = propName; |
| |
| //For proper propName use the propName as childNode name |
| if(PropertyDefinition.isRelativeProperty(propName) |
| || propName.equals(includeAllProp)){ |
| propNodeName = "prop" + i++; |
| } |
| propNodeNames.add(propNodeName); |
| |
| NodeBuilder prop = propNodes.child(propNodeName); |
| markAsNtUnstructured(prop); |
| prop.setProperty(FulltextIndexConstants.PROP_NAME, propName); |
| |
| if (excludes.contains(propName)){ |
| prop.setProperty(FulltextIndexConstants.PROP_INDEX, false); |
| } else if (fullTextEnabled){ |
| prop.setProperty(FulltextIndexConstants.PROP_ANALYZED, true); |
| prop.setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); |
| prop.setProperty(FulltextIndexConstants.PROP_USE_IN_EXCERPT, storageEnabled); |
| prop.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, false); |
| } else { |
| prop.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); |
| |
| if (orderedProps.contains(propName)){ |
| prop.setProperty(FulltextIndexConstants.PROP_ORDERED, true); |
| } |
| } |
| |
| if (propName.equals(includeAllProp)){ |
| prop.setProperty(FulltextIndexConstants.PROP_IS_REGEX, true); |
| } else { |
| //Copy over the property configuration |
| NodeState propDefNode = getPropDefnNode(defn, propName); |
| if (propDefNode != null){ |
| for (PropertyState ps : propDefNode.getProperties()){ |
| prop.setProperty(ps); |
| } |
| } |
| } |
| } |
| |
| //If no propertyType defined then default to UNKNOWN such that none |
| //of the properties get indexed |
| PropertyState supportedTypes = defn.getProperty(INCLUDE_PROPERTY_TYPES); |
| if (supportedTypes == null){ |
| supportedTypes = PropertyStates.createProperty(INCLUDE_PROPERTY_TYPES, TYPES_ALLOW_ALL_NAME); |
| } |
| rule.setProperty(supportedTypes); |
| |
| if (!NT_BASE.equals(typeName)) { |
| rule.setProperty(FulltextIndexConstants.RULE_INHERITED, false); |
| } |
| |
| propNodes.setProperty(OAK_CHILD_ORDER, propNodeNames ,NAMES); |
| markAsNtUnstructured(propNodes); |
| } |
| |
| markAsNtUnstructured(builder); |
| builder.setProperty(OAK_CHILD_ORDER, declaringNodeTypes ,NAMES); |
| return builder; |
| } |
| |
| private static NodeState getPropDefnNode(NodeState defn, String propName){ |
| NodeState propNode = defn.getChildNode(FulltextIndexConstants.PROP_NODE); |
| NodeState propDefNode; |
| if (PropertyDefinition.isRelativeProperty(propName)) { |
| NodeState result = propNode; |
| for (String name : PathUtils.elements(propName)) { |
| result = result.getChildNode(name); |
| } |
| propDefNode = result; |
| } else { |
| propDefNode = propNode.getChildNode(propName); |
| } |
| return propDefNode.exists() ? propDefNode : null; |
| } |
| |
| //~---------------------------------------------< utility > |
| |
| private int determineMaxExtractLength() { |
| int length = getOptionalValue(definition.getChildNode(TIKA), FulltextIndexConstants.TIKA_MAX_EXTRACT_LENGTH, |
| DEFAULT_MAX_EXTRACT_LENGTH); |
| if (length < 0){ |
| return - length * maxFieldLength; |
| } |
| return length; |
| } |
| |
| private NodeState getTikaConfigNode() { |
| return definition.getChildNode(TIKA).getChildNode(TIKA_CONFIG); |
| } |
| |
| private static Set<String> getMultiProperty(NodeState definition, String propName){ |
| PropertyState pse = definition.getProperty(propName); |
| return pse != null ? ImmutableSet.copyOf(pse.getValue(Type.STRINGS)) : Collections.<String>emptySet(); |
| } |
| |
| private static Set<String> toLowerCase(Set<String> values) { |
| Set<String> result = newHashSet(); |
| for(String val : values){ |
| result.add(val.toLowerCase(Locale.ENGLISH)); |
| } |
| return ImmutableSet.copyOf(result); |
| } |
| |
| private static List<String> getAllNodeTypes(ReadOnlyNodeTypeManager ntReg) { |
| try { |
| List<String> typeNames = newArrayList(); |
| NodeTypeIterator ntItr = ntReg.getAllNodeTypes(); |
| while (ntItr.hasNext()){ |
| typeNames.add(ntItr.nextNodeType().getName()); |
| } |
| return typeNames; |
| } catch (RepositoryException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static ReadOnlyNodeTypeManager createNodeTypeManager(final Root root) { |
| return new ReadOnlyNodeTypeManager() { |
| @NotNull |
| @Override |
| protected Tree getTypes() { |
| return root.getTree(NODE_TYPES_PATH); |
| } |
| |
| @NotNull |
| @Override |
| protected NamePathMapper getNamePathMapper() { |
| return NamePathMapper.DEFAULT; |
| } |
| }; |
| } |
| |
| private static String getPrimaryTypeName(NodeState state) { |
| String primaryType = state.getName(JcrConstants.JCR_PRIMARYTYPE); |
| |
| //To ensure compatibility with previous Tree based usage look based on string also |
| if (primaryType == null) { |
| primaryType = state.getString(JcrConstants.JCR_PRIMARYTYPE); |
| } |
| //In case not a proper JCR assume nt:base TODO return null and ignore indexing such nodes |
| //at all |
| return primaryType != null ? primaryType : "nt:base"; |
| } |
| |
| private static Iterable<String> getMixinTypeNames(NodeState state) { |
| PropertyState property = state.getProperty(JcrConstants.JCR_MIXINTYPES); |
| return property != null ? property.getValue(Type.NAMES) : Collections.<String>emptyList(); |
| } |
| |
| private static boolean hasOrderableChildren(NodeState state){ |
| return state.hasProperty(OAK_CHILD_ORDER); |
| } |
| |
| static int getSupportedTypes(NodeState defn, String typePropertyName, int defaultVal) { |
| PropertyState pst = defn.getProperty(typePropertyName); |
| if (pst != null) { |
| int types = 0; |
| for (String inc : pst.getValue(Type.STRINGS)) { |
| if (TYPES_ALLOW_ALL_NAME.equals(inc)){ |
| return TYPES_ALLOW_ALL; |
| } |
| |
| try { |
| types |= 1 << PropertyType.valueFromName(inc); |
| } catch (IllegalArgumentException e) { |
| log.warn("Unknown property type: " + inc); |
| } |
| } |
| return types; |
| } |
| return defaultVal; |
| } |
| |
| static boolean includePropertyType(int includedPropertyTypes, int type){ |
| if(includedPropertyTypes == TYPES_ALLOW_ALL){ |
| return true; |
| } |
| |
| if (includedPropertyTypes == TYPES_ALLOW_NONE){ |
| return false; |
| } |
| |
| return (includedPropertyTypes & (1 << type)) != 0; |
| } |
| |
| private static boolean hasFulltextEnabledIndexRule(List<IndexingRule> rules) { |
| for (IndexingRule rule : rules){ |
| if (rule.isFulltextEnabled()){ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void markAsNtUnstructured(NodeBuilder nb){ |
| nb.setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED, Type.NAME); |
| } |
| |
| protected static IndexFormatVersion determineIndexFormatVersion(NodeState defn) { |
| //Compat mode version if specified has highest priority |
| if (defn.hasProperty(COMPAT_MODE)){ |
| return versionFrom(defn.getProperty(COMPAT_MODE)); |
| } |
| |
| if (defn.hasProperty(INDEX_VERSION)){ |
| return versionFrom(defn.getProperty(INDEX_VERSION)); |
| } |
| |
| //No existing index data i.e. reindex or fresh index |
| if (!defn.getChildNode(INDEX_DATA_CHILD_NAME).exists()){ |
| return determineVersionForFreshIndex(defn); |
| } |
| |
| boolean fullTextEnabled = getOptionalValue(defn, FULL_TEXT_ENABLED, true); |
| |
| //A fulltext index with old indexing format confirms to V1. However |
| //a propertyIndex with old indexing format confirms to V2 |
| return fullTextEnabled ? IndexFormatVersion.V1 : IndexFormatVersion.V2; |
| } |
| |
| static IndexFormatVersion determineVersionForFreshIndex(NodeState defn){ |
| return determineVersionForFreshIndex(defn.getProperty(FULL_TEXT_ENABLED), |
| defn.getProperty(COMPAT_MODE), defn.getProperty(INDEX_VERSION)); |
| } |
| |
| static IndexFormatVersion determineVersionForFreshIndex(NodeBuilder defnb){ |
| return determineVersionForFreshIndex(defnb.getProperty(FULL_TEXT_ENABLED), |
| defnb.getProperty(COMPAT_MODE), defnb.getProperty(INDEX_VERSION)); |
| } |
| |
| private static IndexFormatVersion determineVersionForFreshIndex(PropertyState fulltext, |
| PropertyState compat, |
| PropertyState version){ |
| if (compat != null){ |
| return versionFrom(compat); |
| } |
| |
| IndexFormatVersion defaultToUse = IndexFormatVersion.getDefault(); |
| IndexFormatVersion existing = version != null ? versionFrom(version) : null; |
| |
| //As per OAK-2290 current might be less than current used version. So |
| //set to current only if it is greater than existing |
| |
| //Per setting use default configured |
| IndexFormatVersion result = defaultToUse; |
| |
| //If default configured is lesser than existing then prefer existing |
| if (existing != null){ |
| result = IndexFormatVersion.max(result,existing); |
| } |
| |
| //Check if fulltext is false which indicates its a property index and |
| //hence confirm to V2 or above |
| if (fulltext != null && !fulltext.getValue(Type.BOOLEAN)){ |
| return IndexFormatVersion.max(result, IndexFormatVersion.V2); |
| } |
| |
| return result; |
| } |
| |
| private static String[] getOptionalStrings(NodeState defn, String propertyName) { |
| PropertyState ps = defn.getProperty(propertyName); |
| if (ps != null) { |
| return Iterables.toArray(ps.getValue(Type.STRINGS), String.class); |
| } |
| return null; |
| } |
| |
| private static IndexFormatVersion versionFrom(PropertyState ps){ |
| return IndexFormatVersion.getVersion(Ints.checkedCast(ps.getValue(Type.LONG))); |
| } |
| |
| private static boolean hasIndexingRules(NodeState defn) { |
| return defn.getChildNode(FulltextIndexConstants.INDEX_RULES).exists(); |
| } |
| |
| @Nullable |
| protected static String determineUniqueId(NodeState defn) { |
| return defn.getChildNode(STATUS_NODE).getString(PROP_UID); |
| } |
| |
| private static boolean supportsNRTIndexing(NodeState defn) { |
| return supportsIndexingMode(new ReadOnlyBuilder(defn), INDEXING_MODE_NRT); |
| } |
| |
| private static boolean supportsSyncIndexing(NodeState defn) { |
| return supportsIndexingMode(new ReadOnlyBuilder(defn), INDEXING_MODE_SYNC); |
| } |
| |
| public static boolean supportsSyncOrNRTIndexing(NodeBuilder defn) { |
| return supportsIndexingMode(defn, INDEXING_MODE_NRT) || supportsIndexingMode(defn, INDEXING_MODE_SYNC); |
| } |
| |
| private static boolean supportsIndexingMode(NodeBuilder defn, String mode) { |
| PropertyState async = defn.getProperty(IndexConstants.ASYNC_PROPERTY_NAME); |
| if (async == null){ |
| return false; |
| } |
| return Iterables.contains(async.getValue(Type.STRINGS), mode); |
| } |
| |
| protected static NodeState getIndexDefinitionState(NodeState defn) { |
| if (isDisableStoredIndexDefinition()){ |
| return defn; |
| } |
| NodeState storedState = defn.getChildNode(INDEX_DEFINITION_NODE); |
| return storedState.exists() ? storedState : defn; |
| } |
| |
| private static Map<String, String> buildMimeTypeMap(NodeState node) { |
| ImmutableMap.Builder<String, String> map = ImmutableMap.builder(); |
| for (ChildNodeEntry child : node.getChildNodeEntries()) { |
| for (ChildNodeEntry subChild : child.getNodeState().getChildNodeEntries()) { |
| StringBuilder typeBuilder = new StringBuilder(child.getName()) |
| .append('/') |
| .append(subChild.getName()); |
| PropertyState property = subChild.getNodeState().getProperty(TIKA_MAPPED_TYPE); |
| if (property != null) { |
| map.put(typeBuilder.toString(), property.getValue(Type.STRING)); |
| } |
| } |
| } |
| return map.build(); |
| } |
| |
| private static PropertyDefinition createNodeTypeDefinition(IndexingRule rule, String name, boolean sync) { |
| NodeBuilder builder = EMPTY_NODE.builder(); |
| //A nodetype index just required propertyIndex and sync flags to be set |
| builder.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); |
| if (sync) { |
| builder.setProperty(FulltextIndexConstants.PROP_SYNC, sync); |
| } |
| builder.setProperty(FulltextIndexConstants.PROP_NAME, name); |
| return new PropertyDefinition(rule, name, builder.getNodeState()); |
| } |
| |
| public static class SecureFacetConfiguration { |
| public enum MODE { |
| SECURE, |
| STATISTICAL, |
| INSECURE |
| } |
| |
| private final long randomSeed; |
| private final MODE mode; |
| private final int statisticalFacetSampleSize; |
| |
| SecureFacetConfiguration(long randomSeed, MODE mode, int statisticalFacetSampleSize) { |
| this.randomSeed = randomSeed; |
| this.mode = mode; |
| this.statisticalFacetSampleSize = statisticalFacetSampleSize; |
| } |
| |
| public MODE getMode() { |
| return mode; |
| } |
| |
| public int getStatisticalFacetSampleSize() { |
| return statisticalFacetSampleSize; |
| } |
| |
| public long getRandomSeed() { |
| return randomSeed; |
| } |
| |
| static SecureFacetConfiguration getInstance(long randomSeed, NodeState facetConfigRoot) { |
| if (facetConfigRoot == null) { |
| facetConfigRoot = EMPTY_NODE; |
| } |
| |
| MODE mode = getMode(facetConfigRoot); |
| int statisticalFacetSampleSize; |
| |
| if (mode == MODE.STATISTICAL) { |
| statisticalFacetSampleSize = getStatisticalFacetSampleSize(facetConfigRoot); |
| } else { |
| statisticalFacetSampleSize = -1; |
| } |
| |
| return new SecureFacetConfiguration(randomSeed, mode, statisticalFacetSampleSize); |
| } |
| |
| static MODE getMode(@NotNull final NodeState facetConfigRoot) { |
| PropertyState securePS = facetConfigRoot.getProperty(PROP_SECURE_FACETS); |
| String modeString; |
| |
| if (securePS != null) { |
| if(securePS.getType() == Type.BOOLEAN) { |
| // legacy secure config |
| boolean secure = securePS.getValue(Type.BOOLEAN); |
| return secure ? MODE.SECURE : MODE.INSECURE; |
| } else { |
| modeString = securePS.getValue(Type.STRING); |
| } |
| } else { |
| // use "default" just as placeholder. default case would do the actual default eval |
| modeString = System.getProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, "default"); |
| } |
| |
| switch (modeString) { |
| case PROP_SECURE_FACETS_VALUE_INSECURE: |
| return MODE.INSECURE; |
| case PROP_SECURE_FACETS_VALUE_STATISTICAL: |
| return MODE.STATISTICAL; |
| case PROP_SECURE_FACETS_VALUE_SECURE: |
| default: |
| return MODE.SECURE; |
| } |
| } |
| |
| static int getStatisticalFacetSampleSize(@NotNull final NodeState facetConfigRoot) { |
| int statisticalFacetSampleSize = Integer.getInteger( |
| STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, |
| STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT); |
| int statisticalFacetSampleSizePV = getOptionalValue(facetConfigRoot, |
| PROP_STATISTICAL_FACET_SAMPLE_SIZE, |
| statisticalFacetSampleSize); |
| |
| if (statisticalFacetSampleSizePV > 0) { |
| statisticalFacetSampleSize = statisticalFacetSampleSizePV; |
| } |
| |
| if (statisticalFacetSampleSize < 0) { |
| statisticalFacetSampleSize = STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT; |
| } |
| |
| return statisticalFacetSampleSize; |
| } |
| } |
| } |