You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2021/01/22 17:37:25 UTC
[sling-org-apache-sling-graphql-core] branch master updated:
SLING-10085 - Cache the GraphQL schemas in the DefaultQueryExecutor
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git
The following commit(s) were added to refs/heads/master by this push:
new f83f930 SLING-10085 - Cache the GraphQL schemas in the DefaultQueryExecutor
f83f930 is described below
commit f83f9303d04ef979ab3c1304e9106948af6f27c3
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Fri Jan 22 18:37:18 2021 +0100
SLING-10085 - Cache the GraphQL schemas in the DefaultQueryExecutor
* implemented a LRU cache for the GraphQL schemas
---
.../core/cache/SimpleGraphQLCacheProvider.java | 26 +--
.../graphql/core/engine/DefaultQueryExecutor.java | 183 +++++++++++++++++----
.../sling/graphql/core/hash/SHA256Hasher.java | 53 ++++++
.../core/cache/SimpleGraphQLCacheProviderTest.java | 3 +-
.../core/engine/DefaultQueryExecutorTest.java | 61 ++++++-
5 files changed, 265 insertions(+), 61 deletions(-)
diff --git a/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java b/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
index a2411a3..2e88bb1 100644
--- a/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
+++ b/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
@@ -18,9 +18,6 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package org.apache.sling.graphql.core.cache;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -37,7 +34,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.sling.commons.metrics.Counter;
import org.apache.sling.commons.metrics.MetricsService;
import org.apache.sling.graphql.api.cache.GraphQLCacheProvider;
-import org.apache.sling.graphql.api.SlingGraphQLException;
+import org.apache.sling.graphql.core.hash.SHA256Hasher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.BundleContext;
@@ -157,7 +154,7 @@ public class SimpleGraphQLCacheProvider implements GraphQLCacheProvider {
public String cacheQuery(@NotNull String query, @NotNull String resourceType, @Nullable String selectorString) {
writeLock.lock();
try {
- String hash = getHash(query);
+ String hash = SHA256Hasher.getHash(query);
String key = getCacheKey(hash, resourceType, selectorString);
persistedQueriesCache.put(key, query);
if (persistedQueriesCache.containsKey(key)) {
@@ -179,25 +176,6 @@ public class SimpleGraphQLCacheProvider implements GraphQLCacheProvider {
return key.toString();
}
- @NotNull String getHash(@NotNull String query) {
- StringBuilder buffer = new StringBuilder();
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] hash = digest.digest(query.getBytes(StandardCharsets.UTF_8));
-
- for (byte b : hash) {
- String hex = Integer.toHexString(0xff & b);
- if (hex.length() == 1) {
- buffer.append('0');
- }
- buffer.append(hex);
- }
- } catch (NoSuchAlgorithmException e) {
- throw new SlingGraphQLException("Failed hashing query - " + e.getMessage());
- }
- return buffer.toString();
- }
-
/**
* This implementation provides a simple LRU eviction based on either the number of entries or the memory used by the stored values.
* Synchronization has to happen externally.
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
index bed63df..063f3ff 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
@@ -18,30 +18,37 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package org.apache.sling.graphql.core.engine;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.script.ScriptException;
-import graphql.language.InterfaceTypeDefinition;
-import graphql.language.TypeDefinition;
-import graphql.language.UnionTypeDefinition;
-import graphql.schema.TypeResolver;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.graphql.api.SchemaProvider;
import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingGraphQLException;
+import org.apache.sling.graphql.api.SlingTypeResolver;
import org.apache.sling.graphql.api.engine.QueryExecutor;
import org.apache.sling.graphql.api.engine.ValidationResult;
-import org.apache.sling.graphql.api.SlingTypeResolver;
-import org.apache.sling.graphql.core.util.LogSanitizer;
+import org.apache.sling.graphql.core.hash.SHA256Hasher;
import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
+import org.apache.sling.graphql.core.util.LogSanitizer;
import org.apache.sling.graphql.core.util.SlingGraphQLErrorHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
+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;
@@ -54,12 +61,16 @@ import graphql.ParseAndValidateResult;
import graphql.language.Argument;
import graphql.language.Directive;
import graphql.language.FieldDefinition;
+import graphql.language.InterfaceTypeDefinition;
import graphql.language.ObjectTypeDefinition;
import graphql.language.SourceLocation;
import graphql.language.StringValue;
+import graphql.language.TypeDefinition;
+import graphql.language.UnionTypeDefinition;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
+import graphql.schema.TypeResolver;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
@@ -68,6 +79,7 @@ import graphql.schema.idl.TypeDefinitionRegistry;
@Component(
service = QueryExecutor.class
)
+@Designate(ocd = DefaultQueryExecutor.Config.class)
public class DefaultQueryExecutor implements QueryExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultQueryExecutor.class);
@@ -84,6 +96,12 @@ public class DefaultQueryExecutor implements QueryExecutor {
private static final LogSanitizer cleanLog = new LogSanitizer();
+ private Map<String, String> resourceToHashMap;
+ private Map<String, GraphQLSchema> hashToSchemaMap;
+ private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+ private final Lock readLock = readWriteLock.readLock();
+ private final Lock writeLock = readWriteLock.writeLock();
+
@Reference
private RankedSchemaProviders schemaProvider;
@@ -96,13 +114,39 @@ public class DefaultQueryExecutor implements QueryExecutor {
@Reference
private SlingScalarsProvider scalarsProvider;
+ @ObjectClassDefinition(
+ name = "Apache Sling Default GraphQL Query Executor"
+ )
+ @interface Config {
+ @AttributeDefinition(
+ name = "Schema Cache Size",
+ description = "The number of compiled GraphQL schemas to cache. Since a schema normally doesn't change often, they can be" +
+ " cached and reused, rather than parsed by the engine all the time. The cache is a LRU and will store up to this number of schemas."
+ )
+ int schemaCacheSize() default 128;
+ }
+
+ @Activate
+ public void activate(Config config) {
+ int schemaCacheSize = config.schemaCacheSize();
+ if (schemaCacheSize < 0) {
+ schemaCacheSize = 0;
+ }
+ resourceToHashMap = new LRUCache<>(schemaCacheSize);
+ hashToSchemaMap = new LRUCache<>(schemaCacheSize);
+ }
+
@Override
public ValidationResult validate(@NotNull String query, @NotNull Map<String, Object> variables, @NotNull Resource queryResource,
@NotNull String[] selectors) {
try {
String schemaDef = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
+ if (schemaDef == null) {
+ throw new SlingGraphQLException(String.format("Cannot get a schema for resource %s and selectors %s.", queryResource,
+ Arrays.toString(selectors)));
+ }
LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
- final GraphQLSchema schema = buildSchema(schemaDef, queryResource);
+ final GraphQLSchema schema = getSchema(schemaDef, queryResource, selectors);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.variables(variables)
@@ -133,12 +177,16 @@ public class DefaultQueryExecutor implements QueryExecutor {
String schemaDef = null;
try {
schemaDef = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
+ if (schemaDef == null) {
+ throw new SlingGraphQLException(String.format("Cannot get a schema for resource %s and selectors %s.", queryResource,
+ Arrays.toString(selectors)));
+ }
LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
- final GraphQLSchema schema = buildSchema(schemaDef, queryResource);
+ final GraphQLSchema schema = getSchema(schemaDef, queryResource, selectors);
final GraphQL graphQL = GraphQL.newGraphQL(schema).build();
- if(LOGGER.isDebugEnabled()) {
+ if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Executing query\n[{}]\nat [{}] with variables [{}]",
- cleanLog.sanitize(query), queryResource.getPath(), cleanLog.sanitize(variables.toString()));
+ cleanLog.sanitize(query), queryResource.getPath(), cleanLog.sanitize(variables.toString()));
}
ExecutionInput ei = ExecutionInput.newExecutionInput()
.query(query)
@@ -148,35 +196,29 @@ public class DefaultQueryExecutor implements QueryExecutor {
if (!result.getErrors().isEmpty()) {
StringBuilder errors = new StringBuilder();
for (GraphQLError error : result.getErrors()) {
- errors.append("Error: type=").append(error.getErrorType().toString()).append("; message=").append(error.getMessage()).append(System.lineSeparator());
+ errors.append("Error: type=").append(error.getErrorType().toString()).append("; message=").append(error.getMessage())
+ .append(System.lineSeparator());
if (error.getLocations() != null) {
for (SourceLocation location : error.getLocations()) {
errors.append("location=").append(location.getLine()).append(",").append(location.getColumn()).append(";");
}
}
}
- if(LOGGER.isErrorEnabled()) {
+ if (LOGGER.isErrorEnabled()) {
LOGGER.error("Query failed for Resource {}: query={} Errors:{}, schema={}",
- queryResource.getPath(), cleanLog.sanitize(query), errors, schemaDef);
+ queryResource.getPath(), cleanLog.sanitize(query), errors, schemaDef);
}
}
LOGGER.debug("ExecutionResult.isDataPresent={}", result.isDataPresent());
return result.toSpecification();
} catch (Exception e) {
- final String message = String.format("Query failed for Resource %s: query=%s", queryResource.getPath(), cleanLog.sanitize(query));
+ final String message =
+ String.format("Query failed for Resource %s: query=%s", queryResource.getPath(), cleanLog.sanitize(query));
LOGGER.error(String.format("%s, schema=%s", message, schemaDef), e);
return SlingGraphQLErrorHelper.toSpecification(message, e);
}
}
- private GraphQLSchema buildSchema(String sdl, Resource currentResource) {
- TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
- Iterable<GraphQLScalarType> scalars = scalarsProvider.getCustomScalars(typeRegistry.scalars());
- RuntimeWiring runtimeWiring = buildWiring(typeRegistry, scalars, currentResource);
- SchemaGenerator schemaGenerator = new SchemaGenerator();
- return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
- }
-
private RuntimeWiring buildWiring(TypeDefinitionRegistry typeRegistry, Iterable<GraphQLScalarType> scalars, Resource r) {
List<ObjectTypeDefinition> types = typeRegistry.getTypes(ObjectTypeDefinition.class);
RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
@@ -189,7 +231,7 @@ public class DefaultQueryExecutor implements QueryExecutor {
typeWiring.dataFetcher(field.getName(), fetcher);
}
} catch (SlingGraphQLException e) {
- throw e;
+ throw e;
} catch (Exception e) {
throw new SlingGraphQLException("Exception while building wiring.", e);
}
@@ -218,15 +260,15 @@ public class DefaultQueryExecutor implements QueryExecutor {
}
} catch (SlingGraphQLException e) {
throw e;
- } catch(Exception e) {
+ } catch (Exception e) {
throw new SlingGraphQLException("Exception while building wiring.", e);
}
}
private String getDirectiveArgumentValue(Directive d, String name) {
final Argument a = d.getArgument(name);
- if(a != null && a.getValue() instanceof StringValue) {
- return ((StringValue)a.getValue()).getValue();
+ if (a != null && a.getValue() instanceof StringValue) {
+ return ((StringValue) a.getValue()).getValue();
}
return null;
}
@@ -249,13 +291,13 @@ public class DefaultQueryExecutor implements QueryExecutor {
private DataFetcher<Object> getDataFetcher(FieldDefinition field, Resource currentResource) {
DataFetcher<Object> result = null;
- final Directive d =field.getDirective(FETCHER_DIRECTIVE);
- if(d != null) {
+ final Directive d = field.getDirective(FETCHER_DIRECTIVE);
+ if (d != null) {
final String name = validateFetcherName(getDirectiveArgumentValue(d, FETCHER_NAME));
final String options = getDirectiveArgumentValue(d, FETCHER_OPTIONS);
final String source = getDirectiveArgumentValue(d, FETCHER_SOURCE);
SlingDataFetcher<Object> f = dataFetcherSelector.getSlingFetcher(name);
- if(f != null) {
+ if (f != null) {
result = new SlingDataFetcherWrapper<>(f, currentResource, options, source);
}
}
@@ -265,12 +307,12 @@ public class DefaultQueryExecutor implements QueryExecutor {
private <T extends TypeDefinition<T>> TypeResolver getTypeResolver(TypeDefinition<T> typeDefinition, Resource currentResource) {
TypeResolver resolver = null;
final Directive d = typeDefinition.getDirective(RESOLVER_DIRECTIVE);
- if(d != null) {
+ if (d != null) {
final String name = validateResolverName(getDirectiveArgumentValue(d, RESOLVER_NAME));
final String options = getDirectiveArgumentValue(d, RESOLVER_OPTIONS);
final String source = getDirectiveArgumentValue(d, RESOLVER_SOURCE);
SlingTypeResolver<Object> r = typeResolverSelector.getSlingTypeResolver(name);
- if(r != null) {
+ if (r != null) {
resolver = new SlingTypeResolverWrapper(r, currentResource, options, source);
}
}
@@ -278,8 +320,8 @@ public class DefaultQueryExecutor implements QueryExecutor {
}
private @Nullable String prepareSchemaDefinition(@NotNull SchemaProvider schemaProvider,
- @NotNull org.apache.sling.api.resource.Resource resource,
- @NotNull String[] selectors) throws ScriptException {
+ @NotNull org.apache.sling.api.resource.Resource resource,
+ @NotNull String[] selectors) throws ScriptException {
try {
return schemaProvider.getSchema(resource, selectors);
} catch (Exception e) {
@@ -289,4 +331,79 @@ public class DefaultQueryExecutor implements QueryExecutor {
throw up;
}
}
+
+ GraphQLSchema getSchema(@NotNull String sdl, @NotNull Resource currentResource, @NotNull String[] selectors) {
+ readLock.lock();
+ String newHash = SHA256Hasher.getHash(sdl);
+ /*
+ Since the SchemaProviders that generate the SDL can dynamically change, but also since the resource is passed to the RuntimeWiring,
+ there's a two stage cache:
+
+ 1. a mapping between the resource, selectors and the SDL's hash
+ 2. a mapping between the hash and the compiled GraphQL schema
+ */
+ String resourceToHashMapKey = getCacheKey(currentResource, selectors);
+ String oldHash = resourceToHashMap.get(resourceToHashMapKey);
+ if (!newHash.equals(oldHash)) {
+ readLock.unlock();
+ writeLock.lock();
+ try {
+ oldHash = resourceToHashMap.get(resourceToHashMapKey);
+ if (!newHash.equals(oldHash)) {
+ resourceToHashMap.put(resourceToHashMapKey, newHash);
+ TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
+ Iterable<GraphQLScalarType> scalars = scalarsProvider.getCustomScalars(typeRegistry.scalars());
+ RuntimeWiring runtimeWiring = buildWiring(typeRegistry, scalars, currentResource);
+ SchemaGenerator schemaGenerator = new SchemaGenerator();
+ GraphQLSchema schema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
+ hashToSchemaMap.put(newHash, schema);
+ return schema;
+ }
+ readLock.lock();
+ } finally {
+ writeLock.unlock();
+ }
+ }
+ try {
+ return hashToSchemaMap.get(newHash);
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ private String getCacheKey(@NotNull Resource resource, @NotNull String[] selectors) {
+ return resource.getPath() + ":" + String.join(".", selectors);
+ }
+
+ private static class LRUCache<T> extends LinkedHashMap<String, T> {
+
+ private final int capacity;
+
+ public LRUCache(int capacity) {
+ this.capacity = capacity;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<String, T> eldest) {
+ return size() > capacity;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + Objects.hashCode(capacity);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof LRUCache) {
+ LRUCache<T> other = (LRUCache<T>) o;
+ return super.equals(o) && capacity == other.capacity;
+ }
+ return false;
+ }
+ }
+
}
diff --git a/src/main/java/org/apache/sling/graphql/core/hash/SHA256Hasher.java b/src/main/java/org/apache/sling/graphql/core/hash/SHA256Hasher.java
new file mode 100644
index 0000000..c853034
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/hash/SHA256Hasher.java
@@ -0,0 +1,53 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.graphql.core.hash;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.sling.graphql.api.SlingGraphQLException;
+import org.jetbrains.annotations.NotNull;
+
+public class SHA256Hasher {
+
+ private SHA256Hasher() {
+ }
+
+ @NotNull
+ public static String getHash(@NotNull String message) {
+ StringBuilder buffer = new StringBuilder();
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(message.getBytes(StandardCharsets.UTF_8));
+
+ for (byte b : hash) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ buffer.append('0');
+ }
+ buffer.append(hex);
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new SlingGraphQLException("Failed hashing message.", e);
+ }
+ return buffer.toString();
+ }
+}
+
diff --git a/src/test/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProviderTest.java b/src/test/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProviderTest.java
index d69c19e..31a76eb 100644
--- a/src/test/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProviderTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProviderTest.java
@@ -22,6 +22,7 @@ import org.apache.sling.commons.metrics.Counter;
import org.apache.sling.commons.metrics.MetricsService;
import org.apache.sling.commons.metrics.Timer;
import org.apache.sling.graphql.api.cache.GraphQLCacheProvider;
+import org.apache.sling.graphql.core.hash.SHA256Hasher;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.junit.Before;
import org.junit.Rule;
@@ -60,7 +61,7 @@ public class SimpleGraphQLCacheProviderTest {
context.registerInjectActivateService(new SimpleGraphQLCacheProvider());
SimpleGraphQLCacheProvider provider = (SimpleGraphQLCacheProvider) context.getService(GraphQLCacheProvider.class);
assertNotNull(provider);
- assertEquals("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", provider.getHash("hello world"));
+ assertEquals("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", SHA256Hasher.getHash("hello world"));
}
@Test
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
index 1ff6b1c..cbad4f2 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
@@ -18,11 +18,14 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package org.apache.sling.graphql.core.engine;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
+import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import org.apache.sling.graphql.api.SchemaProvider;
@@ -32,20 +35,24 @@ import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingGraphQLException;
import org.apache.sling.graphql.api.engine.QueryExecutor;
import org.apache.sling.graphql.api.engine.ValidationResult;
+import org.apache.sling.graphql.core.mocks.CharacterTypeResolver;
import org.apache.sling.graphql.core.mocks.DigestDataFetcher;
+import org.apache.sling.graphql.core.mocks.DroidDTO;
import org.apache.sling.graphql.core.mocks.DummyTypeResolver;
import org.apache.sling.graphql.core.mocks.EchoDataFetcher;
import org.apache.sling.graphql.core.mocks.FailingDataFetcher;
+import org.apache.sling.graphql.core.mocks.HumanDTO;
import org.apache.sling.graphql.core.mocks.MockSchemaProvider;
import org.apache.sling.graphql.core.mocks.TestUtil;
-import org.apache.sling.graphql.core.mocks.DroidDTO;
-import org.apache.sling.graphql.core.mocks.HumanDTO;
-import org.apache.sling.graphql.core.mocks.CharacterTypeResolver;
+import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
import org.junit.Test;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
+import graphql.schema.GraphQLSchema;
+
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
@@ -53,6 +60,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -321,4 +329,51 @@ public class DefaultQueryExecutorTest extends ResourceQueryTestBase {
}
}
+ @Test
+ public void testCachedSchemas() throws IOException {
+ // by default we'll get the test-schema
+ final DefaultQueryExecutor queryExecutor = (DefaultQueryExecutor) context.getService(QueryExecutor.class);
+ final RankedSchemaProviders schemaProvider = context.getService(RankedSchemaProviders.class);
+ assertNotNull(queryExecutor);
+ assertNotNull(schemaProvider);
+ String[] selectors = new String[]{};
+ GraphQLSchema schema1 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ GraphQLSchema schema2 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ assertEquals(schema1, schema2);
+
+ // change the schema provider
+ context.registerService(SchemaProvider.class, new MockSchemaProvider("test-schema-selected-foryou"), Constants.SERVICE_RANKING,
+ Integer.MAX_VALUE);
+ GraphQLSchema schema3 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ GraphQLSchema schema4 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ assertEquals(schema3, schema4);
+ assertNotEquals(schema1, schema3);
+ }
+
+ @Test
+ public void testSchemasWithTheCacheDisabled() throws IOException {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put("schemaCacheSize", 0);
+
+ DefaultQueryExecutor queryExecutor = (DefaultQueryExecutor) context.registerService(QueryExecutor.class, new DefaultQueryExecutor(),
+ properties);
+ MockOsgi.injectServices(queryExecutor, context.bundleContext(), properties);
+ MockOsgi.activate(queryExecutor, context.bundleContext(), properties);
+
+ final RankedSchemaProviders schemaProvider = context.getService(RankedSchemaProviders.class);
+ assertNotNull(queryExecutor);
+ assertNotNull(schemaProvider);
+ String[] selectors = new String[]{};
+ GraphQLSchema schema1 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ GraphQLSchema schema2 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ assertNotEquals(schema1, schema2);
+
+ // change the schema provider
+ context.registerService(SchemaProvider.class, new MockSchemaProvider("test-schema-selected-foryou"), Constants.SERVICE_RANKING,
+ Integer.MAX_VALUE);
+ GraphQLSchema schema3 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ GraphQLSchema schema4 = queryExecutor.getSchema(schemaProvider.getSchema(resource, selectors), resource, selectors);
+ assertNotEquals(schema3, schema4);
+ }
+
}