Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java	(revision 1513924)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java	(working copy)
@@ -18,6 +18,9 @@
  */
 package org.apache.jackrabbit.oak.spi.query;
 
+import javax.annotation.CheckForNull;
+
+import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 /**
@@ -111,6 +114,14 @@
      */
     public interface FulltextQueryIndex extends QueryIndex {
         // marker interface
+
+        /**
+         * Returns the NodeAggregator responsible for providing the aggregation
+         * settings or null if aggregation is not available/desired
+         * 
+         */
+        @CheckForNull
+        NodeAggregator getNodeAggregator();
     }
 
 //    /**
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java	(revision 1513924)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java	(working copy)
@@ -16,54 +16,162 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.aggregate;
 
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.oak.query.index.IndexRowImpl;
 import org.apache.jackrabbit.oak.spi.query.Cursor;
+import org.apache.jackrabbit.oak.spi.query.Cursors;
+import org.apache.jackrabbit.oak.spi.query.Cursors.AbstractCursor;
 import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.query.IndexRow;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex.FulltextQueryIndex;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+
 /**
  * A virtual full-text that can aggregate nodes based on aggregate definitions.
  * Internally, it uses another full-text index.
  */
 public class AggregateIndex implements FulltextQueryIndex {
-    
+
     private final FulltextQueryIndex baseIndex;
-    
+
     public AggregateIndex(FulltextQueryIndex baseIndex) {
         this.baseIndex = baseIndex;
     }
 
     @Override
     public double getCost(Filter filter, NodeState rootState) {
-        // TODO dummy implementation
         if (baseIndex == null) {
             return Double.POSITIVE_INFINITY;
         }
+        // pass-through impl
+        if (baseIndex.getNodeAggregator() == null) {
+            return baseIndex.getCost(filter, rootState);
+        }
+        // TODO dummy implementation
         return baseIndex.getCost(filter, rootState);
     }
 
     @Override
     public Cursor query(Filter filter, NodeState rootState) {
-        // TODO dummy implementation
-        return baseIndex.query(filter, rootState);
+        if (baseIndex == null) {
+            return Cursors.newPathCursor(new ArrayList<String>());
+        }
+        // pass-through impl
+        if (baseIndex.getNodeAggregator() == null) {
+            return baseIndex.query(filter, rootState);
+        }
+        return new AggregationCursor(baseIndex.query(
+                newAggregationFilter(filter), rootState),
+                baseIndex.getNodeAggregator(), rootState);
     }
 
+    private static Filter newAggregationFilter(Filter filter) {
+        if (filter instanceof FilterImpl) {
+            FilterImpl impl = (FilterImpl) filter;
+            // for now only keep the fulltext constraints
+            FilterImpl f = new FilterImpl(impl.getSelector(),
+                    impl.getQueryStatement(), impl.getRootTree());
+            f.setFullTextConstraint(impl.getFullTextConstraint());
+            // disables node type checks for now
+            f.setMatchesAllTypes(true);
+            return f;
+        }
+
+        return filter;
+    }
+
     @Override
     public String getPlan(Filter filter, NodeState rootState) {
-        // TODO dummy implementation
         if (baseIndex == null) {
-            return "no plan";
+            return "aggregate no-index";
         }
+        // pass-through impl
+        if (baseIndex.getNodeAggregator() == null) {
+            return baseIndex.getPlan(filter, rootState);
+        }
         return "aggregate " + baseIndex.getPlan(filter, rootState);
     }
 
     @Override
     public String getIndexName() {
-        // TODO dummy implementation
         if (baseIndex == null) {
-            return "aggregat";
+            return "aggregate no-index";
         }
-        return "aggregate." + baseIndex.getIndexName();
+        // pass-through impl
+        if (baseIndex.getNodeAggregator() == null) {
+            return baseIndex.getIndexName();
+        }
+        return "aggregate " + baseIndex.getIndexName();
     }
 
+    @Override
+    public NodeAggregator getNodeAggregator() {
+        return baseIndex.getNodeAggregator();
+    }
+
+    // ----- aggregation aware cursor
+
+    private static class AggregationCursor extends AbstractCursor {
+
+        private final Cursor cursor;
+        private final NodeAggregator aggregator;
+        private final NodeState rootState;
+
+        private boolean init;
+        private boolean closed;
+        private Iterator<String> aggregates = null;
+
+        private String item = null;
+
+        public AggregationCursor(Cursor cursor, NodeAggregator aggregator,
+                NodeState rootState) {
+            this.cursor = cursor;
+            this.aggregator = aggregator;
+            this.rootState = rootState;
+        }
+
+        @Override
+        public boolean hasNext() {
+            if (!closed && !init) {
+                fetchNext();
+                init = true;
+            }
+            return !closed;
+        }
+
+        private void fetchNext() {
+            if (aggregates != null && aggregates.hasNext()) {
+                item = aggregates.next();
+                init = true;
+                return;
+            }
+            aggregates = null;
+            if (cursor.hasNext()) {
+                IndexRow row = cursor.next();
+                String path = row.getPath();
+                aggregates = Iterators.concat(ImmutableSet.of(path).iterator(),
+                        aggregator.getParents(rootState, path));
+                fetchNext();
+                return;
+            }
+            closed = true;
+        }
+
+        @Override
+        public IndexRow next() {
+            if (hasNext() == false) {
+                throw new NoSuchElementException();
+            }
+            init = false;
+            return new IndexRowImpl(item);
+        }
+    }
+
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregator.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregator.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregator.java	(revision 0)
@@ -0,0 +1,140 @@
+/*
+ * 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.aggregate;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.commons.PathUtils.elements;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getDepth;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getName;
+import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * List based NodeAggregator
+ * 
+ */
+public class SimpleNodeAggregator implements NodeAggregator {
+
+    public static final String INCLUDE_ALL = "*";
+
+    private final List<ChildNameRule> aggregates = new ArrayList<ChildNameRule>();
+
+    @Override
+    public Iterator<String> getParents(NodeState root, String path) {
+        return getParents(root, path, true);
+    }
+
+    private Iterator<String> getParents(NodeState root, String path,
+            boolean acceptStarIncludes) {
+
+        int levelsUp = 0;
+        String primaryType = null;
+        for (ChildNameRule r : aggregates) {
+            String name = getName(path);
+            for (String inc : r.includes) {
+                // check node name match
+                if (name.equals(getName(inc))) {
+                    levelsUp = 1;
+                    primaryType = r.primaryType;
+                    break;
+                }
+                if (acceptStarIncludes) {
+                    // check '*' rule, which could span over more than one level
+                    if (INCLUDE_ALL.equals(getName(inc))) {
+                        // basic approach to dealing with a '*' include
+                        levelsUp = Math.max(getDepth(inc), levelsUp);
+                        primaryType = r.primaryType;
+                    }
+                }
+            }
+        }
+        if (levelsUp > 0 && primaryType != null) {
+            List<String> parents = new ArrayList<String>();
+            levelsUp = Math.min(levelsUp, getDepth(path));
+            String parentPath = path;
+            for (int i = 0; i < levelsUp; i++) {
+                parentPath = getParentPath(parentPath);
+                if (isNodeType(root, parentPath, primaryType)) {
+                    parents.add(parentPath);
+                    parents.addAll(newArrayList(getParents(root, parentPath,
+                            false)));
+                    return parents.iterator();
+                }
+            }
+        }
+
+        return new ArrayList<String>().iterator();
+    }
+
+    private static boolean isNodeType(NodeState root, String path, String type) {
+        NodeState state = root;
+        for (String p : elements(path)) {
+            if (state.hasChildNode(p)) {
+                state = state.getChildNode(p);
+            } else {
+                return false;
+            }
+        }
+        PropertyState ps = state.getProperty(JCR_PRIMARYTYPE);
+        if (ps == null) {
+            return false;
+        }
+        return type.equals(ps.getValue(STRING));
+    }
+
+    // ----- builder methods
+
+    /**
+     * Include children with the provided name. '*' means include all children
+     * 
+     * Note: there is no support for property names yet
+     * 
+     */
+    public SimpleNodeAggregator newRuleWithName(String primaryType,
+            List<String> includes) {
+        aggregates.add(new ChildNameRule(primaryType, includes));
+        return this;
+    }
+
+    // ----- aggregation rules
+
+    private static interface Rule {
+
+    }
+
+    private static class ChildNameRule implements Rule {
+
+        private final String primaryType;
+        private final List<String> includes;
+
+        ChildNameRule(String primaryType, List<String> includes) {
+            this.primaryType = primaryType;
+            this.includes = includes;
+        }
+    }
+
+}

Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregator.java
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision Rev URL
Added: svn:eol-style
   + native

Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/NodeAggregator.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/NodeAggregator.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/NodeAggregator.java	(revision 0)
@@ -0,0 +1,29 @@
+/*
+ * 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.aggregate;
+
+import java.util.Iterator;
+
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+public interface NodeAggregator {
+
+    Iterator<String> getParents(NodeState rootState, String path);
+
+}

Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/NodeAggregator.java
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision Rev URL
Added: svn:eol-style
   + native

Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java	(revision 1513924)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java	(working copy)
@@ -55,7 +55,14 @@
      */
     private boolean alwaysFalse;
 
+
     /**
+     * inherited from the selector, duplicated here so it can be over-written by
+     * other filters
+     */
+    private boolean matchesAllTypes;
+
+    /**
      *  The path, or "/" (the root node, meaning no filter) if not set.
      */
     private String path = "/";
@@ -98,6 +105,8 @@
         this.selector = selector;
         this.queryStatement = queryStatement;
         this.rootTree = rootTree;
+        this.matchesAllTypes = selector != null ? selector.matchesAllTypes()
+                : false;
     }
     
     public void setPreparing(boolean preparing) {
@@ -158,7 +167,7 @@
 
     @Override
     public boolean matchesAllTypes() {
-        return selector.matchesAllTypes();
+        return matchesAllTypes;
     }
 
     @Override @Nonnull
@@ -481,4 +490,8 @@
         return queryStatement;
     }
 
+    public void setMatchesAllTypes(boolean matchesAllTypes) {
+        this.matchesAllTypes = matchesAllTypes;
+    }
+
 }
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregatorTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregatorTest.java	(revision 0)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregatorTest.java	(revision 0)
@@ -0,0 +1,194 @@
+/*
+ * 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.aggregate;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_FILE;
+import static org.apache.jackrabbit.JcrConstants.NT_FOLDER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.apache.jackrabbit.oak.plugins.index.aggregate.SimpleNodeAggregator.INCLUDE_ALL;
+
+import java.util.List;
+
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Test;
+
+public class SimpleNodeAggregatorTest {
+
+    @Test
+    public void testNodeName() {
+
+        NodeState root = new MemoryNodeStore().getRoot();
+        NodeBuilder builder = root.builder();
+
+        NodeBuilder file = builder.child("file");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE);
+        file.child(JCR_CONTENT);
+
+        SimpleNodeAggregator agg = new SimpleNodeAggregator().newRuleWithName(
+                NT_FILE, newArrayList(JCR_CONTENT));
+
+        String path = "/file/jcr:content";
+        List<String> actual = newArrayList(agg.getParents(
+                builder.getNodeState(), path));
+
+        assertEquals(newArrayList("/file"), actual);
+
+    }
+
+    @Test
+    public void testNodeNameWrongParentType() {
+
+        NodeState root = new MemoryNodeStore().getRoot();
+        NodeBuilder builder = root.builder();
+
+        NodeBuilder file = builder.child("file");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE + "_");
+        file.child(JCR_CONTENT);
+
+        SimpleNodeAggregator agg = new SimpleNodeAggregator().newRuleWithName(
+                NT_FILE, newArrayList(JCR_CONTENT));
+
+        String path = "/file/jcr:content";
+        List<String> actual = newArrayList(agg.getParents(
+                builder.getNodeState(), path));
+
+        assertTrue(actual.isEmpty());
+
+    }
+
+    @Test
+    public void testStarName() {
+
+        NodeState root = new MemoryNodeStore().getRoot();
+        NodeBuilder builder = root.builder();
+
+        NodeBuilder file = builder.child("file");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE);
+        file.child(JCR_CONTENT);
+
+        SimpleNodeAggregator agg = new SimpleNodeAggregator().newRuleWithName(
+                NT_FILE, newArrayList(INCLUDE_ALL));
+
+        String path = "/file/jcr:content";
+        List<String> actual = newArrayList(agg.getParents(
+                builder.getNodeState(), path));
+
+        assertEquals(newArrayList("/file"), actual);
+
+    }
+
+    @Test
+    public void testStarNameMoreLevels() {
+
+        NodeState root = new MemoryNodeStore().getRoot();
+        NodeBuilder builder = root.builder();
+
+        NodeBuilder file = builder.child("file");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE);
+        file.child(JCR_CONTENT);
+
+        SimpleNodeAggregator agg = new SimpleNodeAggregator().newRuleWithName(
+                NT_FILE, newArrayList("*", "*/*", "*/*/*", "*/*/*/*"));
+
+        String path = "/file/jcr:content";
+        List<String> actual = newArrayList(agg.getParents(
+                builder.getNodeState(), path));
+
+        assertEquals(newArrayList("/file"), actual);
+
+    }
+
+    @Test
+    public void testStarNameWrongParentType() {
+
+        NodeState root = new MemoryNodeStore().getRoot();
+        NodeBuilder builder = root.builder();
+
+        NodeBuilder file = builder.child("file");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE + "_");
+        file.child(JCR_CONTENT);
+
+        SimpleNodeAggregator agg = new SimpleNodeAggregator().newRuleWithName(
+                NT_FILE, newArrayList(INCLUDE_ALL));
+
+        String path = "/file/jcr:content";
+        List<String> actual = newArrayList(agg.getParents(
+                builder.getNodeState(), path));
+
+        assertTrue(actual.isEmpty());
+
+    }
+
+    @Test
+    public void testCascadingStarName() {
+
+        NodeState root = new MemoryNodeStore().getRoot();
+        NodeBuilder builder = root.builder();
+
+        NodeBuilder folder = builder.child("folder");
+        folder.setProperty(JCR_PRIMARYTYPE, NT_FOLDER);
+
+        NodeBuilder file = folder.child("file");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE);
+        file.child(JCR_CONTENT);
+
+        SimpleNodeAggregator agg = new SimpleNodeAggregator().newRuleWithName(
+                NT_FOLDER, newArrayList("file")).newRuleWithName(NT_FILE,
+                newArrayList(INCLUDE_ALL));
+
+        String path = "/folder/file/jcr:content";
+        List<String> actual = newArrayList(agg.getParents(
+                builder.getNodeState(), path));
+
+        assertEquals(newArrayList("/folder/file", "/folder"), actual);
+
+    }
+
+    @Test
+    public void testCascadingNodeName() {
+
+        NodeState root = new MemoryNodeStore().getRoot();
+        NodeBuilder builder = root.builder();
+
+        NodeBuilder folder = builder.child("folder");
+        folder.setProperty(JCR_PRIMARYTYPE, NT_FOLDER);
+
+        NodeBuilder file = folder.child("file");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE);
+        file.child(JCR_CONTENT);
+
+        SimpleNodeAggregator agg = new SimpleNodeAggregator().newRuleWithName(
+                NT_FOLDER, newArrayList("file")).newRuleWithName(NT_FILE,
+                newArrayList(JCR_CONTENT));
+
+        String path = "/folder/file/jcr:content";
+        List<String> actual = newArrayList(agg.getParents(
+                builder.getNodeState(), path));
+
+        assertEquals(newArrayList("/folder/file", "/folder"), actual);
+
+    }
+
+}

Property changes on: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/aggregate/SimpleNodeAggregatorTest.java
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision Rev URL
Added: svn:eol-style
   + native

Index: oak-core/pom.xml
===================================================================
--- oak-core/pom.xml	(revision 1513924)
+++ oak-core/pom.xml	(working copy)
@@ -51,6 +51,7 @@
               org.apache.jackrabbit.oak.plugins.value,
               org.apache.jackrabbit.oak.plugins.commit,
               org.apache.jackrabbit.oak.plugins.index,
+              org.apache.jackrabbit.oak.plugins.index.aggregate,
               org.apache.jackrabbit.oak.plugins.index.nodetype,
               org.apache.jackrabbit.oak.plugins.index.property,
               org.apache.jackrabbit.oak.plugins.memory,
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java	(revision 1513924)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java	(working copy)
@@ -49,6 +49,7 @@
 
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
 import org.apache.jackrabbit.oak.query.fulltext.FullTextAnd;
 import org.apache.jackrabbit.oak.query.fulltext.FullTextExpression;
 import org.apache.jackrabbit.oak.query.fulltext.FullTextOr;
@@ -130,8 +131,11 @@
 
     private final Analyzer analyzer;
 
-    public LuceneIndex(Analyzer analyzer) {
+    private final NodeAggregator aggregator;
+
+    public LuceneIndex(Analyzer analyzer, NodeAggregator aggregator) {
         this.analyzer = analyzer;
+        this.aggregator = aggregator;
     }
 
     @Override
@@ -789,4 +793,9 @@
         return out;
     }
 
+    @Override
+    public NodeAggregator getNodeAggregator() {
+        return aggregator;
+    }
+
 }
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/aggregation/NodeAggregator.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/aggregation/NodeAggregator.java	(revision 1513924)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/aggregation/NodeAggregator.java	(working copy)
@@ -1,44 +0,0 @@
-/*
- * 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.aggregation;
-
-import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
-import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
-import static org.apache.jackrabbit.JcrConstants.NT_FILE;
-import static org.apache.jackrabbit.oak.plugins.index.IndexUtils.getString;
-
-import java.util.List;
-
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-
-import com.google.common.collect.ImmutableList;
-
-public class NodeAggregator {
-
-    public List<AggregatedState> getAggregates(NodeState state) {
-        // FIXME remove hardcoded aggregates
-        String type = getString(state, JCR_PRIMARYTYPE);
-
-        if (NT_FILE.equals(type)) {
-            // include jcr:content node
-            return ImmutableList.of(new AggregatedState(JCR_CONTENT, state
-                    .getChildNode(JCR_CONTENT), null));
-        }
-
-        return ImmutableList.of();
-    }
-}
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/aggregation/AggregatedState.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/aggregation/AggregatedState.java	(revision 1513924)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/aggregation/AggregatedState.java	(working copy)
@@ -1,89 +0,0 @@
-/*
- * 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.aggregation;
-
-import static org.apache.jackrabbit.JcrConstants.JCR_PATH;
-import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-
-import com.google.common.collect.ImmutableSet;
-
-public class AggregatedState {
-
-    private final String name;
-
-    private final NodeState state;
-
-    private final Set<String> included;
-
-    private static final Set<String> blacklist = ImmutableSet.of(JCR_PATH,
-            JCR_UUID);
-
-    public AggregatedState(String name, NodeState state, Set<String> included) {
-        this.name = name;
-        this.state = state;
-        if (included == null) {
-            this.included = ImmutableSet.of();
-        } else {
-            this.included = included;
-        }
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public NodeState getState() {
-        return state;
-    }
-
-    private boolean include(String name) {
-        if (included.contains(name)) {
-            return true;
-        }
-        if (blacklist.contains(name)) {
-            return false;
-        }
-        return included.isEmpty();
-    }
-
-    public Iterable<? extends PropertyState> getProperties() {
-        if (included == null || included.isEmpty()) {
-            return state.getProperties();
-        }
-
-        List<PropertyState> props = new ArrayList<PropertyState>();
-        for (PropertyState property : state.getProperties()) {
-            String pname = property.getName();
-            if (include(pname)) {
-                props.add(property);
-            }
-        }
-        return props;
-    }
-
-    public NodeState get() {
-        return state;
-    }
-
-}
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java	(revision 1513924)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java	(working copy)
@@ -34,7 +34,6 @@
 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.lucene.aggregation.AggregatedState;
 import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -193,22 +192,6 @@
             }
         }
 
-        for (AggregatedState agg : context.getAggregator().getAggregates(state)) {
-            for (PropertyState property : agg.getProperties()) {
-                String pname = property.getName();
-                if (isVisible(pname)
-                        && (context.getPropertyTypes() & (1 << property.getType().tag())) != 0) {
-                    if (Type.BINARY.tag() == property.getType().tag()) {
-                        addBinaryValue(document, property, agg.get());
-                    } else {
-                        for (String v : property.getValue(Type.STRINGS)) {
-                            document.add(newFulltextField(v));
-                        }
-                    }
-                }
-            }
-        }
-
         return document;
     }
 
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java	(revision 1513924)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProvider.java	(working copy)
@@ -22,6 +22,7 @@
 
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex;
 import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -45,6 +46,8 @@
      */
     protected Analyzer analyzer = ANALYZER;
 
+    protected NodeAggregator aggregator = null;
+
     @Override
     @Nonnull
     public List<QueryIndex> getQueryIndexes(NodeState nodeState) {
@@ -52,7 +55,7 @@
     }
 
     protected LuceneIndex newLuceneIndex() {
-        return new LuceneIndex(analyzer);
+        return new LuceneIndex(analyzer, aggregator);
     }
 
     /**
@@ -62,6 +65,13 @@
         this.analyzer = analyzer;
     }
 
+    /**
+     * sets the default node aggregator that will be used at query time
+     */
+    public void setAggregator(NodeAggregator aggregator) {
+        this.aggregator = aggregator;
+    }
+
     // ----- helper builder method
 
     public LuceneIndexProvider with(Analyzer analyzer) {
@@ -69,4 +79,9 @@
         return this;
     }
 
+    public LuceneIndexProvider with(NodeAggregator analyzer) {
+        this.setAggregator(analyzer);
+        return this;
+    }
+
 }
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java	(revision 1513924)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java	(working copy)
@@ -30,7 +30,6 @@
 
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.index.lucene.aggregation.NodeAggregator;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.index.IndexWriter;
@@ -86,8 +85,6 @@
         }
     }
 
-    private static final NodeAggregator aggregator = new NodeAggregator();
-
     private final IndexWriterConfig config;
 
     private static final Parser parser = new AutoDetectParser();
@@ -125,10 +122,6 @@
         return propertyTypes;
     }
 
-    NodeAggregator getAggregator() {
-        return aggregator;
-    }
-
     Parser getParser() {
         return parser;
     }
Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LowCostLuceneIndexProvider.java
===================================================================
--- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LowCostLuceneIndexProvider.java	(revision 1513924)
+++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LowCostLuceneIndexProvider.java	(working copy)
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
+import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.lucene.analysis.Analyzer;
@@ -29,20 +30,13 @@
 
     @Override
     protected LuceneIndex newLuceneIndex() {
-        return new LowCostLuceneIndex(analyzer);
+        return new LowCostLuceneIndex(analyzer, aggregator);
     }
 
-    // ----- helper builder method
-
-    public LowCostLuceneIndexProvider with(Analyzer analyzer) {
-        this.setAnalyzer(analyzer);
-        return this;
-    }
-
     private static class LowCostLuceneIndex extends LuceneIndex {
 
-        public LowCostLuceneIndex(Analyzer analyzer) {
-            super(analyzer);
+        public LowCostLuceneIndex(Analyzer analyzer, NodeAggregator aggregator) {
+            super(analyzer, aggregator);
         }
 
         @Override
Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java
===================================================================
--- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java	(revision 0)
+++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java	(revision 0)
@@ -0,0 +1,190 @@
+/*
+ * 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 static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.JcrConstants.JCR_CONTENT;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_FILE;
+import static org.apache.jackrabbit.JcrConstants.JCR_DATA;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.aggregate.AggregateIndexProvider;
+import org.apache.jackrabbit.oak.plugins.index.aggregate.NodeAggregator;
+import org.apache.jackrabbit.oak.plugins.index.aggregate.SimpleNodeAggregator;
+import static org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState.binaryProperty;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class LuceneIndexAggregationTest extends AbstractQueryTest {
+
+    @Override
+    protected void createTestIndexNode() throws Exception {
+        Tree index = root.getTree("/");
+        createTestIndexNode(index, LuceneIndexConstants.TYPE_LUCENE);
+        root.commit();
+    }
+
+    @Override
+    protected ContentRepository createRepository() {
+        return new Oak()
+                .with(new InitialContent())
+                .with(new OpenSecurityProvider())
+                .with(AggregateIndexProvider
+                        .wrap(new LowCostLuceneIndexProvider()
+                                .with(getNodeAggregator())))
+                .with(new LuceneIndexEditorProvider())
+                .createContentRepository();
+    }
+
+    /**
+     * <code>
+     * <aggregate primaryType="nt:file"> 
+     *   <include>jcr:content</include>
+     *   <include>jcr:content/*</include>
+     *   <include-property>jcr:content/jcr:lastModified</include-property>
+     * </aggregate>
+     * <code>
+     * 
+     */
+    private NodeAggregator getNodeAggregator() {
+        return new SimpleNodeAggregator().newRuleWithName(NT_FILE,
+                newArrayList(JCR_CONTENT, JCR_CONTENT + "/*"));
+    }
+
+    /**
+     * simple index aggregation from jcr:content to nt:file
+     * 
+     */
+    @Test
+    public void testNtFileAggregate() throws Exception {
+
+        String sqlBase = "SELECT * FROM [nt:file] as f WHERE";
+        String sqlCat = sqlBase + " CONTAINS (f.*, 'cat')";
+        String sqlDog = sqlBase + " CONTAINS (f.*, 'dog')";
+
+        Tree file = root.getTree("/").addChild("myFile");
+        file.setProperty(JCR_PRIMARYTYPE, NT_FILE, Type.NAME);
+        Tree resource = file.addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy dog."));
+        root.commit();
+        assertQuery(sqlDog, ImmutableList.of("/myFile"));
+
+        // update jcr:data
+        root.getTree("/")
+                .getChild("myFile")
+                .getChild(JCR_CONTENT)
+                .setProperty(
+                        binaryProperty(JCR_DATA,
+                                "the quick brown fox jumps over the lazy cat."));
+        root.commit();
+        assertQuery(sqlDog, new ArrayList<String>());
+        assertQuery(sqlCat, ImmutableList.of("/myFile"));
+
+        // replace jcr:content with unstructured
+        root.getTree("/").getChild("myFile").getChild(JCR_CONTENT).remove();
+
+        Tree unstrContent = root.getTree("/").getChild("myFile")
+                .addChild(JCR_CONTENT);
+        unstrContent.setProperty(JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED,
+                Type.NAME);
+
+        Tree foo = unstrContent.addChild("foo");
+        foo.setProperty(JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED,
+                Type.NAME);
+        foo.setProperty("text", "the quick brown fox jumps over the lazy dog.");
+        root.commit();
+
+        assertQuery(sqlDog, ImmutableList.of("/myFile"));
+        assertQuery(sqlCat, new ArrayList<String>());
+
+        // remove foo
+        root.getTree("/").getChild("myFile").getChild(JCR_CONTENT)
+                .getChild("foo").remove();
+
+        root.commit();
+        assertQuery(sqlDog, new ArrayList<String>());
+        assertQuery(sqlCat, new ArrayList<String>());
+
+        // replace jcr:content again with resource
+        root.getTree("/").getChild("myFile").getChild(JCR_CONTENT).remove();
+
+        resource = root.getTree("/").getChild("myFile").addChild(JCR_CONTENT);
+        resource.setProperty(JCR_PRIMARYTYPE, "nt:resource", Type.NAME);
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        resource.setProperty(binaryProperty(JCR_DATA,
+                "the quick brown fox jumps over the lazy cat."));
+        root.commit();
+        assertQuery(sqlDog, new ArrayList<String>());
+        assertQuery(sqlCat, ImmutableList.of("/myFile"));
+
+    }
+
+    // /**
+    // * JCR-3160 - Session#move doesn't trigger rebuild of parent node
+    // * aggregation
+    // */
+    // public void testAggregateMove() throws Exception {
+    //
+    // String sql = "SELECT * FROM [nt:folder] as f"
+    // + " WHERE ISDESCENDANTNODE([" + testRoot + "])"
+    // + " AND CONTAINS (f.*, 'dog')";
+    //
+    // Node folderA = testRootNode.addNode("folderA", "nt:folder");
+    // Node folderB = testRootNode.addNode("folderB", "nt:folder");
+    //
+    // ByteArrayOutputStream out = new ByteArrayOutputStream();
+    // Writer writer = new OutputStreamWriter(out, "UTF-8");
+    // writer.write("the quick brown fox jumps over the lazy dog.");
+    // writer.flush();
+    //
+    // Node file = folderA.addNode("myFile", "nt:file");
+    // Node resource = file.addNode("jcr:content", "nt:resource");
+    // resource.setProperty("jcr:lastModified", Calendar.getInstance());
+    // resource.setProperty("jcr:encoding", "UTF-8");
+    // resource.setProperty("jcr:mimeType", "text/plain");
+    // resource.setProperty("jcr:data", session.getValueFactory()
+    // .createBinary(new ByteArrayInputStream(out.toByteArray())));
+    //
+    // testRootNode.getSession().save();
+    // executeSQL2Query(sql, new Node[] { folderA });
+    //
+    // testRootNode.getSession().move(file.getPath(),
+    // folderB.getPath() + "/myFile");
+    // testRootNode.getSession().save();
+    // executeSQL2Query(sql, new Node[] { folderB });
+    // }
+
+}

Property changes on: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexAggregationTest.java
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision Rev URL
Added: svn:eol-style
   + native

Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java
===================================================================
--- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java	(revision 1513924)
+++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java	(working copy)
@@ -68,7 +68,7 @@
 
         NodeState indexed = HOOK.processCommit(before, after);
 
-        QueryIndex queryIndex = new LuceneIndex(analyzer);
+        QueryIndex queryIndex = new LuceneIndex(analyzer, null);
         FilterImpl filter = createFilter(NT_BASE);
         filter.restrictPath("/", Filter.PathRestriction.EXACT);
         filter.restrictProperty("foo", Operator.EQUAL,
@@ -94,7 +94,7 @@
 
         NodeState indexed = HOOK.processCommit(before, after);
 
-        QueryIndex queryIndex = new LuceneIndex(analyzer);
+        QueryIndex queryIndex = new LuceneIndex(analyzer, null);
         FilterImpl filter = createFilter(NT_BASE);
         // filter.restrictPath("/", Filter.PathRestriction.EXACT);
         filter.restrictProperty("foo", Operator.EQUAL,
@@ -125,7 +125,7 @@
 
         NodeState indexed = HOOK.processCommit(before, after);
 
-        QueryIndex queryIndex = new LuceneIndex(analyzer);
+        QueryIndex queryIndex = new LuceneIndex(analyzer, null);
         FilterImpl filter = createFilter(NT_BASE);
         // filter.restrictPath("/", Filter.PathRestriction.EXACT);
         filter.restrictProperty("foo", Operator.EQUAL,
