You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by to...@apache.org on 2018/04/12 11:46:18 UTC
svn commit: r1828972 [3/5] - in /jackrabbit/oak/trunk/oak-search: ./
src/main/java/org/apache/jackrabbit/oak/plugins/index/search/
src/main/java/org/apache/jackrabbit/oak/plugins/index/search/util/
src/test/java/org/apache/jackrabbit/oak/plugins/index/...
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