1010
1111import static biz .netcentric .cq .tools .actool .history .impl .PersistableInstallationLogger .msHumanReadable ;
1212
13+ import java .io .IOException ;
1314import java .util .Collection ;
1415import java .util .HashSet ;
1516import java .util .Iterator ;
2930import javax .jcr .query .InvalidQueryException ;
3031import javax .jcr .query .Query ;
3132import javax .jcr .query .QueryResult ;
33+ import javax .jcr .query .Row ;
3234import javax .jcr .security .AccessControlList ;
3335import javax .jcr .security .AccessControlManager ;
3436import javax .jcr .security .AccessControlPolicy ;
4042import org .slf4j .Logger ;
4143import org .slf4j .LoggerFactory ;
4244
45+ import com .fasterxml .jackson .databind .JsonNode ;
46+ import com .fasterxml .jackson .databind .ObjectMapper ;
47+
4348public class QueryHelper {
4449 public static final Logger LOG = LoggerFactory .getLogger (QueryHelper .class );
4550
4651 private static final String ROOT_REP_POLICY_NODE = "/rep:policy" ;
4752 private static final String ROOT_REPO_POLICY_NODE = "/" + Constants .REPO_POLICY_NODE ;
4853 private static final String HOME_REP_POLICY = "/home/rep:policy" ;
49- private static final String OAK_INDEX_PATH_REP_ACL = "/oak:index/repACL-custom-1" ;
54+
55+ /** every query cost below that threshold means a dedicated index exists, above that threshold means: fallback to traversal */
56+ private static final double COST_THRESHOLD_FOR_QUERY_INDEX = 100d ;
5057
5158 /** Method that returns a set containing all rep:policy nodes from repository excluding those contained in paths which are excluded from
5259 * search
@@ -98,7 +105,12 @@ public static Set<String> getRepPolicyNodePaths(final Session session,
98105 paths .add (HOME_REP_POLICY );
99106 }
100107
101- boolean indexForRepACLExists = session .nodeExists (OAK_INDEX_PATH_REP_ACL );
108+ boolean indexForRepACLExists = false ;
109+ try {
110+ indexForRepACLExists = hasQueryIndexForACLs (session );
111+ } catch (IOException |RepositoryException e ) {
112+ LOG .warn ("Cannot figure out if query index for rep:ACL nodes exist" , e );
113+ }
102114 LOG .debug ("Index for repACL exists: {}" ,indexForRepACLExists );
103115 String queryForAClNodes = indexForRepACLExists ?
104116 "SELECT * FROM [rep:ACL] WHERE ISDESCENDANTNODE([%s])" :
@@ -125,10 +137,32 @@ public static Set<String> getRepPolicyNodePaths(final Session session,
125137 return paths ;
126138 }
127139
140+ static boolean hasQueryIndexForACLs (final Session session ) throws RepositoryException , IOException {
141+ Query query = session .getWorkspace ().getQueryManager ().createQuery ("EXPLAIN MEASURE SELECT * FROM [rep:ACL] AS s WHERE ISDESCENDANTNODE([/])" , Query .JCR_SQL2 );
142+ QueryResult queryResult = query .execute ();
143+ Row row = queryResult .getRows ().nextRow ();
144+ // inspired by https://github.com/apache/jackrabbit-oak/blob/cc8adb42d89bc4625138a62ab074e7794a4d39ab/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java#L1092
145+ String plan = row .getValue ("plan" ).getString ();
146+ String costJson = plan .substring (plan .lastIndexOf ('{' ));
147+
148+ // use jackson for JSON parsing
149+ ObjectMapper mapper = new ObjectMapper ();
150+
151+ // read the json strings and convert it into JsonNode
152+ JsonNode node = mapper .readTree (costJson );
153+ double cost = node .get ("s" ).asDouble (Double .MAX_VALUE );
154+ // look at https://jackrabbit.apache.org/oak/docs/query/query-engine.html#cost-calculation for the threshold
155+ // https://github.com/apache/jackrabbit-oak/blob/cc8adb42d89bc4625138a62ab074e7794a4d39ab/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java#L75
156+
157+ // for traversing cost = estimation of node count
158+ // for property index = between 2 and 100
159+ LOG .debug ("Cost for rep:ACL query is estimated with {}" , cost );
160+ return cost <= COST_THRESHOLD_FOR_QUERY_INDEX ;
161+ }
162+
128163 /** Get Nodes with XPATH Query. */
129164 public static Set <String > getNodePathsFromQuery (final Session session ,
130- final String xpathQuery ) throws InvalidQueryException ,
131- RepositoryException {
165+ final String xpathQuery ) throws RepositoryException {
132166 return getNodePathsFromQuery (session , xpathQuery , Query .XPATH );
133167 }
134168
0 commit comments