Author: catholicon
Date: Wed Dec  5 00:21:54 2018
New Revision: 1848182

URL: http://svn.apache.org/viewvc?rev=1848182&view=rev
Log:
OAK-7929: Incorrect Facet Count With Large Dataset and ACLs

* Allow secure and sampleSize under <index-def>/facets
* These can be overridden by JVM command line param
* Inject seed into index definition during indexing cycle
* Rename FilteredFacetCounts to SecureFacetCounts to serve for secure
configuration
* StatisticalSortedSetDocValueFacetCounts would do random sampling of
results wrt what ratio are accessible. Use this ratio to extrapolate
facet counts returned by index
* Add TemporarySystemProperty rule to ease writing tests which set system 
property

Added:
    
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/junit/TemporarySystemProperty.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/SecureSortedSetDocValuesFacetCounts.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java
   (with props)
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionFacetConfigTest.java
   (with props)
Removed:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FilteredSortedSetDocValuesFacetCounts.java
Modified:
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
    
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
    
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
    jackrabbit/oak/trunk/oak-search/pom.xml
    
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java
    
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java
    
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditorContext.java

Added: 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/junit/TemporarySystemProperty.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/junit/TemporarySystemProperty.java?rev=1848182&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/junit/TemporarySystemProperty.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/junit/TemporarySystemProperty.java
 Wed Dec  5 00:21:54 2018
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.commons.junit;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.Properties;
+
+public class TemporarySystemProperty extends ExternalResource {
+
+    Properties oldProps = new Properties();
+
+    @Override
+    protected void before() {
+        oldProps.putAll(System.getProperties());
+    }
+
+    @Override
+    protected void after() {
+        System.setProperties(oldProps);
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/junit/TemporarySystemProperty.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java?rev=1848182&r1=1848181&r2=1848182&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java
 Wed Dec  5 00:21:54 2018
@@ -120,7 +120,6 @@ import org.apache.lucene.search.highligh
 import org.apache.lucene.search.postingshighlight.PostingsHighlighter;
 import org.apache.lucene.search.spell.SuggestWord;
 import org.apache.lucene.search.suggest.Lookup;
-import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.Version;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -129,7 +128,6 @@ import org.slf4j.LoggerFactory;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Predicates.in;
 import static com.google.common.base.Predicates.notNull;
 import static com.google.common.collect.Lists.newArrayListWithCapacity;
 import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
@@ -353,7 +351,7 @@ public class LucenePropertyIndex extends
                             long f = PERF_LOGGER.start();
                             if (facetProvider == null) {
                                 facetProvider = new LuceneFacetProvider(
-                                        FacetHelper.getFacets(searcher, query, 
docs, plan, indexNode.getDefinition().isSecureFacets())
+                                        FacetHelper.getFacets(searcher, query, 
plan, indexNode.getDefinition().getSecureFacetConfiguration())
                                 );
                                 PERF_LOGGER.end(f, -1, "facets retrieved");
                             }

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java?rev=1848182&r1=1848181&r2=1848182&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/FacetHelper.java
 Wed Dec  5 00:21:54 2018
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import 
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.SecureFacetConfiguration;
 import org.apache.jackrabbit.oak.spi.query.QueryConstants;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
@@ -35,7 +36,7 @@ import org.apache.lucene.facet.sortedset
 import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetCounts;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.Sort;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,7 +59,8 @@ public class FacetHelper {
         return new NodeStateFacetsConfig(definition);
     }
 
-    public static Facets getFacets(IndexSearcher searcher, Query query, 
TopDocs docs, QueryIndex.IndexPlan plan, boolean secure) throws IOException {
+    public static Facets getFacets(IndexSearcher searcher, Query query, 
QueryIndex.IndexPlan plan,
+                                   SecureFacetConfiguration 
secureFacetConfiguration) throws IOException {
         Facets facets = null;
         @SuppressWarnings("unchecked")
         List<String> facetFields = (List<String>) 
plan.getAttribute(ATTR_FACET_FIELDS);
@@ -70,10 +72,23 @@ public class FacetHelper {
                 try {
                     DefaultSortedSetDocValuesReaderState state = new 
DefaultSortedSetDocValuesReaderState(
                             searcher.getIndexReader(), 
FieldNames.createFacetFieldName(facetField));
-                        FacetsCollector.search(searcher, query, 10, 
facetsCollector);
-                    facetsMap.put(facetField, secure ?
-                            new FilteredSortedSetDocValuesFacetCounts(state, 
facetsCollector, plan.getFilter(), docs) :
-                            new SortedSetDocValuesFacetCounts(state, 
facetsCollector));
+                    FacetsCollector.search(searcher, query, null,1, 
Sort.INDEXORDER, facetsCollector);
+
+                    switch (secureFacetConfiguration.getMode()) {
+                        case INSECURE:
+                            facets = new SortedSetDocValuesFacetCounts(state, 
facetsCollector);
+                            break;
+                        case STATISTICAL:
+                            facets = new 
StatisticalSortedSetDocValuesFacetCounts(state, facetsCollector, 
plan.getFilter(),
+                                    secureFacetConfiguration);
+                            break;
+                        case SECURE:
+                        default:
+                            facets = new 
SecureSortedSetDocValuesFacetCounts(state, facetsCollector, plan.getFilter());
+                            break;
+                    }
+
+                    facetsMap.put(facetField, facets);
 
                 } catch (IllegalArgumentException iae) {
                     LOGGER.warn("facets for {} not yet indexed", facetField);

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/SecureSortedSetDocValuesFacetCounts.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/SecureSortedSetDocValuesFacetCounts.java?rev=1848182&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/SecureSortedSetDocValuesFacetCounts.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/SecureSortedSetDocValuesFacetCounts.java
 Wed Dec  5 00:21:54 2018
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.lucene.util;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.facet.FacetResult;
+import org.apache.lucene.facet.FacetsCollector;
+import org.apache.lucene.facet.FacetsCollector.MatchingDocs;
+import org.apache.lucene.facet.FacetsConfig;
+import org.apache.lucene.facet.LabelAndValue;
+import org.apache.lucene.facet.sortedset.DefaultSortedSetDocValuesReaderState;
+import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetCounts;
+import org.apache.lucene.facet.sortedset.SortedSetDocValuesReaderState;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * ACL filtered version of {@link SortedSetDocValuesFacetCounts}
+ */
+class SecureSortedSetDocValuesFacetCounts extends 
SortedSetDocValuesFacetCounts {
+
+    private final FacetsCollector facetsCollector;
+    private final Filter filter;
+    private final IndexReader reader;
+    private final SortedSetDocValuesReaderState state;
+    private FacetResult facetResult = null;
+
+    SecureSortedSetDocValuesFacetCounts(DefaultSortedSetDocValuesReaderState 
state, FacetsCollector facetsCollector, Filter filter) throws IOException {
+        super(state, facetsCollector);
+        this.reader = state.origReader;
+        this.facetsCollector = facetsCollector;
+        this.filter = filter;
+        this.state = state;
+    }
+
+    @Override
+    public FacetResult getTopChildren(int topN, String dim, String... path) 
throws IOException {
+        if (facetResult == null) {
+            facetResult = getTopChildren0(topN, dim, path);
+        }
+
+        return facetResult;
+    }
+
+    private FacetResult getTopChildren0(int topN, String dim, String... path) 
throws IOException {
+        FacetResult topChildren = super.getTopChildren(topN, dim, path);
+
+        if (topChildren == null) {
+            return null;
+        }
+
+        InaccessibleFacetCountManager inaccessibleFacetCountManager =
+                new InaccessibleFacetCountManager(dim, reader, filter, state, 
facetsCollector, topChildren.labelValues);
+        inaccessibleFacetCountManager.filterFacets();
+        LabelAndValue[] labelAndValues = 
inaccessibleFacetCountManager.updateLabelAndValue();
+
+        int childCount = labelAndValues.length;
+        Number value = 0;
+        for (LabelAndValue lv : labelAndValues) {
+            value = value.longValue() + lv.value.longValue();
+        }
+
+        return new FacetResult(dim, path, value, labelAndValues, childCount);
+    }
+
+    static class InaccessibleFacetCountManager {
+        private final String dimension;
+        private final IndexReader reader;
+        private final Filter filter;
+        private final SortedSetDocValuesReaderState state;
+        private final FacetsCollector facetsCollector;
+        private final LabelAndValue[] labelAndValues;
+        private final Map<String, Integer> labelToIndexMap;
+        private final long[] inaccessibleCounts;
+
+        InaccessibleFacetCountManager(String dimension,
+                                      IndexReader reader, Filter filter, 
SortedSetDocValuesReaderState state,
+                                      FacetsCollector facetsCollector, 
LabelAndValue[] labelAndValues) {
+            this.dimension = dimension;
+            this.reader = reader;
+            this.filter = filter;
+            this.state = state;
+            this.facetsCollector = facetsCollector;
+            this.labelAndValues = labelAndValues;
+            inaccessibleCounts = new long[labelAndValues.length];
+
+            Map<String, Integer> map = Maps.newHashMap();
+            for (int i = 0; i < labelAndValues.length; i++) {
+                LabelAndValue lv = labelAndValues[i];
+                map.put(lv.label, i);
+            }
+            labelToIndexMap = Collections.unmodifiableMap(map);
+        }
+
+        void filterFacets() throws IOException {
+            List<MatchingDocs> matchingDocsList = 
facetsCollector.getMatchingDocs();
+            for (MatchingDocs matchingDocs : matchingDocsList) {
+                DocIdSet bits = matchingDocs.bits;
+
+                DocIdSetIterator docIdSetIterator = bits.iterator();
+                int doc = docIdSetIterator.nextDoc();
+                while (doc != DocIdSetIterator.NO_MORE_DOCS) {
+                    int docId = matchingDocs.context.docBase + doc;
+                    filterFacet(docId);
+                    doc = docIdSetIterator.nextDoc();
+                }
+            }
+        }
+
+        private void filterFacet(int docId) throws IOException {
+            Document document = reader.document(docId);
+
+            // filter using doc values (avoiding requiring stored values)
+            if 
(!filter.isAccessible(document.getField(FieldNames.PATH).stringValue() + "/" + 
dimension)) {
+
+                SortedSetDocValues docValues = state.getDocValues();
+                docValues.setDocument(docId);
+                TermsEnum termsEnum = docValues.termsEnum();
+
+                long ord = docValues.nextOrd();
+
+                while (ord != SortedSetDocValues.NO_MORE_ORDS) {
+                    termsEnum.seekExact(ord);
+                    String facetDVTerm = termsEnum.term().utf8ToString();
+                    String [] facetDVDimPaths = 
FacetsConfig.stringToPath(facetDVTerm);
+
+                    // first element is dimention name
+                    for (int i = 1; i < facetDVDimPaths.length; i++) {
+                        markInaccessbile(facetDVDimPaths[i]);
+                    }
+
+                    ord = docValues.nextOrd();
+                }
+            }
+        }
+
+        void markInaccessbile(@NotNull String label) {
+            inaccessibleCounts[labelToIndexMap.get(label)]++;
+        }
+
+        LabelAndValue[] updateLabelAndValue() {
+            int numZeros = 0;
+
+            LabelAndValue[] newValues;
+
+            for (int i = 0; i < labelAndValues.length; i++) {
+                LabelAndValue lv = labelAndValues[i];
+                long inaccessibleCount = 
inaccessibleCounts[labelToIndexMap.get(lv.label)];
+
+                if (inaccessibleCount > 0) {
+                    long newValue = lv.value.longValue() - inaccessibleCount;
+
+                    if (newValue <= 0) {
+                        newValue = 0;
+                        numZeros++;
+                    }
+
+                    labelAndValues[i] = new LabelAndValue(lv.label, newValue);
+                }
+            }
+
+            if (numZeros > 0) {
+                newValues = new LabelAndValue[labelAndValues.length - 
numZeros];
+                int i = 0;
+                for (LabelAndValue lv : labelAndValues) {
+                    if (lv.value.longValue() > 0) {
+                        newValues[i++] = lv;
+                    }
+                }
+            } else {
+                newValues = labelAndValues;
+            }
+
+            return newValues;
+        }
+    }
+}
\ No newline at end of file

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

Added: 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java?rev=1848182&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/util/StatisticalSortedSetDocValuesFacetCounts.java
 Wed Dec  5 00:21:54 2018
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.lucene.util;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.AbstractIterator;
+import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
+import 
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.SecureFacetConfiguration;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.facet.FacetResult;
+import org.apache.lucene.facet.FacetsCollector;
+import org.apache.lucene.facet.FacetsCollector.MatchingDocs;
+import org.apache.lucene.facet.LabelAndValue;
+import org.apache.lucene.facet.sortedset.DefaultSortedSetDocValuesReaderState;
+import org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetCounts;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+
+import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
+
+/**
+ * ACL filtered version of {@link SortedSetDocValuesFacetCounts}
+ */
+class StatisticalSortedSetDocValuesFacetCounts extends 
SortedSetDocValuesFacetCounts {
+    private static final Logger LOG = 
LoggerFactory.getLogger(StatisticalSortedSetDocValuesFacetCounts.class);
+
+    private final FacetsCollector facetsCollector;
+    private final Filter filter;
+    private final IndexReader reader;
+    private final SecureFacetConfiguration secureFacetConfiguration;
+    private FacetResult facetResult = null;
+
+    
StatisticalSortedSetDocValuesFacetCounts(DefaultSortedSetDocValuesReaderState 
state,
+                                                    FacetsCollector 
facetsCollector, Filter filter,
+                                                    SecureFacetConfiguration 
secureFacetConfiguration) throws IOException {
+        super(state, facetsCollector);
+        this.reader = state.origReader;
+        this.facetsCollector = facetsCollector;
+        this.filter = filter;
+        this.secureFacetConfiguration = secureFacetConfiguration;
+    }
+
+    @Override
+    public FacetResult getTopChildren(int topN, String dim, String... path) 
throws IOException {
+        if (facetResult == null) {
+            facetResult = getTopChildren0(topN, dim, path);
+        }
+
+        return facetResult;
+    }
+
+    private FacetResult getTopChildren0(int topN, String dim, String... path) 
throws IOException {
+        FacetResult topChildren = super.getTopChildren(topN, dim, path);
+
+        if (topChildren == null) {
+            return null;
+        }
+
+        LabelAndValue[] labelAndValues = topChildren.labelValues;
+
+        List<MatchingDocs> matchingDocsList = 
facetsCollector.getMatchingDocs();
+
+        int hitCount = 0;
+        for (MatchingDocs matchingDocs : matchingDocsList) {
+            hitCount += matchingDocs.totalHits;
+        }
+        int sampleSize = 
secureFacetConfiguration.getStatisticalFacetSampleSize();
+        long randomSeed = secureFacetConfiguration.getRandomSeed();
+
+        LOG.debug("Sampling facet dim {}; hitCount: {}, sampleSize: {}, seed: 
{}", dim, hitCount, sampleSize, randomSeed);
+
+        Stopwatch w = Stopwatch.createStarted();
+        Iterator<Integer> docIterator = 
getMatchingDocIterator(matchingDocsList);
+        Iterator<Integer> sampleIterator = docIterator;
+        if (sampleSize < hitCount) {
+            sampleIterator = getSampledMatchingDocIterator(docIterator, 
randomSeed, hitCount, sampleSize);
+        } else {
+            sampleSize = hitCount;
+        }
+        int accessibleSampleCount = getAccessibleSampleCount(dim, 
sampleIterator);
+        w.stop();
+
+        LOG.debug("Evaluated accessible samples {} in {}", 
accessibleSampleCount, w);
+
+        labelAndValues = updateLabelAndValueIfRequired(labelAndValues, 
sampleSize, accessibleSampleCount);
+
+        int childCount = labelAndValues.length;
+        Number value = 0;
+        for (LabelAndValue lv : labelAndValues) {
+            value = value.longValue() + lv.value.longValue();
+        }
+
+        return new FacetResult(dim, path, value, labelAndValues, childCount);
+    }
+
+    private Iterator<Integer> getMatchingDocIterator(final List<MatchingDocs> 
matchingDocsList) {
+        Iterator<MatchingDocs> matchingDocsListIterator = 
matchingDocsList.iterator();
+
+        return new AbstractIterator<Integer>() {
+            MatchingDocs matchingDocs = null;
+            DocIdSetIterator docIdSetIterator = null;
+            int nextDocId = NO_MORE_DOCS;
+            @Override
+            protected Integer computeNext() {
+                try {
+                    loadNextMatchingDocsIfRequired();
+
+                    if (nextDocId == NO_MORE_DOCS) {
+                        return endOfData();
+                    } else {
+                        int ret = nextDocId;
+                        nextDocId = docIdSetIterator.nextDoc();
+                        return matchingDocs.context.docBase + ret;
+                    }
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            private void loadNextMatchingDocsIfRequired() throws IOException {
+                while (nextDocId == NO_MORE_DOCS) {
+                    if (matchingDocsListIterator.hasNext()) {
+                        matchingDocs = matchingDocsListIterator.next();
+                        docIdSetIterator = matchingDocs.bits.iterator();
+                        nextDocId = docIdSetIterator.nextDoc();
+                    } else {
+                        return;
+                    }
+                }
+            }
+        };
+    }
+
+    private Iterator<Integer> getSampledMatchingDocIterator(Iterator<Integer> 
matchingDocs,
+                                                            long randomdSeed, 
int hitCount, int sampleSize) {
+        TapeSampling<Integer> tapeSampling = new TapeSampling<>(new 
Random(randomdSeed), matchingDocs, hitCount, sampleSize);
+
+        return tapeSampling.getSamples();
+    }
+
+    private int getAccessibleSampleCount(String dim, Iterator<Integer> 
sampleIterator) throws IOException {
+        int count = 0;
+        while (sampleIterator.hasNext()) {
+            int docId = sampleIterator.next();
+            Document doc = reader.document(docId);
+
+            if 
(filter.isAccessible(doc.getField(FieldNames.PATH).stringValue() + "/" + dim)) {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    private LabelAndValue[] updateLabelAndValueIfRequired(LabelAndValue[] 
labelAndValues,
+                                                          int sampleSize, int 
accessibleCount) {
+        if (accessibleCount < sampleSize) {
+            int numZeros = 0;
+
+            LabelAndValue[] newValues;
+
+            {
+                LabelAndValue[] proportionedLVs = new 
LabelAndValue[labelAndValues.length];
+
+                for (int i = 0; i < labelAndValues.length; i++) {
+                    LabelAndValue lv = labelAndValues[i];
+                    long count = Math.floorDiv(lv.value.longValue() * 
accessibleCount, sampleSize);
+                    if (count == 0) {
+                        numZeros++;
+                    }
+                    proportionedLVs[i] = new LabelAndValue(lv.label, count);
+                }
+
+                labelAndValues = proportionedLVs;
+            }
+
+            if (numZeros > 0) {
+                newValues = new LabelAndValue[labelAndValues.length - 
numZeros];
+                int i = 0;
+                for (LabelAndValue lv : labelAndValues) {
+                    if (lv.value.longValue() > 0) {
+                        newValues[i++] = lv;
+                    }
+                }
+            } else {
+                newValues = labelAndValues;
+            }
+
+            return newValues;
+        } else {
+            return labelAndValues;
+        }
+    }
+}
\ No newline at end of file

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

Modified: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java?rev=1848182&r1=1848181&r2=1848182&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
 Wed Dec  5 00:21:54 2018
@@ -133,14 +133,7 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.plugins.index.lucene.TestUtil.useV2;
 import static 
org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorTest.createCal;
 import static 
org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_NAMES;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.ORDERED_PROP_NAMES;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_ANALYZED;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NAME;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NODE;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NODE_SCOPE_INDEX;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_PROPERTY_INDEX;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_TYPE;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.*;
 import static 
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.INDEX_DEFINITION_NODE;
 import static 
org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
 import static 
org.apache.jackrabbit.oak.spi.filter.PathFilter.PROP_EXCLUDED_PATHS;
@@ -2908,6 +2901,7 @@ public class LucenePropertyIndexTest ext
         idxb.indexRule("nt:base").property("foo").propertyIndex();
         Tree idx = root.getTree("/").getChild("oak:index").addChild("test1");
         idxb.build(idx);
+        idx.setProperty(PROP_RANDOM_SEED, new Random().nextLong());
         root.commit();
 
         AsyncIndexInfoService asyncService = new 
AsyncIndexInfoServiceImpl(nodeStore);
@@ -3153,6 +3147,124 @@ public class LucenePropertyIndexTest ext
         }
     }
 
+    @Test
+    public void injectRandomSeedDuringReindex() throws Exception{
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo").propertyIndex();
+        Tree idx = root.getTree("/").getChild("oak:index").addChild("test1");
+        idxb.build(idx);
+
+        root.commit();
+
+        // Push a change to get another indexing cycle run
+        root.getTree("/").addChild("force-index-run");
+        root.commit();
+
+        NodeState idxState = NodeStateUtils.getNode(nodeStore.getRoot(), 
idx.getPath());
+
+        long defSeed = 
idxState.getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+        long clonedSeed = 
idxState.getChildNode(INDEX_DEFINITION_NODE).getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+
+        assertTrue("Injected def (" + defSeed + ")and clone (" + clonedSeed + 
" seeds aren't same", defSeed == clonedSeed);
+    }
+
+    @Test
+    public void injectRandomSeedDuringRefresh() throws Exception{
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo").propertyIndex();
+        Tree idx = root.getTree("/").getChild("oak:index").addChild("test1");
+        idxb.build(idx);
+
+        root.commit();
+
+        long seed = new Random().nextLong();
+
+        // Push a change to get another indexing cycle run
+        idx.setProperty(PROP_RANDOM_SEED, seed);
+        idx.setProperty(PROP_REFRESH_DEFN, true);
+        root.commit();
+
+        NodeState idxState = NodeStateUtils.getNode(nodeStore.getRoot(), 
idx.getPath());
+
+        long defSeed = 
idxState.getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+        long clonedSeed = 
idxState.getChildNode(INDEX_DEFINITION_NODE).getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+
+        assertEquals("Random seed not updated", seed, defSeed);
+        assertTrue("Injected def (" + defSeed + ")and clone (" + clonedSeed + 
" seeds aren't same", defSeed == clonedSeed);
+    }
+
+    @Test
+    public void injectRandomSeedDuringUpdate() throws Exception{
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo").propertyIndex();
+        Tree idx = root.getTree("/").getChild("oak:index").addChild("test1");
+        idxb.build(idx);
+
+        root.commit();
+
+        long seed = new Random().nextLong();
+
+        // Update seed
+        idx.setProperty(PROP_RANDOM_SEED, seed);
+        root.commit();
+
+        NodeState idxState = NodeStateUtils.getNode(nodeStore.getRoot(), 
idx.getPath());
+
+        long defSeed = 
idxState.getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+        long clonedSeed = 
idxState.getChildNode(INDEX_DEFINITION_NODE).getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+
+        assertEquals("Random seed not updated", seed, defSeed);
+        assertTrue("Injected def (" + defSeed + ")and clone (" + clonedSeed + 
" seeds aren't same", defSeed == clonedSeed);
+    }
+
+    @Test
+    public void injectGarbageSeed() throws Exception {
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo").propertyIndex();
+        Tree idx = root.getTree("/").getChild("oak:index").addChild("test1");
+        idxb.build(idx);
+
+        root.commit();
+
+        long orignalSeed = NodeStateUtils.getNode(nodeStore.getRoot(), 
idx.getPath())
+                .getProperty(PROP_RANDOM_SEED)
+                .getValue(Type.LONG);
+
+        // Update seed
+        idx.setProperty(PROP_RANDOM_SEED, "garbage");
+        root.commit();
+
+        NodeState idxState = NodeStateUtils.getNode(nodeStore.getRoot(), 
idx.getPath());
+
+        long defSeed = 
idxState.getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+        long clonedSeed = 
idxState.getChildNode(INDEX_DEFINITION_NODE).getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+
+        assertNotEquals("Random seed not updated", orignalSeed, defSeed);
+        assertNotEquals("Random seed updated to garbage", "garbage", defSeed);
+        assertTrue("Injected def (" + defSeed + ")and clone (" + clonedSeed + 
" seeds aren't same", defSeed == clonedSeed);
+    }
+
+    @Test
+    public void injectStringLongSeed() throws Exception {
+        IndexDefinitionBuilder idxb = new IndexDefinitionBuilder().noAsync();
+        idxb.indexRule("nt:base").property("foo").propertyIndex();
+        Tree idx = root.getTree("/").getChild("oak:index").addChild("test1");
+        idxb.build(idx);
+
+        root.commit();
+        // Update seed
+        idx.setProperty(PROP_RANDOM_SEED, "-12312");
+        root.commit();
+
+        NodeState idxState = NodeStateUtils.getNode(nodeStore.getRoot(), 
idx.getPath());
+
+        long defSeed = 
idxState.getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+        long clonedSeed = 
idxState.getChildNode(INDEX_DEFINITION_NODE).getProperty(PROP_RANDOM_SEED).getValue(Type.LONG);
+
+        assertEquals("Random seed udpated to garbage", -12312, defSeed);
+        assertTrue("Injected def (" + defSeed + ")and clone (" + clonedSeed + 
" seeds aren't same", defSeed == clonedSeed);
+    }
+
     private void assertPlanAndQuery(String query, String planExpectation, 
List<String> paths) {
         assertPlanAndQuery(query, planExpectation, paths, false);
     }

Added: 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java?rev=1848182&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/SecureFacetTest.java
 Wed Dec  5 00:21:54 2018
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.index.lucene;
+
+import com.google.common.collect.Maps;
+import com.google.common.io.Closer;
+import 
org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import 
org.apache.jackrabbit.oak.plugins.index.lucene.util.IndexDefinitionBuilder;
+import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.query.facet.FacetResult;
+import org.apache.jackrabbit.oak.query.facet.FacetResult.Facet;
+import org.apache.jackrabbit.oak.spi.commit.Observer;
+import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import javax.jcr.*;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.security.Privilege;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.apache.jackrabbit.commons.JcrUtils.getOrCreateByPath;
+import static org.apache.jackrabbit.oak.InitialContentHelper.INITIAL_CONTENT;
+import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.*;
+import static org.junit.Assert.*;
+
+public class SecureFacetTest {
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder(new 
File("target"));
+
+    private Closer closer;
+
+    private Session session;
+    private Node indexNode;
+    private QueryManager qe;
+
+    private static final int NUM_LEAF_NODES = 
STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT;
+    private static final int NUM_LABELS = 4;
+    private Map<String, Integer> actualLabelCount = Maps.newHashMap();
+    private Map<String, Integer> actualAclLabelCount = Maps.newHashMap();
+    private Map<String, Integer> actualAclPar1LabelCount = Maps.newHashMap();
+
+    @Before
+    public void setup() throws Exception {
+        closer = Closer.create();
+        createRepository();
+        createIndex();
+    }
+
+    private void createRepository() throws RepositoryException, IOException {
+        ExecutorService executorService = Executors.newFixedThreadPool(2);
+        closer.register(new ExecutorCloser(executorService));
+        IndexCopier copier = new IndexCopier(executorService, 
temporaryFolder.getRoot());
+        LuceneIndexEditorProvider editorProvider = new 
LuceneIndexEditorProvider(copier);
+        LuceneIndexProvider queryIndexProvider = new 
LuceneIndexProvider(copier);
+        NodeStore nodeStore = new MemoryNodeStore(INITIAL_CONTENT);
+        Oak oak = new Oak(nodeStore)
+                .with((QueryIndexProvider) queryIndexProvider)
+                .with((Observer) queryIndexProvider)
+                .with(editorProvider);
+
+        Jcr jcr = new Jcr(oak);
+        @NotNull Repository repository = jcr.createRepository();
+
+        session = repository.login(new SimpleCredentials("admin", 
"admin".toCharArray()), null);
+        closer.register(session::logout);
+
+        // we'd always query anonymously
+        Session anonSession = repository.login(new GuestCredentials());
+        closer.register(anonSession::logout);
+        qe = anonSession.getWorkspace().getQueryManager();
+    }
+
+    @After
+    public void after() throws IOException {
+        closer.close();
+        IndexDefinition.setDisableStoredIndexDefinition(false);
+    }
+
+    private void createIndex() throws RepositoryException {
+        IndexDefinitionBuilder idxBuilder = new IndexDefinitionBuilder();
+        idxBuilder.noAsync().evaluatePathRestrictions()
+                .indexRule("nt:base")
+                .property("cons")
+                .propertyIndex()
+                .property("foo")
+                .getBuilderTree().setProperty(PROP_FACETS, true);
+
+        indexNode = getOrCreateByPath("/oak:index", "nt:unstructured", session)
+                .addNode("index", INDEX_DEFINITIONS_NODE_TYPE);
+        idxBuilder.build(indexNode);
+        session.save();
+    }
+
+    private void createLargeDataset() throws RepositoryException {
+        Random rGen = new Random(42);
+        int[] labelCount = new int[NUM_LABELS];
+        int[] aclLabelCount = new int[NUM_LABELS];
+        int[] aclPar1LabelCount = new int[NUM_LABELS];
+
+        Node par = allow(getOrCreateByPath("/parent", "oak:Unstructured", 
session));
+
+        for (int i = 0; i < 4; i++) {
+            Node subPar = par.addNode("par" + i);
+            for (int j = 0; j < NUM_LEAF_NODES; j++) {
+                Node child = subPar.addNode("c" + j);
+                child.setProperty("cons", "val");
+
+                // Add a random label out of "l0", "l1", "l2", "l3"
+                int labelNum = rGen.nextInt(NUM_LABELS);
+                child.setProperty("foo", "l" + labelNum);
+
+                labelCount[labelNum]++;
+                if (i != 0) {
+                    aclLabelCount[labelNum]++;
+                }
+                if (i == 1) {
+                    aclPar1LabelCount[labelNum]++;
+                }
+            }
+
+            // deny access for one sub-parent
+            if (i == 0) {
+                deny(subPar);
+            }
+        }
+
+        session.save();
+
+        for (int i = 0; i < labelCount.length; i++) {
+            actualLabelCount.put("l" + i, labelCount[i]);
+            actualAclLabelCount.put("l" + i, aclLabelCount[i]);
+            actualAclPar1LabelCount.put("l" + i, aclPar1LabelCount[i]);
+        }
+
+        assertNotEquals("Acl-ed and actual counts mustn't be same", 
actualLabelCount, actualAclLabelCount);
+    }
+
+    @Test
+    public void secureFacets() throws Exception {
+        createLargeDataset();
+
+        assertEquals(actualAclLabelCount, getFacets());
+    }
+
+    @Test
+    public void secureFacets_withOneLabelInaccessible() throws Exception {
+        createLargeDataset();
+        Node inaccessibleChild = 
deny(session.getNode("/parent").addNode("par4")).addNode("c0");
+        inaccessibleChild.setProperty("cons", "val");
+        inaccessibleChild.setProperty("foo", "l4");
+        session.save();
+
+        assertEquals(actualAclLabelCount, getFacets());
+    }
+
+    @Test
+    public void insecureFacets() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + 
FACETS, "nt:unstructured", session);
+        facetConfig.setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_INSECURE);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        session.save();
+
+        createLargeDataset();
+
+        assertEquals(actualLabelCount, getFacets());
+    }
+
+    @Test
+    public void statisticalFacets() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + 
FACETS, "nt:unstructured", session);
+        facetConfig.setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        session.save();
+
+        createLargeDataset();
+
+        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();
+            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
+    public void statisticalFacets_withHitCountSameAsSampleSize() throws 
Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + 
FACETS, "nt:unstructured", session);
+        facetConfig.setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        session.save();
+
+        createLargeDataset();
+
+        Map<String, Integer> facets = getFacets("/parent/par1");
+        assertEquals("Unexpected number of facets", 
actualAclPar1LabelCount.size(), facets.size());
+
+        for (Map.Entry<String, Integer> facet : 
actualAclPar1LabelCount.entrySet()) {
+            String facetLabel = facet.getKey();
+            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
+    public void statisticalFacets_withOneLabelInaccessible() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + 
FACETS, "nt:unstructured", session);
+        facetConfig.setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        session.save();
+
+        createLargeDataset();
+        Node inaccessibleChild = 
deny(session.getNode("/parent").addNode("par4")).addNode("c0");
+        inaccessibleChild.setProperty("cons", "val");
+        inaccessibleChild.setProperty("foo", "l4");
+        session.save();
+
+        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();
+            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
+    public void secureFacets_withAdminSession() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + 
FACETS, "nt:unstructured", session);
+        facetConfig.setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_INSECURE);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        session.save();
+
+        createLargeDataset();
+
+        qe = session.getWorkspace().getQueryManager();
+
+        assertEquals(actualLabelCount, getFacets());
+    }
+
+    @Test
+    public void statisticalFacets_withAdminSession() throws Exception {
+        Node facetConfig = getOrCreateByPath(indexNode.getPath() + "/" + 
FACETS, "nt:unstructured", session);
+        facetConfig.setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        indexNode.setProperty(PROP_REFRESH_DEFN, true);
+        session.save();
+
+        createLargeDataset();
+
+        qe = session.getWorkspace().getQueryManager();
+
+        Map<String, Integer> facets = getFacets();
+        assertEquals("Unexpected number of facets", actualLabelCount.size(), 
facets.size());
+
+        for (Map.Entry<String, Integer> facet : actualLabelCount.entrySet()) {
+            String facetLabel = facet.getKey();
+            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() throws Exception {
+        return getFacets(null);
+    }
+
+    private Map<String, Integer> getFacets(String path) throws Exception {
+        Map<String, Integer> map = Maps.newHashMap();
+
+        String pathCons = "";
+        if (path != null) {
+            pathCons = " AND ISDESCENDANTNODE('" + path + "')";
+        }
+        String query = "SELECT [rep:facet(foo)] FROM [nt:base] WHERE [cons] = 
'val'" + pathCons;
+
+        Query q = qe.createQuery(query, Query.JCR_SQL2);
+        QueryResult queryResult = q.execute();
+
+        FacetResult facetResult = new FacetResult(queryResult);
+
+        Set<String> dims = facetResult.getDimensions();
+        for (String dim : dims) {
+            List<Facet> facets = facetResult.getFacets(dim);
+            for (Facet facet : facets) {
+                map.put(facet.getLabel(), facet.getCount());
+            }
+        }
+
+        return map;
+    }
+
+    @SuppressWarnings("UnusedReturnValue")
+    private Node deny(Node node) throws RepositoryException {
+        AccessControlUtils.deny(node, "anonymous", Privilege.JCR_ALL);
+        return node;
+    }
+
+    private Node allow(Node node) throws RepositoryException {
+        AccessControlUtils.allow(node, "anonymous", Privilege.JCR_READ);
+        return node;
+    }
+}

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

Modified: jackrabbit/oak/trunk/oak-search/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/pom.xml?rev=1848182&r1=1848181&r2=1848182&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-search/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-search/pom.xml Wed Dec  5 00:21:54 2018
@@ -162,6 +162,13 @@
             <classifier>tests</classifier>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-commons</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 </project>

Modified: 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java?rev=1848182&r1=1848181&r2=1848182&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/FulltextIndexConstants.java
 Wed Dec  5 00:21:54 2018
@@ -306,6 +306,15 @@ public interface FulltextIndexConstants
      */
     String PROP_SECURE_FACETS = "secure";
 
+    String PROP_SECURE_FACETS_VALUE_INSECURE = "insecure";
+    String PROP_SECURE_FACETS_VALUE_STATISTICAL = "statistical";
+    String PROP_SECURE_FACETS_VALUE_SECURE = "secure";
+    String PROP_SECURE_FACETS_VALUE_JVM_PARAM = "oak.facets.secure";
+
+    String STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM = 
"oak.facet.statistical.sampleSize";
+    String PROP_STATISTICAL_FACET_SAMPLE_SIZE = "sampleSize";
+    int STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT = 1000;
+
     /**
      * Optional (index definition) property indicating max number of facets 
that will be retrieved
      * in query
@@ -335,6 +344,13 @@ public interface FulltextIndexConstants
     String PROP_REFRESH_DEFN = "refresh";
 
     /**
+     * Long property that keep seed for random number generation. One example 
usage of this is
+     * to randomly sample query results to statistically check for ACLs to 
extrapolate facet
+     * counts
+     */
+    String PROP_RANDOM_SEED = "seed";
+
+    /**
      * Boolean property to indicate that nodes nodetype matching indexRule name
      * should be indexed
      */

Modified: 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java?rev=1848182&r1=1848181&r2=1848182&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinition.java
 Wed Dec  5 00:21:54 2018
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.regex.Pattern;
 
 import javax.jcr.PropertyType;
@@ -79,25 +80,7 @@ import static org.apache.jackrabbit.oak.
 import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEXING_MODE_NRT;
 import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEXING_MODE_SYNC;
 import static 
org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_COUNT;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.BLOB_SIZE;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.COMPAT_MODE;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EVALUATE_PATH_RESTRICTION;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EXCLUDE_PROPERTY_NAMES;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.EXPERIMENTAL_STORAGE;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.FACETS;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.FIELD_BOOST;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.FULL_TEXT_ENABLED;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_NAMES;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INCLUDE_PROPERTY_TYPES;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.INDEX_DATA_CHILD_NAME;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.ORDERED_PROP_NAMES;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_FACETS_TOP_CHILDREN;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_NODE;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.PROP_SECURE_FACETS;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.TIKA;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.TIKA_CONFIG;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.TIKA_MAPPED_TYPE;
-import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.TIKA_MIME_TYPES;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.*;
 import static 
org.apache.jackrabbit.oak.plugins.index.search.PropertyDefinition.DEFAULT_BOOST;
 import static 
org.apache.jackrabbit.oak.plugins.index.search.util.ConfigUtil.getOptionalValue;
 import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
@@ -244,7 +227,8 @@ public class IndexDefinition implements
 
     private final boolean suggestAnalyzed;
 
-    private final boolean secureFacets;
+    private final SecureFacetConfiguration secureFacets;
+    private final long randomSeed;
 
     private final int numberOfTopFacets;
 
@@ -411,12 +395,21 @@ public class IndexDefinition implements
         this.queryPaths = getOptionalStrings(defn, IndexConstants.QUERY_PATHS);
         this.suggestAnalyzed = evaluateSuggestAnalyzed(defn, false);
 
+        {
+            PropertyState randomPS = defn.getProperty(PROP_RANDOM_SEED);
+            if (randomPS != null && randomPS.getType() == Type.LONG) {
+                randomSeed = randomPS.getValue(Type.LONG);
+            } else {
+                // create a random number
+                randomSeed = UUID.randomUUID().getMostSignificantBits();
+            }
+        }
         if (defn.hasChildNode(FACETS)) {
             NodeState facetsConfig =  defn.getChildNode(FACETS);
-            this.secureFacets = getOptionalValue(facetsConfig, 
PROP_SECURE_FACETS, true);
+            this.secureFacets = 
SecureFacetConfiguration.getInstance(randomSeed, facetsConfig);
             this.numberOfTopFacets = getOptionalValue(facetsConfig, 
PROP_FACETS_TOP_CHILDREN, DEFAULT_FACET_COUNT);
         } else {
-            this.secureFacets = true;
+            this.secureFacets = 
SecureFacetConfiguration.getInstance(randomSeed, null);
             this.numberOfTopFacets = DEFAULT_FACET_COUNT;
         }
 
@@ -879,7 +872,7 @@ public class IndexDefinition implements
         return suggestAnalyzed;
     }
 
-    public boolean isSecureFacets() {
+    public SecureFacetConfiguration getSecureFacetConfiguration() {
         return secureFacets;
     }
 
@@ -1814,4 +1807,97 @@ public class IndexDefinition implements
         return new PropertyDefinition(rule, name, builder.getNodeState());
     }
 
+    public static class SecureFacetConfiguration {
+        public enum MODE {
+            SECURE,
+            STATISTICAL,
+            INSECURE
+        }
+
+        private final long randomSeed;
+        private final MODE mode;
+        private final int statisticalFacetSampleSize;
+
+        SecureFacetConfiguration(long randomSeed, MODE mode, int 
statisticalFacetSampleSize) {
+            this.randomSeed = randomSeed;
+            this.mode = mode;
+            this.statisticalFacetSampleSize = statisticalFacetSampleSize;
+        }
+
+        public MODE getMode() {
+            return mode;
+        }
+
+        public int getStatisticalFacetSampleSize() {
+            return statisticalFacetSampleSize;
+        }
+
+        public long getRandomSeed() {
+            return randomSeed;
+        }
+
+        static SecureFacetConfiguration getInstance(long randomSeed, NodeState 
facetConfigRoot) {
+            if (facetConfigRoot == null) {
+                facetConfigRoot = EMPTY_NODE;
+            }
+
+            MODE mode = getMode(facetConfigRoot);
+            int statisticalFacetSampleSize;
+
+            if (mode == MODE.STATISTICAL) {
+                statisticalFacetSampleSize = 
getStatisticalFacetSampleSize(facetConfigRoot);
+            } else {
+                statisticalFacetSampleSize = -1;
+            }
+
+            return new SecureFacetConfiguration(randomSeed, mode, 
statisticalFacetSampleSize);
+        }
+
+        static MODE getMode(@NotNull final NodeState facetConfigRoot) {
+            PropertyState securePS = 
facetConfigRoot.getProperty(PROP_SECURE_FACETS);
+            String modeString;
+
+            if (securePS != null) {
+                if(securePS.getType() == Type.BOOLEAN) {
+                    // legacy secure config
+                    boolean secure = securePS.getValue(Type.BOOLEAN);
+                    return secure ? MODE.SECURE : MODE.INSECURE;
+                } else {
+                    modeString = securePS.getValue(Type.STRING);
+                }
+            } else {
+                // use "default" just as placeholder. default case would do 
the actual default eval
+                modeString = 
System.getProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, "default");
+            }
+
+            switch (modeString) {
+                case PROP_SECURE_FACETS_VALUE_INSECURE:
+                    return MODE.INSECURE;
+                case PROP_SECURE_FACETS_VALUE_STATISTICAL:
+                    return MODE.STATISTICAL;
+                case PROP_SECURE_FACETS_VALUE_SECURE:
+                default:
+                    return MODE.SECURE;
+            }
+        }
+
+        static int getStatisticalFacetSampleSize(@NotNull final NodeState 
facetConfigRoot) {
+            int statisticalFacetSampleSize = Integer.getInteger(
+                    STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM,
+                    STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT);
+            int statisticalFacetSampleSizePV = 
getOptionalValue(facetConfigRoot,
+                    PROP_STATISTICAL_FACET_SAMPLE_SIZE,
+                    statisticalFacetSampleSize);
+
+            if (statisticalFacetSampleSizePV > 0) {
+                statisticalFacetSampleSize = statisticalFacetSampleSizePV;
+            }
+
+            if (statisticalFacetSampleSize < 0) {
+                statisticalFacetSampleSize = 
STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT;
+            }
+
+            return statisticalFacetSampleSize;
+        }
+    }
 }

Modified: 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditorContext.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditorContext.java?rev=1848182&r1=1848181&r2=1848182&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditorContext.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditorContext.java
 Wed Dec  5 00:21:54 2018
@@ -20,8 +20,10 @@ package org.apache.jackrabbit.oak.plugin
 
 import java.io.IOException;
 import java.util.Calendar;
+import java.util.UUID;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.PerfLogger;
 import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
@@ -44,6 +46,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+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.IndexDefinition.INDEX_DEFINITION_NODE;
 
@@ -248,6 +251,16 @@ public abstract class FulltextIndexEdito
 
   private IndexDefinition createIndexDefinition(NodeState root, NodeBuilder 
definition, IndexingContext
       indexingContext, boolean asyncIndexing) {
+
+    // A good time to check and see if we want to inject our random
+    // seed - required due to OAK-7929 but could be useful otherwise too.
+    Long defRandom = 
getLongPropertyOrNull(definition.getProperty(PROP_RANDOM_SEED));
+    if (defRandom == null) {
+      long seed = UUID.randomUUID().getMostSignificantBits();
+      definition.setProperty(PROP_RANDOM_SEED, seed);
+      defRandom = seed;
+    }
+
     NodeState defnState = definition.getBaseState();
     if (asyncIndexing && !IndexDefinition.isDisableStoredIndexDefinition()){
       if (definition.getBoolean(PROP_REFRESH_DEFN)){
@@ -262,6 +275,17 @@ public abstract class FulltextIndexEdito
         definition.setChildNode(INDEX_DEFINITION_NODE, 
NodeStateCloner.cloneVisibleState(defnState));
         log.info("Stored the cloned index definition for [{}]. Changes in 
index definition would now only be " +
             "effective post reindexing", indexingContext.getIndexPath());
+      } else {
+        // This is neither reindex nor refresh. So, let's update cloned def 
with random seed
+        // if it doesn't match what's there in main definition
+        // Reindex would require another indexing cycle to update cloned def.
+        // Refresh would anyway is going to do a real clone so that's ok
+        NodeState clonedState = defnState.getChildNode(INDEX_DEFINITION_NODE);
+        Long clonedRandom = 
getLongPropertyOrNull(clonedState.getProperty(PROP_RANDOM_SEED));
+
+        if (clonedRandom == null || clonedRandom != defRandom) {
+          
definition.getChildNode(INDEX_DEFINITION_NODE).setProperty(PROP_RANDOM_SEED, 
defRandom);
+        }
       }
     }
     return newDefinitionBuilder()
@@ -270,4 +294,19 @@ public abstract class FulltextIndexEdito
             .indexPath(indexingContext.getIndexPath())
             .build();
   }
+
+  private Long getLongPropertyOrNull(PropertyState propertyState) {
+    Long ret = null;
+
+    if (propertyState != null) {
+      try {
+        ret = propertyState.getValue(Type.LONG);
+      } catch (Exception e) {
+        // It's ok to ignore the exception, but let's log it to help debugging.
+        log.debug("Error occurred while reading property as long.", e);
+      }
+    }
+
+    return ret;
+  }
 }

Added: 
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionFacetConfigTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionFacetConfigTest.java?rev=1848182&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionFacetConfigTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionFacetConfigTest.java
 Wed Dec  5 00:21:54 2018
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.index.search;
+
+import org.apache.jackrabbit.oak.commons.junit.TemporarySystemProperty;
+import 
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.SecureFacetConfiguration;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Random;
+
+import static 
org.apache.jackrabbit.oak.plugins.index.search.FulltextIndexConstants.*;
+import static 
org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition.SecureFacetConfiguration.MODE;
+import static 
org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class IndexDefinitionFacetConfigTest {
+
+    @Rule
+    public TemporarySystemProperty temporarySystemProperty = new 
TemporarySystemProperty();
+
+    private NodeState root = EMPTY_NODE;
+
+    private final NodeBuilder builder = root.builder();
+
+    private static final long RANDOM_SEED = 1L;
+
+    @Test
+    public void defaultConfig() {
+        SecureFacetConfiguration config = 
SecureFacetConfiguration.getInstance(RANDOM_SEED, root);
+
+        assertEquals(config.getMode(), MODE.SECURE);
+
+        int sampleSize = config.getStatisticalFacetSampleSize();
+        assertEquals(-1, sampleSize);
+    }
+
+    @Test
+    public void nonSecureConfigMode() {
+        SecureFacetConfiguration config;
+
+        builder.setProperty(PROP_SECURE_FACETS, "insecure");
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
builder.getNodeState());
+        assertEquals(-1, config.getStatisticalFacetSampleSize());
+
+        builder.setProperty(PROP_SECURE_FACETS, "statistical");
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
builder.getNodeState());
+        assertEquals(STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT, 
config.getStatisticalFacetSampleSize());
+    }
+
+    @Test
+    public void systemPropSecureFacet() {
+        SecureFacetConfiguration config;
+
+        System.setProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, "random");
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, root);
+        assertEquals(MODE.SECURE, config.getMode());
+
+        System.setProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, "secure");
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, root);
+        assertEquals(MODE.SECURE, config.getMode());
+
+        System.setProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, "insecure");
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, root);
+        assertEquals(MODE.INSECURE, config.getMode());
+
+        System.setProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, "insecure");
+        builder.setProperty(PROP_SECURE_FACETS, "secure");
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
builder.getNodeState());
+        assertEquals(MODE.SECURE, config.getMode());
+    }
+
+    @Test
+    public void systemPropSecureFacetStatisticalSampleSize() {
+        int sampleSize;
+
+        System.setProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, 
PROP_SECURE_FACETS_VALUE_STATISTICAL);
+
+        System.setProperty(STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, "10");
+        sampleSize = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
root).getStatisticalFacetSampleSize();
+        assertEquals(10, sampleSize);
+
+        System.setProperty(STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, "-10");
+        sampleSize = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
root).getStatisticalFacetSampleSize();
+        assertEquals(STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT, sampleSize);
+
+        System.setProperty(STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, 
"100000000000");
+        sampleSize = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
root).getStatisticalFacetSampleSize();
+        assertEquals(STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT, sampleSize);
+    }
+
+    @Test
+    public void invalidSecureFacetSampleSize() {
+        int sampleSize;
+        NodeBuilder configBuilder = 
builder.child("config").setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_STATISTICAL);
+        NodeState nodeState = configBuilder.getNodeState();
+
+        configBuilder.setProperty(PROP_STATISTICAL_FACET_SAMPLE_SIZE, -10);
+        sampleSize = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
nodeState).getStatisticalFacetSampleSize();
+        assertEquals(STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT, sampleSize);
+
+        System.setProperty(STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, "10");
+        configBuilder.setProperty(PROP_STATISTICAL_FACET_SAMPLE_SIZE, -20);
+        nodeState = configBuilder.getNodeState();
+        sampleSize = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
nodeState).getStatisticalFacetSampleSize();
+        assertEquals(10, sampleSize);
+
+        System.setProperty(STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, "-10");
+        configBuilder.setProperty(PROP_STATISTICAL_FACET_SAMPLE_SIZE, -20);
+        sampleSize = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
nodeState).getStatisticalFacetSampleSize();
+        assertEquals(STATISTICAL_FACET_SAMPLE_SIZE_DEFAULT, sampleSize);
+
+        System.setProperty(STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, "-10");
+        configBuilder.setProperty(PROP_STATISTICAL_FACET_SAMPLE_SIZE, 10);
+        nodeState = configBuilder.getNodeState();
+        sampleSize = SecureFacetConfiguration.getInstance(RANDOM_SEED, 
nodeState).getStatisticalFacetSampleSize();
+        assertEquals(10, sampleSize);
+    }
+
+    @Test
+    public void orderingOfOverrides() {
+        System.setProperty(PROP_SECURE_FACETS_VALUE_JVM_PARAM, "insecure");
+        System.setProperty(STATISTICAL_FACET_SAMPLE_SIZE_JVM_PARAM, "10");
+
+        NodeState nodeState;
+        SecureFacetConfiguration config;
+        int sampleSize;
+
+        nodeState = builder.child("config1").setProperty(PROP_SECURE_FACETS, 
"secure").getNodeState();
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, nodeState);
+        assertEquals(MODE.SECURE, config.getMode());
+        assertEquals(-1, config.getStatisticalFacetSampleSize());
+
+        nodeState = builder.child("config2")
+                .setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_STATISTICAL)
+                .setProperty(PROP_STATISTICAL_FACET_SAMPLE_SIZE, 20)
+                .getNodeState();
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, nodeState);
+        sampleSize = config.getStatisticalFacetSampleSize();
+        assertEquals(20, sampleSize);
+
+        nodeState = builder.child("config3")
+                .setProperty(PROP_SECURE_FACETS, 
PROP_SECURE_FACETS_VALUE_STATISTICAL)
+                .getNodeState();
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, nodeState);
+        sampleSize = config.getStatisticalFacetSampleSize();
+        assertEquals(10, sampleSize);
+    }
+
+    @Test
+    public void legacyConfig() {
+        NodeState ns = builder.setProperty(PROP_SECURE_FACETS, 
true).getNodeState();
+        SecureFacetConfiguration config = 
SecureFacetConfiguration.getInstance(RANDOM_SEED, ns);
+        assertEquals(MODE.SECURE, config.getMode());
+        assertEquals(-1, config.getStatisticalFacetSampleSize());
+
+        ns = builder.setProperty(PROP_SECURE_FACETS, false).getNodeState();
+        config = SecureFacetConfiguration.getInstance(RANDOM_SEED, ns);
+        assertEquals(MODE.INSECURE, config.getMode());
+        assertEquals(-1, config.getStatisticalFacetSampleSize());
+    }
+
+    @Test
+    public void absentFacetConfigNode() {
+        IndexDefinition idxDefn = new IndexDefinition(root, root, "/foo");
+
+        SecureFacetConfiguration config = 
idxDefn.getSecureFacetConfiguration();
+
+        assertEquals(MODE.SECURE, config.getMode());
+    }
+
+    @Test
+    public void randomSeed() {
+        long seed = new Random().nextLong();
+        builder.setProperty(PROP_RANDOM_SEED, seed);
+        root = builder.getNodeState();
+        IndexDefinition idxDefn = new IndexDefinition(root, root, "/foo");
+
+        SecureFacetConfiguration config = 
idxDefn.getSecureFacetConfiguration();
+
+        assertEquals(seed, config.getRandomSeed());
+    }
+
+    @Test
+    public void randomSeedWithoutOneInDef() {
+        long seed1 = new IndexDefinition(root, root, 
"/foo").getSecureFacetConfiguration().getRandomSeed();
+        long seed2 = new IndexDefinition(root, root, 
"/foo").getSecureFacetConfiguration().getRandomSeed();
+
+        assertNotEquals(seed1, seed2);
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-search/src/test/java/org/apache/jackrabbit/oak/plugins/index/search/IndexDefinitionFacetConfigTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to