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 ch...@apache.org on 2014/11/26 09:06:53 UTC

svn commit: r1641771 [1/2] - in /jackrabbit/oak/trunk/oak-lucene/src: main/java/org/apache/jackrabbit/oak/plugins/index/lucene/ main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/ test/java/org/apache/jackrabbit/oak/plugins/index/lucene/

Author: chetanm
Date: Wed Nov 26 08:06:52 2014
New Revision: 1641771

URL: http://svn.apache.org/r1641771
Log:
OAK-2268 - Support index time Aggregation of repository nodes

Refer to OAK-2268 for implementation notes

Added:
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/Aggregate.java   (with props)
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/AggregateTest.java   (with props)
Removed:
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/RelativeProperty.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/RelativePropertyTest.java
Modified:
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
    jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/ConfigUtil.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinitionTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlannerTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest2.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexQueryTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/TestUtil.java

Added: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/Aggregate.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/Aggregate.java?rev=1641771&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/Aggregate.java (added)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/Aggregate.java Wed Nov 26 08:06:52 2014
@@ -0,0 +1,535 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.index.lucene;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.annotation.CheckForNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.lucene.util.ConfigUtil;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Iterables.toArray;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Lists.newArrayListWithCapacity;
+import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+
+class Aggregate {
+    public static final String MATCH_ALL = "*";
+
+    /**
+     * recursive aggregation (for same type nodes) limit default value.
+     */
+    public static final int RECURSIVE_AGGREGATION_LIMIT_DEFAULT = 5;
+    private final String nodeTypeName;
+    private final List<? extends Include> includes;
+    final int reAggregationLimit;
+    private final List<NodeInclude> relativeNodeIncludes;
+
+    Aggregate(String nodeTypeName) {
+       this(nodeTypeName, Collections.<Include>emptyList());
+    }
+
+    Aggregate(String nodeTypeName, List<? extends Include> includes) {
+        this(nodeTypeName, includes, RECURSIVE_AGGREGATION_LIMIT_DEFAULT);
+    }
+
+    Aggregate(String nodeTypeName, List<? extends Include> includes,
+              int recursionLimit) {
+        this.nodeTypeName = nodeTypeName;
+        this.includes = ImmutableList.copyOf(includes);
+        this.reAggregationLimit = recursionLimit;
+        this.relativeNodeIncludes = findRelativeNodeIncludes(includes);
+    }
+
+    public List<? extends Include> getIncludes() {
+        return includes;
+    }
+
+    public void collectAggregates(NodeState root, ResultCollector collector) throws CommitFailedException {
+        if (nodeTypeName.equals(ConfigUtil.getPrimaryTypeName(root))) {
+            List<Matcher> matchers = createMatchers();
+            collectAggregates(root, matchers, collector);
+        }
+    }
+
+    public List<Matcher> createMatchers(AggregateRoot root){
+        List<Matcher> matchers = newArrayListWithCapacity(includes.size());
+        for (Include include : includes) {
+            matchers.add(new Matcher(this, include, root));
+        }
+        return matchers;
+    }
+
+    public boolean hasRelativeNodeInclude(String nodePath) {
+        for (NodeInclude ni : relativeNodeIncludes){
+            if (ni.matches(nodePath)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return nodeTypeName;
+    }
+
+    private static void collectAggregates(NodeState nodeState, List<Matcher> matchers,
+                                          ResultCollector collector) throws CommitFailedException {
+        for (ChildNodeEntry cne : nodeState.getChildNodeEntries()) {
+            List<Matcher> nextSet = newArrayListWithCapacity(matchers.size());
+            for (Matcher m : matchers) {
+                Matcher result = m.match(cne.getName(), cne.getNodeState());
+                if (result.getStatus() == Matcher.Status.MATCH_FOUND){
+                    result.collectResults(collector);
+                }
+
+                if (result.getStatus() != Matcher.Status.FAIL){
+                    nextSet.addAll(result.nextSet());
+                }
+            }
+            if (!nextSet.isEmpty()) {
+                collectAggregates(cne.getNodeState(), nextSet, collector);
+            }
+        }
+    }
+
+    private List<Matcher> createMatchers() {
+        List<Matcher> matchers = newArrayListWithCapacity(includes.size());
+        for (Include include : includes) {
+            matchers.add(new Matcher(this, include));
+        }
+        return matchers;
+    }
+
+    private static List<NodeInclude> findRelativeNodeIncludes(List<? extends Include> includes) {
+        List<NodeInclude> result = newArrayList();
+        for (Include i : includes){
+            if (i instanceof NodeInclude){
+                NodeInclude ni = (NodeInclude) i;
+                if (ni.relativeNode){
+                    result.add(ni);
+                }
+            }
+        }
+        return ImmutableList.copyOf(result);
+    }
+
+    public static interface AggregateMapper {
+        @CheckForNull
+        Aggregate getAggregate(String nodeTypeName);
+    }
+
+    //~-----------------------------------------------------< Includes >
+
+    public static abstract class Include<T> {
+        protected final String[] elements;
+
+        public Include(String pattern) {
+            this.elements = computeElements(pattern);
+        }
+
+        public boolean match(String name, NodeState nodeState, int depth) {
+            String element = elements[depth];
+            if (MATCH_ALL.equals(element)) {
+                return true;
+            } else if (element.equals(name)) {
+                return true;
+            }
+            return false;
+        }
+
+        public int maxDepth() {
+            return elements.length;
+        }
+
+        public void collectResults(T rootInclude, String rootIncludePath,
+                                   String nodePath, NodeState nodeState,  ResultCollector results)
+                throws CommitFailedException {
+            collectResults(nodePath, nodeState, results);
+        }
+
+        public void collectResults(String nodePath, NodeState nodeState,
+                                            ResultCollector results) throws CommitFailedException {
+
+        }
+
+        public abstract boolean aggregatesProperty(String name);
+
+        @CheckForNull
+        public Aggregate getAggregate(NodeState matchedNodeState) {
+            return null;
+        }
+    }
+
+    public static class NodeInclude extends Include<NodeInclude> {
+        final String primaryType;
+        final boolean relativeNode;
+        private final String pattern;
+        private final AggregateMapper aggMapper;
+
+        public NodeInclude(AggregateMapper mapper, String pattern) {
+            this(mapper, null, pattern, false);
+        }
+
+        public NodeInclude(AggregateMapper mapper, String primaryType, String pattern, boolean relativeNode) {
+            super(pattern);
+            this.pattern = pattern;
+            this.primaryType = primaryType;
+            this.aggMapper = mapper;
+            this.relativeNode = relativeNode;
+        }
+
+        @Override
+        public boolean match(String name, NodeState nodeState, int depth) {
+            //As per JR2 the primaryType is enforced on last element
+            //last segment -> add to collector if node type matches
+            if (depth == maxDepth() - 1
+                    && primaryType != null
+                    && !primaryType.equals(ConfigUtil.getPrimaryTypeName(nodeState))) {
+                return false;
+            }
+            return super.match(name, nodeState, depth);
+        }
+
+        @Override
+        public void collectResults(NodeInclude rootInclude, String rootIncludePath, String nodePath,
+                                   NodeState nodeState, ResultCollector results) throws CommitFailedException {
+            //For supporting jcr:contains(jcr:content, 'foo')
+            if (rootInclude != this && rootInclude.relativeNode){
+                results.onResult(new NodeIncludeResult(nodePath, rootIncludePath, nodeState));
+            }
+
+            //For supporting jcr:contains(., 'foo')
+            results.onResult(new NodeIncludeResult(nodePath, nodeState));
+        }
+
+        @Override
+        public boolean aggregatesProperty(String name) {
+            return true;
+        }
+
+        @Override
+        public Aggregate getAggregate(NodeState matchedNodeState) {
+            return aggMapper.getAggregate(ConfigUtil.getPrimaryTypeName(matchedNodeState));
+        }
+
+        @Override
+        public String toString() {
+            return "NodeInclude{" +
+                    "primaryType='" + primaryType + '\'' +
+                    ", relativeNode=" + relativeNode +
+                    ", pattern='" + pattern + '\'' +
+                    '}';
+        }
+
+        public boolean matches(String nodePath) {
+            List<String> pathElements = ImmutableList.copyOf(PathUtils.elements(nodePath));
+            if (pathElements.size() != elements.length){
+                return false;
+            }
+
+            for (int i = 0; i < elements.length; i++){
+                String element = elements[i];
+                if (MATCH_ALL.equals(element)) {
+                    continue;
+                }
+
+                if (!element.equals(pathElements.get(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    public static class PropertyInclude extends Include<PropertyInclude> {
+        private final PropertyDefinition propertyDefinition;
+        private final String propertyName;
+        private final Pattern pattern;
+        private final String parentPath;
+
+        public PropertyInclude(PropertyDefinition pd) {
+            super(getParentPath(pd.name));
+            this.propertyDefinition = pd;
+            this.propertyName = PathUtils.getName(pd.name);
+            this.parentPath = getParentPath(pd.name);
+
+            if (pd.isRegexp) {
+                pattern = Pattern.compile(propertyName);
+            } else {
+                pattern = null;
+            }
+        }
+
+        @Override
+        public void collectResults(String nodePath, NodeState nodeState, ResultCollector results)
+                throws CommitFailedException {
+            if (pattern != null) {
+                for (PropertyState ps : nodeState.getProperties()) {
+                    if (pattern.matcher(ps.getName()).matches()) {
+                        results.onResult(new PropertyIncludeResult(ps, propertyDefinition, parentPath));
+                    }
+                }
+            } else {
+                PropertyState ps = nodeState.getProperty(propertyName);
+                if (ps != null) {
+                    results.onResult(new PropertyIncludeResult(ps, propertyDefinition, parentPath));
+                }
+            }
+        }
+
+        @Override
+        public boolean aggregatesProperty(String name) {
+            if (pattern != null){
+                return pattern.matcher(name).matches();
+            }
+            return propertyName.equals(name);
+        }
+
+        @Override
+        public String toString() {
+            return propertyDefinition.toString();
+        }
+    }
+
+    public static interface ResultCollector {
+        void onResult(NodeIncludeResult result) throws CommitFailedException;
+
+        void onResult(PropertyIncludeResult result) throws CommitFailedException;
+    }
+
+    public static class NodeIncludeResult {
+        final NodeState nodeState;
+        final String nodePath;
+        final String rootIncludePath;
+
+        public NodeIncludeResult(String nodePath, NodeState nodeState) {
+            this(nodePath, null, nodeState);
+        }
+
+        public NodeIncludeResult(String nodePath, String rootIncludePath, NodeState nodeState) {
+            this.nodePath = nodePath;
+            this.nodeState = nodeState;
+            this.rootIncludePath = rootIncludePath;
+        }
+
+        public boolean isRelativeNode(){
+            return  rootIncludePath != null;
+        }
+
+        @Override
+        public String toString() {
+            return "NodeIncludeResult{" +
+                    "nodePath='" + nodePath + '\'' +
+                    ", rootIncludePath='" + rootIncludePath + '\'' +
+                    '}';
+        }
+    }
+
+    public static class PropertyIncludeResult {
+        final PropertyState propertyState;
+        final PropertyDefinition pd;
+        final String propertyPath;
+        final String nodePath;
+
+        public PropertyIncludeResult(PropertyState propertyState, PropertyDefinition pd,
+                                     String parentPath) {
+            this.propertyState = propertyState;
+            this.pd = pd;
+            this.nodePath = parentPath;
+            this.propertyPath = PathUtils.concat(parentPath, propertyState.getName());
+        }
+    }
+
+    public static interface AggregateRoot {
+        void markDirty();
+    }
+
+    public static class Matcher {
+        public static enum Status {CONTINUE, MATCH_FOUND, FAIL}
+
+        private static class RootState {
+            final AggregateRoot root;
+            final Aggregate rootAggregate;
+            final Include rootInclude;
+
+            private RootState(AggregateRoot root, Aggregate rootAggregate, Include rootInclude) {
+                this.root = root;
+                this.rootAggregate = rootAggregate;
+                this.rootInclude = rootInclude;
+            }
+        }
+
+        private final RootState rootState;
+        private final Include currentInclude;
+        /**
+         * Current depth in the include pattern.
+         */
+        private final int depth;
+        private final Status status;
+        private final NodeState matchedNodeState;
+        private final String currentPath;
+
+        private final List<String> aggregateStack;
+
+        public Matcher(Aggregate aggregate, Include currentInclude) {
+            this(aggregate, currentInclude, null);
+        }
+
+        public Matcher(Aggregate aggregate, Include include, AggregateRoot root) {
+            this.rootState = new RootState(root, aggregate, include);
+            this.depth = 0;
+            this.currentInclude = include;
+            this.status = Status.CONTINUE;
+            this.currentPath = null;
+            this.matchedNodeState = null;
+            this.aggregateStack = Collections.emptyList();
+        }
+
+        private Matcher(Matcher m, Status status, int depth) {
+            checkArgument(status == Status.FAIL);
+            this.rootState = m.rootState;
+            this.depth = depth;
+            this.currentInclude = m.currentInclude;
+            this.status = status;
+            this.currentPath = null;
+            this.matchedNodeState = null;
+            this.aggregateStack = m.aggregateStack;
+        }
+
+        private Matcher(Matcher m, Status status, int depth,
+                        NodeState matchedNodeState, String currentPath) {
+            checkArgument(status != Status.FAIL);
+            this.rootState = m.rootState;
+            this.depth = depth;
+            this.currentInclude = m.currentInclude;
+            this.status = status;
+            this.matchedNodeState = matchedNodeState;
+            this.currentPath = currentPath;
+            this.aggregateStack = m.aggregateStack;
+        }
+
+        private Matcher(Matcher m, Include i, String currentPath) {
+            checkArgument(m.status == Status.MATCH_FOUND);
+            this.rootState = m.rootState;
+            this.depth = 0;
+            this.currentInclude = i;
+            this.status = Status.CONTINUE;
+            this.matchedNodeState = null;
+            this.currentPath = currentPath;
+
+            List<String> paths = newArrayList(m.aggregateStack);
+            paths.add(currentPath);
+            this.aggregateStack = ImmutableList.copyOf(paths);
+        }
+
+        public Matcher match(String name, NodeState nodeState) {
+            boolean result = currentInclude.match(name, nodeState, depth);
+            if (result){
+                if (hasMore()){
+                    return new Matcher(this, Status.CONTINUE, depth, nodeState, path(name));
+                } else {
+                    return new Matcher(this, Status.MATCH_FOUND, depth, nodeState, path(name));
+                }
+            } else {
+                return new Matcher(this, Status.FAIL, depth);
+            }
+        }
+
+        public Collection<Matcher> nextSet() {
+            checkArgument(status != Status.FAIL);
+
+            if (status == Status.MATCH_FOUND){
+                Aggregate nextAgg = currentInclude.getAggregate(matchedNodeState);
+                if (nextAgg != null){
+                    int recursionLevel = aggregateStack.size() + 1;
+
+                    if (recursionLevel >= rootState.rootAggregate.reAggregationLimit){
+                        return Collections.emptyList();
+                    }
+
+                    List<Matcher> result = Lists.newArrayListWithCapacity(nextAgg.includes.size());
+                    for (Include i : nextAgg.includes){
+                        result.add(new Matcher(this,  i, currentPath));
+                    }
+                    return result;
+                }
+                return Collections.emptyList();
+            }
+
+            return Collections.singleton(new Matcher(this, status, depth + 1,
+                    null, currentPath));
+        }
+
+        public void collectResults(ResultCollector results)
+                throws CommitFailedException {
+            checkArgument(status == Status.MATCH_FOUND);
+            String rootIncludePath = aggregateStack.isEmpty() ?  null : aggregateStack.get(0);
+            currentInclude.collectResults(rootState.rootInclude, rootIncludePath,
+                    currentPath, matchedNodeState, results);
+        }
+
+        public void markRootDirty() {
+            checkArgument(status == Status.MATCH_FOUND);
+            rootState.root.markDirty();
+        }
+
+        public Status getStatus() {
+            return status;
+        }
+
+        public boolean aggregatesProperty(String name) {
+            checkArgument(status == Status.MATCH_FOUND);
+            return currentInclude.aggregatesProperty(name);
+        }
+
+        private boolean hasMore() {
+            return depth < currentInclude.maxDepth() - 1;
+        }
+
+        private String path(String nodeName){
+            if (currentPath == null){
+                return nodeName;
+            } else {
+                return PathUtils.concat(currentPath, nodeName);
+            }
+        }
+    }
+
+    //~--------------------------------------------------< utility >
+
+    private static String[] computeElements(String path) {
+        return toArray(elements(path), String.class);
+    }
+
+}

Propchange: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/Aggregate.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldFactory.java Wed Nov 26 08:06:52 2014
@@ -104,6 +104,10 @@ public final class FieldFactory {
         return new TextField(FULLTEXT, value, NO);
     }
 
+    public static Field newFulltextField(String name, String value) {
+        return new TextField(FieldNames.createFulltextFieldName(name), value, NO);
+    }
+
     /**
      * Date values are saved with sec resolution
      * @param date jcr data string

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/FieldNames.java Wed Nov 26 08:06:52 2014
@@ -45,7 +45,12 @@ public final class FieldNames {
     /**
      * Prefix for all field names that are fulltext indexed by property name.
      */
-    public static final String FULLTEXT_PREFIX = ":full";
+    public static final String ANALYZED_FIELD_PREFIX = "full:";
+
+    /**
+     * Prefix used for storing fulltext of relative node
+     */
+    public static final String FULLTEXT_RELATIVE_NODE = "fullnode:";
 
     /**
      * Used to select only the PATH field from the lucene documents
@@ -66,6 +71,13 @@ public final class FieldNames {
     }
 
     public static String createAnalyzedFieldName(String pname) {
-        return FULLTEXT_PREFIX + pname;
+        return ANALYZED_FIELD_PREFIX + pname;
+    }
+
+    public static String createFulltextFieldName(String nodeRelativePath) {
+        if (nodeRelativePath == null){
+            return FULLTEXT;
+        }
+        return FULLTEXT_RELATIVE_NODE + nodeRelativePath;
     }
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java Wed Nov 26 08:06:52 2014
@@ -20,7 +20,6 @@
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +43,7 @@ import org.apache.jackrabbit.JcrConstant
 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.namepath.NamePathMapper;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
 import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
@@ -62,6 +62,7 @@ import static com.google.common.collect.
 import static com.google.common.collect.Sets.newHashSet;
 import static org.apache.jackrabbit.JcrConstants.NT_BASE;
 import static org.apache.jackrabbit.oak.api.Type.NAMES;
+import static org.apache.jackrabbit.oak.commons.PathUtils.isAbsolute;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.DECLARING_NODE_TYPES;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.ENTRY_COUNT_PROPERTY_NAME;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_COUNT;
@@ -84,7 +85,7 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.NODE_TYPES_PATH;
 import static org.apache.jackrabbit.oak.plugins.tree.TreeConstants.OAK_CHILD_ORDER;
 
-class IndexDefinition {
+class IndexDefinition implements Aggregate.AggregateMapper{
     private static final Logger log = LoggerFactory.getLogger(IndexDefinition.class);
 
     /**
@@ -123,12 +124,6 @@ class IndexDefinition {
 
     private final Codec codec;
 
-    private final Set<String> relativePropNames;
-
-    private final List<RelativeProperty> relativeProperties;
-
-    private final int relativePropsMaxLevels;
-
     /**
      * Defines the maximum estimated entry count configured.
      * Defaults to {#DEFAULT_ENTRY_COUNT}
@@ -150,6 +145,10 @@ class IndexDefinition {
 
     private final IndexFormatVersion version;
 
+    private final Map<String, Aggregate> aggregates;
+
+    private final boolean indexesAllTypes;
+
     public IndexDefinition(NodeState root, NodeState defn) {
         this(root, defn, null);
     }
@@ -170,6 +169,8 @@ class IndexDefinition {
         this.blobSize = getOptionalValue(defn, BLOB_SIZE, DEFAULT_BLOB_SIZE);
         this.testMode = getOptionalValue(defn, LuceneIndexConstants.TEST_MODE, false);
 
+        this.aggregates = collectAggregates(defn);
+
         NodeState rulesState = defn.getChildNode(LuceneIndexConstants.INDEX_RULES);
         if (!rulesState.exists()){
             rulesState = createIndexRules(defn).getNodeState();
@@ -179,12 +180,9 @@ class IndexDefinition {
         this.indexRules = collectIndexRules(rulesState, definedIndexRules);
         this.definedRules = ImmutableList.copyOf(definedIndexRules);
 
-        this.relativeProperties = collectRelativeProperties(definedIndexRules);
         this.fullTextEnabled = hasFulltextEnabledIndexRule(definedIndexRules);
         this.propertyIndexEnabled = hasPropertyIndexEnabledIndexRule(definedIndexRules);
 
-        this.relativePropNames = collectRelPropertyNames(relativeProperties);
-        this.relativePropsMaxLevels = getRelPropertyMaxLevels(relativeProperties);
         this.evaluatePathRestrictions = getOptionalValue(defn, EVALUATE_PATH_RESTRICTION, false);
 
         String functionName = getOptionalValue(defn, LuceneIndexConstants.FUNC_NAME, null);
@@ -200,6 +198,8 @@ class IndexDefinition {
         } else {
             this.entryCount = DEFAULT_ENTRY_COUNT;
         }
+
+        this.indexesAllTypes = areAllTypesIndexed();
     }
 
     public boolean isFullTextEnabled() {
@@ -241,36 +241,6 @@ class IndexDefinition {
         return entryCount;
     }
 
-    public Collection<RelativeProperty> getRelativeProps() {
-        return relativeProperties;
-    }
-
-    /**
-     * Collects the relative properties where the property name matches given name. Note
-     * that multiple relative properties can end with same name e.g. foo/bar, baz/bar
-     *
-     * @param name property name without path
-     * @param relProps matching relative properties where the relative property path ends
-     *                 with given name
-     */
-    public void collectRelPropsForName(String name, Collection<RelativeProperty> relProps){
-        if(hasRelativeProperty(name)){
-            for(RelativeProperty rp : relativeProperties){
-                if(rp.name.equals(name)){
-                    relProps.add(rp);
-                }
-            }
-        }
-    }
-
-    boolean hasRelativeProperties(){
-        return !relativeProperties.isEmpty();
-    }
-
-    boolean hasRelativeProperty(String name) {
-        return relativePropNames.contains(name);
-    }
-
     public IndexFormatVersion getVersion() {
         return version;
     }
@@ -287,34 +257,48 @@ class IndexDefinition {
         return evaluatePathRestrictions;
     }
 
+    public boolean indexesAllTypes() {
+        return indexesAllTypes;
+    }
+
     @Override
     public String toString() {
         return "IndexDefinition : " + indexName;
     }
 
-    //~------------------------------------------< Internal >
+    //~---------------------------------------------------< Aggregates >
 
-    private int getRelPropertyMaxLevels(Collection<RelativeProperty> props) {
-        int max = -1;
-        for (RelativeProperty prop : props) {
-            max = Math.max(max, prop.ancestors.length);
+    @CheckForNull
+    public Aggregate getAggregate(String nodeType){
+        Aggregate agg = aggregates.get(nodeType);
+        return agg != null ? agg : null;
+    }
+
+    private Map<String, Aggregate> collectAggregates(NodeState defn) {
+        Map<String, Aggregate> aggregateMap = newHashMap();
+
+        for (ChildNodeEntry cne : defn.getChildNode(LuceneIndexConstants.AGGREGATES).getChildNodeEntries()) {
+            String nodeType = cne.getName();
+            int recursionLimit = getOptionalValue(cne.getNodeState(), LuceneIndexConstants.AGG_RECURSIVE_LIMIT,
+                    Aggregate.RECURSIVE_AGGREGATION_LIMIT_DEFAULT);
+
+            List<Aggregate.Include> includes = newArrayList();
+            for (ChildNodeEntry include : cne.getNodeState().getChildNodeEntries()) {
+                NodeState is = include.getNodeState();
+                String primaryType = is.getString(LuceneIndexConstants.AGG_PRIMARY_TYPE);
+                String path = is.getString(LuceneIndexConstants.AGG_PATH);
+                boolean relativeNode = getOptionalValue(is, LuceneIndexConstants.AGG_RELATIVE_NODE, false);
+                if (path == null) {
+                    log.warn("Aggregate pattern in {} does not have required property [{}]. {} aggregate rule would " +
+                            "be ignored", this, LuceneIndexConstants.AGG_PATH, include.getName());
+                    continue;
+                }
+                includes.add(new Aggregate.NodeInclude(this, primaryType, path, relativeNode));
+            }
+            aggregateMap.put(nodeType, new Aggregate(nodeType, includes, recursionLimit));
         }
-        return max;
-    }
-
-    public int getRelPropertyMaxLevels() {
-        return relativePropsMaxLevels;
-    }
 
-    private Codec createCodec() {
-        String codecName = getOptionalValue(definition, LuceneIndexConstants.CODEC_NAME, null);
-        Codec codec = null;
-        if (codecName != null) {
-            codec = Codec.forName(codecName);
-        } else if (fullTextEnabled) {
-            codec = new OakCodec();
-        }
-        return codec;
+        return ImmutableMap.copyOf(aggregateMap);
     }
 
     //~---------------------------------------------------< IndexRule >
@@ -440,6 +424,11 @@ class IndexDefinition {
         return ImmutableMap.copyOf(nt2rules);
     }
 
+    private boolean areAllTypesIndexed() {
+        IndexingRule ntBaseRule = getApplicableIndexingRule(NT_BASE);
+        return ntBaseRule != null;
+    }
+
     public class IndexingRule {
         private final String baseNodeType;
         private final String nodeTypeName;
@@ -453,9 +442,8 @@ class IndexDefinition {
         final boolean fulltextEnabled;
         final boolean propertyIndexEnabled;
 
-        //TODO To be relooked with support for aggregation
-        final Map<String,RelativeProperty> relativeProps;
-        final Set<String> relativePropNames;
+        final Aggregate aggregate;
+        final Aggregate propAggregate;
 
 
         IndexingRule(String nodeTypeName, NodeState config) {
@@ -469,14 +457,14 @@ class IndexDefinition {
             this.propertyTypes = getSupportedTypes(config, INCLUDE_PROPERTY_TYPES, TYPES_ALLOW_ALL);
 
             List<NamePattern> namePatterns = newArrayList();
-            Map<String,RelativeProperty> relativeProps = newHashMap();
-            this.propConfigs = collectPropConfigs(config, namePatterns, relativeProps);
+            List<Aggregate.Include> propIncludes = newArrayList();
+            this.propConfigs = collectPropConfigs(config, namePatterns, propIncludes);
+            this.propAggregate = new Aggregate(nodeTypeName, propIncludes);
+            this.aggregate = combine(propAggregate, nodeTypeName);
 
             this.namePatterns = ImmutableList.copyOf(namePatterns);
             this.fulltextEnabled = hasAnyFullTextEnabledProperty();
             this.propertyIndexEnabled = hasAnyPropertyIndexConfigured();
-            this.relativeProps = ImmutableMap.copyOf(relativeProps);
-            this.relativePropNames = collectRelPropertyNames(this.relativeProps.values());
         }
 
         /**
@@ -497,9 +485,9 @@ class IndexDefinition {
             this.inherited = original.inherited;
             this.propertyTypes = original.propertyTypes;
             this.fulltextEnabled = original.fulltextEnabled;
-            this.relativeProps = original.relativeProps;
-            this.relativePropNames = original.relativePropNames;
             this.propertyIndexEnabled = original.propertyIndexEnabled;
+            this.propAggregate = original.propAggregate;
+            this.aggregate = combine(propAggregate, nodeTypeName);
         }
 
         /**
@@ -533,6 +521,10 @@ class IndexDefinition {
             return str;
         }
 
+        public boolean isAggregated(String nodePath) {
+            return aggregate.hasRelativeNodeInclude(nodePath);
+        }
+
         /**
          * Returns <code>true</code> if this rule applies to the given node
          * <code>state</code>.
@@ -590,12 +582,12 @@ class IndexDefinition {
            return IndexDefinition.includePropertyType(propertyTypes, type);
         }
 
-        public Collection<RelativeProperty> getRelativeProps() {
-            return relativeProps.values();
+        public Aggregate getAggregate() {
+            return aggregate;
         }
 
         private Map<String, PropertyDefinition> collectPropConfigs(NodeState config, List<NamePattern> patterns,
-                                                                   Map<String,RelativeProperty> relativeProps) {
+                                                                   List<Aggregate.Include> propAggregate) {
             Map<String, PropertyDefinition> propDefns = newHashMap();
             NodeState propNode = config.getChildNode(LuceneIndexConstants.PROP_NODE);
 
@@ -621,8 +613,8 @@ class IndexDefinition {
                         propDefns.put(pd.name, pd);
                     }
 
-                    if (RelativeProperty.isRelativeProperty(pd.name)){
-                        relativeProps.put(pd.name, new RelativeProperty(pd.name, pd));
+                    if (isRelativeProperty(pd.name)){
+                        propAggregate.add(new Aggregate.PropertyInclude(pd));
                     }
                 }
             }
@@ -658,6 +650,16 @@ class IndexDefinition {
             }
             return false;
         }
+
+        private Aggregate combine(Aggregate propAggregate, String nodeTypeName){
+            Aggregate nodeTypeAgg = IndexDefinition.this.getAggregate(nodeTypeName);
+            List<Aggregate.Include> includes = newArrayList();
+            includes.addAll(propAggregate.getIncludes());
+            if (nodeTypeAgg != null){
+                includes.addAll(nodeTypeAgg.getIncludes());
+            }
+            return new Aggregate(nodeTypeName, includes);
+        }
     }
 
     /**
@@ -772,7 +774,7 @@ class IndexDefinition {
                 String propNodeName = propName;
 
                 //For proper propName use the propName as childNode name
-                if(RelativeProperty.isRelativeProperty(propName)
+                if(isRelativeProperty(propName)
                         || propName.equals(includeAllProp)){
                     propNodeName = "prop" + i++;
                 }
@@ -834,17 +836,31 @@ class IndexDefinition {
     private static NodeState getPropDefnNode(NodeState defn, String propName){
         NodeState propNode = defn.getChildNode(LuceneIndexConstants.PROP_NODE);
         NodeState propDefNode;
-        if (RelativeProperty.isRelativeProperty(propName)) {
-            propDefNode = new RelativeProperty(propName).getPropDefnNode(propNode);
+        if (isRelativeProperty(propName)) {
+            NodeState result = propNode;
+            for (String name : PathUtils.elements(propName)) {
+                result = result.getChildNode(name);
+            }
+            propDefNode = result;
         } else {
             propDefNode = propNode.getChildNode(propName);
         }
         return propDefNode.exists() ? propDefNode : null;
     }
 
-
     //~---------------------------------------------< utility >
 
+    private Codec createCodec() {
+        String codecName = getOptionalValue(definition, LuceneIndexConstants.CODEC_NAME, null);
+        Codec codec = null;
+        if (codecName != null) {
+            codec = Codec.forName(codecName);
+        } else if (fullTextEnabled) {
+            codec = new OakCodec();
+        }
+        return codec;
+    }
+
     private static String determineIndexName(NodeState defn, String indexPath) {
         String indexName = defn.getString(PROP_NAME);
         if (indexName ==  null){
@@ -967,22 +983,6 @@ class IndexDefinition {
         return false;
     }
 
-    private static Set<String> collectRelPropertyNames(Collection<RelativeProperty> props) {
-        Set<String> propNames = newHashSet();
-        for (RelativeProperty prop : props) {
-            propNames.add(prop.name);
-        }
-        return ImmutableSet.copyOf(propNames);
-    }
-
-    private static List<RelativeProperty> collectRelativeProperties(List<IndexingRule> indexRules) {
-        List<RelativeProperty> relProps = newArrayList();
-        for (IndexingRule rule : indexRules){
-            relProps.addAll(rule.getRelativeProps());
-        }
-        return ImmutableList.copyOf(relProps);
-    }
-
     private static void markAsNtUnstructured(NodeBuilder nb){
         nb.setProperty(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED, Type.NAME);
     }
@@ -1060,4 +1060,8 @@ class IndexDefinition {
     private static boolean hasIndexingRules(NodeState defn) {
         return defn.getChildNode(LuceneIndexConstants.INDEX_RULES).exists();
     }
+
+    private static boolean isRelativeProperty(String propertyName){
+        return !isAbsolute(propertyName) && PathUtils.getNextSlash(propertyName, 0) > 0;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexPlanner.java Wed Nov 26 08:06:52 2014
@@ -20,26 +20,37 @@
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.annotation.CheckForNull;
 
+import com.google.common.collect.Iterables;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.IndexingRule;
 import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextTerm;
+import org.apache.jackrabbit.oak.query.fulltext.FullTextVisitor;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.lucene.index.IndexReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static com.google.common.collect.Lists.newArrayListWithCapacity;
 import static com.google.common.collect.Maps.newHashMap;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getAncestorPath;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getDepth;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
 import static org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction;
 import static org.apache.jackrabbit.oak.spi.query.QueryIndex.IndexPlan;
 import static org.apache.jackrabbit.oak.spi.query.QueryIndex.OrderEntry;
 
 class IndexPlanner {
+    private static final Logger log = LoggerFactory.getLogger(IndexPlanner.class);
     private final IndexDefinition defn;
     private final Filter filter;
     private final String indexPath;
@@ -88,9 +99,11 @@ class IndexPlanner {
     }
 
     private IndexPlan.Builder getPlanBuilder() {
+        log.debug("Evaluating plan with index definition {}", defn);
         FullTextExpression ft = filter.getFullTextConstraint();
 
         if (!defn.getVersion().isAtLeast(IndexFormatVersion.V2)){
+            log.debug("Index is old format. Not supported");
             return null;
         }
 
@@ -133,8 +146,11 @@ class IndexPlanner {
         }
 
         boolean evalPathRestrictions = canEvalPathRestrictions();
-        //TODO For the full text case need to determine if all field names
-        //used in fulltext expression are fulltext indexed or not
+        boolean canEvalAlFullText = canEvalAllFullText(indexingRule, ft);
+
+        if (ft != null && !canEvalAlFullText){
+            return null;
+        }
 
         //Fulltext expression can also be like jcr:contains(jcr:content/metadata/@format, 'image')
 
@@ -157,6 +173,10 @@ class IndexPlanner {
                 costPerEntryFactor = 1;
             }
 
+            if (ft == null){
+                result.enableNonFullTextConstraints();
+            }
+
             return plan.setCostPerEntry(1.0 / costPerEntryFactor);
         }
 
@@ -170,6 +190,71 @@ class IndexPlanner {
         return null;
     }
 
+    private boolean canEvalAllFullText(final IndexingRule indexingRule, FullTextExpression ft) {
+        if (ft == null){
+            return false;
+        }
+
+        final HashSet<String> relPaths = new HashSet<String>();
+        final HashSet<String> nonIndexedPaths = new HashSet<String>();
+        final AtomicBoolean relativeParentsFound = new AtomicBoolean();
+        ft.accept(new FullTextVisitor.FullTextVisitorBase() {
+            @Override
+            public boolean visit(FullTextTerm term) {
+                String p = term.getPropertyName();
+                String propertyPath = null;
+                String nodePath = null;
+                if (p == null) {
+                    relPaths.add("");
+                } else if (p.startsWith("../") || p.startsWith("./")) {
+                    relPaths.add(p);
+                    relativeParentsFound.set(true);
+                } else if (getDepth(p) > 1) {
+                    String parent = getParentPath(p);
+                    if (LucenePropertyIndex.isNodePath(p)){
+                        nodePath = parent;
+                    } else {
+                        propertyPath = p;
+                    }
+                    relPaths.add(parent);
+                } else {
+                    propertyPath = p;
+                    relPaths.add("");
+                }
+
+                if (nodePath != null
+                        && !indexingRule.isAggregated(nodePath)){
+                    nonIndexedPaths.add(p);
+                } else if (propertyPath != null
+                        && !indexingRule.isIndexed(propertyPath)){
+                    nonIndexedPaths.add(p);
+                }
+
+                return true;
+            }
+        });
+
+        if (relativeParentsFound.get()){
+            log.debug("Relative parents found {} which are not supported", relPaths);
+            return false;
+        }
+
+        if (!nonIndexedPaths.isEmpty()){
+            if (relPaths.size() > 1){
+                log.debug("Following relative  property paths are not index", relPaths);
+                return false;
+            }
+            result.setParentPath(Iterables.getOnlyElement(relPaths, ""));
+            //Such path translation would only work if index contains
+            //all the nodes
+            return defn.indexesAllTypes();
+        } else {
+            result.setParentPath("");
+        }
+
+        return true;
+    }
+
     private boolean canEvalPathRestrictions() {
         if (filter.getPathRestriction() == Filter.PathRestriction.NO_RESTRICTION){
             return false;
@@ -250,6 +335,7 @@ class IndexPlanner {
                     }
                 }
             }
+            log.debug("No applicable IndexingRule found for any of the superTypes {}", filter.getSupertypes());
         }
         return null;
     }
@@ -275,6 +361,11 @@ class IndexPlanner {
         private List<PropertyDefinition> sortedProperties = newArrayList();
         private Map<String, PropertyDefinition> propDefns = newHashMap();
 
+        private boolean nonFullTextConstraints;
+        private int parentDepth;
+        private String parentPathSegment;
+        private boolean relativize;
+
         public PlanResult(String indexPath, IndexDefinition defn, IndexingRule indexingRule) {
             this.indexPath = indexPath;
             this.indexDefinition = defn;
@@ -288,5 +379,51 @@ class IndexPlanner {
         public PropertyDefinition getOrderedProperty(int index){
             return sortedProperties.get(index);
         }
+
+        public boolean isPathTransformed(){
+            return relativize;
+        }
+
+        /**
+         * Transforms the given path if the query involved relative properties and index
+         * is not making use of aggregated properties. If the path
+         *
+         * @param path path to transform
+         * @return transformed path. Returns null if the path does not confirm to relative
+         * path requirements
+         */
+        @CheckForNull
+        public String transformPath(String path){
+            if (isPathTransformed()){
+                // get the base path
+                // ensure the path ends with the given
+                // relative path
+                if (!path.endsWith(parentPathSegment)) {
+                    return null;
+                }
+                return getAncestorPath(path, parentDepth);
+            }
+            return path;
+        }
+
+        public boolean evaluateNonFullTextConstraints(){
+            return nonFullTextConstraints;
+        }
+
+        private void setParentPath(String relativePath){
+            parentPathSegment = "/" + relativePath;
+            if (relativePath.isEmpty()){
+                // we only restrict non-full-text conditions if there is
+                // no relative property in the full-text constraint
+                enableNonFullTextConstraints();
+            } else {
+                relativize = true;
+                parentDepth = getDepth(relativePath);
+            }
+        }
+
+        private void enableNonFullTextConstraints(){
+            nonFullTextConstraints = true;
+        }
     }
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java Wed Nov 26 08:06:52 2014
@@ -154,4 +154,28 @@ public interface LuceneIndexConstants {
      * Regex to allow inclusion of all immediate properties of the node
      */
     String REGEX_ALL_PROPS = "^[^\\/]*$";
+
+    /**
+     * Node name storing the aggregate rules
+     */
+    String AGGREGATES = "aggregates";
+
+    String AGG_PRIMARY_TYPE = "primaryType";
+
+    /**
+     * Name of property which stores the aggregate include pattern like <code>jcr:content/metadata</code>
+     */
+    String AGG_PATH = "path";
+
+    /**
+     * Limit for maximum number of reaggregates allowed. For example if there is an aggregate of nt:folder
+     * and it also includes nt:folder then aggregation would traverse down untill this limit is hit
+     */
+    String AGG_RECURSIVE_LIMIT = "reaggregateLimit";
+
+    /**
+     * Boolean property indicating that separate fulltext field should be created for
+     * node represented by this pattern
+     */
+    String AGG_RELATIVE_NODE = "relativeNode";
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java Wed Nov 26 08:06:52 2014
@@ -23,21 +23,29 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldFactory.newPathField;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldFactory.newPropertyField;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.TermFactory.newPathTerm;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.util.ConfigUtil.getPrimaryTypeName;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
+import com.google.common.collect.Sets;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 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.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback;
+import org.apache.jackrabbit.oak.plugins.index.lucene.Aggregate.Matcher;
 import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
 import org.apache.jackrabbit.oak.plugins.tree.ImmutableTree;
 import org.apache.jackrabbit.oak.spi.commit.Editor;
@@ -67,7 +75,7 @@ import org.slf4j.LoggerFactory;
  * 
  * @see LuceneIndex
  */
-public class LuceneIndexEditor implements IndexEditor {
+public class LuceneIndexEditor implements IndexEditor, Aggregate.AggregateRoot {
 
     private static final Logger log =
             LoggerFactory.getLogger(LuceneIndexEditor.class);
@@ -87,19 +95,19 @@ public class LuceneIndexEditor implement
 
     private final NodeState root;
 
-    private List<RelativeProperty> changedRelativeProps;
-
     /**
      * Flag indicating if the current tree being traversed has a deleted parent.
      */
     private final boolean isDeleted;
 
-    private final int deletedMaxLevels;
-
     private ImmutableTree current;
 
     private IndexDefinition.IndexingRule indexingRule;
 
+    private List<Matcher> currentMatchers = Collections.emptyList();
+
+    private final MatcherState matcherState;
+
     LuceneIndexEditor(NodeState root, NodeBuilder definition, Analyzer analyzer,
         IndexUpdateCallback updateCallback) throws CommitFailedException {
         this.parent = null;
@@ -109,18 +117,19 @@ public class LuceneIndexEditor implement
                 updateCallback);
         this.root = root;
         this.isDeleted = false;
-        this.deletedMaxLevels = -1;
+        this.matcherState = MatcherState.NONE;
     }
 
     private LuceneIndexEditor(LuceneIndexEditor parent, String name,
-            boolean isDeleted, int deletedMaxLevels) {
+                              MatcherState matcherState,
+            boolean isDeleted) {
         this.parent = parent;
         this.name = name;
         this.path = null;
         this.context = parent.context;
         this.root = parent.root;
         this.isDeleted = isDeleted;
-        this.deletedMaxLevels = deletedMaxLevels;
+        this.matcherState = matcherState;
     }
 
     public String getPath() {
@@ -147,6 +156,10 @@ public class LuceneIndexEditor implement
         }
 
         indexingRule = getDefinition().getApplicableIndexingRule(current);
+
+        if (indexingRule != null) {
+            currentMatchers = indexingRule.getAggregate().createMatchers(this);
+        }
     }
 
     @Override
@@ -162,8 +175,8 @@ public class LuceneIndexEditor implement
             }
         }
 
-        if (changedRelativeProps != null) {
-            markParentsOnRelPropChange();
+        for (Matcher m : matcherState.affectedMatchers){
+            m.markRootDirty();
         }
 
         if (parent == null) {
@@ -182,30 +195,30 @@ public class LuceneIndexEditor implement
     @Override
     public void propertyAdded(PropertyState after) {
         markPropertyChanged(after.getName());
-        checkForRelativePropertyChange(after.getName());
+        checkAggregates(after.getName());
     }
 
     @Override
     public void propertyChanged(PropertyState before, PropertyState after) {
         markPropertyChanged(before.getName());
-        checkForRelativePropertyChange(before.getName());
+        checkAggregates(before.getName());
     }
 
     @Override
     public void propertyDeleted(PropertyState before) {
         markPropertyChanged(before.getName());
-        checkForRelativePropertyChange(before.getName());
+        checkAggregates(before.getName());
     }
 
     @Override
     public Editor childNodeAdded(String name, NodeState after) {
-        return new LuceneIndexEditor(this, name, false, -1);
+        return new LuceneIndexEditor(this, name, getMatcherState(name, after), false);
     }
 
     @Override
     public Editor childNodeChanged(
             String name, NodeState before, NodeState after) {
-        return new LuceneIndexEditor(this, name, false, -1);
+        return new LuceneIndexEditor(this, name, getMatcherState(name, after), false);
     }
 
     @Override
@@ -228,18 +241,9 @@ public class LuceneIndexEditor implement
             }
         }
 
-        if (getDefinition().hasRelativeProperties()) {
-            int maxLevelsDown;
-            if (isDeleted) {
-                maxLevelsDown = deletedMaxLevels - 1;
-            } else {
-                maxLevelsDown = getDefinition()
-                        .getRelPropertyMaxLevels();
-            }
-            if (maxLevelsDown > 0) {
-                // need to update aggregated properties on deletes
-                return new LuceneIndexEditor(this, name, true, maxLevelsDown);
-            }
+        MatcherState ms = getMatcherState(name, before);
+        if (!ms.isEmpty()){
+            return new LuceneIndexEditor(this, name, ms, true);
         }
         return null; // no need to recurse down the removed subtree
     }
@@ -286,10 +290,10 @@ public class LuceneIndexEditor implement
                 dirty |= addTypedOrderedFields(fields, property, pname, pd);
             }
 
-            dirty |= indexProperty(path, fields, state, property, pname, pd);
+            dirty |= indexProperty(path, fields, state, property, pname, false, pd);
         }
 
-        dirty |= indexRelativeProperties(path, fields, state);
+        dirty |= indexAggregates(path, fields, state);
 
         if (isUpdate && !dirty) {
             // updated the state but had no relevant changes
@@ -320,15 +324,17 @@ public class LuceneIndexEditor implement
     }
 
     private boolean indexProperty(String path,
-                                  List<Field> fields, NodeState state,
+                                  List<Field> fields,
+                                  NodeState state,
                                   PropertyState property,
                                   String pname,
+                                  boolean aggregateMode,
                                   PropertyDefinition pd) throws CommitFailedException {
         boolean includeTypeForFullText = indexingRule.includePropertyType(property.getType().tag());
         if (Type.BINARY.tag() == property.getType().tag()
                 && includeTypeForFullText) {
             this.context.indexUpdate();
-            fields.addAll(newBinary(property, state, path + "@" + pname));
+            fields.addAll(newBinary(property, state, null, path + "@" + pname));
             return true;
         }  else {
             boolean dirty = false;
@@ -345,7 +351,7 @@ public class LuceneIndexEditor implement
                         fields.add(newPropertyField(analyzedPropName, value, !pd.skipTokenization(pname), pd.stored));
                     }
 
-                    if (pd.nodeScopeIndex) {
+                    if (pd.nodeScopeIndex && !aggregateMode) {
                         Field field = newFulltextField(value);
                         field.setBoost(pd.boost);
                         fields.add(field);
@@ -448,7 +454,7 @@ public class LuceneIndexEditor implement
     }
 
     private List<Field> newBinary(
-            PropertyState property, NodeState state, String path) {
+            PropertyState property, NodeState state, String nodePath, String path) {
         List<Field> fields = new ArrayList<Field>();
         Metadata metadata = new Metadata();
         if (JCR_DATA.equals(property.getName())) {
@@ -463,59 +469,180 @@ public class LuceneIndexEditor implement
         }
 
         for (Blob v : property.getValue(Type.BINARIES)) {
-            fields.add(newFulltextField(parseStringValue(v, metadata, path)));
+            if (nodePath != null){
+                fields.add(newFulltextField(nodePath, parseStringValue(v, metadata, path)));
+            } else {
+                fields.add(newFulltextField(parseStringValue(v, metadata, path)));
+            }
+
         }
         return fields;
     }
 
-    private boolean indexRelativeProperties(String path, List<Field> fields, NodeState state) throws CommitFailedException {
+    //~-------------------------------------------------------< Aggregate >
+
+    @Override
+    public void markDirty() {
+        propertiesChanged = true;
+    }
+
+    private MatcherState getMatcherState(String name, NodeState after) {
+        List<Matcher> matched = Lists.newArrayList();
+        List<Matcher> inherited = Lists.newArrayList();
+        for (Matcher m : Iterables.concat(matcherState.inherited, currentMatchers)) {
+            Matcher result = m.match(name, after);
+            if (result.getStatus() == Matcher.Status.MATCH_FOUND){
+                matched.add(result);
+            }
+
+            if (result.getStatus() != Matcher.Status.FAIL){
+                inherited.addAll(result.nextSet());
+            }
+        }
+
+        if (!matched.isEmpty() || !inherited.isEmpty()) {
+            return new MatcherState(matched, inherited);
+        }
+        return MatcherState.NONE;
+    }
+
+    private boolean indexAggregates(final String path, final List<Field> fields,
+                                    final NodeState state) throws CommitFailedException {
+        final AtomicBoolean dirtyFlag = new AtomicBoolean();
+        indexingRule.getAggregate().collectAggregates(state, new Aggregate.ResultCollector() {
+            @Override
+            public void onResult(Aggregate.NodeIncludeResult result) throws CommitFailedException {
+                boolean dirty = indexAggregatedNode(path, fields, result);
+                if (dirty) {
+                    dirtyFlag.set(true);
+                }
+            }
+
+            @Override
+            public void onResult(Aggregate.PropertyIncludeResult result) throws CommitFailedException {
+                boolean dirty = false;
+                if (result.pd.ordered) {
+                    dirty |= addTypedOrderedFields(fields, result.propertyState,
+                            result.propertyPath, result.pd);
+                }
+                dirty |= indexProperty(path, fields, state, result.propertyState,
+                        result.propertyPath, true, result.pd);
+
+                if (dirty) {
+                    dirtyFlag.set(true);
+                }
+            }
+        });
+        return dirtyFlag.get();
+    }
+
+    /**
+     * Create the fulltext field from the aggregated nodes. If result is for aggregate for a relative node
+     * include then
+     * @param path current node path
+     * @param fields indexed fields
+     * @param result aggregate result
+     * @return true if a field was created for passed node result
+     * @throws CommitFailedException
+     */
+    private boolean indexAggregatedNode(String path, List<Field> fields, Aggregate.NodeIncludeResult result)
+            throws CommitFailedException {
+        //rule for node being aggregated might be null if such nodes
+        //are not indexed on there own. In such cases we rely in current
+        //rule for some checks
+        IndexDefinition.IndexingRule ruleAggNode = context.getDefinition()
+                .getApplicableIndexingRule(getPrimaryTypeName(result.nodeState));
         boolean dirty = false;
-        for (RelativeProperty rp : indexingRule.getRelativeProps()){
-            String pname = rp.propertyPath;
 
-            PropertyState property = rp.getProperty(state);
+        for (PropertyState property : result.nodeState.getProperties()){
+            String pname = property.getName();
 
-            if (property == null){
+            if (!isVisible(pname)) {
                 continue;
             }
 
-            if (rp.getPropertyDefinition().ordered) {
-                dirty |= addTypedOrderedFields(fields, property, pname, rp.getPropertyDefinition());
+            //Check if type is indexed
+            int type = property.getType().tag();
+            if (ruleAggNode != null ) {
+                if (!ruleAggNode.includePropertyType(type)) {
+                    continue;
+                }
+            } else if (!indexingRule.includePropertyType(type)){
+                continue;
             }
 
-            dirty |= indexProperty(path, fields, state, property, pname, rp.getPropertyDefinition());
+            if (Type.BINARY == property.getType()) {
+                String aggreagtedNodePath = PathUtils.concat(path, result.nodePath);
+                this.context.indexUpdate();
+                //Here the fulltext is being created for aggregate root hence nodePath passed
+                //should be null
+                String nodePath = result.isRelativeNode() ? result.rootIncludePath : null;
+                fields.addAll(newBinary(property, result.nodeState, nodePath, aggreagtedNodePath + "@" + pname));
+                dirty = true;
+            } else {
+                PropertyDefinition pd = null;
+                if (ruleAggNode != null){
+                    pd = ruleAggNode.getConfig(pname);
+                }
+
+                if (pd != null && !pd.nodeScopeIndex){
+                    continue;
+                }
+
+                for (String value : property.getValue(Type.STRINGS)) {
+                    this.context.indexUpdate();
+                    Field field = result.isRelativeNode() ?
+                            newFulltextField(result.rootIncludePath, value) : newFulltextField(value) ;
+                    if (pd != null) {
+                        field.setBoost(pd.boost);
+                    }
+                    fields.add(field);
+                    dirty = true;
+                }
+            }
         }
         return dirty;
     }
 
-    private void checkForRelativePropertyChange(String name) {
-        if (isIndexable() && getDefinition().hasRelativeProperty(name)) {
-            getDefinition().collectRelPropsForName(name, getChangedRelProps());
+    /**
+     * Determines which all matchers are affected by this property change
+     *
+     * @param name modified property name
+     */
+    private void checkAggregates(String name) {
+        for (Matcher m : matcherState.matched) {
+            if (!matcherState.affectedMatchers.contains(m)
+                    && m.aggregatesProperty(name)) {
+                matcherState.affectedMatchers.add(m);
+            }
         }
     }
 
-    private void markParentsOnRelPropChange() {
-        for (RelativeProperty rp : changedRelativeProps) {
-            LuceneIndexEditor p = this;
-            for (String parentName : rp.ancestors) {
-                if (p == null || !p.name.equals(parentName)) {
-                    p = null;
-                    break;
-                }
-                p = p.parent;
-            }
+    private static class MatcherState {
+        final static MatcherState NONE = new MatcherState(Collections.<Matcher>emptyList(),
+                Collections.<Matcher>emptyList());
+
+        final List<Matcher> matched;
+        final List<Matcher> inherited;
+        final Set<Matcher> affectedMatchers;
 
-            if (p != null) {
-                p.relativePropertyChanged();
+        public MatcherState(List<Matcher> matched,
+                            List<Matcher> inherited){
+            this.matched = matched;
+            this.inherited = inherited;
+
+            //Affected matches would only be used when there are
+            //some matched matchers
+            if (matched.isEmpty()){
+                affectedMatchers = Collections.emptySet();
+            } else {
+                affectedMatchers = Sets.newIdentityHashSet();
             }
         }
-    }
 
-    private List<RelativeProperty> getChangedRelProps(){
-        if (changedRelativeProps == null) {
-            changedRelativeProps = Lists.newArrayList();
+        public boolean isEmpty() {
+            return matched.isEmpty() && inherited.isEmpty();
         }
-        return changedRelativeProps;
     }
 
     private void markPropertyChanged(String name) {
@@ -534,10 +661,6 @@ public class LuceneIndexEditor implement
         return indexingRule != null;
     }
 
-    private void relativePropertyChanged() {
-        propertiesChanged = true;
-    }
-
     private String parseStringValue(Blob v, Metadata metadata, String path) {
         WriteOutContentHandler handler = new WriteOutContentHandler();
         try {

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java Wed Nov 26 08:06:52 2014
@@ -22,9 +22,7 @@ import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Deque;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -101,8 +99,6 @@ import static org.apache.jackrabbit.JcrC
 import static org.apache.jackrabbit.oak.api.Type.LONG;
 import static org.apache.jackrabbit.oak.api.Type.STRING;
 import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot;
-import static org.apache.jackrabbit.oak.commons.PathUtils.getAncestorPath;
-import static org.apache.jackrabbit.oak.commons.PathUtils.getDepth;
 import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.FieldNames.PATH;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.VERSION;
@@ -223,30 +219,19 @@ public class LucenePropertyIndex impleme
         checkState(index != null, "The Lucene index is not available");
         try {
             FullTextExpression ft = filter.getFullTextConstraint();
-            Set<String> relPaths = getRelativePaths(ft);
-            if (relPaths.size() > 1) {
-                return new MultiLuceneIndex(filter, root, relPaths).getPlan();
-            }
-            String parent = relPaths.size() == 0 ? "" : relPaths.iterator().next();
-            // we only restrict non-full-text conditions if there is
-            // no relative property in the full-text constraint
-            boolean nonFullTextConstraints = parent.isEmpty();
             StringBuilder sb = new StringBuilder("lucene:");
             String path = pr(plan).indexPath;
             sb.append(getIndexName(plan))
                     .append("(")
                     .append(path)
                     .append(") ");
-            sb.append(getQuery(plan, null, nonFullTextConstraints, analyzer));
+            sb.append(getQuery(plan, null, analyzer));
             if(plan.getSortOrder() != null && !plan.getSortOrder().isEmpty()){
                 sb.append(" ordering:").append(plan.getSortOrder());
             }
             if (ft != null) {
                 sb.append(" ft:(").append(ft).append(")");
             }
-            if (!parent.isEmpty()) {
-                sb.append(" parent:").append(parent);
-            }
             return sb.toString();
         } finally {
             index.release();
@@ -262,17 +247,7 @@ public class LucenePropertyIndex impleme
     public Cursor query(final IndexPlan plan, NodeState rootState) {
         final Filter filter = plan.getFilter();
         final Sort sort = getSort(plan);
-        FullTextExpression ft = filter.getFullTextConstraint();
-        Set<String> relPaths = getRelativePaths(ft);
-        if (relPaths.size() > 1) {
-            return new MultiLuceneIndex(filter, rootState, relPaths).query();
-        }
-
-        final String parent = relPaths.size() == 0 ? "" : relPaths.iterator().next();
-        // we only restrict non-full-text conditions if there is
-        // no relative property in the full-text constraint
-        final boolean nonFullTextConstraints = parent.isEmpty();
-        final int parentDepth = getDepth(parent);
+        final PlanResult pr = pr(plan);
         QueryEngineSettings settings = filter.getQueryEngineSettings();
         Iterator<LuceneResultRow> itr = new AbstractIterator<LuceneResultRow>() {
             private final Deque<LuceneResultRow> queue = Queues.newArrayDeque();
@@ -299,17 +274,10 @@ public class LucenePropertyIndex impleme
                     if ("".equals(path)) {
                         path = "/";
                     }
-                    if (!parent.isEmpty()) {
-                        // TODO OAK-828 this breaks node aggregation
-                        // get the base path
-                        // ensure the path ends with the given
-                        // relative path
-                        // if (!path.endsWith("/" + parent)) {
-                        // continue;
-                        // }
-                        path = getAncestorPath(path, parentDepth);
+                    if (pr.isPathTransformed()) {
+                        path = pr.transformPath(path);
                         // avoid duplicate entries
-                        if (seenPaths.contains(path)) {
+                        if (path == null || seenPaths.contains(path)) {
                             return null;
                         }
                         seenPaths.add(path);
@@ -331,8 +299,7 @@ public class LucenePropertyIndex impleme
                 checkState(indexNode != null);
                 try {
                     IndexSearcher searcher = indexNode.getSearcher();
-                    Query query = getQuery(plan, searcher.getIndexReader(),
-                            nonFullTextConstraints, analyzer);
+                    Query query = getQuery(plan, searcher.getIndexReader(), analyzer);
                     TopDocs docs;
                     long time = System.currentTimeMillis();
                     if (lastDoc != null) {
@@ -382,6 +349,17 @@ public class LucenePropertyIndex impleme
         return null;
     }
 
+    /**
+     * In a fulltext term for jcr:contains(foo, 'bar') 'foo'
+     * is the property name. While in jcr:contains(foo/*, 'bar')
+     * 'foo' is node name
+     *
+     * @return true if the term is related to node
+     */
+    public static boolean isNodePath(String fulltextTermPath){
+        return fulltextTermPath.endsWith("/*");
+    }
+
     private IndexNode acquireIndexNode(IndexPlan plan) {
         return tracker.acquireIndexNode(pr(plan).indexPath);
     }
@@ -427,54 +405,6 @@ public class LucenePropertyIndex impleme
     }
 
     /**
-     * Get the set of relative paths of a full-text condition. For example, for
-     * the condition "contains(a/b, 'hello') and contains(c/d, 'world'), the set
-     * { "a", "c" } is returned. If there are no relative properties, then one
-     * entry is returned (the empty string). If there is no expression, then an
-     * empty set is returned.
-     *
-     * @param ft the full-text expression
-     * @return the set of relative paths (possibly empty)
-     */
-    private static Set<String> getRelativePaths(FullTextExpression ft) {
-        //TODO This would need to be relooked as with aggregation we would be
-        //aggregating child properties also
-        if (ft == null) {
-            // there might be no full-text constraint when using the
-            // LowCostLuceneIndexProvider which is used for testing
-            // TODO if the LowCostLuceneIndexProvider is removed, we should do
-            // the following instead:
-
-            // throw new
-            // IllegalStateException("Lucene index is used even when no full-text conditions are used for filter "
-            // + filter);
-
-            return Collections.emptySet();
-        }
-        final HashSet<String> relPaths = new HashSet<String>();
-        ft.accept(new FullTextVisitor.FullTextVisitorBase() {
-
-            @Override
-            public boolean visit(FullTextTerm term) {
-                String p = term.getPropertyName();
-                if (p == null) {
-                    relPaths.add("");
-                } else if (p.startsWith("../") || p.startsWith("./")) {
-                    throw new IllegalArgumentException("Relative parent is not supported:" + p);
-                } else if (getDepth(p) > 1) {
-                    String parent = getParentPath(p);
-                    relPaths.add(parent);
-                } else {
-                    relPaths.add("");
-                }
-                return true;
-            }
-        });
-        return relPaths;
-    }
-
-
-    /**
      * Get the Lucene query for the given filter.
      *
      * @param plan index plan containing filter details
@@ -486,8 +416,7 @@ public class LucenePropertyIndex impleme
      * @param defn nodestate that contains the index definition
      * @return the Lucene query
      */
-    private static Query getQuery(IndexPlan plan, IndexReader reader,
-            boolean nonFullTextConstraints, Analyzer analyzer) {
+    private static Query getQuery(IndexPlan plan, IndexReader reader, Analyzer analyzer) {
         List<Query> qs = new ArrayList<Query>();
         Filter filter = plan.getFilter();
         FullTextExpression ft = filter.getFullTextConstraint();
@@ -498,7 +427,7 @@ public class LucenePropertyIndex impleme
             // when using the LowCostLuceneIndexProvider
             // which is used for testing
         } else {
-            qs.add(getFullTextQuery(defn, ft, analyzer, reader));
+            qs.add(getFullTextQuery(plan, ft, analyzer, reader));
         }
 
 
@@ -527,7 +456,7 @@ public class LucenePropertyIndex impleme
                     throw new RuntimeException(e);
                 }
             }
-        } else if (nonFullTextConstraints) {
+        } else if (planResult.evaluateNonFullTextConstraints()) {
             addNonFullTextConstraints(qs, plan, reader);
         }
 
@@ -872,8 +801,9 @@ public class LucenePropertyIndex impleme
         }
     }
 
-    static Query getFullTextQuery(final IndexDefinition defn, FullTextExpression ft,
+    static Query getFullTextQuery(final IndexPlan plan, FullTextExpression ft,
                                   final Analyzer analyzer, final IndexReader reader) {
+        final PlanResult pr = pr(plan);
         // a reference to the query, so it can be set in the visitor
         // (a "non-local return")
         final AtomicReference<Query> result = new AtomicReference<Query>();
@@ -883,7 +813,7 @@ public class LucenePropertyIndex impleme
             public boolean visit(FullTextOr or) {
                 BooleanQuery q = new BooleanQuery();
                 for (FullTextExpression e : or.list) {
-                    Query x = getFullTextQuery(defn, e, analyzer, reader);
+                    Query x = getFullTextQuery(plan, e, analyzer, reader);
                     q.add(x, SHOULD);
                 }
                 result.set(q);
@@ -894,7 +824,7 @@ public class LucenePropertyIndex impleme
             public boolean visit(FullTextAnd and) {
                 BooleanQuery q = new BooleanQuery();
                 for (FullTextExpression e : and.list) {
-                    Query x = getFullTextQuery(defn, e, analyzer, reader);
+                    Query x = getFullTextQuery(plan, e, analyzer, reader);
                     // Lucene can't deal with "must(must_not(x))"
                     if (x instanceof BooleanQuery) {
                         BooleanQuery bq = (BooleanQuery) x;
@@ -913,7 +843,7 @@ public class LucenePropertyIndex impleme
             public boolean visit(FullTextTerm term) {
                 String p = term.getPropertyName();
                 if (p != null) {
-                    p = FieldNames.createAnalyzedFieldName(p);
+                    p = getLuceneFieldName(p, pr);
                 }
                 Query q = tokenToQuery(term.getText(), p, analyzer, reader);
                 if (q == null) {
@@ -936,6 +866,24 @@ public class LucenePropertyIndex impleme
         return result.get();
     }
 
+    static String getLuceneFieldName(String p, PlanResult pr) {
+        if (isNodePath(p)){
+            if (pr.isPathTransformed()){
+                p = PathUtils.getName(p);
+            } else {
+                //Get rid of /* as aggregated fulltext field name is the
+                //node relative path
+                p = FieldNames.createFulltextFieldName(PathUtils.getParentPath(p));
+            }
+        } else {
+            if (pr.isPathTransformed()){
+                p = PathUtils.getName(p);
+            }
+            p = FieldNames.createAnalyzedFieldName(p);
+        }
+        return p;
+    }
+
     static Query tokenToQuery(String text, String fieldName, Analyzer analyzer, IndexReader reader) {
         if (analyzer == null) {
             return null;

Modified: jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/ConfigUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/ConfigUtil.java?rev=1641771&r1=1641770&r2=1641771&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/ConfigUtil.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/ConfigUtil.java Wed Nov 26 08:06:52 2014
@@ -20,6 +20,7 @@
 package org.apache.jackrabbit.oak.plugins.index.lucene.util;
 
 import com.google.common.primitives.Ints;
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -45,4 +46,9 @@ public class ConfigUtil {
         PropertyState ps = definition.getProperty(propName);
         return ps == null ? defaultVal : ps.getValue(Type.DOUBLE).floatValue();
     }
+
+    public static String getPrimaryTypeName(NodeState nodeState) {
+        PropertyState ps = nodeState.getProperty(JcrConstants.JCR_PRIMARYTYPE);
+        return (ps == null) ? JcrConstants.NT_BASE : ps.getValue(Type.NAME);
+    }
 }