OAK-4922 - Implement number of facets retrieved in query configurable for LucenePropertyIndex

git-svn-id: https://svn.apache.org/repos/asf/jackrabbit/oak/branches/1.4@1781917 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/oak-doc/src/site/markdown/query/lucene.md b/oak-doc/src/site/markdown/query/lucene.md
index 07f55c7..ee856df 100644
--- a/oak-doc/src/site/markdown/query/lucene.md
+++ b/oak-doc/src/site/markdown/query/lucene.md
@@ -1131,18 +1131,27 @@
  index definition.
  By default ACL checks are always performed on facets by the Lucene property index however this can be avoided by setting
  the property _secure_ to _false_ in the _facets_ configuration node.
+`@since Oak 1.5.15` The no. of facets to be retrieved is configurable via the _topChildren_ property, which defaults to 10.
+
 ```
+/oak:index/lucene-with-unsecure-facets
+  - jcr:primaryType = "oak:QueryIndexDefinition"
+  - compatVersion = 2
+  - type = "lucene"
+  - async = "async"
+  + facets
+    - topChildren = 100
+    - secure = false
+  + indexRules
+    - jcr:primaryType = "nt:unstructured"
     + nt:base
       + properties
         - jcr:primaryType = "nt:unstructured"
         + jcr:title
           - facets = true
           - propertyIndex = true
-          + facets
-            - secure = false
 ```
 
-
 #### Score Explanation
 
 `@since Oak 1.3.12`
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
index 78aadcc..29a6bc4 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexDefinition.java
@@ -133,11 +133,16 @@
     static final int TYPES_ALLOW_ALL = -1;
 
     /**
-     * Deafult suggesterUpdateFrequencyMinutes
+     * Default suggesterUpdateFrequencyMinutes
      */
     static final int DEFAULT_SUGGESTER_UPDATE_FREQUENCY_MINUTES = 10;
 
     /**
+     * Default no. of facets retrieved
+     */
+    static final int DEFAULT_FACET_COUNT = 10;
+
+    /**
      * native sort order
      */
     static final OrderEntry NATIVE_SORT_ORDER = new OrderEntry(JCR_SCORE, Type.UNDEFINED,
@@ -215,6 +220,8 @@
 
     private final boolean secureFacets;
 
+    private final int numberOfTopFacets;
+
     private final boolean suggestEnabled;
 
     private final boolean spellcheckEnabled;
@@ -286,7 +293,16 @@
         this.queryPaths = getQueryPaths(defn);
         this.saveDirListing = getOptionalValue(defn, LuceneIndexConstants.SAVE_DIR_LISTING, true);
         this.suggestAnalyzed = evaluateSuggestAnalyzed(defn, false);
-        this.secureFacets = defn.hasChildNode(FACETS) && getOptionalValue(defn.getChildNode(FACETS), PROP_SECURE_FACETS, true);
+
+        if (defn.hasChildNode(FACETS)) {
+            NodeState facetsConfig =  defn.getChildNode(FACETS);
+            this.secureFacets = getOptionalValue(facetsConfig, PROP_SECURE_FACETS, true);
+            this.numberOfTopFacets = getOptionalValue(facetsConfig, PROP_FACETS_TOP_CHILDREN, DEFAULT_FACET_COUNT);
+        } else {
+            this.secureFacets = true;
+            this.numberOfTopFacets = DEFAULT_FACET_COUNT;
+        }
+
         this.suggestEnabled = evaluateSuggestionEnabled();
         this.spellcheckEnabled = evaluateSpellcheckEnabled();
     }
@@ -686,6 +702,10 @@
         return secureFacets;
     }
 
+    public int getNumberOfTopFacets() {
+        return numberOfTopFacets;
+    }
+
     public class IndexingRule {
         private final String baseNodeType;
         private final String nodeTypeName;
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
index ccca3f2..532b423 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexConstants.java
@@ -326,6 +326,13 @@
     String PROP_SECURE_FACETS = "secure";
 
     /**
+     * Optional (index definition) property indicating max number of facets that will be retrieved
+     * in query
+     * Default is {@link IndexDefinition#DEFAULT_FACET_COUNT}
+     */
+    String PROP_FACETS_TOP_CHILDREN = "topChildren";
+
+    /**
      * Optional (property definition) property indicating whether facets should be created
      * for this property
      */
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
index c2de1cf..f2e7e00 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
@@ -1528,6 +1528,7 @@
         LuceneResultRow currentRow;
         private final SizeEstimator sizeEstimator;
         private long estimatedSize;
+        private int numberOfFacets;
 
         LucenePathCursor(final Iterator<LuceneResultRow> it, final IndexPlan plan, QueryEngineSettings settings, SizeEstimator sizeEstimator) {
             pathPrefix = plan.getPathPrefix();
@@ -1551,7 +1552,10 @@
                 }
 
             };
-            pathCursor = new PathCursor(pathIterator, getPlanResult(plan).isUniquePathsRequired(), settings);
+
+            PlanResult planResult = getPlanResult(plan);
+            pathCursor = new PathCursor(pathIterator, planResult.isUniquePathsRequired(), settings);
+            numberOfFacets = planResult.indexDefinition.getNumberOfTopFacets();
         }
 
 
@@ -1607,7 +1611,7 @@
                         Facets facets = currentRow.facets;
                         try {
                             if (facets != null) {
-                                FacetResult topChildren = facets.getTopChildren(10, facetFieldName);
+                                FacetResult topChildren = facets.getTopChildren(numberOfFacets, facetFieldName);
                                 if (topChildren != null) {
                                     JsopWriter writer = new JsopBuilder();
                                     writer.object();
diff --git a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
index 7f12dd6..8e42798 100644
--- a/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
+++ b/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
@@ -47,7 +47,7 @@
     private static final Logger LOGGER = LoggerFactory.getLogger(FacetHelper.class);
 
     /**
-     * IndexPaln Attribute name which refers to the name of the fields that should used for facets.
+     * IndexPaln Attribute name which refers to the name of the fields that should be used for facets.
      */
     public static final String ATTR_FACET_FIELDS = "oak.facet.fields";
 
diff --git a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
index 6de018d..fc4b73c 100644
--- a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
+++ b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/FacetTest.java
@@ -20,6 +20,7 @@
 
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryManager;
@@ -38,6 +39,8 @@
 public class FacetTest extends AbstractQueryTest {
 
     public static final String FACET_CONFING_PROP_PATH = "/oak:index/luceneGlobal/indexRules/nt:base/properties/allProps/facets";
+    public static final String FACET_CONFING_NODE_PATH = "/oak:index/luceneGlobal/facets";
+    public static final String INDEX_CONFING_NODE_PATH = "/oak:index/luceneGlobal";
 
     @Before
     protected void setUp() throws Exception {
@@ -48,6 +51,13 @@
             superuser.save();
             superuser.refresh(true);
         }
+
+        if (!superuser.nodeExists(FACET_CONFING_NODE_PATH)) {
+            Node node = superuser.getNode(INDEX_CONFING_NODE_PATH);
+            node.addNode(LuceneIndexConstants.FACETS);
+            superuser.save();
+            superuser.refresh(true);
+        }
     }
 
     @After
@@ -59,6 +69,13 @@
             superuser.save();
             superuser.refresh(true);
         }
+
+        if (superuser.nodeExists(FACET_CONFING_NODE_PATH)) {
+            superuser.getNode(FACET_CONFING_NODE_PATH).remove();
+            superuser.save();
+            superuser.refresh(true);
+        }
+
         super.tearDown();
     }
 
@@ -301,4 +318,147 @@
         assertFalse(nodes.hasNext());
     }
 
+    public void testFacetRetrievalDefaultNumberOfFacets() throws RepositoryException {
+        Session session = superuser;
+        Node n1 = testRootNode.addNode("node1");
+        String pn = "jcr:title";
+        n1.setProperty(pn, "hello 1");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty(pn, "hallo 2");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty(pn, "hallo 3");
+        Node n4 = testRootNode.addNode("node4");
+        n4.setProperty(pn, "hallo 4");
+        Node n5 = testRootNode.addNode("node5");
+        n5.setProperty(pn, "hallo 5");
+        Node n6 = testRootNode.addNode("node6");
+        n6.setProperty(pn, "hallo 6");
+        Node n7 = testRootNode.addNode("node7");
+        n7.setProperty(pn, "hallo 7");
+        Node n8 = testRootNode.addNode("node8");
+        n8.setProperty(pn, "hallo 8");
+        Node n9 = testRootNode.addNode("node9");
+        n9.setProperty(pn, "hallo 9");
+        Node n10 = testRootNode.addNode("node10");
+        n10.setProperty(pn, "hallo 10");
+        Node n11 = testRootNode.addNode("node11");
+        n11.setProperty(pn, "hallo 11");
+        Node n12 = testRootNode.addNode("node12");
+        n12.setProperty(pn, "hallo 12");
+        session.save();
+
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        String sql2 = "select [jcr:path], [rep:facet(" + pn + ")] from [nt:base] " +
+            "where contains([" + pn + "], 'hallo') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        FacetResult facetResult = new FacetResult(result);
+        assertNotNull(facetResult);
+        assertNotNull(facetResult.getDimensions());
+        assertEquals(1, facetResult.getDimensions().size());
+        assertTrue(facetResult.getDimensions().contains(pn));
+        List<FacetResult.Facet> facets = facetResult.getFacets(pn);
+        assertNotNull(facets);
+        assertEquals(10, facets.size());
+    }
+
+    public void testFacetRetrievalNumberOfFacetsConfiguredHigherThanDefault() throws RepositoryException {
+
+        Node facetsConfig = superuser.getNode(FACET_CONFING_NODE_PATH);
+        facetsConfig.setProperty(LuceneIndexConstants.PROP_FACETS_TOP_CHILDREN, 11);
+        superuser.save();
+        superuser.refresh(true);
+
+        Session session = superuser;
+        Node n1 = testRootNode.addNode("node1");
+        String pn = "jcr:title";
+        n1.setProperty(pn, "hello 1");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty(pn, "hallo 2");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty(pn, "hallo 3");
+        Node n4 = testRootNode.addNode("node4");
+        n4.setProperty(pn, "hallo 4");
+        Node n5 = testRootNode.addNode("node5");
+        n5.setProperty(pn, "hallo 5");
+        Node n6 = testRootNode.addNode("node6");
+        n6.setProperty(pn, "hallo 6");
+        Node n7 = testRootNode.addNode("node7");
+        n7.setProperty(pn, "hallo 7");
+        Node n8 = testRootNode.addNode("node8");
+        n8.setProperty(pn, "hallo 8");
+        Node n9 = testRootNode.addNode("node9");
+        n9.setProperty(pn, "hallo 9");
+        Node n10 = testRootNode.addNode("node10");
+        n10.setProperty(pn, "hallo 10");
+        Node n11 = testRootNode.addNode("node11");
+        n11.setProperty(pn, "hallo 11");
+        Node n12 = testRootNode.addNode("node12");
+        n12.setProperty(pn, "hallo 12");
+        session.save();
+
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        String sql2 = "select [jcr:path], [rep:facet(" + pn + ")] from [nt:base] " +
+            "where contains([" + pn + "], 'hallo') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        FacetResult facetResult = new FacetResult(result);
+        assertNotNull(facetResult);
+        assertNotNull(facetResult.getDimensions());
+        assertEquals(1, facetResult.getDimensions().size());
+        assertTrue(facetResult.getDimensions().contains(pn));
+        List<FacetResult.Facet> facets = facetResult.getFacets(pn);
+        assertNotNull(facets);
+        assertEquals(11, facets.size());
+    }
+
+    public void testFacetRetrievalNumberOfFacetsConfiguredLowerThanDefault() throws RepositoryException {
+
+        Node facetsConfig = superuser.getNode(FACET_CONFING_NODE_PATH);
+        facetsConfig.setProperty(LuceneIndexConstants.PROP_FACETS_TOP_CHILDREN, 7);
+        superuser.save();
+        superuser.refresh(true);
+
+        Session session = superuser;
+        Node n1 = testRootNode.addNode("node1");
+        String pn = "jcr:title";
+        n1.setProperty(pn, "hello 1");
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty(pn, "hallo 2");
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty(pn, "hallo 3");
+        Node n4 = testRootNode.addNode("node4");
+        n4.setProperty(pn, "hallo 4");
+        Node n5 = testRootNode.addNode("node5");
+        n5.setProperty(pn, "hallo 5");
+        Node n6 = testRootNode.addNode("node6");
+        n6.setProperty(pn, "hallo 6");
+        Node n7 = testRootNode.addNode("node7");
+        n7.setProperty(pn, "hallo 7");
+        Node n8 = testRootNode.addNode("node8");
+        n8.setProperty(pn, "hallo 8");
+        Node n9 = testRootNode.addNode("node9");
+        n9.setProperty(pn, "hallo 9");
+        Node n10 = testRootNode.addNode("node10");
+        n10.setProperty(pn, "hallo 10");
+        Node n11 = testRootNode.addNode("node11");
+        n11.setProperty(pn, "hallo 11");
+        Node n12 = testRootNode.addNode("node12");
+        n12.setProperty(pn, "hallo 12");
+        session.save();
+
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        String sql2 = "select [jcr:path], [rep:facet(" + pn + ")] from [nt:base] " +
+            "where contains([" + pn + "], 'hallo') order by [jcr:path]";
+        Query q = qm.createQuery(sql2, Query.JCR_SQL2);
+        QueryResult result = q.execute();
+        FacetResult facetResult = new FacetResult(result);
+        assertNotNull(facetResult);
+        assertNotNull(facetResult.getDimensions());
+        assertEquals(1, facetResult.getDimensions().size());
+        assertTrue(facetResult.getDimensions().contains(pn));
+        List<FacetResult.Facet> facets = facetResult.getFacets(pn);
+        assertNotNull(facets);
+        assertEquals(7, facets.size());
+    }
 }
\ No newline at end of file