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

daim pushed a commit to branch DetailedGC/OAK-10199
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit c8e95876347639332d4d46082a232fd5e7859cc8
Author: Mark Adamcin <[email protected]>
AuthorDate: Wed Jan 10 08:36:38 2024 -0800

    OAK-10424 : Allow Fast Query Size and Insecure Facets to be selectively 
enabled with query options for permitted principals
---
 .../query/SessionQuerySettingsProviderService.java |  89 +++++++
 .../oak/jcr/repository/RepositoryImpl.java         |  34 ++-
 .../jackrabbit/oak/jcr/session/SessionContext.java |  14 +-
 .../oak/jcr/OakSegmentTarRepositoryStub.java       |  20 +-
 .../oak/jcr/query/WhiteboardResultSizeTest.java    | 264 +++++++++++++++++++++
 .../oak/spi/query/SessionQuerySettings.java        |  37 +++
 .../spi/query/SessionQuerySettingsProvider.java    |  41 ++++
 .../jackrabbit/oak/spi/query/package-info.java     |   2 +-
 8 files changed, 489 insertions(+), 12 deletions(-)

diff --git 
a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SessionQuerySettingsProviderService.java
 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SessionQuerySettingsProviderService.java
new file mode 100644
index 0000000000..5d2a068acf
--- /dev/null
+++ 
b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SessionQuerySettingsProviderService.java
@@ -0,0 +1,89 @@
+/*
+ * 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.query;
+
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.spi.query.SessionQuerySettings;
+import org.apache.jackrabbit.oak.spi.query.SessionQuerySettingsProvider;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Overrides oak.fastQuerySize system property when available.
+ */
+@Component(configurationPolicy = ConfigurationPolicy.REQUIRE, immediate = true)
+@Designate(ocd = SessionQuerySettingsProviderService.Configuration.class)
+public class SessionQuerySettingsProviderService implements 
SessionQuerySettingsProvider {
+
+    @ObjectClassDefinition(
+            name = "Apache Jackrabbit Session Query Settings Provider Service",
+            description = "Provides Session-specific query settings exposed by 
Oak QueryEngine."
+    )
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface Configuration {
+        @AttributeDefinition(name = "Direct Counts Principals", description = 
"Principal names for which executed query result counts directly reflect the 
index estimate.")
+        String[] directCountsPrincipals() default {};
+    }
+
+    void configure(Configuration config) {
+        this.directCountsAllowedPrincipals = Optional.ofNullable(config)
+                .map(cfg -> (Set<String>) new 
HashSet<>(Arrays.asList(cfg.directCountsPrincipals())))
+                .orElse(Collections.emptySet());
+    }
+
+    @Activate
+    protected void activate(Configuration config) {
+        configure(config);
+    }
+
+    @Modified
+    protected void modified(Configuration config) {
+        configure(config);
+    }
+
+    private Set<String> directCountsAllowedPrincipals = Collections.emptySet();
+
+    @Override
+    public SessionQuerySettings getQuerySettings(@NotNull ContentSession 
session) {
+        final Set<String> principals = directCountsAllowedPrincipals;
+        final boolean directCountsAllowed = 
session.getAuthInfo().getPrincipals().stream()
+                .anyMatch(principal -> 
principals.contains(principal.getName()));
+        return new SessionQuerySettings() {
+            @Override
+            public boolean useDirectResultCount() {
+                return directCountsAllowed;
+            }
+        };
+    }
+}
diff --git 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
index dd77a88ca1..12ad5f7f87 100644
--- 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
+++ 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/repository/RepositoryImpl.java
@@ -23,6 +23,7 @@ import static 
org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils.registerM
 
 import java.io.Closeable;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
@@ -61,6 +62,8 @@ import 
org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
 import org.apache.jackrabbit.oak.spi.gc.DelegatingGCMonitor;
 import org.apache.jackrabbit.oak.spi.gc.GCMonitor;
 import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.query.SessionQuerySettings;
+import org.apache.jackrabbit.oak.spi.query.SessionQuerySettingsProvider;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
@@ -118,6 +121,7 @@ public class RepositoryImpl implements JackrabbitRepository 
{
     private final Registration gcMonitorRegistration;
     private final MountInfoProvider mountInfoProvider;
     private final BlobAccessProvider blobAccessProvider;
+    private final SessionQuerySettingsProvider sessionQuerySettingsProvider;
 
     /**
      * {@link ThreadLocal} counter that keeps track of the save operations
@@ -172,6 +176,8 @@ public class RepositoryImpl implements JackrabbitRepository 
{
         this.mountInfoProvider = WhiteboardUtils.getService(whiteboard, 
MountInfoProvider.class);
         this.blobAccessProvider = WhiteboardUtils.getService(whiteboard, 
BlobAccessProvider.class);
         this.frozenNodeLogger = new FrozenNodeLogger(clock, whiteboard);
+        this.sessionQuerySettingsProvider = 
Optional.ofNullable(WhiteboardUtils.getService(whiteboard, 
SessionQuerySettingsProvider.class))
+                .orElseGet(() -> new 
FastQuerySizeSettingsProvider(fastQueryResultSize));
     }
 
     //---------------------------------------------------------< Repository 
>---
@@ -371,7 +377,8 @@ public class RepositoryImpl implements JackrabbitRepository 
{
             Map<String, Object> attributes, SessionDelegate delegate, int 
observationQueueLength,
             CommitRateLimiter commitRateLimiter) {
         return new SessionContext(this, statisticManager, securityProvider, 
whiteboard, attributes,
-                delegate, observationQueueLength, commitRateLimiter, 
mountInfoProvider, blobAccessProvider, fastQueryResultSize);
+                delegate, observationQueueLength, commitRateLimiter, 
mountInfoProvider, blobAccessProvider,
+                
sessionQuerySettingsProvider.getQuerySettings(delegate.getContentSession()));
     }
 
     /**
@@ -564,4 +571,29 @@ public class RepositoryImpl implements 
JackrabbitRepository {
             }
         }
     }
+
+    /**
+     * This is a fallback implementation of {@link 
org.apache.jackrabbit.oak.spi.query.SessionQuerySettingsProvider} that
+     * unifies and replaces the previous handling of {@code 
fastQueryResultSize} and {@code oak.fastQuerySize} here
+     * and in the {@link org.apache.jackrabbit.oak.jcr.session.SessionContext}.
+     */
+    private static class FastQuerySizeSettingsProvider implements 
SessionQuerySettingsProvider {
+        private final boolean fastQueryResultSize;
+
+        public FastQuerySizeSettingsProvider(boolean fastQueryResultSize) {
+            this.fastQueryResultSize = fastQueryResultSize;
+        }
+
+        @Override
+        public @NotNull SessionQuerySettings getQuerySettings(@NotNull 
ContentSession session) {
+            return new SessionQuerySettings() {
+                @Override
+                public boolean useDirectResultCount() {
+                    return 
Optional.ofNullable(System.getProperty("oak.fastQuerySize"))
+                            .map(Boolean::valueOf)
+                            
.orElse(FastQuerySizeSettingsProvider.this.fastQueryResultSize);
+                }
+            };
+        }
+    }
 }
diff --git 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
index fd1d4cdfba..5221ef41c9 100644
--- 
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
+++ 
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java
@@ -57,6 +57,7 @@ import 
org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
 import org.apache.jackrabbit.oak.plugins.observation.CommitRateLimiter;
 import org.apache.jackrabbit.oak.plugins.value.jcr.ValueFactoryImpl;
 import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.query.SessionQuerySettings;
 import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
 import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
@@ -114,7 +115,7 @@ public class SessionContext implements NamePathMapper {
     /** Paths of all session scoped locks held by this session. */
     private final Set<String> sessionScopedLocks = newHashSet();
     
-    private final boolean fastQueryResultSize;
+    private final SessionQuerySettings sessionQuerySettings;
 
     public SessionContext(
              @NotNull Repository repository, @NotNull StatisticManager 
statisticManager,
@@ -123,7 +124,7 @@ public class SessionContext implements NamePathMapper {
              int observationQueueLength, CommitRateLimiter commitRateLimiter) {
         
         this(repository, statisticManager, securityProvider, whiteboard, 
attributes, delegate,
-            observationQueueLength, commitRateLimiter, null, null, false);
+            observationQueueLength, commitRateLimiter, null, null, null);
     }
 
     public SessionContext(
@@ -132,7 +133,7 @@ public class SessionContext implements NamePathMapper {
             @NotNull Map<String, Object> attributes, @NotNull final 
SessionDelegate delegate,
             int observationQueueLength, CommitRateLimiter commitRateLimiter,
             MountInfoProvider mountInfoProvider, @Nullable BlobAccessProvider 
blobAccessProvider,
-            boolean fastQueryResultSize) {
+            @Nullable SessionQuerySettings sessionQuerySettings) {
         this.repository = checkNotNull(repository);
         this.statisticManager = statisticManager;
         this.securityProvider = checkNotNull(securityProvider);
@@ -150,7 +151,7 @@ public class SessionContext implements NamePathMapper {
                 delegate.getNamespaces(), delegate.getIdManager());
         this.valueFactory = new ValueFactoryImpl(
                 delegate.getRoot(), namePathMapper, this.blobAccessProvider);
-        this.fastQueryResultSize = fastQueryResultSize;
+        this.sessionQuerySettings = sessionQuerySettings;
     }
 
     public final Map<String, Object> getAttributes() {
@@ -324,10 +325,7 @@ public class SessionContext implements NamePathMapper {
     }
     
     public boolean getFastQueryResultSize() {
-        if (System.getProperty("oak.fastQuerySize") != null) {
-            return Boolean.getBoolean("oak.fastQuerySize");
-        }
-        return fastQueryResultSize;
+        return this.sessionQuerySettings != null && 
this.sessionQuerySettings.useDirectResultCount();
     }
 
     @Nullable
diff --git 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakSegmentTarRepositoryStub.java
 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakSegmentTarRepositoryStub.java
index 45ecbdd58e..093f70a9c2 100644
--- 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakSegmentTarRepositoryStub.java
+++ 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakSegmentTarRepositoryStub.java
@@ -27,6 +27,7 @@ import org.apache.jackrabbit.oak.Oak;
 import org.apache.jackrabbit.oak.segment.SegmentNodeStoreBuilders;
 import org.apache.jackrabbit.oak.segment.file.FileStore;
 import org.apache.jackrabbit.oak.segment.file.FileStoreBuilder;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 
 /**
  * A repository stub implementation for Oak Segment Tar
@@ -49,8 +50,9 @@ public class OakSegmentTarRepositoryStub extends 
BaseRepositoryStub {
         try {
             File directory = new File("target", "segment-tar-" + 
System.currentTimeMillis());
             this.store = 
FileStoreBuilder.fileStoreBuilder(directory).withMaxFileSize(1).build();
-            Jcr jcr = new Jcr(new 
Oak(SegmentNodeStoreBuilders.builder(store).build()));
-            preCreateRepository(jcr);
+            Oak oak = new Oak(SegmentNodeStoreBuilders.builder(store).build());
+            Jcr jcr = new Jcr(oak);
+            preCreateRepository(jcr, oak.getWhiteboard());
             this.repository = jcr.createRepository();
             loadTestContent(repository);
         } catch (Exception e) {
@@ -64,6 +66,20 @@ public class OakSegmentTarRepositoryStub extends 
BaseRepositoryStub {
         }));
     }
 
+    /**
+     * Override in subclass and perform additional configuration on the
+     * {@link Jcr} builder before the repository is created. This default
+     * implementation set query engine settings as returned by
+     * {@link #getQueryEngineSettings()} and adds a
+     * {@link 
org.apache.jackrabbit.oak.plugins.document.bundlor.BundlingConfigInitializer}.
+     *
+     * @param jcr the builder.
+     * @param whiteboard the oak whiteboard
+     */
+    protected void preCreateRepository(Jcr jcr, Whiteboard whiteboard) {
+        this.preCreateRepository(jcr);
+    }
+
     /**
      * Returns the configured repository instance.
      *
diff --git 
a/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/WhiteboardResultSizeTest.java
 
b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/WhiteboardResultSizeTest.java
new file mode 100644
index 0000000000..de5fbae473
--- /dev/null
+++ 
b/oak-lucene/src/test/java/org/apache/jackrabbit/oak/jcr/query/WhiteboardResultSizeTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.jcr.query;
+
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import 
org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.jcr.LuceneOakRepositoryStub;
+import org.apache.jackrabbit.oak.spi.query.SessionQuerySettingsProvider;
+import org.apache.jackrabbit.oak.query.SessionQuerySettingsProviderService;
+import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.test.RepositoryStub;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.security.Privilege;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Performs the same queries as {@link ResultSizeTest#testResultSize()} and 
expects the same results, with the only
+ * differences being the registration of a {@link 
org.apache.jackrabbit.oak.spi.query.SessionQuerySettingsProvider} service
+ * in the whiteboard, and the runtime reconfiguration of the service before 
each login, in place of the calls to
+ * {@code System.setProperty("oak.fastQuerySize", true/false)}.
+ */
+public class WhiteboardResultSizeTest {
+
+    /**
+     * Non-static inner class extending RepositoryStub which is intended to be 
created directly by a test
+     * and not by the TCK RepositoryHelper, purely so that it can mirror the 
behavior of
+     * {@link org.apache.jackrabbit.oak.jcr.query.ResultSizeTest} with the 
only differences being the registration of
+     * a {@link 
org.apache.jackrabbit.oak.spi.query.SessionQuerySettingsProvider} service in 
the whiteboard, and the
+     * runtime reconfiguration of the service before each login, in place of 
the calls to
+     * {@code System.setProperty("oak.fastQuerySize", true/false)}.
+     */
+    public class OakResultSizeStub extends LuceneOakRepositoryStub {
+        public OakResultSizeStub(Properties settings) throws 
RepositoryException {
+            super(settings);
+        }
+
+        @Override
+        protected void preCreateRepository(Jcr jcr, Whiteboard whiteboard) {
+            // register the SessionQuerySettingsProvider service before 
creation of the repository.
+            whiteboard.register(SessionQuerySettingsProvider.class,
+                    settingsProviderService,
+                    Collections.emptyMap());
+            super.preCreateRepository(jcr, whiteboard);
+        }
+    }
+
+    @SessionQuerySettingsProviderService.Configuration(directCountsPrincipals 
= {"admin"})
+    static class AdminAllowed {
+        /* class which hosts a config annotation for reflection */
+    }
+
+    @SessionQuerySettingsProviderService.Configuration
+    static class NoneAllowed {
+        /* class which hosts a config annotation for reflection */
+    }
+
+    private SessionQuerySettingsProviderService settingsProviderService =
+            new SessionQuerySettingsProviderService();
+    private RepositoryStub stub;
+    private volatile Repository repository;
+    private volatile Session adminSession;
+
+    /**
+     * Reconfigures the singleton {@link 
org.apache.jackrabbit.oak.query.SessionQuerySettingsProviderService} instance 
between queries.
+     *
+     * @param config a config annotation
+     * @throws Exception for reflection errors
+     */
+    private void reconfigure(SessionQuerySettingsProviderService.Configuration 
config) throws Exception {
+        Method method = 
SessionQuerySettingsProviderService.class.getDeclaredMethod("configure",
+                SessionQuerySettingsProviderService.Configuration.class);
+        method.setAccessible(true);
+        method.invoke(settingsProviderService, config);
+    }
+
+    protected Session getAdminSession() throws Exception {
+        if (adminSession == null) {
+            adminSession = createAdminSession();
+            AccessControlUtils.addAccessControlEntry(adminSession, "/", 
EveryonePrincipal.getInstance(),
+                    new String[]{Privilege.JCR_READ}, true);
+            adminSession.save();
+        }
+        return adminSession;
+    }
+
+    private void createData() throws Exception {
+        Session session = getAdminSession();
+        Node testRootNode = session.getRootNode().addNode("testroot", 
"nt:unstructured");
+        for (int i = 0; i < 200; i++) {
+            Node n = testRootNode.addNode("node" + i);
+            n.setProperty("text", "Hello World");
+        }
+        session.save();
+    }
+
+    @Test
+    public void testResultSize() throws Exception {
+        doTestResultSize(false);
+    }
+
+    private void doTestResultSize(boolean aggregateAtQueryTime) throws 
Exception {
+        createData();
+        int expectedForUnion = 400;
+        int expectedForTwoConditions = aggregateAtQueryTime ? 400 : 200;
+        doTestResultSize(false, expectedForTwoConditions);
+        doTestResultSize(true, expectedForUnion);
+    }
+
+    /**
+     * Compare with {@code 
org.apache.jackrabbit.oak.jcr.query.ResultSizeTest#doTestResultSize(boolean, 
int)}.
+     */
+    private void doTestResultSize(boolean union, int expected) throws 
Exception {
+        Session session = null;
+        QueryManager qm;
+        Query q;
+        long result;
+        NodeIterator it;
+        StringBuilder buff;
+
+        String xpath;
+        if (union) {
+            xpath = "/jcr:root//*[jcr:contains(@text, 'Hello') or 
jcr:contains(@text, 'World')]";
+        } else {
+            xpath = "/jcr:root//*[jcr:contains(@text, 'Hello World')]";
+        }
+
+        final CompletableFuture<String> fastSizeResult = new 
CompletableFuture<>();
+
+        
reconfigure(AdminAllowed.class.getAnnotation(SessionQuerySettingsProviderService.Configuration.class));
+        try {
+            session = this.createAdminSession();
+            assertEquals("nt:unstructured",
+                    
session.getNode("/testroot").getPrimaryNodeType().getName());
+            qm = session.getWorkspace().getQueryManager();
+            q = qm.createQuery(xpath, "xpath");
+            it = q.execute().getNodes();
+            result = it.getSize();
+            assertTrue("size: " + result + " expected around " + expected,
+                    result > expected - 50 &&
+                            result < expected + 50);
+            buff = new StringBuilder();
+            while (it.hasNext()) {
+                Node n = it.nextNode();
+                buff.append(n.getPath()).append('\n');
+            }
+            fastSizeResult.complete(buff.toString());
+            q = qm.createQuery(xpath, "xpath");
+            q.setLimit(90);
+            it = q.execute().getNodes();
+            assertEquals(90, it.getSize());
+        } finally {
+            if (session != null) {
+                session.logout();
+            }
+        }
+
+        
reconfigure(NoneAllowed.class.getAnnotation(SessionQuerySettingsProviderService.Configuration.class));
+        try {
+            session = this.createAdminSession();
+            qm = session.getWorkspace().getQueryManager();
+            q = qm.createQuery(xpath, "xpath");
+            it = q.execute().getNodes();
+            result = it.getSize();
+            assertEquals(-1, result);
+            buff = new StringBuilder();
+            while (it.hasNext()) {
+                Node n = it.nextNode();
+                buff.append(n.getPath()).append('\n');
+            }
+            String regularResult = buff.toString();
+            assertEquals(regularResult, fastSizeResult.get());
+        } finally {
+            if (session != null) {
+                session.logout();
+            }
+        }
+    }
+
+    // ----< repository initialization methods >--------
+
+    @Before
+    public void setUp() throws Exception {
+        getAdminSession();
+    }
+
+    @After
+    public void logout() {
+        if (adminSession != null) {
+            adminSession.logout();
+            adminSession = null;
+        }
+        // release repository field
+        if (repository instanceof JackrabbitRepository) {
+            ((JackrabbitRepository) repository).shutdown();
+        }
+        repository = null;
+        stub = null;
+    }
+
+    protected RepositoryStub getStub() throws Exception {
+        if (stub == null) {
+            Properties properties = new Properties();
+            try (InputStream is = 
RepositoryStub.class.getClassLoader().getResourceAsStream(RepositoryStub.STUB_IMPL_PROPS))
 {
+                if (is != null) {
+                    properties.load(is);
+                }
+            }
+            stub = new OakResultSizeStub(properties);
+        }
+        return stub;
+    }
+
+    protected Repository getRepository() throws Exception {
+        if (repository == null) {
+            repository = getStub().getRepository();
+        }
+        return repository;
+    }
+
+    protected Session createAdminSession() throws Exception {
+        return getRepository().login(getAdminCredentials());
+    }
+
+    protected Credentials getAdminCredentials() {
+        return stub.getSuperuserCredentials();
+    }
+
+}
diff --git 
a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/SessionQuerySettings.java
 
b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/SessionQuerySettings.java
new file mode 100644
index 0000000000..80b58b6167
--- /dev/null
+++ 
b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/SessionQuerySettings.java
@@ -0,0 +1,37 @@
+/*
+ * 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.spi.query;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * User-specific settings which may be passed by the query engine to index 
providers during query planning and iteration
+ * of results.
+ */
+@ProviderType
+public interface SessionQuerySettings {
+
+    /**
+     * Return true to use the index provider's query result count.
+     *
+     * @return true to use the index provider's query result count
+     */
+    boolean useDirectResultCount();
+}
diff --git 
a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/SessionQuerySettingsProvider.java
 
b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/SessionQuerySettingsProvider.java
new file mode 100644
index 0000000000..67d994f715
--- /dev/null
+++ 
b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/SessionQuerySettingsProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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.spi.query;
+
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Provides {@link SessionQuerySettings} for principals with access to the 
content
+ * repository.
+ */
+@ProviderType
+@FunctionalInterface
+public interface SessionQuerySettingsProvider {
+
+    /**
+     * Return the applicable {@link SessionQuerySettings} for the given 
session.
+     *
+     * @param session the subject principal's content session
+     * @return the applicable query settings
+     */
+    SessionQuerySettings getQuerySettings(@NotNull ContentSession session);
+}
diff --git 
a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java
 
b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java
index 0f958cf31a..1287024c94 100644
--- 
a/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java
+++ 
b/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java
@@ -18,7 +18,7 @@
 /**
  * This package contains oak query index related classes.
  */
-@Version("1.7.0")
+@Version("1.8.0")
 package org.apache.jackrabbit.oak.spi.query;
 
 import org.osgi.annotation.versioning.Version;

Reply via email to