You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by al...@apache.org on 2011/10/12 15:53:10 UTC

svn commit: r1182367 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/query/lucene/ test/java/org/apache/jackrabbit/core/query/ test/java/org/apache/jackrabbit/core/query/lucene/

Author: alexparvulescu
Date: Wed Oct 12 13:53:09 2011
New Revision: 1182367

URL: http://svn.apache.org/viewvc?rev=1182367&view=rev
Log:
JCR-2989 Support for embedded index aggregates

Added:
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest2.java   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingQueueTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java?rev=1182367&r1=1182366&r2=1182367&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java Wed Oct 12 13:53:09 2011
@@ -43,6 +43,14 @@ public interface AggregateRule {
      */
     NodeState getAggregateRoot(NodeState nodeState)
             throws ItemStateException, RepositoryException;
+    
+    /**
+     * recursive aggregation (for same type nodes) limit. embedded aggregation
+     * of nodes that have the same type can go only this levels up.
+     * 
+     * A value eq to 0 gives unlimited aggregation.
+     */
+    long getRecursiveAggregationLimit();
 
     /**
      * Returns the node states that are part of the indexing aggregate of the

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java?rev=1182367&r1=1182366&r2=1182367&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java Wed Oct 12 13:53:09 2011
@@ -16,32 +16,32 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
-import org.apache.jackrabbit.spi.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.id.PropertyId;
+import org.apache.jackrabbit.core.state.ChildNodeEntry;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.PropertyState;
 import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.commons.name.NameConstants;
-import org.apache.jackrabbit.spi.commons.name.PathBuilder;
+import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
 import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
-import org.apache.jackrabbit.core.state.NodeState;
-import org.apache.jackrabbit.core.state.ItemStateManager;
-import org.apache.jackrabbit.core.state.ItemStateException;
-import org.apache.jackrabbit.core.state.ChildNodeEntry;
-import org.apache.jackrabbit.core.state.PropertyState;
-import org.apache.jackrabbit.core.HierarchyManager;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.id.PropertyId;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.PathBuilder;
 import org.apache.jackrabbit.util.Text;
+import org.w3c.dom.CharacterData;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
-import org.w3c.dom.CharacterData;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.NamespaceException;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Arrays;
 
 /**
  * <code>AggregateRule</code> defines a configuration for a node index
@@ -82,6 +82,30 @@ class AggregateRuleImpl implements Aggre
     private final HierarchyManager hmgr;
 
     /**
+     * recursive aggregation (for same type nodes) default value.
+     */
+    private static final boolean RECURSIVE_AGGREGATION_DEFAULT = false;
+
+    /**
+     * flag to enable recursive aggregation (for same type nodes).
+     */
+    private final boolean recursiveAggregation;
+
+    /**
+     * recursive aggregation (for same type nodes) limit default value.
+     */
+
+    protected static final long RECURSIVE_AGGREGATION_LIMIT_DEFAULT = 100;
+
+    /**
+     * recursive aggregation (for same type nodes) limit. embedded aggregation
+     * of nodes that have the same type can go only this levels up.
+     * 
+     * A value eq to 0 gives unlimited aggregation.
+     */
+    private final long recursiveAggregationLimit;
+
+    /**
      * Creates a new indexing aggregate using the given <code>config</code>.
      *
      * @param config     the configuration for this indexing aggregate.
@@ -107,6 +131,8 @@ class AggregateRuleImpl implements Aggre
         this.propertyIncludes = getPropertyIncludes(config);
         this.ism = ism;
         this.hmgr = hmgr;
+        this.recursiveAggregation = getRecursiveAggregation(config);
+        this.recursiveAggregationLimit = getRecursiveAggregationLimit(config);
     }
 
     /**
@@ -125,14 +151,21 @@ class AggregateRuleImpl implements Aggre
         for (NodeInclude nodeInclude : nodeIncludes) {
             NodeState aggregateRoot = nodeInclude.matches(nodeState);
             if (aggregateRoot != null && aggregateRoot.getNodeTypeName().equals(nodeTypeName)) {
-                return aggregateRoot;
+                boolean sameNodeTypeAsRoot = nodeState.getNodeTypeName().equals(aggregateRoot.getNodeTypeName());
+                if(!sameNodeTypeAsRoot || (sameNodeTypeAsRoot && recursiveAggregation)){
+                    return aggregateRoot;
+                }
             }
         }
+        
         // check property includes
         for (PropertyInclude propertyInclude : propertyIncludes) {
             NodeState aggregateRoot = propertyInclude.matches(nodeState);
             if (aggregateRoot != null && aggregateRoot.getNodeTypeName().equals(nodeTypeName)) {
-                return aggregateRoot;
+                boolean sameNodeTypeAsRoot = nodeState.getNodeTypeName().equals(aggregateRoot.getNodeTypeName());
+                if(!sameNodeTypeAsRoot || (sameNodeTypeAsRoot && recursiveAggregation)){
+                    return aggregateRoot;
+                }
             }
         }
         return null;
@@ -153,7 +186,12 @@ class AggregateRuleImpl implements Aggre
         if (nodeState.getNodeTypeName().equals(nodeTypeName)) {
             List<NodeState> nodeStates = new ArrayList<NodeState>();
             for (NodeInclude nodeInclude : nodeIncludes) {
-                nodeStates.addAll(Arrays.asList(nodeInclude.resolve(nodeState)));
+                for (NodeState childNs : nodeInclude.resolve(nodeState)) {
+                    boolean sameNodeTypeAsRoot = nodeState.getNodeTypeName().equals(childNs.getNodeTypeName());
+                    if (!sameNodeTypeAsRoot || (sameNodeTypeAsRoot && recursiveAggregation)) {
+                        nodeStates.add(childNs);
+                    }
+                }
             }
             if (nodeStates.size() > 0) {
                 return nodeStates.toArray(new NodeState[nodeStates.size()]);
@@ -179,6 +217,13 @@ class AggregateRuleImpl implements Aggre
         return null;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public long getRecursiveAggregationLimit() {
+        return recursiveAggregationLimit;
+    }
+
     //---------------------------< internal >-----------------------------------
 
     /**
@@ -269,7 +314,30 @@ class AggregateRuleImpl implements Aggre
         }
         return includes.toArray(new PropertyInclude[includes.size()]);
     }
-
+    
+    private boolean getRecursiveAggregation(Node config) {
+        Node rAttr = config.getAttributes().getNamedItem("recursive");
+        if (rAttr == null) {
+            return RECURSIVE_AGGREGATION_DEFAULT;
+        }
+        return Boolean.valueOf(rAttr.getNodeValue());
+    }
+
+    private long getRecursiveAggregationLimit(Node config)
+            throws RepositoryException {
+        Node rAttr = config.getAttributes().getNamedItem("recursiveLimit");
+        if (rAttr == null) {
+            return RECURSIVE_AGGREGATION_LIMIT_DEFAULT;
+        }
+        try {
+            return Long.valueOf(rAttr.getNodeValue());
+        } catch (NumberFormatException e) {
+            throw new RepositoryException(
+                    "Unable to read indexing configuration (recursiveLimit).",
+                    e);
+        }
+    }
+    
     //---------------------------< internal >-----------------------------------
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java?rev=1182367&r1=1182366&r2=1182367&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java Wed Oct 12 13:53:09 2011
@@ -1587,29 +1587,73 @@ public class SearchIndex extends Abstrac
 
     /**
      * Retrieves the root of the indexing aggregate for <code>state</code> and
-     * puts it into <code>map</code>.
+     * puts it into <code>aggregates</code>  map.
      *
      * @param state the node state for which we want to retrieve the aggregate
      *              root.
-     * @param map   aggregate roots are collected in this map.
+     * @param aggregates aggregate roots are collected in this map.
      */
-    protected void retrieveAggregateRoot(
-            NodeState state, Map<NodeId, NodeState> map) {
-        if (indexingConfig != null) {
-            AggregateRule[] aggregateRules = indexingConfig.getAggregateRules();
-            if (aggregateRules == null) {
-                return;
-            }
+    protected void retrieveAggregateRoot(NodeState state,
+            Map<NodeId, NodeState> aggregates) {
+        retrieveAggregateRoot(state, aggregates, state.getNodeId().toString(), 0);
+    }
+    
+    /**
+     * Retrieves the root of the indexing aggregate for <code>state</code> and
+     * puts it into <code>aggregates</code> map.
+     * 
+     * @param state
+     *            the node state for which we want to retrieve the aggregate
+     *            root.
+     * @param aggregates
+     *            aggregate roots are collected in this map.
+     * @param originNodeId
+     *            the originating node, used for reporting only
+     * @param level
+     *            current aggregation level, used to limit recursive aggregation
+     *            of nodes that have the same type
+     */
+    private void retrieveAggregateRoot(NodeState state,
+            Map<NodeId, NodeState> aggregates, String originNodeId, long level) {
+        if (indexingConfig == null) {
+            return;
+        }
+        AggregateRule[] aggregateRules = indexingConfig.getAggregateRules();
+        if (aggregateRules == null) {
+            return;
+        }
+        for (AggregateRule aggregateRule : aggregateRules) {
+            NodeState root = null;
             try {
-                for (AggregateRule aggregateRule : aggregateRules) {
-                    NodeState root = aggregateRule.getAggregateRoot(state);
-                    if (root != null) {
-                        map.put(root.getNodeId(), root);
-                    }
-                }
+                root = aggregateRule.getAggregateRoot(state);
             } catch (Exception e) {
-                log.warn("Unable to get aggregate root for "
-                        + state.getNodeId(), e);
+                log.warn("Unable to get aggregate root for " + state.getNodeId(), e);
+            }
+            if (root == null) {
+                continue;
+            }
+            if (root.getNodeTypeName().equals(state.getNodeTypeName())) {
+                level++;
+            } else {
+                level = 0;
+            }
+
+            // JCR-2989 Support for embedded index aggregates
+            if ((aggregateRule.getRecursiveAggregationLimit() == 0)
+                    || (aggregateRule.getRecursiveAggregationLimit() != 0 && level <= aggregateRule
+                            .getRecursiveAggregationLimit())) {
+
+                // check if the update parent is already in the
+                // map, then all its parents are already there so I can
+                // skip this update subtree
+                if (aggregates.put(root.getNodeId(), root) == null) {
+                    retrieveAggregateRoot(root, aggregates, originNodeId, level);
+                }
+            } else {
+                log.warn(
+                        "Reached {} levels of recursive aggregation for nodeId {}, type {}, will stop at nodeId {}. Are you sure this did not occur by mistake? Please check the indexing-configuration.xml.",
+                        new Object[] { level, originNodeId,
+                                root.getNodeTypeName(), root.getNodeId() });
             }
         }
     }
@@ -1618,11 +1662,11 @@ public class SearchIndex extends Abstrac
      * Retrieves the root of the indexing aggregate for <code>removedIds</code>
      * and puts it into <code>map</code>.
      *
-     * @param removedIds     the ids of removed nodes.
-     * @param map            aggregate roots are collected in this map
+     * @param removedIds the ids of removed nodes.
+     * @param aggregates aggregate roots are collected in this map
      */
     protected void retrieveAggregateRoot(
-            Set<NodeId> removedIds, Map<NodeId, NodeState> map) {
+            Set<NodeId> removedIds, Map<NodeId, NodeState> aggregates) {
         if(removedIds.isEmpty() || indexingConfig == null){
             return;
         }
@@ -1648,8 +1692,14 @@ public class SearchIndex extends Abstrac
                             Document doc = reader.document(
                                     tDocs.doc(), FieldSelectors.UUID);
                             NodeId nId = new NodeId(doc.get(FieldNames.UUID));
-                            map.put(nId, (NodeState) ism.getItemState(nId));
+                            NodeState nodeState = (NodeState) ism.getItemState(nId);
+                            aggregates.put(nId, nodeState);
                             found++;
+
+                            // JCR-2989 Support for embedded index aggregates
+                            int sizeBefore = aggregates.size();
+                            retrieveAggregateRoot(nodeState, aggregates);
+                            found += aggregates.size() - sizeBefore;
                         }
                     }
                 } finally {

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java?rev=1182367&r1=1182366&r2=1182367&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java Wed Oct 12 13:53:09 2011
@@ -16,8 +16,8 @@
  */
 package org.apache.jackrabbit.core.query;
 
-import javax.jcr.Session;
 import javax.jcr.Node;
+import javax.jcr.Session;
 
 /**
  * <code>AbstractIndexingTest</code> is a base class for all indexing
@@ -25,7 +25,7 @@ import javax.jcr.Node;
  */
 public class AbstractIndexingTest extends AbstractQueryTest {
 
-    protected static final String WORKSPACE_NAME = "indexing-test";
+    private static final String WORKSPACE_NAME = "indexing-test";
 
     protected Session session;
 
@@ -33,7 +33,7 @@ public class AbstractIndexingTest extend
 
     protected void setUp() throws Exception {
         super.setUp();
-        session = getHelper().getSuperuserSession(WORKSPACE_NAME);
+        session = getHelper().getSuperuserSession(getWorkspaceName());
         testRootNode = cleanUpTestRoot(session);
         // overwrite query manager
         qm = session.getWorkspace().getQueryManager();
@@ -48,4 +48,8 @@ public class AbstractIndexingTest extend
         testRootNode = null;
         super.tearDown();
     }
+
+    protected String getWorkspaceName() {
+        return WORKSPACE_NAME;
+    }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingQueueTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingQueueTest.java?rev=1182367&r1=1182366&r2=1182367&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingQueueTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingQueueTest.java Wed Oct 12 13:53:09 2011
@@ -86,7 +86,7 @@ public class IndexingQueueTest extends A
         session = null;
         superuser.logout();
         superuser = null;
-        TestHelper.shutdownWorkspace(WORKSPACE_NAME, repo);
+        TestHelper.shutdownWorkspace(getWorkspaceName(), repo);
 
         // delete index
         try {
@@ -101,7 +101,7 @@ public class IndexingQueueTest extends A
         Thread t = new Thread(new Runnable() {
             public void run() {
                 try {
-                    session = getHelper().getSuperuserSession(WORKSPACE_NAME);
+                    session = getHelper().getSuperuserSession(getWorkspaceName());
                 } catch (RepositoryException e) {
                     throw new RuntimeException(e);
                 }
@@ -142,7 +142,7 @@ public class IndexingQueueTest extends A
         session = null;
         superuser.logout();
         superuser = null;
-        TestHelper.shutdownWorkspace(WORKSPACE_NAME, repo);
+        TestHelper.shutdownWorkspace(getWorkspaceName(), repo);
 
         // delete index
         try {
@@ -153,7 +153,7 @@ public class IndexingQueueTest extends A
 
         BlockingParser.unblock();
         // start workspace again by getting a session
-        session = getHelper().getSuperuserSession(WORKSPACE_NAME);
+        session = getHelper().getSuperuserSession(getWorkspaceName());
 
         qm = session.getWorkspace().getQueryManager();
 

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java?rev=1182367&r1=1182366&r2=1182367&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java Wed Oct 12 13:53:09 2011
@@ -22,18 +22,23 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
+import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.List;
 
 import javax.jcr.Node;
 
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.io.input.NullInputStream;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.core.query.AbstractIndexingTest;
 
 /**
- * <code>IndexingAggregateTest</code> checks if the nt:file nt:resource
- * aggregate defined in workspace indexing-test works properly.
+ * <code>SQL2IndexingAggregateTest</code> checks if aggregation rules defined in
+ * workspace indexing-test work properly.
+ * 
+ * See src/test/repository/workspaces/indexing-test/indexing-configuration.xml
  */
 public class SQL2IndexingAggregateTest extends AbstractIndexingTest {
 
@@ -46,6 +51,112 @@ public class SQL2IndexingAggregateTest e
         testRootNode.getSession().save();
     }
 
+    /**
+     * 
+     * this test is very similar to
+     * {@link SQL2IndexingAggregateTest#testNtFileAggregate()
+     * testNtFileAggregate} but checks embedded index aggregates.
+     * 
+     * The aggregation hierarchy is defined in
+     * src/test/repository/workspaces/indexing-test/indexing-configuration.xml
+     * 
+     * basically a folder aggregates other folders and files that aggregate a
+     * stream of content.
+     * 
+     * see <a href="https://issues.apache.org/jira/browse/JCR-2989">JCR-2989</a>
+     * 
+     * nt:folder: recursive="true" recursiveLimit="10"
+     * 
+     */
+    @SuppressWarnings("unchecked")
+    public void testDeepHierarchy() throws Exception {
+
+        // this parameter IS the 'recursiveLimit' defined in the index
+        // config file
+        int definedRecursiveLimit = 10;
+        int levelsDeep = 14;
+
+        List<Node> allNodes = new ArrayList<Node>();
+        List<Node> updatedNodes = new ArrayList<Node>();
+        List<Node> staleNodes = new ArrayList<Node>();
+
+        String sqlBase = "SELECT * FROM [nt:folder] as f WHERE ";
+        String sqlCat = sqlBase + " CONTAINS (f.*, 'cat')";
+        String sqlDog = sqlBase + " CONTAINS (f.*, 'dog')";
+
+        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 folderRoot = testRootNode.addNode("myFolder", "nt:folder");
+        Node folderChild = folderRoot;
+        allNodes.add(folderChild);
+
+        for (int i = 0; i < levelsDeep; i++) {
+            folderChild.addNode("0" + i + "-dummy", "nt:folder");
+            folderChild = folderChild.addNode("0" + i, "nt:folder");
+            allNodes.add(folderChild);
+
+            // -2 because:
+            // 1 because 'i' starts at 0,
+            // +
+            // 1 because we are talking about same node type aggregation levels
+            // extra to the current node that is updated
+            if (i > levelsDeep - definedRecursiveLimit - 2) {
+                updatedNodes.add(folderChild);
+            }
+        }
+        staleNodes.addAll(CollectionUtils.disjunction(allNodes, updatedNodes));
+
+        Node file = folderChild.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();
+
+        // because of the optimizations, the first save is expected to update
+        // ALL nodes
+        // executeSQL2Query(sqlDog, allNodes.toArray(new Node[] {}));
+
+        // update jcr:data
+        out.reset();
+        writer.write("the quick brown fox jumps over the lazy cat.");
+        writer.flush();
+        resource.setProperty("jcr:data", session.getValueFactory()
+                .createBinary(new ByteArrayInputStream(out.toByteArray())));
+        testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, staleNodes.toArray(new Node[] {}));
+        executeSQL2Query(sqlCat, updatedNodes.toArray(new Node[] {}));
+
+        // replace jcr:content with unstructured
+        resource.remove();
+        Node unstrContent = file.addNode("jcr:content", "nt:unstructured");
+        Node foo = unstrContent.addNode("foo");
+        foo.setProperty("text", "the quick brown fox jumps over the lazy dog.");
+        testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, allNodes.toArray(new Node[] {}));
+        executeSQL2Query(sqlCat, new Node[] {});
+
+        // remove foo
+        foo.remove();
+        testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, staleNodes.toArray(new Node[] {}));
+        executeSQL2Query(sqlCat, new Node[] {});
+
+    }
+
+    /**
+     * simple index aggregation from jcr:content to nt:file
+     * 
+     * The aggregation hierarchy is defined in
+     * src/test/repository/workspaces/indexing-test/indexing-configuration.xml
+     * 
+     */
     public void testNtFileAggregate() throws Exception {
 
         String sqlBase = "SELECT * FROM [nt:file] as f"
@@ -75,6 +186,7 @@ public class SQL2IndexingAggregateTest e
         resource.setProperty("jcr:data", session.getValueFactory()
                 .createBinary(new ByteArrayInputStream(out.toByteArray())));
         testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, new Node[] {});
         executeSQL2Query(sqlCat, new Node[] { file });
 
         // replace jcr:content with unstructured
@@ -84,12 +196,13 @@ public class SQL2IndexingAggregateTest e
         foo.setProperty("text", "the quick brown fox jumps over the lazy dog.");
         testRootNode.getSession().save();
         executeSQL2Query(sqlDog, new Node[] { file });
+        executeSQL2Query(sqlCat, new Node[] {});
 
         // remove foo
         foo.remove();
         testRootNode.getSession().save();
-
         executeSQL2Query(sqlDog, new Node[] {});
+        executeSQL2Query(sqlCat, new Node[] {});
 
         // replace jcr:content again with resource
         unstrContent.remove();
@@ -100,11 +213,88 @@ public class SQL2IndexingAggregateTest e
         resource.setProperty("jcr:data", session.getValueFactory()
                 .createBinary(new ByteArrayInputStream(out.toByteArray())));
         testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, new Node[] {});
         executeSQL2Query(sqlCat, new Node[] { file });
 
     }
 
     /**
+     * By default, the recursive aggregation is turned off.
+     * 
+     * The aggregation hierarchy is defined in
+     * src/test/repository/workspaces/indexing-test/indexing-configuration.xml
+     */
+    public void testDefaultRecursiveAggregation() throws Exception {
+
+        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 parent = testRootNode.addNode(
+                "testDefaultRecursiveAggregation_parent",
+                JcrConstants.NT_UNSTRUCTURED);
+
+        Node child = parent.addNode("testDefaultRecursiveAggregation_child",
+                JcrConstants.NT_UNSTRUCTURED);
+        child.setProperty("type", "testnode");
+        child.setProperty("jcr:encoding", "UTF-8");
+        child.setProperty("jcr:mimeType", "text/plain");
+        child.setProperty(
+                "jcr:data",
+                session.getValueFactory().createBinary(
+                        new ByteArrayInputStream(out.toByteArray())));
+        testRootNode.getSession().save();
+
+        String sqlBase = "SELECT * FROM [nt:unstructured] as u WHERE CONTAINS (u.*, 'dog') ";
+        String sqlParent = sqlBase + " AND ISCHILDNODE([" + testRoot + "])";
+        String sqlChild = sqlBase + " AND ISCHILDNODE([" + parent.getPath()
+                + "])";
+
+        executeSQL2Query(sqlParent, new Node[] {});
+        executeSQL2Query(sqlChild, new Node[] { child });
+    }
+
+    /**
+     * Tests that even if there is a rule to include same type node aggregates
+     * by property name, the recursive flag will still ignore them.
+     * 
+     * It should issue a log warning, though.
+     * 
+     * The aggregation hierarchy is defined in
+     * src/test/repository/workspaces/indexing-test/indexing-configuration.xml
+     */
+    public void testRecursiveAggregationExclusion() throws Exception {
+
+        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 parent = testRootNode.addNode(
+                "testDefaultRecursiveAggregation_parent",
+                JcrConstants.NT_UNSTRUCTURED);
+
+        Node child = parent.addNode("aggregated-node",
+                JcrConstants.NT_UNSTRUCTURED);
+        child.setProperty("type", "testnode");
+        child.setProperty("jcr:encoding", "UTF-8");
+        child.setProperty("jcr:mimeType", "text/plain");
+        child.setProperty(
+                "jcr:data",
+                session.getValueFactory().createBinary(
+                        new ByteArrayInputStream(out.toByteArray())));
+        testRootNode.getSession().save();
+
+        String sqlBase = "SELECT * FROM [nt:unstructured] as u WHERE CONTAINS (u.*, 'dog') ";
+        String sqlParent = sqlBase + " AND ISCHILDNODE([" + testRoot + "])";
+        String sqlChild = sqlBase + " AND ISCHILDNODE([" + parent.getPath()
+                + "])";
+        executeSQL2Query(sqlParent, new Node[] {});
+        executeSQL2Query(sqlChild, new Node[] { child });
+    }
+
+    /**
      * checks that while text extraction runs in the background, the node is
      * available for query on any of its properties
      * 

Added: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest2.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest2.java?rev=1182367&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest2.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest2.java Wed Oct 12 13:53:09 2011
@@ -0,0 +1,162 @@
+/*
+ * 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.core.query.lucene;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import javax.jcr.Node;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
+
+/**
+ * <code>SQL2IndexingAggregateTest2</code> checks if aggregation rules defined
+ * in workspace indexing-test-2 work properly.
+ * 
+ * See src/test/repository/workspaces/indexing-test-2/indexing-configuration.xml
+ */
+public class SQL2IndexingAggregateTest2 extends AbstractIndexingTest {
+
+    protected static final String WORKSPACE_NAME_NEW = "indexing-test-2";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        for (Node c : JcrUtils.getChildNodes(testRootNode)) {
+            testRootNode.getSession().removeItem(c.getPath());
+        }
+        testRootNode.getSession().save();
+    }
+
+    @Override
+    protected String getWorkspaceName() {
+        return WORKSPACE_NAME_NEW;
+    }
+
+    /**
+     * recursive="true" recursiveLimit="-1"
+     * 
+     * The aggregation hierarchy is defined in
+     * src/test/repository/workspaces/indexing-test-2/indexing-configuration.xml
+     * 
+     * see <a href="https://issues.apache.org/jira/browse/JCR-2989">JCR-2989</a>
+     */
+    public void testNegativeRecursiveAggregationLimit() throws Exception {
+
+        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 parent = testRootNode.addNode(
+                "testDefaultRecursiveAggregation_parent",
+                JcrConstants.NT_UNSTRUCTURED);
+
+        Node child = parent.addNode("testDefaultRecursiveAggregation_child",
+                JcrConstants.NT_UNSTRUCTURED);
+        child.setProperty("type", "testnode");
+        child.setProperty("jcr:encoding", "UTF-8");
+        child.setProperty("jcr:mimeType", "text/plain");
+        child.setProperty(
+                "jcr:data",
+                session.getValueFactory().createBinary(
+                        new ByteArrayInputStream(out.toByteArray())));
+        testRootNode.getSession().save();
+
+        String sqlBase = "SELECT * FROM [nt:unstructured] as u WHERE CONTAINS (u.*, 'dog') ";
+        String sqlParent = sqlBase + " AND ISCHILDNODE([" + testRoot + "])";
+        String sqlChild = sqlBase + " AND ISCHILDNODE([" + parent.getPath()
+                + "])";
+
+        executeSQL2Query(sqlParent, new Node[] {});
+        executeSQL2Query(sqlChild, new Node[] { child });
+    }
+
+    /**
+     * this should traverse the *entire* hierarchy to aggregate indexes
+     * 
+     * see <a href="https://issues.apache.org/jira/browse/JCR-2989">JCR-2989</a>
+     */
+    public void testUnlimitedRecursiveAggregation() throws Exception {
+
+        long levelsDeep = AggregateRuleImpl.RECURSIVE_AGGREGATION_LIMIT_DEFAULT + 10;
+        List<Node> expectedNodes = new ArrayList<Node>();
+
+        String sqlBase = "SELECT * FROM [nt:folder] as f WHERE ";
+        String sqlCat = sqlBase + " CONTAINS (f.*, 'cat')";
+        String sqlDog = sqlBase + " CONTAINS (f.*, 'dog')";
+
+        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 folderRoot = testRootNode.addNode("myFolder", "nt:folder");
+        Node folderChild = folderRoot;
+        expectedNodes.add(folderChild);
+
+        for (int i = 0; i < levelsDeep; i++) {
+            folderChild.addNode("0" + i + "-dummy", "nt:folder");
+            folderChild = folderChild.addNode("0" + i, "nt:folder");
+            expectedNodes.add(folderChild);
+        }
+
+        Node file = folderChild.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(sqlDog, expectedNodes.toArray(new Node[] {}));
+
+        // update jcr:data
+        out.reset();
+        writer.write("the quick brown fox jumps over the lazy cat.");
+        writer.flush();
+        resource.setProperty("jcr:data", session.getValueFactory()
+                .createBinary(new ByteArrayInputStream(out.toByteArray())));
+        testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, new Node[] {});
+        executeSQL2Query(sqlCat, expectedNodes.toArray(new Node[] {}));
+
+        // replace jcr:content with unstructured
+        resource.remove();
+        Node unstrContent = file.addNode("jcr:content", "nt:unstructured");
+        Node foo = unstrContent.addNode("foo");
+        foo.setProperty("text", "the quick brown fox jumps over the lazy dog.");
+        testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, expectedNodes.toArray(new Node[] {}));
+        executeSQL2Query(sqlCat, new Node[] {});
+
+        // remove foo
+        foo.remove();
+        testRootNode.getSession().save();
+        executeSQL2Query(sqlDog, new Node[] {});
+        executeSQL2Query(sqlCat, new Node[] {});
+
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest2.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java?rev=1182367&r1=1182366&r2=1182367&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java Wed Oct 12 13:53:09 2011
@@ -43,6 +43,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(ChainedTermEnumTest.class);
         suite.addTestSuite(IndexingConfigurationImplTest.class);
         suite.addTestSuite(SQL2IndexingAggregateTest.class);
+        suite.addTestSuite(SQL2IndexingAggregateTest2.class);
 
         return suite;
     }