This is an automated email from the ASF dual-hosted git repository.

fortino pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 0440e096dc OAK-10553: improved elastic statistical facets (#1217)
0440e096dc is described below

commit 0440e096dc7a460d206c77a5f178796db31a0ef4
Author: Fabrizio Fortino <[email protected]>
AuthorDate: Fri Nov 17 16:23:39 2023 +0100

    OAK-10553: improved elastic statistical facets (#1217)
    
    * OAK-10553: improved elastic statical facets (no need to perform a full 
index traverse)
    
    * OAK-10553: improved FacetCommonTest
    
    * OAK-10553: minor improvements
    
    * OAK-10553: revise interrupted exception handling
    
    * OAK-10553: improved statistical facets accuracy
    
    * OAK-10553: better message in case facets time out
---
 .../index/elastic/ElasticIndexDefinition.java      |   8 +
 .../index/elastic/index/ElasticIndexWriter.java    |   2 +-
 .../index/elastic/query/ElasticRequestHandler.java | 124 ++++++------
 .../query/async/ElasticResultRowAsyncIterator.java |   9 +-
 .../query/async/facets/ElasticFacetProvider.java   |  16 +-
 .../facets/ElasticInsecureFacetAsyncProvider.java  |   6 +-
 .../facets/ElasticSecureFacetAsyncProvider.java    |  25 ++-
 .../ElasticStatisticalFacetAsyncProvider.java      | 223 ++++++++++++---------
 .../index/elastic/ElasticConnectionRule.java       |  16 +-
 .../plugins/index/elastic/ElasticFacetTest.java    |   2 +-
 .../oak/plugins/index/FacetCommonTest.java         | 103 ++++++----
 11 files changed, 312 insertions(+), 222 deletions(-)

diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
index 9ad83c5191..82d0374fdd 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticIndexDefinition.java
@@ -75,6 +75,12 @@ public class ElasticIndexDefinition extends IndexDefinition {
     public static final String FAIL_ON_ERROR = "failOnError";
     public static final boolean FAIL_ON_ERROR_DEFAULT = true;
 
+    /**
+     * When 0, the index name gets dynamically generated by adding a random 
suffix to the index name.
+     */
+    public static final String INDEX_NAME_SEED = "indexNameSeed";
+    public static final long INDEX_NAME_SEED_DEFAULT = 0L;
+
     /**
      * Hidden property for storing a seed value to be used as suffix in remote 
index name.
      */
@@ -135,6 +141,7 @@ public class ElasticIndexDefinition extends IndexDefinition 
{
     public final Integer trackTotalHits;
     public final String dynamicMapping;
     public final boolean failOnError;
+    public final long indexNameSeed;
 
     private final Map<String, List<PropertyDefinition>> propertiesByName;
     private final List<PropertyDefinition> dynamicBoostProperties;
@@ -160,6 +167,7 @@ public class ElasticIndexDefinition extends IndexDefinition 
{
         this.failOnError = getOptionalValue(defn, FAIL_ON_ERROR,
                 Boolean.parseBoolean(System.getProperty(TYPE_ELASTICSEARCH + 
"." + FAIL_ON_ERROR, Boolean.toString(FAIL_ON_ERROR_DEFAULT)))
         );
+        this.indexNameSeed = getOptionalValue(defn, INDEX_NAME_SEED, 
INDEX_NAME_SEED_DEFAULT);
         this.similarityTagsFields = getOptionalValues(defn, 
SIMILARITY_TAGS_FIELDS, Type.STRINGS, String.class, 
SIMILARITY_TAGS_FIELDS_DEFAULT);
 
         this.propertiesByName = getDefinedRules()
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
index 13968326f7..3bbd7d3daf 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/index/ElasticIndexWriter.java
@@ -76,7 +76,7 @@ class ElasticIndexWriter implements 
FulltextIndexWriter<ElasticDocument> {
         // old index until the new one gets enabled) during incremental 
reindexing
         if (this.reindex) {
             try {
-                long seed = UUID.randomUUID().getMostSignificantBits();
+                long seed = indexDefinition.indexNameSeed == 0L ? 
UUID.randomUUID().getMostSignificantBits() : indexDefinition.indexNameSeed;
                 // merge gets called on node store later in the indexing flow
                 
definitionBuilder.setProperty(ElasticIndexDefinition.PROP_INDEX_NAME_SEED, 
seed);
                 // let's store the current mapping version in the index 
definition
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
index 3ec9e51a92..e4cd77b21d 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/ElasticRequestHandler.java
@@ -63,6 +63,7 @@ import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
 import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.ElasticPropertyDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets.ElasticFacetProvider;
@@ -150,70 +151,69 @@ public class ElasticRequestHandler {
     }
 
     public Query baseQuery() {
-        return Query.of(fn -> {
-                    fn.bool(fnb -> {
+        return Query.of(q -> q.bool(baseQueryBuilder().build()));
+    }
 
-                        FullTextExpression ft = filter.getFullTextConstraint();
+    public BoolQuery.Builder baseQueryBuilder() {
+        BoolQuery.Builder bqb = new BoolQuery.Builder();
+        FullTextExpression ft = filter.getFullTextConstraint();
 
-                        if (ft != null) {
-                            fnb.must(fullTextQuery(ft, planResult));
-                        }
+        if (ft != null) {
+            bqb.must(fullTextQuery(ft, planResult));
+        }
 
-                        if (propertyRestrictionQuery != null) {
-                            if (propertyRestrictionQuery.startsWith("mlt?")) {
-                                List<PropertyDefinition> sp = 
elasticIndexDefinition.getSimilarityProperties();
-                                String mltQueryString = 
propertyRestrictionQuery.substring("mlt?".length());
-                                Map<String, String> mltParams = 
MoreLikeThisHelperUtil.getParamMapFromMltQuery(mltQueryString);
-                                String queryNodePath = 
mltParams.get(MoreLikeThisHelperUtil.MLT_STREAM_BODY);
-
-                                if (queryNodePath == null) {
-                                    // TODO : See if we might want to support 
like Text here (passed as null in
-                                    // above constructors)
-                                    // IT is not supported in our lucene 
implementation.
-                                    throw new IllegalArgumentException(
-                                            "Missing required field 
stream.body in MLT query: " + mltQueryString);
-                                }
-                                if (sp.isEmpty()) {
-                                    // SimilarityImpl in oak-core sets 
property restriction for sim search and the
-                                    // query is something like
-                                    // 
mlt?mlt.fl=:path&mlt.mindf=0&stream.body=<path> . We need parse this query
-                                    // string and turn into a query
-                                    // elastic can understand.
-                                    fnb.must(m -> 
m.moreLikeThis(mltQuery(mltParams)));
-                                } else {
-                                    fnb.must(m -> 
m.bool(similarityQuery(queryNodePath, sp)));
-                                }
-
-                                // Add should clause to improve relevance 
using similarity tags only when similarity is
-                                // enabled and there is at least one 
similarity tag property
-                                if 
(elasticIndexDefinition.areSimilarityTagsEnabled() &&
-                                        
!elasticIndexDefinition.getSimilarityTagsProperties().isEmpty()) {
-                                    // add should clause to improve relevance 
using similarity tags
-                                    fnb.should(s -> s
-                                            .moreLikeThis(m -> m
-                                                    
.fields(ElasticIndexDefinition.SIMILARITY_TAGS)
-                                                    .like(l -> l.document(d -> 
d.id(ElasticIndexUtils.idFromPath(queryNodePath))))
-                                                    .minTermFreq(1)
-                                                    .minDocFreq(1)
-                                                    
.boost(elasticIndexDefinition.getSimilarityTagsBoost())
-                                            )
-                                    );
-                                }
-
-                            } else {
-                                fnb.must(m -> m.queryString(qs -> 
qs.query(propertyRestrictionQuery)));
-                            }
+        if (propertyRestrictionQuery != null) {
+            if (propertyRestrictionQuery.startsWith("mlt?")) {
+                List<PropertyDefinition> sp = 
elasticIndexDefinition.getSimilarityProperties();
+                String mltQueryString = 
propertyRestrictionQuery.substring("mlt?".length());
+                Map<String, String> mltParams = 
MoreLikeThisHelperUtil.getParamMapFromMltQuery(mltQueryString);
+                String queryNodePath = 
mltParams.get(MoreLikeThisHelperUtil.MLT_STREAM_BODY);
+
+                if (queryNodePath == null) {
+                    // TODO : See if we might want to support like Text here 
(passed as null in
+                    // above constructors)
+                    // IT is not supported in our lucene implementation.
+                    throw new IllegalArgumentException(
+                            "Missing required field stream.body in MLT query: 
" + mltQueryString);
+                }
+                if (sp.isEmpty()) {
+                    // SimilarityImpl in oak-core sets property restriction 
for sim search and the
+                    // query is something like
+                    // mlt?mlt.fl=:path&mlt.mindf=0&stream.body=<path> . We 
need parse this query
+                    // string and turn into a query
+                    // elastic can understand.
+                    bqb.must(m -> m.moreLikeThis(mltQuery(mltParams)));
+                } else {
+                    bqb.must(m -> m.bool(similarityQuery(queryNodePath, sp)));
+                }
 
-                        } else if 
(planResult.evaluateNonFullTextConstraints()) {
-                            for (Query constraint : 
nonFullTextConstraints(indexPlan, planResult)) {
-                                fnb.filter(constraint);
-                            }
-                        }
-                        return fnb;
-                    });
-                    return fn;
+                // Add should clause to improve relevance using similarity 
tags only when similarity is
+                // enabled and there is at least one similarity tag property
+                if (elasticIndexDefinition.areSimilarityTagsEnabled() &&
+                        
!elasticIndexDefinition.getSimilarityTagsProperties().isEmpty()) {
+                    // add should clause to improve relevance using similarity 
tags
+                    bqb.should(s -> s
+                            .moreLikeThis(m -> m
+                                    
.fields(ElasticIndexDefinition.SIMILARITY_TAGS)
+                                    .like(l -> l.document(d -> 
d.id(ElasticIndexUtils.idFromPath(queryNodePath))))
+                                    .minTermFreq(1)
+                                    .minDocFreq(1)
+                                    
.boost(elasticIndexDefinition.getSimilarityTagsBoost())
+                            )
+                    );
                 }
-        );
+
+            } else {
+                bqb.must(m -> m.queryString(qs -> 
qs.query(propertyRestrictionQuery)));
+            }
+
+        } else if (planResult.evaluateNonFullTextConstraints()) {
+            for (Query constraint : nonFullTextConstraints(indexPlan, 
planResult)) {
+                bqb.filter(constraint);
+            }
+        }
+
+        return bqb;
     }
 
     public @NotNull List<SortOptions> baseSorts() {
@@ -271,10 +271,10 @@ public class ElasticRequestHandler {
         return propertyRestrictionQuery != null && 
propertyRestrictionQuery.startsWith(SUGGEST_PREFIX);
     }
 
-    public ElasticFacetProvider getAsyncFacetProvider(ElasticResponseHandler 
responseHandler) {
+    public ElasticFacetProvider getAsyncFacetProvider(ElasticConnection 
connection, ElasticResponseHandler responseHandler) {
         return requiresFacets()
-                ? 
ElasticFacetProvider.getProvider(planResult.indexDefinition.getSecureFacetConfiguration(),
 this,
-                        responseHandler, filter::isAccessible)
+                ? 
ElasticFacetProvider.getProvider(planResult.indexDefinition.getSecureFacetConfiguration(),
 connection,
+                        elasticIndexDefinition, this, responseHandler, 
filter::isAccessible)
                 : null;
     }
 
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
index 05fc293b6a..b350762fe8 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/ElasticResultRowAsyncIterator.java
@@ -95,7 +95,7 @@ public class ElasticResultRowAsyncIterator implements 
ElasticQueryIterator, Elas
         this.indexPlan = indexPlan;
         this.rowInclusionPredicate = rowInclusionPredicate;
         this.metricHandler = metricHandler;
-        this.elasticFacetProvider = 
elasticRequestHandler.getAsyncFacetProvider(elasticResponseHandler);
+        this.elasticFacetProvider = 
elasticRequestHandler.getAsyncFacetProvider(indexNode.getConnection(), 
elasticResponseHandler);
         this.elasticQueryScanner = initScanner();
     }
 
@@ -110,6 +110,7 @@ public class ElasticResultRowAsyncIterator implements 
ElasticQueryIterator, Elas
             try {
                 nextRow = queue.take();
             } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();  // restore interrupt 
status
                 throw new IllegalStateException("Error reading next result 
from Elastic", e);
             }
         }
@@ -155,6 +156,7 @@ public class ElasticResultRowAsyncIterator implements 
ElasticQueryIterator, Elas
                 queue.put(new FulltextResultRow(path, searchHit.score() != 
null ? searchHit.score() : 0.0,
                         elasticResponseHandler.excerpts(searchHit), 
elasticFacetProvider, null));
             } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();  // restore interrupt 
status
                 throw new IllegalStateException("Error producing results into 
the iterator queue", e);
             }
         }
@@ -165,6 +167,7 @@ public class ElasticResultRowAsyncIterator implements 
ElasticQueryIterator, Elas
         try {
             queue.put(POISON_PILL);
         } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();  // restore interrupt status
             throw new IllegalStateException("Error inserting poison pill into 
the iterator queue", e);
         }
     }
@@ -173,8 +176,8 @@ public class ElasticResultRowAsyncIterator implements 
ElasticQueryIterator, Elas
         List<ElasticResponseListener> listeners = new ArrayList<>();
         // TODO: we could avoid to register this listener when the client is 
interested in facets only. It would save space and time
         listeners.add(this);
-        if (elasticFacetProvider != null) {
-            listeners.add(elasticFacetProvider);
+        if (elasticFacetProvider != null && elasticFacetProvider instanceof 
ElasticResponseListener) {
+            listeners.add((ElasticResponseListener) elasticFacetProvider);
         }
 
         return new ElasticQueryScanner(listeners);
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
index 9b1c3a5737..8b3058d4d7 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticFacetProvider.java
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets;
 
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
@@ -25,13 +27,15 @@ import 
org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
 import java.util.function.Predicate;
 
 /**
- * Provider of facets through an {@link ElasticResponseListener}
+ * Provider of facets for Elasticsearch
  */
-public interface ElasticFacetProvider extends FulltextIndex.FacetProvider, 
ElasticResponseListener {
+public interface ElasticFacetProvider extends FulltextIndex.FacetProvider {
 
     /**
      * Returns the appropriate provider based on the {@link 
SecureFacetConfiguration}
      * @param facetConfiguration the {@link SecureFacetConfiguration} to 
extract facet options
+     * @param connection the {@link ElasticConnection} to perform requests
+     * @param indexDefinition the {@link ElasticIndexDefinition} to extract 
index options
      * @param requestHandler the {@link ElasticRequestHandler} to perform 
actions at request time
      * @param responseHandler the {@link ElasticResponseHandler} to decode 
responses
      * @param isAccessible a {@link Predicate} to check if a node is accessible
@@ -41,6 +45,8 @@ public interface ElasticFacetProvider extends 
FulltextIndex.FacetProvider, Elast
      */
     static ElasticFacetProvider getProvider(
             SecureFacetConfiguration facetConfiguration,
+            ElasticConnection connection,
+            ElasticIndexDefinition indexDefinition,
             ElasticRequestHandler requestHandler,
             ElasticResponseHandler responseHandler,
             Predicate<String> isAccessible
@@ -51,9 +57,9 @@ public interface ElasticFacetProvider extends 
FulltextIndex.FacetProvider, Elast
                 facetProvider = new ElasticInsecureFacetAsyncProvider();
                 break;
             case STATISTICAL:
-                facetProvider = new ElasticStatisticalFacetAsyncProvider(
-                        requestHandler, responseHandler, isAccessible,
-                        facetConfiguration.getRandomSeed(), 
facetConfiguration.getStatisticalFacetSampleSize()
+                facetProvider = new 
ElasticStatisticalFacetAsyncProvider(connection, indexDefinition,
+                        requestHandler, responseHandler, isAccessible, 
facetConfiguration.getRandomSeed(),
+                        facetConfiguration.getStatisticalFacetSampleSize()
                 );
                 break;
             case SECURE:
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
index 50f1bd13c2..ef207e402e 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticInsecureFacetAsyncProvider.java
@@ -45,8 +45,12 @@ class ElasticInsecureFacetAsyncProvider implements 
ElasticFacetProvider, Elastic
     public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String 
columnName) {
         LOG.trace("Requested facets for {} - Latch count: {}", columnName, 
latch.getCount());
         try {
-            latch.await(15, TimeUnit.SECONDS);
+            boolean completed = latch.await(15, TimeUnit.SECONDS);
+            if (!completed) {
+                throw new IllegalStateException("Timed out while waiting for 
facets");
+            }
         } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();  // restore interrupt status
             throw new IllegalStateException("Error while waiting for facets", 
e);
         }
         LOG.trace("Reading facets for {} from aggregations {}", columnName, 
aggregations);
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
index 2cc1b3e704..a02a6afcb1 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticSecureFacetAsyncProvider.java
@@ -41,15 +41,14 @@ import java.util.stream.Collectors;
  */
 class ElasticSecureFacetAsyncProvider implements ElasticFacetProvider, 
ElasticResponseListener.SearchHitListener {
 
-    protected static final Logger LOG = 
LoggerFactory.getLogger(ElasticSecureFacetAsyncProvider.class);
-
-    protected final Set<String> facetFields;
-    private final Map<String, Map<String, Integer>> facetsMap = new 
ConcurrentHashMap<>();
-    private Map<String, List<FulltextIndex.Facet>> facets;
-    protected final ElasticResponseHandler elasticResponseHandler;
-    protected final Predicate<String> isAccessible;
+    private static final Logger LOG = 
LoggerFactory.getLogger(ElasticSecureFacetAsyncProvider.class);
 
+    private final Set<String> facetFields;
+    private final Map<String, Map<String, Integer>> accessibleFacetCounts = 
new ConcurrentHashMap<>();
+    private final ElasticResponseHandler elasticResponseHandler;
+    private final Predicate<String> isAccessible;
     private final CountDownLatch latch = new CountDownLatch(1);
+    private Map<String, List<FulltextIndex.Facet>> facets;
 
     ElasticSecureFacetAsyncProvider(
             ElasticRequestHandler elasticRequestHandler,
@@ -78,7 +77,7 @@ class ElasticSecureFacetAsyncProvider implements 
ElasticFacetProvider, ElasticRe
             for (String field: facetFields) {
                 JsonNode value = searchHit.source().get(field);
                 if (value != null) {
-                    facetsMap.compute(field, (column, facetValues) -> {
+                    accessibleFacetCounts.compute(field, (column, facetValues) 
-> {
                         if (facetValues == null) {
                             Map<String, Integer> values = new HashMap<>();
                             values.put(value.asText(), 1);
@@ -96,7 +95,7 @@ class ElasticSecureFacetAsyncProvider implements 
ElasticFacetProvider, ElasticRe
     @Override
     public void endData() {
         // create Facet objects, order by count (desc) and then by label (asc)
-        facets = facetsMap.entrySet()
+        facets = accessibleFacetCounts.entrySet()
                 .stream()
                 .collect(Collectors.toMap
                         (Map.Entry::getKey, x -> x.getValue().entrySet()
@@ -112,7 +111,7 @@ class ElasticSecureFacetAsyncProvider implements 
ElasticFacetProvider, ElasticRe
                                 .collect(Collectors.toList())
                         )
                 );
-        LOG.trace("End data {}", facetsMap);
+        LOG.trace("End data {}", facets);
         latch.countDown();
     }
 
@@ -120,8 +119,12 @@ class ElasticSecureFacetAsyncProvider implements 
ElasticFacetProvider, ElasticRe
     public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String 
columnName) {
         LOG.trace("Requested facets for {} - Latch count: {}", columnName, 
latch.getCount());
         try {
-            latch.await(15, TimeUnit.SECONDS);
+            boolean completed = latch.await(15, TimeUnit.SECONDS);
+            if (!completed) {
+                throw new IllegalStateException("Timed out while waiting for 
facets");
+            }
         } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();  // restore interrupt status
             throw new IllegalStateException("Error while waiting for facets", 
e);
         }
         LOG.trace("Reading facets for {} from {}", columnName, facets);
diff --git 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
index 4611062dc1..bc79385308 100644
--- 
a/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
+++ 
b/oak-search-elastic/src/main/java/org/apache/jackrabbit/oak/plugins/index/elastic/query/async/facets/ElasticStatisticalFacetAsyncProvider.java
@@ -18,145 +18,178 @@ package 
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.facets;
 
 import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
 import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
+import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.SearchResponse;
 import co.elastic.clients.elasticsearch.core.search.Hit;
+import co.elastic.clients.elasticsearch.core.search.SourceConfig;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticConnection;
+import org.apache.jackrabbit.oak.plugins.index.elastic.ElasticIndexDefinition;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticRequestHandler;
 import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.ElasticResponseHandler;
-import 
org.apache.jackrabbit.oak.plugins.index.elastic.query.async.ElasticResponseListener;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
 import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
- * An {@link ElasticSecureFacetAsyncProvider} extension that subscribes also 
on Elastic Aggregation events.
+ * An {@link ElasticFacetProvider} extension that performs random sampling on 
the result set to compute facets.
  * SearchHit events are sampled and then used to adjust facets coming from 
Aggregations in order to minimize
- * access checks. This provider could improve facets performance but only when 
the result set is quite big.
+ * access checks. This provider could improve facets performance especially 
when the result set is quite big.
  */
-public class ElasticStatisticalFacetAsyncProvider extends 
ElasticSecureFacetAsyncProvider
-        implements ElasticResponseListener.AggregationListener {
+public class ElasticStatisticalFacetAsyncProvider implements 
ElasticFacetProvider {
 
-    private final int sampleSize;
+    private static final Logger LOG = 
LoggerFactory.getLogger(ElasticStatisticalFacetAsyncProvider.class);
+
+    private final ElasticResponseHandler elasticResponseHandler;
+    private final Predicate<String> isAccessible;
+    private final Set<String> facetFields;
+    private final Map<String, List<FulltextIndex.Facet>> allFacets = new 
HashMap<>();
+    private final Map<String, Map<String, Integer>> accessibleFacetCounts = 
new ConcurrentHashMap<>();
+    private Map<String, List<FulltextIndex.Facet>> facets;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private int sampled;
     private long totalHits;
 
-    private final Random rGen;
-    private int sampled = 0;
-    private int seen = 0;
-    private long accessibleCount = 0;
+    ElasticStatisticalFacetAsyncProvider(ElasticConnection connection, 
ElasticIndexDefinition indexDefinition,
+                                         ElasticRequestHandler 
elasticRequestHandler, ElasticResponseHandler elasticResponseHandler,
+                                         Predicate<String> isAccessible, long 
randomSeed, int sampleSize) {
 
-    private final Map<String, List<FulltextIndex.Facet>> facetMap = new 
HashMap<>();
+        this.elasticResponseHandler = elasticResponseHandler;
+        this.isAccessible = isAccessible;
+        this.facetFields = 
elasticRequestHandler.facetFields().collect(Collectors.toSet());
 
-    private final CountDownLatch latch = new CountDownLatch(1);
+        BoolQuery.Builder builder = elasticRequestHandler.baseQueryBuilder();
+        builder.should(sb -> sb.functionScore(fsb ->
+                fsb.functions(f -> f.randomScore(rsb -> rsb.seed("" + 
randomSeed).field(FieldNames.PATH)))
+        ));
 
-    ElasticStatisticalFacetAsyncProvider(ElasticRequestHandler 
elasticRequestHandler,
-                                         ElasticResponseHandler 
elasticResponseHandler,
-                                         Predicate<String> isAccessible,
-                                         long randomSeed, int sampleSize) {
-        super(elasticRequestHandler, elasticResponseHandler, isAccessible);
-        this.sampleSize = sampleSize;
-        this.rGen = new Random(randomSeed);
-    }
+        SearchRequest searchRequest = SearchRequest.of(srb -> 
srb.index(indexDefinition.getIndexAlias())
+                .trackTotalHits(thb -> thb.enabled(true))
+                .source(SourceConfig.of(scf -> scf.filter(ff -> 
ff.includes(FieldNames.PATH).includes(new ArrayList<>(facetFields)))))
+                .query(Query.of(qb -> qb.bool(builder.build())))
+                .aggregations(elasticRequestHandler.aggregations())
+                .size(sampleSize)
+        );
 
-    @Override
-    public void startData(long totalHits) {
-        this.totalHits = totalHits;
+        LOG.trace("Kicking search query with random sampling {}", 
searchRequest);
+        CompletableFuture<SearchResponse<ObjectNode>> searchFuture =
+                connection.getAsyncClient().search(searchRequest, 
ObjectNode.class);
+
+        searchFuture.whenCompleteAsync((searchResponse, throwable) -> {
+            try {
+                if (throwable != null) {
+                    LOG.error("Error while retrieving sample documents", 
throwable);
+                } else {
+                    List<Hit<ObjectNode>> searchHits = 
searchResponse.hits().hits();
+                    this.sampled = searchHits != null ? searchHits.size() : 0;
+                    if (sampled > 0) {
+                        this.totalHits = searchResponse.hits().total().value();
+                        processAggregations(searchResponse.aggregations());
+                        searchResponse.hits().hits().forEach(this::processHit);
+                        computeStatisticalFacets();
+                    }
+                }
+            } finally {
+                latch.countDown();
+            }
+        });
     }
 
     @Override
-    public void on(Hit<ObjectNode> searchHit) {
-        if (totalHits < sampleSize) {
-            super.on(searchHit);
-        } else {
-            if (sampleSize == sampled) {
-                return;
+    public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String 
columnName) {
+        LOG.trace("Requested facets for {} - Latch count: {}", columnName, 
latch.getCount());
+        try {
+            boolean completed = latch.await(15, TimeUnit.SECONDS);
+            if (!completed) {
+                throw new IllegalStateException("Timed out while waiting for 
facets");
             }
-            int r = rGen.nextInt((int) (totalHits - seen)) + 1;
-            seen++;
-
-            if (r <= sampleSize - sampled) {
-                sampled++;
-                final String path = elasticResponseHandler.getPath(searchHit);
-                if (path != null && isAccessible.test(path)) {
-                    accessibleCount++;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();  // restore interrupt status
+            throw new IllegalStateException("Error while waiting for facets", 
e);
+        }
+        LOG.trace("Reading facets for {} from {}", columnName, facets);
+        return facets != null ? 
facets.get(FulltextIndex.parseFacetField(columnName)) : null;
+    }
+
+    private void processHit(Hit<ObjectNode> searchHit) {
+        final String path = elasticResponseHandler.getPath(searchHit);
+        if (path != null && isAccessible.test(path)) {
+            for (String field : facetFields) {
+                JsonNode value = searchHit.source().get(field);
+                if (value != null) {
+                    accessibleFacetCounts.compute(field, (column, facetValues) 
-> {
+                        if (facetValues == null) {
+                            Map<String, Integer> values = new HashMap<>();
+                            values.put(value.asText(), 1);
+                            return values;
+                        } else {
+                            facetValues.merge(value.asText(), 1, Integer::sum);
+                            return facetValues;
+                        }
+                    });
                 }
             }
         }
     }
 
-    @Override
-    public void on(Map<String, Aggregate> aggregations) {
+    private void processAggregations(Map<String, Aggregate> aggregations) {
         for (String field : facetFields) {
             List<StringTermsBucket> buckets = 
aggregations.get(field).sterms().buckets().array();
-            facetMap.put(field, buckets.stream()
+            allFacets.put(field, buckets.stream()
                     .map(b -> new FulltextIndex.Facet(b.key().stringValue(), 
(int) b.docCount()))
                     .collect(Collectors.toList())
             );
         }
     }
 
-    @Override
-    public void endData() {
-        if (totalHits < sampleSize) {
-            super.endData();
-        } else {
-            for (String facet: facetMap.keySet()) {
-                facetMap.compute(facet, (s, facets1) -> 
updateLabelAndValueIfRequired(facets1));
-            }
-            latch.countDown();
-        }
-    }
-
-    @Override
-    public List<FulltextIndex.Facet> getFacets(int numberOfFacets, String 
columnName) {
-        if (totalHits < sampleSize) {
-            return super.getFacets(numberOfFacets, columnName);
-        } else {
-            LOG.trace("Requested facets for {} - Latch count: {}", columnName, 
latch.getCount());
-            try {
-                latch.await(15, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-                throw new IllegalStateException("Error while waiting for 
facets", e);
-            }
-            LOG.trace("Reading facets for {} from {}", columnName, facetMap);
-            return facetMap.get(FulltextIndex.parseFacetField(columnName));
-        }
-    }
-
-    private List<FulltextIndex.Facet> 
updateLabelAndValueIfRequired(List<FulltextIndex.Facet> labelAndValues) {
-        if (accessibleCount < sampleSize) {
-            int numZeros = 0;
-            List<FulltextIndex.Facet> newValues;
-            {
-                List<FulltextIndex.Facet> proportionedLVs = new LinkedList<>();
-                for (FulltextIndex.Facet labelAndValue : labelAndValues) {
-                    long count = labelAndValue.getCount() * accessibleCount / 
sampleSize;
-                    if (count == 0) {
-                        numZeros++;
+    private void computeStatisticalFacets() {
+        for (String facetKey : allFacets.keySet()) {
+            if (accessibleFacetCounts.containsKey(facetKey)) {
+                Map<String, Integer> accessibleFacet = 
accessibleFacetCounts.get(facetKey);
+                List<FulltextIndex.Facet> uncheckedFacet = 
allFacets.get(facetKey);
+                for (FulltextIndex.Facet facet : uncheckedFacet) {
+                    if (accessibleFacet.containsKey(facet.getLabel())) {
+                        double sampleProportion = (double) 
accessibleFacet.get(facet.getLabel()) / sampled;
+                        // returned count is the minimum between the 
accessible count and the count computed from the sample
+                        accessibleFacet.put(facet.getLabel(), 
Math.min(facet.getCount(), (int) (sampleProportion * totalHits)));
                     }
-                    proportionedLVs.add(new 
FulltextIndex.Facet(labelAndValue.getLabel(), Math.toIntExact(count)));
                 }
-                labelAndValues = proportionedLVs;
             }
-            if (numZeros > 0) {
-                newValues = new LinkedList<>();
-                for (FulltextIndex.Facet lv : labelAndValues) {
-                    if (lv.getCount() > 0) {
-                        newValues.add(lv);
-                    }
-                }
-            } else {
-                newValues = labelAndValues;
-            }
-            return newValues;
-        } else {
-            return labelAndValues;
         }
+        // create Facet objects, order by count (desc) and then by label (asc)
+        facets = accessibleFacetCounts.entrySet()
+                .stream()
+                .collect(Collectors.toMap
+                        (Map.Entry::getKey, x -> x.getValue().entrySet()
+                                .stream()
+                                .map(e -> new FulltextIndex.Facet(e.getKey(), 
e.getValue()))
+                                .sorted((f1, f2) -> {
+                                    int f1Count = f1.getCount();
+                                    int f2Count = f2.getCount();
+                                    if (f1Count == f2Count) {
+                                        return 
f1.getLabel().compareTo(f2.getLabel());
+                                    } else return f2Count - f1Count;
+                                })
+                                .collect(Collectors.toList())
+                        )
+                );
+        LOG.trace("Statistical facets {}", facets);
     }
+
 }
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
index 9bb4cf022d..d47210d8f4 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticConnectionRule.java
@@ -48,8 +48,16 @@ public class ElasticConnectionRule extends ExternalResource {
     private ElasticConnectionModel elasticConnectionModel;
 
     public ElasticConnectionRule(String elasticConnectionString) {
+        this(elasticConnectionString,
+                "elastic_test_" +
+                        RandomStringUtils.random(5, true, false).toLowerCase() 
+
+                        System.currentTimeMillis()
+        );
+    }
+
+    public ElasticConnectionRule(String elasticConnectionString, String 
indexPrefix) {
         this.elasticConnectionString = elasticConnectionString;
-        indexPrefix = "elastic_test_" + RandomStringUtils.random(5, true, 
false).toLowerCase();
+        this.indexPrefix = indexPrefix;
     }
 
     public ElasticsearchContainer elastic;
@@ -105,7 +113,7 @@ public class ElasticConnectionRule extends ExternalResource 
{
             elasticConnectionModel.elasticPort = port;
             elasticConnectionModel.elasticApiKey = apiKey;
             elasticConnectionModel.elasticApiSecret = apiSecret;
-            elasticConnectionModel.indexPrefix = indexPrefix + 
System.currentTimeMillis();
+            elasticConnectionModel.indexPrefix = indexPrefix;
         } catch (URISyntaxException e) {
             LOG.error("Provided elastic connection string is not valid ", e);
         }
@@ -118,7 +126,7 @@ public class ElasticConnectionRule extends ExternalResource 
{
         elasticConnectionModel.elasticPort = 
elastic.getMappedPort(ElasticConnection.DEFAULT_PORT);
         elasticConnectionModel.elasticApiKey = null;
         elasticConnectionModel.elasticApiSecret = null;
-        elasticConnectionModel.indexPrefix = indexPrefix + 
System.currentTimeMillis();
+        elasticConnectionModel.indexPrefix = indexPrefix;
     }
 
     private Map<String, String> getUriQueryParams(URI uri) {
@@ -145,7 +153,7 @@ public class ElasticConnectionRule extends ExternalResource 
{
             String apiSecret = queryParams.get("key_secret");
 
             return ElasticConnection.newBuilder()
-                    .withIndexPrefix(indexPrefix + System.currentTimeMillis())
+                    .withIndexPrefix(indexPrefix)
                     .withConnectionParameters(scheme, host, port)
                     .withApiKeys(apiKey, apiSecret)
                     .build();
diff --git 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
index 6ede34abec..9a42b9c21a 100644
--- 
a/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
+++ 
b/oak-search-elastic/src/test/java/org/apache/jackrabbit/oak/plugins/index/elastic/ElasticFacetTest.java
@@ -28,7 +28,7 @@ public class ElasticFacetTest extends FacetCommonTest {
 
     @ClassRule
     public static final ElasticConnectionRule elasticRule =
-            new 
ElasticConnectionRule(ElasticTestUtils.ELASTIC_CONNECTION_STRING);
+            new 
ElasticConnectionRule(ElasticTestUtils.ELASTIC_CONNECTION_STRING, 
"elastic_test_");
 
     protected Repository createJcrRepository() {
         indexOptions = new ElasticIndexOptions();
diff --git 
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
 
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
index 09e9bea7a5..92ef62c671 100644
--- 
a/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
+++ 
b/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/FacetCommonTest.java
@@ -32,16 +32,17 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
 import javax.jcr.security.Privilege;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Random;
-import java.util.UUID;
 import java.util.stream.Collectors;
 
 import static org.apache.jackrabbit.commons.JcrUtils.getOrCreateByPath;
 import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.FACETS;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_RANDOM_SEED;
 import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_REFRESH_DEFN;
 import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS;
 import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS_VALUE_INSECURE;
@@ -67,14 +68,19 @@ public abstract class FacetCommonTest extends 
AbstractJcrTest {
     private final Map<String, Integer> actualLabelCount = new HashMap<>();
     private final Map<String, Integer> actualAclLabelCount = new HashMap<>();
     private final Map<String, Integer> actualAclPar1LabelCount = new 
HashMap<>();
+    private static final Random INDEX_SUFFIX_RANDOMIZER = new Random(7);
 
 
     @Before
     public void createIndex() throws RepositoryException {
-        String indexName = UUID.randomUUID().toString();
         IndexDefinitionBuilder builder = 
indexOptions.createIndex(indexOptions.createIndexDefinitionBuilder(), false);
         builder.noAsync().evaluatePathRestrictions();
         builder.getBuilderTree().setProperty("jcr:primaryType", 
"oak:QueryIndexDefinition", Type.NAME);
+        // Statistical facets in Elasticsearch use a random function with a 
fixed seed but the results are not
+        // consistent when the index name changes. So we set the index name to 
a fixed values.
+        String indexName = "FacetCommonTestIndex" + 
INDEX_SUFFIX_RANDOMIZER.nextInt(1000);
+        builder.getBuilderTree().setProperty(PROP_RANDOM_SEED, 3000L, 
Type.LONG);
+        builder.getBuilderTree().setProperty("indexNameSeed", 300L, Type.LONG);
         IndexDefinitionBuilder.IndexRule indexRule = 
builder.indexRule(JcrConstants.NT_BASE);
         indexRule.property("cons").propertyIndex();
         
indexRule.property("foo").propertyIndex().getBuilderTree().setProperty(FACET_PROP,
 true, Type.BOOLEAN);
@@ -173,18 +179,34 @@ public abstract class FacetCommonTest extends 
AbstractJcrTest {
 
         createDataset(NUM_LEAF_NODES_FOR_LARGE_DATASET);
 
-        assertEventually(() -> assertEquals("Unexpected number of facets", 
actualAclLabelCount.size(), getFacets().size()));
+        assertEventually(() -> {
+            Map<String, Integer> facets = getFacets();
+            assertEquals("Unexpected number of facets", 
actualAclLabelCount.size(), facets.size());
 
-        for (Map.Entry<String, Integer> facet : 
actualAclLabelCount.entrySet()) {
-            String facetLabel = facet.getKey();
-            assertEventually(() -> {
-                int facetCount = getFacets().get(facetLabel);
-                float ratio = ((float) facetCount) / facet.getValue();
-                assertTrue("Facet count for label: " + facetLabel + " is 
outside of 10% margin of error. " +
-                                "Expected: " + facet.getValue() + "; Got: " + 
facetCount + "; Ratio: " + ratio,
-                        Math.abs(ratio - 1) < 0.1);
-            });
-        }
+            for (Map.Entry<String, Integer> facet : 
actualAclLabelCount.entrySet()) {
+                String facetLabel = facet.getKey();
+                assertEventually(() -> {
+                    int facetCount = facets.get(facetLabel);
+                    float ratio = ((float) facetCount) / facet.getValue();
+                    assertTrue("Facet count for label: " + facetLabel + " is 
outside of 10% margin of error. " +
+                                    "Expected: " + facet.getValue() + "; Got: 
" + facetCount + "; Ratio: " + ratio,
+                            Math.abs(ratio - 1) < 0.1);
+                });
+            }
+
+            try {
+                // Verify that the query result is not affected by the facet 
sampling
+                int rowCounter = 0;
+                RowIterator rows = getQueryResult(null).getRows();
+                while (rows.hasNext()) {
+                    rows.nextRow();
+                    rowCounter++;
+                }
+                assertEquals("Unexpected number of rows", 3000, rowCounter);
+            } catch (RepositoryException e) {
+                throw new RuntimeException(e);
+            }
+        });
     }
 
     @Test
@@ -196,11 +218,14 @@ public abstract class FacetCommonTest extends 
AbstractJcrTest {
 
         createDataset(NUM_LEAF_NODES_FOR_SMALL_DATASET);
 
-        assertEventually(() -> assertEquals("Unexpected number of facets", 
actualAclLabelCount.size(), getFacets().size()));
+        assertEventually(() -> {
+            Map<String, Integer> facets = getFacets();
+            assertEquals("Unexpected number of facets", 
actualAclLabelCount.size(), facets.size());
 
-        // Since the hit count is less than sample size -> flow should have 
switched to secure facet count instead of statistical
-        // and thus the count should be exactly equal
-        assertEventually(() -> assertEquals(actualAclLabelCount, getFacets()));
+            // Since the hit count is less than sample size -> flow should 
have switched to secure facet count instead of statistical
+            // and thus the count should be exactly equal
+            assertEquals(actualAclLabelCount, facets);
+        });
     }
 
     @Test
@@ -242,19 +267,16 @@ public abstract class FacetCommonTest extends 
AbstractJcrTest {
         assertEventually(() -> {
             Map<String, Integer> facets = getFacets();
             assertEquals("Unexpected number of facets", 
actualAclLabelCount.size(), facets.size());
-        });
-
-        for (Map.Entry<String, Integer> facet : 
actualAclLabelCount.entrySet()) {
 
-            assertEventually(() -> {
+            for (Map.Entry<String, Integer> facet : 
actualAclLabelCount.entrySet()) {
                 String facetLabel = facet.getKey();
-                int facetCount = getFacets().get(facetLabel);
+                int facetCount = facets.get(facetLabel);
                 float ratio = ((float) facetCount) / facet.getValue();
                 assertTrue("Facet count for label: " + facetLabel + " is 
outside of 10% margin of error. " +
                                 "Expected: " + facet.getValue() + "; Got: " + 
facetCount + "; Ratio: " + ratio,
                         Math.abs(ratio - 1) < 0.1);
-            });
-        }
+            }
+        });
     }
 
     @Test
@@ -279,18 +301,16 @@ public abstract class FacetCommonTest extends 
AbstractJcrTest {
         assertEventually(() -> {
             Map<String, Integer> facets = getFacets();
             assertEquals("Unexpected number of facets", 
actualLabelCount.size(), facets.size());
-        });
 
-        for (Map.Entry<String, Integer> facet : actualLabelCount.entrySet()) {
-            assertEventually(() -> {
+            for (Map.Entry<String, Integer> facet : 
actualLabelCount.entrySet()) {
                 String facetLabel = facet.getKey();
-                int facetCount = getFacets().get(facetLabel);
+                int facetCount = facets.get(facetLabel);
                 float ratio = ((float) facetCount) / facet.getValue();
                 assertTrue("Facet count for label: " + facetLabel + " is 
outside of 5% margin of error. " +
                                 "Expected: " + facet.getValue() + "; Got: " + 
facetCount + "; Ratio: " + ratio,
                         Math.abs(ratio - 1) < 0.05);
-            });
-        }
+            }
+        });
     }
 
     private Map<String, Integer> getFacets() {
@@ -308,11 +328,23 @@ public abstract class FacetCommonTest extends 
AbstractJcrTest {
     }
 
     private Map<String, Integer> getFacets(String path) {
+        QueryResult queryResult = getQueryResult(path);
+        long start = LOG_PERF.start("Getting the Facet Results...");
+        FacetResult facetResult = new FacetResult(queryResult);
+        LOG_PERF.end(start, -1, "Facet Results fetched");
+
+        return facetResult.getDimensions()
+                .stream()
+                .flatMap(dim -> 
Objects.requireNonNull(facetResult.getFacets(dim)).stream())
+                .collect(Collectors.toMap(FacetResult.Facet::getLabel, 
FacetResult.Facet::getCount));
+    }
+
+    private QueryResult getQueryResult(String path) {
         String pathCons = "";
         if (path != null) {
             pathCons = " AND ISDESCENDANTNODE('" + path + "')";
         }
-        String query = "SELECT [rep:facet(foo)], [rep:facet(bar)], 
[rep:facet(baz)] FROM [nt:base] WHERE [cons] = 'val'" + pathCons;
+        String query = "SELECT [jcr:path], [rep:facet(foo)], [rep:facet(bar)], 
[rep:facet(baz)] FROM [nt:base] WHERE [cons] = 'val'" + pathCons;
         Query q;
         QueryResult queryResult;
         try {
@@ -321,14 +353,7 @@ public abstract class FacetCommonTest extends 
AbstractJcrTest {
         } catch (RepositoryException e) {
             throw new RuntimeException(e);
         }
-        long start = LOG_PERF.start("Getting the Facet Results...");
-        FacetResult facetResult = new FacetResult(queryResult);
-        LOG_PERF.end(start, -1, "Facet Results fetched");
-
-        return facetResult.getDimensions()
-                .stream()
-                .flatMap(dim -> 
Objects.requireNonNull(facetResult.getFacets(dim)).stream())
-                .collect(Collectors.toMap(FacetResult.Facet::getLabel, 
FacetResult.Facet::getCount));
+        return queryResult;
     }
 
     protected void assertEventually(Runnable r) {

Reply via email to