blob: 366cc33220eb223cba061597d0ca3297205cb7c8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.jackrabbit.oak.plugins.index.lucene;
import javax.annotation.CheckForNull;
import javax.jcr.PropertyType;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.IndexingRule;
import org.apache.jackrabbit.oak.plugins.index.lucene.util.LuceneIndexHelper;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.Iterables.toArray;
import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
import static org.apache.jackrabbit.oak.commons.PathUtils.isAbsolute;
import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.FIELD_BOOST;
import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.PROP_IS_REGEX;
import static org.apache.jackrabbit.oak.plugins.index.lucene.util.ConfigUtil.getOptionalValue;
class PropertyDefinition {
private static final Logger log = LoggerFactory.getLogger(PropertyDefinition.class);
/**
* The default boost: 1.0f.
*/
static final float DEFAULT_BOOST = 1.0f;
/**
* Property name. By default derived from the NodeState name which has the
* property definition. However in case property name is a pattern, relative
* property etc then it should be defined via 'name' property in NodeState.
* In such case NodeState name can be set to anything
*/
final String name;
private final int propertyType;
/**
* The boost value for a property.
*/
final float boost;
final boolean isRegexp;
final boolean index;
final boolean stored;
final boolean nodeScopeIndex;
final boolean propertyIndex;
final boolean analyzed;
final boolean ordered;
final boolean nullCheckEnabled;
final boolean notNullCheckEnabled;
final int includedPropertyTypes;
final boolean relative;
final boolean useInSuggest;
final boolean useInSpellcheck;
final boolean facet;
final String[] ancestors;
final boolean excludeFromAggregate;
/**
* Property name excluding the relativePath. For regular expression based definition
* its set to null
*/
@CheckForNull
final String nonRelativeName;
public PropertyDefinition(IndexingRule idxDefn, String nodeName, NodeState defn) {
this.isRegexp = getOptionalValue(defn, PROP_IS_REGEX, false);
this.name = getName(defn, nodeName);
this.relative = isRelativeProperty(name);
this.boost = getOptionalValue(defn, FIELD_BOOST, DEFAULT_BOOST);
//By default if a property is defined it is indexed
this.index = getOptionalValue(defn, LuceneIndexConstants.PROP_INDEX, true);
this.stored = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_USE_IN_EXCERPT, false);
this.nodeScopeIndex = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_NODE_SCOPE_INDEX, false);
//If boost is specified then that field MUST be analyzed
if (defn.hasProperty(FIELD_BOOST)){
this.analyzed = true;
} else {
this.analyzed = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_ANALYZED, false);
}
//If node is not set for full text then a property definition indicates that definition is for property index
this.propertyIndex = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_PROPERTY_INDEX, false);
this.ordered = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_ORDERED, false);
this.includedPropertyTypes = IndexDefinition.getSupportedTypes(defn, LuceneIndexConstants.PROP_INCLUDED_TYPE,
IndexDefinition.TYPES_ALLOW_ALL);
//TODO Add test case for above cases
this.propertyType = getPropertyType(idxDefn, nodeName, defn);
this.useInSuggest = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_USE_IN_SUGGEST, false);
this.useInSpellcheck = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_USE_IN_SPELLCHECK, false);
this.nullCheckEnabled = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_NULL_CHECK_ENABLED, false);
this.notNullCheckEnabled = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, false);
this.excludeFromAggregate = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_EXCLUDE_FROM_AGGREGATE, false);
this.nonRelativeName = determineNonRelativeName();
this.ancestors = computeAncestors(name);
this.facet = getOptionalValueIfIndexed(defn, LuceneIndexConstants.PROP_FACETS, false);
validate();
}
/**
* If 'analyzed' is enabled then property value would be used to evaluate the
* contains clause related to those properties. In such mode also some properties
* would be skipped from analysis
*
* @param propertyName name of the property to check. As property definition might
* be regEx based this is required to be passed explicitly
* @return true if the property value should be tokenized/analyzed
*/
public boolean skipTokenization(String propertyName) {
//For regEx case check against a whitelist
if (isRegexp && LuceneIndexHelper.skipTokenization(propertyName)){
return true;
}
return !analyzed;
}
public boolean fulltextEnabled(){
return index && (analyzed || nodeScopeIndex);
}
public boolean propertyIndexEnabled(){
return index && propertyIndex;
}
public boolean isTypeDefined(){
return propertyType != PropertyType.UNDEFINED;
}
/**
* Returns the property type. If no explicit type is defined the default is assumed
* to be {@link javax.jcr.PropertyType#STRING}
*
* @return propertyType as per javax.jcr.PropertyType
*/
public int getType(){
//If no explicit type is defined we assume it to be string
return isTypeDefined() ? propertyType : PropertyType.STRING;
}
public boolean includePropertyType(int type){
return IndexDefinition.includePropertyType(includedPropertyTypes, type);
}
@Override
public String toString() {
return "PropertyDefinition{" +
"name='" + name + '\'' +
", propertyType=" + propertyType +
", boost=" + boost +
", isRegexp=" + isRegexp +
", index=" + index +
", stored=" + stored +
", nodeScopeIndex=" + nodeScopeIndex +
", propertyIndex=" + propertyIndex +
", analyzed=" + analyzed +
", ordered=" + ordered +
", useInSuggest=" + useInSuggest+
", nullCheckEnabled=" + nullCheckEnabled +
", notNullCheckEnabled=" + notNullCheckEnabled +
'}';
}
static boolean isRelativeProperty(String propertyName){
return !isAbsolute(propertyName) && PathUtils.getNextSlash(propertyName, 0) > 0;
}
//~---------------------------------------------< internal >
private boolean getOptionalValueIfIndexed(NodeState definition, String propName, boolean defaultVal){
//If property is not to be indexed then all other config would be
//set to false ignoring whatever is defined in config for them
if (!index){
return false;
}
return getOptionalValue(definition, propName, defaultVal);
}
private void validate() {
if (nullCheckEnabled && isRegexp){
throw new IllegalStateException(String.format("%s can be set to true for property definition using " +
"regular expression", LuceneIndexConstants.PROP_NULL_CHECK_ENABLED));
}
}
private String determineNonRelativeName() {
if (isRegexp){
return null;
}
if (!relative){
return name;
}
return PathUtils.getName(name);
}
private static String[] computeAncestors(String parentPath) {
return toArray(copyOf(elements(PathUtils.getParentPath(parentPath))), String.class);
}
private static String getName(NodeState definition, String defaultName){
PropertyState ps = definition.getProperty(LuceneIndexConstants.PROP_NAME);
return ps == null ? defaultName : ps.getValue(Type.STRING);
}
private static int getPropertyType(IndexingRule idxDefn, String name, NodeState defn) {
int type = PropertyType.UNDEFINED;
if (defn.hasProperty(LuceneIndexConstants.PROP_TYPE)) {
String typeName = defn.getString(LuceneIndexConstants.PROP_TYPE);
try {
type = PropertyType.valueFromName(typeName);
} catch (IllegalArgumentException e) {
log.warn("Invalid property type {} for property {} in Index {}", typeName, name, idxDefn);
}
}
return type;
}
}