Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,299 @@ +/* + * 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 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.search.IndexDefinition.IndexingRule; +import org.apache.jackrabbit.oak.plugins.index.search.util.FunctionIndexProcessor; +import org.apache.jackrabbit.oak.plugins.index.property.ValuePattern; +import org.apache.jackrabbit.oak.plugins.index.search.util.IndexHelper; +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.search.FulltextIndexConstants.FIELD_BOOST; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_IS_REGEX; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_WEIGHT; +import static org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil.getOptionalValue; + +public class PropertyDefinition { + private static final Logger log = LoggerFactory.getLogger(PropertyDefinition.class); + + private static final String[] EMPTY_ANCESTORS = new String[0]; + + /** + * 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; + + final int weight; + + /** + * Property name excluding the relativePath. For regular expression based definition + * its set to null + */ + @CheckForNull + final String nonRelativeName; + + /** + * For function-based indexes: the function name, in Polish notation. + */ + final String function; + + /** + * For function-based indexes: the function code, as tokens. + */ + final String[] functionCode; + + public final ValuePattern valuePattern; + + public final boolean sync; + + public final boolean unique; + + 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); + this.weight = getOptionalValue(defn, PROP_WEIGHT, 5); + + //By default if a property is defined it is indexed + this.index = getOptionalValue(defn, FulltextIndexConstants.PROP_INDEX, true); + this.stored = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_USE_IN_EXCERPT, false); + this.nodeScopeIndex = getOptionalValueIfIndexed(defn, FulltextIndexConstants.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, FulltextIndexConstants.PROP_ANALYZED, false); + } + + this.ordered = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_ORDERED, false); + this.includedPropertyTypes = IndexDefinition.getSupportedTypes(defn, FulltextIndexConstants.PROP_INCLUDED_TYPE, + IndexDefinition.TYPES_ALLOW_ALL); + + //TODO Add test case for above cases + + this.propertyType = getPropertyType(idxDefn, nodeName, defn); + this.useInSuggest = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_USE_IN_SUGGEST, false); + this.useInSpellcheck = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_USE_IN_SPELLCHECK, false); + this.nullCheckEnabled = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, false); + this.notNullCheckEnabled = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, false); + this.excludeFromAggregate = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_EXCLUDE_FROM_AGGREGATE, false); + this.nonRelativeName = determineNonRelativeName(); + this.ancestors = computeAncestors(name); + this.facet = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_FACETS, false); + this.function = FunctionIndexProcessor.convertToPolishNotation( + getOptionalValue(defn, FulltextIndexConstants.PROP_FUNCTION, null)); + this.functionCode = FunctionIndexProcessor.getFunctionCode(this.function); + this.valuePattern = new ValuePattern(defn); + this.unique = getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_UNIQUE, false); + this.sync = unique || getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_SYNC, false); + + //If some property is set to sync then propertyIndex mode is always enabled + this.propertyIndex = sync || getOptionalValueIfIndexed(defn, FulltextIndexConstants.PROP_PROPERTY_INDEX, 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 && IndexHelper.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 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 + + ", function=" + function + + '}'; + } + + static boolean isRelativeProperty(String propertyName){ + return !isAbsolute(propertyName) + && !FulltextIndexConstants.REGEX_ALL_PROPS.equals(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", FulltextIndexConstants.PROP_NULL_CHECK_ENABLED)); + } + } + + private String determineNonRelativeName() { + if (isRegexp){ + return null; + } + + if (!relative){ + return name; + } + + return PathUtils.getName(name); + } + + private static String[] computeAncestors(String path) { + if (FulltextIndexConstants.REGEX_ALL_PROPS.equals(path)) { + return EMPTY_ANCESTORS; + } else { + return toArray(copyOf(elements(PathUtils.getParentPath(path))), String.class); + } + } + + + private static String getName(NodeState definition, String defaultName){ + PropertyState ps = definition.getProperty(FulltextIndexConstants.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(FulltextIndexConstants.PROP_TYPE)) { + String typeName = defn.getString(FulltextIndexConstants.PROP_TYPE); + try { + type = PropertyType.valueFromName(typeName); + } catch (IllegalArgumentException e) { + log.warn("Invalid property type {} for property {} in Index {}", typeName, name, idxDefn); + } + } + return type; + } +}
Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyDefinition.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyUpdateCallback.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyUpdateCallback.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyUpdateCallback.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyUpdateCallback.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,53 @@ +/* + * 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 javax.annotation.Nullable; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.PropertyState; + +/** + * Callback to be invoked for each indexable property change + */ +public interface PropertyUpdateCallback { + + /** + * Invoked upon any change in property either added, updated or removed. + * Implementation can determine if property is added, updated or removed based + * on whether before or after is null + * + * @param nodePath path of node for which is to be indexed for this property change + * @param propertyRelativePath relative path of the property wrt the indexed node + * @param pd property definition associated with the property to be indexed + * @param before before state. Is null when property is added. For other cases its not null + * @param after after state of the property. Is null when property is removed. For other cases its not null + */ + void propertyUpdated(String nodePath, String propertyRelativePath, PropertyDefinition pd, + @Nullable PropertyState before, @Nullable PropertyState after); + + /** + * Invoked after editor has traversed all the changes + * + * @throws CommitFailedException in case some validation fails + */ + void done() throws CommitFailedException; + +} Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/PropertyUpdateCallback.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/SizeEstimator.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/SizeEstimator.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/SizeEstimator.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/SizeEstimator.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,30 @@ +/* + * 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; + +public interface SizeEstimator { + + /** + * Get the estimated size, or -1 if not known. + * + * @return the size + */ + long getSize(); + +} Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/SizeEstimator.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,86 @@ +/* + * 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.util; + +import java.util.Collections; + +import javax.annotation.CheckForNull; + +import com.google.common.primitives.Ints; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.api.Blob; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Utility class to retrieve configuration values for index definitions + */ +public class ConfigUtil { + + public static boolean getOptionalValue(NodeState definition, String propName, boolean defaultVal){ + PropertyState ps = definition.getProperty(propName); + return ps == null ? defaultVal : ps.getValue(Type.BOOLEAN); + } + + public static int getOptionalValue(NodeState definition, String propName, int defaultVal){ + PropertyState ps = definition.getProperty(propName); + return ps == null ? defaultVal : Ints.checkedCast(ps.getValue(Type.LONG)); + } + + public static String getOptionalValue(NodeState definition, String propName, String defaultVal){ + PropertyState ps = definition.getProperty(propName); + return ps == null ? defaultVal : ps.getValue(Type.STRING); + } + + public static float getOptionalValue(NodeState definition, String propName, float defaultVal){ + PropertyState ps = definition.getProperty(propName); + return ps == null ? defaultVal : ps.getValue(Type.DOUBLE).floatValue(); + } + + public static double getOptionalValue(NodeState definition, String propName, double defaultVal){ + PropertyState ps = definition.getProperty(propName); + return ps == null ? defaultVal : ps.getValue(Type.DOUBLE); + } + + public static String getPrimaryTypeName(NodeState nodeState) { + PropertyState ps = nodeState.getProperty(JcrConstants.JCR_PRIMARYTYPE); + return (ps == null) ? JcrConstants.NT_BASE : ps.getValue(Type.NAME); + } + + public static Iterable<String> getMixinNames(NodeState nodeState) { + PropertyState ps = nodeState.getProperty(JcrConstants.JCR_MIXINTYPES); + return (ps == null) ? Collections.<String>emptyList() : ps.getValue(Type.NAMES); + } + + /** + * Assumes that given state is of type nt:file and then reads + * the jcr:content/@jcr:data property to get the binary content + */ + @CheckForNull + public static Blob getBlob(NodeState state, String resourceName){ + NodeState contentNode = state.getChildNode(JcrConstants.JCR_CONTENT); + checkArgument(contentNode.exists(), "Was expecting to find jcr:content node to read resource %s", resourceName); + PropertyState property = contentNode.getProperty(JcrConstants.JCR_DATA); + return property != null ? property.getValue(Type.BINARY) : null; + } +} Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/ConfigUtil.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/FunctionIndexProcessor.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/FunctionIndexProcessor.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/FunctionIndexProcessor.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/FunctionIndexProcessor.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,279 @@ +/* + * 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.util; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; + +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.memory.EmptyPropertyState; +import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; +import org.apache.jackrabbit.oak.spi.query.QueryConstants; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A parser for function-based indexes. It converts the human-readable function + * definition (XPath) to the internal Polish notation. + */ +public class FunctionIndexProcessor { + + private static final Logger LOG = + LoggerFactory.getLogger(FunctionIndexProcessor.class); + + private String remaining; + + private static final PropertyState EMPTY_PROPERTY_STATE = EmptyPropertyState.emptyProperty("empty", Type.STRINGS); + + private FunctionIndexProcessor(String function) { + this.remaining = function; + } + + /** + * Get the list of properties used in the given function code. + * + * @param functionCode the tokens, for example ["function", "lower", "@name"] + * @return the list of properties, for example ["name"] + */ + public static String[] getProperties(String[] functionCode) { + ArrayList<String> properties = new ArrayList<String>(); + for(String token : functionCode) { + if (token.startsWith("@")) { + String propertyName = token.substring(1); + properties.add(propertyName); + } + } + return properties.toArray(new String[0]); + } + + /** + * Try to calculate the value for the given function code. + * + * @param path the path of the node + * @param state the node state + * @param functionCode the tokens, for example ["function", "lower", "@name"] + * @return null, or the calculated value + */ + public static PropertyState tryCalculateValue(String path, NodeState state, String[] functionCode) { + Deque<PropertyState> stack = new ArrayDeque<PropertyState>(); + for (int i = functionCode.length - 1; i > 0; i--) { + String token = functionCode[i]; + PropertyState ps; + if (token.startsWith("@")) { + String propertyName = token.substring(1); + ps = getProperty(path, state, propertyName); + } else { + ps = calculateFunction(token, stack); + } + if (ps == null) { + ps = EMPTY_PROPERTY_STATE; + } + stack.push(ps); + } + + PropertyState ret = stack.pop(); + return ret==EMPTY_PROPERTY_STATE ? null : ret; + } + + /** + * Split the polish notation into a tokens that can more easily be processed. + * + * @param functionDescription in polish notation, for example "function*lower*{@literal @}name" + * @return tokens, for example ["function", "lower", "{@literal @}name"] + */ + public static String[] getFunctionCode(String functionDescription) { + if (functionDescription == null) { + return null; + } + return functionDescription.split("\\*"); + } + + private static PropertyState calculateFunction(String functionName, + Deque<PropertyState> stack) { + PropertyState ps = stack.pop(); + if ("coalesce".equals(functionName)) { + // coalesce (a, b) => (a != null ? a : b) + // we pop stack again to consume the second parameter + // also, if ps is EMPTY_PROPERTY_STATE, then newly popped value is to be used + PropertyState ps2 = stack.pop(); + if (ps == EMPTY_PROPERTY_STATE) { + ps = ps2; + } + } + if (ps == EMPTY_PROPERTY_STATE) { + return ps; + } + Type<?> type = null; + ArrayList<Object> values = new ArrayList<Object>(ps.count()); + for (int i = 0; i < ps.count(); i++) { + String s = ps.getValue(Type.STRING, i); + Object x; + if ("lower".equals(functionName)) { + x = s.toLowerCase(); + type = Type.STRING; + } else if ("coalesce".equals(functionName)) { + x = s; + type = Type.STRING; + } else if ("upper".equals(functionName)) { + x = s.toUpperCase(); + type = Type.STRING; + } else if ("length".equals(functionName)) { + x = (long) s.length(); + type = Type.LONG; + } else { + LOG.debug("Unknown function {}", functionName); + return null; + } + values.add(x); + } + PropertyState result; + if (values.size() == 1) { + result = PropertyStates.createProperty("value", values.get(0), type); + } else { + type = type.getArrayType(); + result = PropertyStates.createProperty("value", values, type); + } + return result; + } + + private static PropertyState getProperty(String path, NodeState state, + String propertyName) { + if (PathUtils.getDepth(propertyName) != 1) { + for(String n : PathUtils.elements(PathUtils.getParentPath(propertyName))) { + state = state.getChildNode(n); + if (!state.exists()) { + return null; + } + } + propertyName = PathUtils.getName(propertyName); + } + PropertyState ps; + if (":localname".equals(propertyName)) { + ps = PropertyStates.createProperty("value", + getLocalName(PathUtils.getName(path)), Type.STRING); + } else if (":name".equals(propertyName)) { + ps = PropertyStates.createProperty("value", + PathUtils.getName(path), Type.STRING); + } else { + ps = state.getProperty(propertyName); + } + if (ps == null || ps.count() == 0) { + return null; + } + return ps; + } + + private static String getLocalName(String name) { + int colon = name.indexOf(':'); + // TODO LOCALNAME: evaluation of local name might not be correct + return colon < 0 ? name : name.substring(colon + 1); + } + + /** + * Convert a function (in human-readable form) to the polish notation. + * + * @param function the function, for example "lower([name])" + * @return the polish notation, for example "function*lower*{@literal @}name" + */ + public static String convertToPolishNotation(String function) { + if (function == null) { + return null; + } + FunctionIndexProcessor p = new FunctionIndexProcessor(function); + return QueryConstants.FUNCTION_RESTRICTION_PREFIX + p.parse(); + } + + String parse() { + if (match("fn:local-name()") || match("localname()")) { + return "@:localname"; + } + if (match("fn:name()") || match("name()")) { + return "@:name"; + } + if (match("fn:upper-case(") || match("upper(")) { + return "upper*" + parse() + read(")"); + } + if (match("fn:lower-case(") || match("lower(")) { + return "lower*" + parse() + read(")"); + } + if (match("fn:coalesce(") || match("coalesce(")) { + return "coalesce*" + parse() + readCommaAndWhitespace() + parse() + read(")"); + } + if (match("fn:string-length(") || match("length(")) { + return "length*" + parse() + read(")"); + } + + // property name + if (match("[")) { + String prop = remaining; + int indexOfComma = remaining.indexOf(","); + if (indexOfComma > 0) { + prop = remaining.substring(0, indexOfComma); + } + prop = prop.substring(0, prop.lastIndexOf(']')); + remaining = remaining.substring(prop.length() + 1); + return property(prop.replaceAll("]]", "]")); + } else { + String prop = remaining; + int paren = remaining.indexOf(')'); + int comma = remaining.indexOf(','); + int end = comma; + if (paren >=0) { + end = (end < 0) ? paren : Math.min(end, paren); + } + if (end >= 0) { + prop = remaining.substring(0, end); + } + remaining = remaining.substring(prop.length()); + return property(prop.replaceAll("@", "")); + } + } + + String property(String p) { + return "@" + p; + } + + private String read(String string) { + match(string); + return ""; + } + + private String + readCommaAndWhitespace() { + while (match(" ")) { + } + match(","); + while (match(" ")) { + } + return "*"; + } + + private boolean match(String string) { + if (remaining.startsWith(string)) { + remaining = remaining.substring(string.length()); + return true; + } + return false; + } + +} Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/FunctionIndexProcessor.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexDefinitionBuilder.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexDefinitionBuilder.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexDefinitionBuilder.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexDefinitionBuilder.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,561 @@ +/* + * 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.util; + +import java.util.Map; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.jackrabbit.oak.api.PropertyState; +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.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.tree.factories.TreeFactory; +import org.apache.jackrabbit.oak.spi.filter.PathFilter; +import org.apache.jackrabbit.oak.spi.state.EqualsDiff; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import static java.util.Arrays.asList; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED; +import static org.apache.jackrabbit.oak.api.Type.NAME; +import static org.apache.jackrabbit.oak.api.Type.STRINGS; +import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; + +public final class IndexDefinitionBuilder { + private final NodeBuilder builder; + private final Tree tree; + private final Map<String, IndexRule> rules = Maps.newHashMap(); + private final Map<String, AggregateRule> aggRules = Maps.newHashMap(); + private final Tree indexRule; + private final boolean autoManageReindexFlag; + private Tree aggregatesTree; + private final NodeState initial; + private boolean reindexRequired; + + + public IndexDefinitionBuilder(){ + this(EMPTY_NODE.builder()); + } + + public IndexDefinitionBuilder(NodeBuilder nodeBuilder){ + this(nodeBuilder, true); + } + + public IndexDefinitionBuilder(NodeBuilder nodeBuilder, boolean autoManageReindexFlag){ + this.autoManageReindexFlag = autoManageReindexFlag; + this.builder = nodeBuilder; + this.initial = nodeBuilder.getNodeState(); + this.tree = TreeFactory.createTree(builder); + tree.setProperty("async", "async"); + setType(); + tree.setProperty(JCR_PRIMARYTYPE, "oak:QueryIndexDefinition", NAME); + indexRule = getOrCreateChild(tree, FulltextIndexConstants.INDEX_RULES); + } + + public IndexDefinitionBuilder evaluatePathRestrictions(){ + tree.setProperty(FulltextIndexConstants.EVALUATE_PATH_RESTRICTION, true); + return this; + } + + public IndexDefinitionBuilder includedPaths(String ... paths){ + tree.setProperty(PathFilter.PROP_INCLUDED_PATHS, asList(paths), STRINGS); + return this; + } + + public IndexDefinitionBuilder excludedPaths(String ... paths){ + tree.setProperty(PathFilter.PROP_EXCLUDED_PATHS, asList(paths), STRINGS); + return this; + } + + public IndexDefinitionBuilder queryPaths(String ... paths){ + tree.setProperty(IndexConstants.QUERY_PATHS, asList(paths), STRINGS); + return this; + } + + public IndexDefinitionBuilder supersedes(String ... paths){ + tree.setProperty(IndexConstants.SUPERSEDED_INDEX_PATHS, asList(paths), STRINGS); + return this; + } + + public IndexDefinitionBuilder noAsync(){ + tree.removeProperty("async"); + return this; + } + + public IndexDefinitionBuilder async(String ... asyncVals){ + tree.removeProperty("async"); + tree.setProperty("async", asList(asyncVals), STRINGS); + return this; + } + + public IndexDefinitionBuilder nodeTypeIndex() { + tree.setProperty(FulltextIndexConstants.PROP_INDEX_NODE_TYPE, true); + return this; + } + + public Tree getBuilderTree(){ + return tree; + } + + public NodeState build(){ + setReindexFlagIfRequired(); + return builder.getNodeState(); + } + + public Tree build(Tree tree){ + NodeStateCopyUtils.copyToTree(build(), tree); + return tree; + } + + public Node build(Node node) throws RepositoryException { + NodeStateCopyUtils.copyToNode(build(), node); + return node; + } + + public boolean isReindexRequired() { + if (reindexRequired){ + return true; + } + return !SelectiveEqualsDiff.equals(initial, builder.getNodeState()); + } + + private void setReindexFlagIfRequired(){ + if (!reindexRequired && !SelectiveEqualsDiff.equals(initial, builder.getNodeState()) && autoManageReindexFlag){ + tree.setProperty("reindex", true); + reindexRequired = true; + } + } + + private void setType() { + PropertyState type = tree.getProperty(IndexConstants.TYPE_PROPERTY_NAME); + if (type == null || !"disabled".equals(type.getValue(Type.STRING))) { + tree.setProperty(IndexConstants.TYPE_PROPERTY_NAME, "fulltext"); + } + } + + //~--------------------------------------< IndexRule > + + public IndexRule indexRule(String type){ + IndexRule rule = rules.get(type); + if (rule == null){ + rule = new IndexRule(getOrCreateChild(indexRule, type), type); + rules.put(type, rule); + } + return rule; + } + + public boolean hasIndexRule(String type){ + return indexRule.hasChild(type); + } + + public static class IndexRule { + private final Tree indexRule; + private final String ruleName; + private final Map<String, PropertyRule> props = Maps.newHashMap(); + private final Set<String> propNodeNames = Sets.newHashSet(); + + private IndexRule(Tree indexRule, String type) { + this.indexRule = indexRule; + this.ruleName = type; + loadExisting(); + } + + public IndexRule indexNodeName(){ + indexRule.setProperty(FulltextIndexConstants.INDEX_NODE_NAME, true); + return this; + } + + public IndexRule includePropertyTypes(String ... types){ + indexRule.setProperty(FulltextIndexConstants.INCLUDE_PROPERTY_TYPES, asList(types), STRINGS); + return this; + } + + public IndexRule sync() { + indexRule.setProperty(FulltextIndexConstants.PROP_SYNC, true); + return this; + } + + public PropertyRule property(String name){ + return property(name, false); + } + + public PropertyRule property(String name, boolean regex) { + return property(null, name, regex); + } + + public PropertyRule property(String propDefnNodeName, String name) { + return property(propDefnNodeName, name, false); + } + + public PropertyRule property(String propDefnNodeName, String name, boolean regex){ + PropertyRule propRule = props.get(name); + if (propRule == null){ + Tree propTree = findExisting(name); + if (propTree == null){ + if (propDefnNodeName == null){ + propDefnNodeName = createPropNodeName(name, regex); + } + propTree = getOrCreateChild(getPropsTree(), propDefnNodeName); + } + propRule = new PropertyRule(this, propTree, name, regex); + props.put(name != null ? name : propDefnNodeName, propRule); + } + return propRule; + } + + private void loadExisting() { + if (!indexRule.hasChild(FulltextIndexConstants.PROP_NAME)) { + return; + } + + for (Tree tree : getPropsTree().getChildren()){ + if (!tree.hasProperty(FulltextIndexConstants.PROP_NAME)){ + continue; + } + String name = tree.getProperty(FulltextIndexConstants.PROP_NAME).getValue(Type.STRING); + boolean regex = false; + if (tree.hasProperty(FulltextIndexConstants.PROP_IS_REGEX)) { + regex = tree.getProperty(FulltextIndexConstants.PROP_IS_REGEX).getValue(Type.BOOLEAN); + } + PropertyRule pr = new PropertyRule(this, tree, name, regex); + props.put(name, pr); + } + } + + private Tree findExisting(String name) { + for (Tree tree : getPropsTree().getChildren()){ + if (name.equals(tree.getProperty(FulltextIndexConstants.PROP_NAME).getValue(Type.STRING))){ + return tree; + } + } + return null; + } + + private String createPropNodeName(String name, boolean regex) { + name = regex ? "prop" : getSafePropName(name); + if (name.isEmpty()){ + name = "prop"; + } + if (propNodeNames.contains(name)){ + name = name + "_" + propNodeNames.size(); + } + propNodeNames.add(name); + return name; + } + + public String getRuleName() { + return ruleName; + } + + public boolean hasPropertyRule(String propName){ + return findExisting(propName) != null; + } + + private Tree getPropsTree() { + return getOrCreateChild(indexRule, FulltextIndexConstants.PROP_NODE); + } + } + + public static class PropertyRule { + private final IndexRule indexRule; + private final Tree propTree; + + private PropertyRule(IndexRule indexRule, Tree propTree, String name, boolean regex) { + this.indexRule = indexRule; + this.propTree = propTree; + if (name != null) { + propTree.setProperty(FulltextIndexConstants.PROP_NAME, name); + } + if (regex) { + propTree.setProperty(FulltextIndexConstants.PROP_IS_REGEX, true); + } + } + + public PropertyRule useInExcerpt(){ + propTree.setProperty(FulltextIndexConstants.PROP_USE_IN_EXCERPT, true); + return this; + } + + public PropertyRule useInSpellcheck(){ + propTree.setProperty(FulltextIndexConstants.PROP_USE_IN_SPELLCHECK, true); + return this; + } + + public PropertyRule type(String type){ + //This would throw an IAE if type is invalid + PropertyType.valueFromName(type); + propTree.setProperty(FulltextIndexConstants.PROP_TYPE, type); + return this; + } + + public PropertyRule useInSuggest(){ + propTree.setProperty(FulltextIndexConstants.PROP_USE_IN_SUGGEST, true); + return this; + } + + public PropertyRule analyzed(){ + propTree.setProperty(FulltextIndexConstants.PROP_ANALYZED, true); + return this; + } + + public PropertyRule nodeScopeIndex(){ + propTree.setProperty(FulltextIndexConstants.PROP_NODE_SCOPE_INDEX, true); + return this; + } + + public PropertyRule ordered(){ + propTree.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + return this; + } + + public PropertyRule ordered(String type){ + type(type); + propTree.setProperty(FulltextIndexConstants.PROP_ORDERED, true); + return this; + } + + public PropertyRule disable() { + propTree.setProperty(FulltextIndexConstants.PROP_INDEX, false); + return this; + } + + public PropertyRule propertyIndex(){ + propTree.setProperty(FulltextIndexConstants.PROP_PROPERTY_INDEX, true); + return this; + } + + public PropertyRule nullCheckEnabled(){ + propTree.setProperty(FulltextIndexConstants.PROP_NULL_CHECK_ENABLED, true); + return this; + } + + public PropertyRule excludeFromAggregation(){ + propTree.setProperty(FulltextIndexConstants.PROP_EXCLUDE_FROM_AGGREGATE, true); + return this; + } + + public PropertyRule notNullCheckEnabled(){ + propTree.setProperty(FulltextIndexConstants.PROP_NOT_NULL_CHECK_ENABLED, true); + return this; + } + + public PropertyRule weight(int weight){ + propTree.setProperty(FulltextIndexConstants.PROP_WEIGHT, weight); + return this; + } + + public PropertyRule valuePattern(String valuePattern){ + propTree.setProperty(IndexConstants.VALUE_PATTERN, valuePattern); + return this; + } + + public PropertyRule valueExcludedPrefixes(String... values){ + propTree.setProperty(IndexConstants.VALUE_EXCLUDED_PREFIXES, asList(values), STRINGS); + return this; + } + + public PropertyRule valueIncludedPrefixes(String... values){ + propTree.setProperty(IndexConstants.VALUE_INCLUDED_PREFIXES, asList(values), STRINGS); + return this; + } + + public PropertyRule sync(){ + propTree.setProperty(FulltextIndexConstants.PROP_SYNC, true); + return this; + } + + public PropertyRule unique(){ + propTree.setProperty(FulltextIndexConstants.PROP_UNIQUE, true); + return this; + } + + public PropertyRule function(String fn) { + propTree.setProperty(FulltextIndexConstants.PROP_FUNCTION, fn); + return this; + } + + public IndexRule enclosingRule(){ + return indexRule; + } + + public Tree getBuilderTree(){ + return propTree; + } + + public PropertyRule property(String name){ + return indexRule.property(name, false); + } + + public PropertyRule property(String name, boolean regex) { + return indexRule.property(null, name, regex); + } + + public PropertyRule property(String propDefnNodeName, String name) { + return indexRule.property(propDefnNodeName, name, false); + } + } + + //~--------------------------------------< Aggregates > + + public AggregateRule aggregateRule(String type){ + if (aggregatesTree == null){ + aggregatesTree = getOrCreateChild(tree, FulltextIndexConstants.AGGREGATES); + } + AggregateRule rule = aggRules.get(type); + if (rule == null){ + rule = new AggregateRule(getOrCreateChild(aggregatesTree, type)); + aggRules.put(type, rule); + } + return rule; + } + + public AggregateRule aggregateRule(String primaryType, String ... includes){ + AggregateRule rule = aggregateRule(primaryType); + for (String include : includes){ + rule.include(include); + } + return rule; + } + + public static class AggregateRule { + private final Tree aggregate; + private final Map<String, Include> includes = Maps.newHashMap(); + + private AggregateRule(Tree aggregate) { + this.aggregate = aggregate; + loadExisting(aggregate); + } + + public Include include(String includePath) { + Include include = includes.get(includePath); + if (include == null){ + Tree includeTree = findExisting(includePath); + if (includeTree == null){ + includeTree = getOrCreateChild(aggregate, "include" + includes.size()); + } + include = new Include(this, includeTree); + includes.put(includePath, include); + } + include.path(includePath); + return include; + } + + private Tree findExisting(String includePath) { + for (Tree tree : aggregate.getChildren()){ + if (includePath.equals(tree.getProperty(FulltextIndexConstants.AGG_PATH).getValue(Type.STRING))){ + return tree; + } + } + return null; + } + + private void loadExisting(Tree aggregate) { + for (Tree tree : aggregate.getChildren()){ + if (tree.hasProperty(FulltextIndexConstants.AGG_PATH)) { + Include include = new Include(this, tree); + includes.put(include.getPath(), include); + } + } + } + + public static class Include { + private final AggregateRule aggregateRule; + private final Tree include; + + private Include(AggregateRule aggregateRule, Tree include) { + this.aggregateRule = aggregateRule; + this.include = include; + } + + public Include path(String includePath) { + include.setProperty(FulltextIndexConstants.AGG_PATH, includePath); + return this; + } + + public Include relativeNode(){ + include.setProperty(FulltextIndexConstants.AGG_RELATIVE_NODE, true); + return this; + } + + public Include include(String path){ + return aggregateRule.include(path); + } + + public String getPath(){ + return include.getProperty(FulltextIndexConstants.AGG_PATH).getValue(Type.STRING); + } + } + } + + private static Tree getOrCreateChild(Tree tree, String name){ + if (tree.hasChild(name)){ + return tree.getChild(name); + } + Tree child = tree.addChild(name); + child.setOrderableChildren(true); + child.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); + return child; + } + + static class SelectiveEqualsDiff extends EqualsDiff { + public static boolean equals(NodeState before, NodeState after) { + return before.exists() == after.exists() + && after.compareAgainstBaseState(before, new SelectiveEqualsDiff()); + } + + @Override + public boolean propertyChanged(PropertyState before, PropertyState after) { + if (IndexConstants.ASYNC_PROPERTY_NAME.equals(before.getName())){ + Set<String> asyncBefore = getAsyncValuesWithoutNRT(before); + Set<String> asyncAfter = getAsyncValuesWithoutNRT(after); + return asyncBefore.equals(asyncAfter); + } + return false; + } + + private Set<String> getAsyncValuesWithoutNRT(PropertyState state){ + Set<String> async = Sets.newHashSet(state.getValue(Type.STRINGS)); + async.remove(IndexConstants.INDEXING_MODE_NRT); + async.remove(IndexConstants.INDEXING_MODE_SYNC); + return async; + } + } + + static String getSafePropName(String relativePropName) { + String propName = PathUtils.getName(relativePropName); + int indexOfColon = propName.indexOf(':'); + if (indexOfColon > 0){ + propName = propName.substring(indexOfColon + 1); + } + + //Just keep ascii chars + propName = propName.replaceAll("\\W", ""); + return propName; + } +} Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexDefinitionBuilder.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexHelper.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexHelper.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexHelper.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexHelper.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,181 @@ +/* + * 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.util; + +import java.util.Set; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableSet.of; +import static com.google.common.collect.Sets.newHashSet; +import static javax.jcr.PropertyType.TYPENAME_BINARY; +import static javax.jcr.PropertyType.TYPENAME_STRING; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.JcrConstants.JCR_UUID; +import static org.apache.jackrabbit.oak.api.Type.NAME; +import static org.apache.jackrabbit.oak.api.Type.STRINGS; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.ASYNC_PROPERTY_NAME; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EXCLUDE_PROPERTY_NAMES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EXPERIMENTAL_STORAGE; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_NAMES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_TYPES; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PERSISTENCE_FILE; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PERSISTENCE_NAME; +import static org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PERSISTENCE_PATH; +import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; +import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.GROUP_PROPERTY_NAMES; +import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.USER_PROPERTY_NAMES; + +public class IndexHelper { + + public static final Set<String> JR_PROPERTY_INCLUDES = of(TYPENAME_STRING, + TYPENAME_BINARY); + + /** + * Nodes that represent content that shold not be tokenized (like UUIDs, + * etc) + * + */ + private final static Set<String> NOT_TOKENIZED = newHashSet(JCR_UUID); + + static { + NOT_TOKENIZED.addAll(USER_PROPERTY_NAMES); + NOT_TOKENIZED.addAll(GROUP_PROPERTY_NAMES); + } + + private IndexHelper() { + } + + public static NodeBuilder newFTIndexDefinition( + @Nonnull NodeBuilder index, @Nonnull String name, String type, + @Nullable Set<String> propertyTypes) { + return newFTIndexDefinition(index, name, type, propertyTypes, null, null, null); + } + + public static NodeBuilder newFTIndexDefinition( + @Nonnull NodeBuilder index, @Nonnull String name, String type, + @Nullable Set<String> propertyTypes, + @Nullable Set<String> excludes, @Nullable String async) { + return newFTIndexDefinition(index, type, name, propertyTypes, excludes, + async, null); + } + + public static NodeBuilder newFTIndexDefinition( + @Nonnull NodeBuilder index, @Nonnull String name, String type, + @Nullable Set<String> propertyTypes, + @Nullable Set<String> excludes, @Nullable String async, + @Nullable Boolean stored) { + if (index.hasChildNode(name)) { + return index.child(name); + } + index = index.child(name); + index.setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, type) + .setProperty(REINDEX_PROPERTY_NAME, true); + if (async != null) { + index.setProperty(ASYNC_PROPERTY_NAME, async); + } + if (propertyTypes != null && !propertyTypes.isEmpty()) { + index.setProperty(createProperty(INCLUDE_PROPERTY_TYPES, + propertyTypes, STRINGS)); + } + if (excludes != null && !excludes.isEmpty()) { + index.setProperty(createProperty(EXCLUDE_PROPERTY_NAMES, excludes, + STRINGS)); + } + if (stored != null) { + index.setProperty(createProperty(EXPERIMENTAL_STORAGE, stored)); + } + return index; + } + + public static NodeBuilder newFTFileIndexDefinition( + @Nonnull NodeBuilder index, @Nonnull String name, String type, + @Nullable Set<String> propertyTypes, @Nonnull String path) { + return newFTFileIndexDefinition(index, type, name, propertyTypes, null, + path, null); + } + + public static NodeBuilder newFTFileIndexDefinition( + @Nonnull NodeBuilder index, @Nonnull String name, String type, + @Nullable Set<String> propertyTypes, + @Nullable Set<String> excludes, @Nonnull String path, + @Nullable String async) { + if (index.hasChildNode(name)) { + return index.child(name); + } + index = index.child(name); + index.setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, type) + .setProperty(PERSISTENCE_NAME, PERSISTENCE_FILE) + .setProperty(PERSISTENCE_PATH, path) + .setProperty(REINDEX_PROPERTY_NAME, true); + if (async != null) { + index.setProperty(ASYNC_PROPERTY_NAME, async); + } + if (propertyTypes != null && !propertyTypes.isEmpty()) { + index.setProperty(createProperty(INCLUDE_PROPERTY_TYPES, + propertyTypes, STRINGS)); + } + if (excludes != null && !excludes.isEmpty()) { + index.setProperty(createProperty(EXCLUDE_PROPERTY_NAMES, excludes, + STRINGS)); + } + return index; + } + + public static NodeBuilder newFTPropertyIndexDefinition( + @Nonnull NodeBuilder index, @Nonnull String name, String type, + @Nonnull Set<String> includes, + @Nonnull String async) { + checkArgument(!includes.isEmpty(), "Lucene property index " + + "requires explicit list of property names to be indexed"); + + index = index.child(name); + index.setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, type) + .setProperty(REINDEX_PROPERTY_NAME, true); + index.setProperty(FulltextIndexConstants.FULL_TEXT_ENABLED, false); + index.setProperty(createProperty(INCLUDE_PROPERTY_NAMES, includes, STRINGS)); + + if (async != null) { + index.setProperty(ASYNC_PROPERTY_NAME, async); + } + return index; + } + + /** + * Nodes that represent UUIDs and shold not be tokenized + * + */ + public static boolean skipTokenization(String name) { + return NOT_TOKENIZED.contains(name); + } + + public static boolean isIndexNodeOfType(NodeState node, String type){ + return type.equals(node.getString(TYPE_PROPERTY_NAME)); + } +} Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/IndexHelper.java ------------------------------------------------------------------------------ svn:eol-style = native Added: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/NodeStateCopyUtils.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/NodeStateCopyUtils.java?rev=1828972&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/NodeStateCopyUtils.java (added) +++ jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/NodeStateCopyUtils.java Thu Apr 12 11:46:17 2018 @@ -0,0 +1,154 @@ +/* + * 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.util; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.ValueFactory; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.commons.JcrUtils; +import org.apache.jackrabbit.oak.api.Blob; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.tree.factories.TreeFactory; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.jackrabbit.oak.api.Type.NAMES; + +final class NodeStateCopyUtils { + private static final String OAK_CHILD_ORDER = ":childOrder"; + + public static void copyToTree(NodeState state, Tree tree){ + tree.setOrderableChildren(state.hasProperty(OAK_CHILD_ORDER)); + copyProps(state, tree); + + Tree src = TreeFactory.createReadOnlyTree(state); + for (Tree srcChild : src.getChildren()){ + String childName = srcChild.getName(); + Tree child = tree.addChild(childName); + copyToTree(state.getChildNode(childName), child); + } + } + + public static void copyToNode(NodeState state, Node node) throws RepositoryException { + copyProps(state, node); + + Tree src = TreeFactory.createReadOnlyTree(state); + for (Tree srcChild : src.getChildren()){ + String childName = srcChild.getName(); + + if (NodeStateUtils.isHidden(childName)){ + continue; + } + + NodeState childState = state.getChildNode(childName); + Node child = JcrUtils.getOrAddNode(node, childName, primaryType(childState)); + copyToNode(childState, child); + } + } + + private static void copyProps(NodeState state, Tree tree) { + for (PropertyState ps : state.getProperties()){ + if (!ps.getName().equals(OAK_CHILD_ORDER)){ + tree.setProperty(ps); + } + } + } + + private static void copyProps(NodeState state, Node node) throws RepositoryException { + ValueFactory vf = node.getSession().getValueFactory(); + for (PropertyState ps : state.getProperties()){ + String name = ps.getName(); + if (name.equals(JcrConstants.JCR_PRIMARYTYPE) + || name.equals(OAK_CHILD_ORDER)){ + continue; + } + + if (name.equals(JcrConstants.JCR_MIXINTYPES)){ + for (String n : ps.getValue(NAMES)) { + node.addMixin(n); + } + continue; + } + + if (NodeStateUtils.isHidden(name)){ + continue; + } + + if (ps.isArray()){ + Value[] values = new Value[ps.count()]; + for (int i = 0; i < ps.count(); i++) { + values[i] = createValue(vf, ps, i); + } + node.setProperty(name, values, ps.getType().tag()); + } else { + node.setProperty(name, createValue(vf, ps, -1), ps.getType().tag()); + } + } + } + + private static Value createValue(ValueFactory vf, PropertyState ps, int index) throws RepositoryException { + switch(ps.getType().tag()) { + case PropertyType.STRING : + return vf.createValue(getValue(ps, Type.STRING, index)); + case PropertyType.BINARY: + Blob blob = getValue(ps, Type.BINARY, index); + Binary bin = vf.createBinary(blob.getNewStream()); + return vf.createValue(bin); + case PropertyType.LONG: + return vf.createValue(getValue(ps, Type.LONG, index)); + case PropertyType.DOUBLE: + return vf.createValue(getValue(ps, Type.DOUBLE, index)); + case PropertyType.DATE: + return vf.createValue(getValue(ps, Type.DATE, index)); + case PropertyType.BOOLEAN: + return vf.createValue(getValue(ps, Type.BOOLEAN, index)); + case PropertyType.NAME: + return vf.createValue(getValue(ps, Type.NAME, index)); + case PropertyType.PATH: + return vf.createValue(getValue(ps, Type.PATH, index)); + case PropertyType.REFERENCE: + return vf.createValue(getValue(ps, Type.REFERENCE, index)); + case PropertyType.WEAKREFERENCE: + return vf.createValue(getValue(ps, Type.WEAKREFERENCE, index)); + case PropertyType.URI: + return vf.createValue(getValue(ps, Type.URI, index)); + case PropertyType.DECIMAL: + return vf.createValue(getValue(ps, Type.DECIMAL, index)); + default: + throw new IllegalStateException("Unsupported type " + ps.getType()); + } + } + + private static <T> T getValue(PropertyState ps, Type<T> type, int index){ + return index < 0 ? ps.getValue(type) : ps.getValue(type, index); + } + + private static String primaryType(NodeState state){ + return checkNotNull(state.getName(JcrConstants.JCR_PRIMARYTYPE), "jcr:primaryType not defined for %s", state); + } +} Propchange: jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/NodeStateCopyUtils.java ------------------------------------------------------------------------------ svn:eol-style = native