Merge branch 'master' into SLING-11229-SLING-11230
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java
index 76b7718..986100d 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/JcrResourceUtil.java
@@ -43,7 +43,7 @@
/**
* Helper method to execute a JCR query.
- *
+ *
* @param session the session
* @param query the query
* @param language the language
@@ -52,8 +52,26 @@
*/
public static QueryResult query(Session session, String query,
String language) throws RepositoryException {
+ return query(session, query, language, 0, Long.MAX_VALUE);
+ }
+
+ /**
+ * Helper method to execute a JCR query.
+ *
+ * @param session the session
+ * @param query the query
+ * @param language the language
+ * @param offset the offset to start at
+ * @param limit the limit to the number of results to return
+ * @return the query's result
+ * @throws RepositoryException if the {@link QueryManager} cannot be retrieved
+ */
+ public static QueryResult query(Session session, String query,
+ String language, long offset, long limit) throws RepositoryException {
QueryManager qManager = session.getWorkspace().getQueryManager();
Query q = qManager.createQuery(query, language);
+ q.setOffset(offset);
+ q.setLimit(limit);
return q.execute();
}
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/BasicQueryLanguageProvider.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/BasicQueryLanguageProvider.java
index 35f9849..5acf880 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/BasicQueryLanguageProvider.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/BasicQueryLanguageProvider.java
@@ -63,6 +63,10 @@
this.providerContext = ctx;
}
+ protected QueryResult query(final ResolveContext<JcrProviderState> ctx, final String query, final String language) throws RepositoryException{
+ return JcrResourceUtil.query(ctx.getProviderState().getSession(), query, language);
+ }
+
@Override
public String[] getSupportedLanguages(final ResolveContext<JcrProviderState> ctx) {
try {
@@ -77,7 +81,7 @@
final String query,
final String language) {
try {
- final QueryResult res = JcrResourceUtil.query(ctx.getProviderState().getSession(), query, language);
+ final QueryResult res = query(ctx, query, language);
return new JcrNodeResourceIterator(ctx.getResourceResolver(),
null, null,
res.getNodes(),
@@ -97,7 +101,7 @@
final String queryLanguage = ArrayUtils.contains(getSupportedLanguages(ctx), language) ? language : DEFAULT_QUERY_LANGUAGE;
try {
- final QueryResult result = JcrResourceUtil.query(ctx.getProviderState().getSession(), query, queryLanguage);
+ final QueryResult result = query(ctx, query, queryLanguage);
final String[] colNames = result.getColumnNames();
final RowIterator rows = result.getRows();
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
index bdb7790..d7809d6 100644
--- a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProvider.java
@@ -32,8 +32,6 @@
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.NotNull;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
@@ -64,6 +62,8 @@
import org.apache.sling.spi.resource.provider.ResolveContext;
import org.apache.sling.spi.resource.provider.ResourceContext;
import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
@@ -73,6 +73,9 @@
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -88,6 +91,7 @@
ResourceProvider.PROPERTY_AUTHENTICATE + "=" + ResourceProvider.AUTHENTICATE_REQUIRED,
Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
})
+@Designate(ocd = JcrResourceProvider.Config.class)
public class JcrResourceProvider extends ResourceProvider<JcrProviderState> {
/** Logger */
@@ -103,6 +107,16 @@
IGNORED_PROPERTIES.add("jcr:createdBy");
}
+ @ObjectClassDefinition(name = "Apache Sling JCR Resource Provider", description = "Provides Sling resources based on the Java Content Repository")
+ public @interface Config {
+
+ @AttributeDefinition(name = "Enable Query Limit", description = "If set to true, the JcrResourceProvider will support parsing query start and limits from comments in the queries and set a default limit for all other queries using the findResources methods")
+ boolean enable_query_limit() default false;
+
+ @AttributeDefinition(name = "Default Query Limit", description = "The default query limit for queries using the findResources methods")
+ long default_query_limit() default 10000L;
+ }
+
@Reference(name = REPOSITORY_REFERENCE_NAME, service = SlingRepository.class)
private ServiceReference<SlingRepository> repositoryReference;
@@ -122,12 +136,14 @@
private volatile JcrProviderStateFactory stateFactory;
+ private Config config;
+
private final AtomicReference<DynamicClassLoaderManager> classLoaderManagerReference = new AtomicReference<>();
private AtomicReference<URIProvider[]> uriProviderReference = new AtomicReference<>();
@Activate
- protected void activate(final ComponentContext context) {
+ protected void activate(final ComponentContext context, final Config config) throws RepositoryException {
SlingRepository repository = context.locateService(REPOSITORY_REFERENCE_NAME,
this.repositoryReference);
if (repository == null) {
@@ -138,6 +154,8 @@
return;
}
+ this.config = config;
+
this.repository = repository;
this.stateFactory = new JcrProviderStateFactory(repositoryReference, repository,
@@ -634,6 +652,9 @@
public @Nullable QueryLanguageProvider<JcrProviderState> getQueryLanguageProvider() {
final ProviderContext ctx = this.getProviderContext();
if ( ctx != null ) {
+ if(config.enable_query_limit()){
+ return new LimitingQueryLanguageProvider(ctx, config.default_query_limit());
+ }
return new BasicQueryLanguageProvider(ctx);
}
return null;
diff --git a/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/LimitingQueryLanguageProvider.java b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/LimitingQueryLanguageProvider.java
new file mode 100644
index 0000000..be86ae6
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/resource/internal/helper/jcr/LimitingQueryLanguageProvider.java
@@ -0,0 +1,129 @@
+/*
+ * 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.sling.jcr.resource.internal.helper.jcr;
+
+import java.io.IOException;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.query.QueryResult;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.ImmutableTriple;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.sling.jcr.resource.internal.helper.JcrResourceUtil;
+import org.apache.sling.spi.resource.provider.ProviderContext;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LimitingQueryLanguageProvider extends BasicQueryLanguageProvider {
+
+ private final Logger log = LoggerFactory.getLogger(LimitingQueryLanguageProvider.class);
+
+ /** The limit to set for queries */
+ private final long defaultLimit;
+
+ public LimitingQueryLanguageProvider(final ProviderContext ctx, long defaultLimit) {
+ super(ctx);
+ this.defaultLimit = defaultLimit;
+ }
+
+ @Override
+ protected QueryResult query(final ResolveContext<JcrProviderState> ctx, final String query, final String language)
+ throws RepositoryException {
+ Triple<String, Long, Long> settings = extractQuerySettings(query);
+ return JcrResourceUtil.query(ctx.getProviderState().getSession(), settings.getLeft(), language,
+ settings.getMiddle(), settings.getRight());
+ }
+
+ protected Triple<String, Long, Long> extractQuerySettings(String query) {
+ query = query.trim();
+ if (query.endsWith("*/")) {
+ Pair<Long, Long> settings = parseQueryComment(
+ query.substring(query.lastIndexOf("/*") + 2, query.lastIndexOf("*/")));
+ return new ImmutableTriple<>(query.substring(0, query.lastIndexOf("/*")),
+ settings.getLeft(), settings.getRight());
+ } else {
+ return new ImmutableTriple<>(query, 0L, defaultLimit);
+ }
+ }
+
+ private Pair<Long, Long> parseQueryComment(String query) {
+ Map<String, Object> parsed = new HashMap<>();
+ StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(query));
+ int currentToken;
+ try {
+ currentToken = tokenizer.nextToken();
+ boolean key = true;
+ Object current = null;
+ while (currentToken != StreamTokenizer.TT_EOF) {
+ if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
+ if (!key) {
+ parsed.put((String) current, tokenizer.nval);
+ key = true;
+ current = null;
+ } else {
+ throw new IOException(
+ "Encountered unexpected numeric key: " + tokenizer.toString());
+ }
+ } else if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
+ if (!key) {
+ parsed.put((String) current, tokenizer.nval);
+ key = true;
+ } else if (current == null) {
+ current = tokenizer.sval;
+ } else {
+ throw new IOException(
+ "Encountered unmatched key value pair: " + tokenizer.toString());
+ }
+ } else if (((char) currentToken) == '=') {
+ key = false;
+ } else if (((char) currentToken) == ',' || ((char) currentToken) == ';') {
+ // nothing really required, just ignoring as it's a separator
+ } else {
+ throw new IOException(
+ "Encountered unexpected character parsing query comment: " + tokenizer.toString());
+ }
+ currentToken = tokenizer.nextToken();
+ }
+ } catch (Exception e) {
+ log.warn("Failed to parse query comment due to exception: {}", e.toString());
+ return new ImmutablePair<>(0L, defaultLimit);
+ }
+ return new ImmutablePair<>(getKeyAsLong(parsed, "slingQueryStart", 0L),
+ getKeyAsLong(parsed, "slingQueryLimit", defaultLimit));
+ }
+
+ private Long getKeyAsLong(Map<String, Object> parsed, String key, Long defaultVal) {
+ return Optional.ofNullable(parsed.get(key)).map(v -> {
+ if (v instanceof String) {
+ return Long.parseLong(v.toString());
+ } else {
+ return ((Double) v).longValue();
+ }
+ }).orElse(defaultVal);
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
index 5dbdf7c..c52f271 100644
--- a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderSessionHandlingTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -47,6 +48,7 @@
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.api.JcrResourceConstants;
+import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProvider.Config;
import org.apache.sling.spi.resource.provider.ResolveContext;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.junit.After;
@@ -235,7 +237,20 @@
when(ctx.locateService(anyString(), Mockito.<ServiceReference<Object>>any())).thenReturn(repo);
jcrResourceProvider = new JcrResourceProvider();
- jcrResourceProvider.activate(ctx);
+ jcrResourceProvider.activate(ctx, new Config() {
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return null;
+ }
+ @Override
+ public boolean enable_query_limit() {
+ return false;
+ }
+ @Override
+ public long default_query_limit() {
+ return 0;
+ }
+ });
jcrProviderState = jcrResourceProvider.authenticate(authInfo);
}
diff --git a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
index 684ce80..de99546 100644
--- a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/JcrResourceProviderTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.sling.jcr.resource.internal.helper.jcr;
+import java.lang.annotation.Annotation;
import java.security.Principal;
import javax.jcr.Node;
@@ -26,9 +27,9 @@
import javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
-import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
+import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProvider.Config;
import org.apache.sling.spi.resource.provider.ResolveContext;
import org.apache.sling.spi.resource.provider.ResourceContext;
import org.junit.Assert;
@@ -50,7 +51,20 @@
ComponentContext ctx = Mockito.mock(ComponentContext.class);
Mockito.when(ctx.locateService(Mockito.anyString(), Mockito.any(ServiceReference.class))).thenReturn(repo);
jcrResourceProvider = new JcrResourceProvider();
- jcrResourceProvider.activate(ctx);
+ jcrResourceProvider.activate(ctx, new Config() {
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return null;
+ }
+ @Override
+ public boolean enable_query_limit() {
+ return false;
+ }
+ @Override
+ public long default_query_limit() {
+ return 0;
+ }
+ });
}
@Override
diff --git a/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/LimitingQueryLanguageProviderTest.java b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/LimitingQueryLanguageProviderTest.java
new file mode 100644
index 0000000..77dd127
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/resource/internal/helper/jcr/LimitingQueryLanguageProviderTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.sling.jcr.resource.internal.helper.jcr;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.sling.spi.resource.provider.ProviderContext;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LimitingQueryLanguageProviderTest {
+
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> testCases() {
+ return Arrays.asList(new Object[][] {
+ testCase("JCR-SQL2 No Settings", "SELECT * FROM [nt:folder]", 10L,
+ "SELECT * FROM [nt:folder]",
+ 0L, 10L),
+ testCase("JCR-SQL2 With Limit", "SELECT * FROM [nt:folder] /* slingQueryLimit=20 */", 10L,
+ "SELECT * FROM [nt:folder] ",
+ 0L, 20L),
+ testCase("JCR-SQL2 With Limit", "SELECT * FROM [nt:folder] /* slingQueryStart=2, slingQueryLimit=20 */",
+ 10L,
+ "SELECT * FROM [nt:folder] ",
+ 2L, 20L),
+ testCase("JCR-SQL2 With Limit", "SELECT * FROM [nt:folder] /* someotherkey=2, slingQueryLimit=20 */",
+ 10L,
+ "SELECT * FROM [nt:folder] ",
+ 0L, 20L),
+ testCase("JCR-SQL2 With Limit", "SELECT * FROM [nt:folder] /* someotherkey=2, slingQueryLimit=20 */",
+ 10L,
+ "SELECT * FROM [nt:folder] ",
+ 0L, 20L),
+ testCase("XPath With Limit",
+ " /jcr:root/content//element(*, sling:Folder)[@sling:resourceType='x'] /* slingQueryStart=2, slingQueryLimit=20 */",
+ 10L,
+ "/jcr:root/content//element(*, sling:Folder)[@sling:resourceType='x'] ",
+ 2L, 20L),
+ testCase("XPath With Invalid Key",
+ " /jcr:root/content//element(*, sling:Folder)[@sling:resourceType='x'] /* 2=2, slingQueryLimit=20 */",
+ 10L,
+ "/jcr:root/content//element(*, sling:Folder)[@sling:resourceType='x'] ",
+ 0L, 10L)
+ });
+ }
+
+ public static Object[] testCase(String name, String query, long defaultLimit, String expectedQuery,
+ long expectedStart,
+ long expectedLimit) {
+ return new Object[] {
+ name, query, defaultLimit, expectedQuery, expectedStart, expectedLimit
+ };
+
+ }
+
+ private String name;
+ private String query;
+ private Long defaultLimit;
+ private String expectedQuery;
+ private Long expectedStart;
+ private Long expectedLimit;
+
+ public LimitingQueryLanguageProviderTest(String name, String query, long defaultLimit,
+ String expectedQuery,
+ long expectedStart,
+ long expectedLimit) {
+ this.name = name;
+ this.query = query;
+ this.defaultLimit = defaultLimit;
+ this.expectedQuery = expectedQuery;
+ this.expectedStart = expectedStart;
+ this.expectedLimit = expectedLimit;
+ }
+
+ @Test
+ public void testQueryLanguageProvider() {
+ LimitingQueryLanguageProvider provider = new LimitingQueryLanguageProvider(mock(ProviderContext.class),
+ defaultLimit);
+
+ Triple<String, Long, Long> settings = provider.extractQuerySettings(query);
+
+ assertEquals(expectedQuery, settings.getLeft());
+ assertEquals(expectedStart, settings.getMiddle());
+ assertEquals(expectedLimit, settings.getRight());
+
+ }
+
+}