SLING-10085 - Cache the GraphQL schemas in the DefaultQueryExecutor

* cache the TypeRegistryDefinition, based on the schema's text representation;
the GraphQL schema object cannot be cached, due to its embedded runtime
wiring, which holds a Resource object
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 063f3ff..3591bca 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
@@ -97,10 +97,11 @@
     private static final LogSanitizer cleanLog = new LogSanitizer();
 
     private Map<String, String> resourceToHashMap;
-    private Map<String, GraphQLSchema> hashToSchemaMap;
+    private Map<String, TypeDefinitionRegistry> hashToSchemaMap;
     private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
     private final Lock readLock = readWriteLock.readLock();
     private final Lock writeLock = readWriteLock.writeLock();
+    private final SchemaGenerator schemaGenerator = new SchemaGenerator();
 
     @Reference
     private RankedSchemaProviders schemaProvider;
@@ -146,7 +147,8 @@
                         Arrays.toString(selectors)));
             }
             LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
-            final GraphQLSchema schema = getSchema(schemaDef, queryResource, selectors);
+            final TypeDefinitionRegistry typeDefinitionRegistry = getTypeDefinitionRegistry(schemaDef, queryResource, selectors);
+            final GraphQLSchema schema = buildSchema(typeDefinitionRegistry, queryResource);
             ExecutionInput executionInput = ExecutionInput.newExecutionInput()
                     .query(query)
                     .variables(variables)
@@ -182,7 +184,8 @@
                         Arrays.toString(selectors)));
             }
             LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
-            final GraphQLSchema schema = getSchema(schemaDef, queryResource, selectors);
+            final TypeDefinitionRegistry typeDefinitionRegistry = getTypeDefinitionRegistry(schemaDef, queryResource, selectors);
+            final GraphQLSchema schema = buildSchema(typeDefinitionRegistry, queryResource);
             final GraphQL graphQL = GraphQL.newGraphQL(schema).build();
             if (LOGGER.isDebugEnabled()) {
                 LOGGER.debug("Executing query\n[{}]\nat [{}] with variables [{}]",
@@ -332,7 +335,7 @@
         }
     }
 
-    GraphQLSchema getSchema(@NotNull String sdl, @NotNull Resource currentResource, @NotNull String[] selectors) {
+    TypeDefinitionRegistry getTypeDefinitionRegistry(@NotNull String sdl, @NotNull Resource currentResource, @NotNull String[] selectors) {
         readLock.lock();
         String newHash = SHA256Hasher.getHash(sdl);
         /*
@@ -352,12 +355,8 @@
                 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;
+                    hashToSchemaMap.put(newHash, typeRegistry);
+                    return typeRegistry;
                 }
                 readLock.lock();
             } finally {
@@ -371,6 +370,12 @@
         }
     }
 
+    private GraphQLSchema buildSchema(@NotNull TypeDefinitionRegistry typeRegistry, @NotNull Resource currentResource) {
+        Iterable<GraphQLScalarType> scalars = scalarsProvider.getCustomScalars(typeRegistry.scalars());
+        RuntimeWiring runtimeWiring = buildWiring(typeRegistry, scalars, currentResource);
+        return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
+    }
+
     private String getCacheKey(@NotNull Resource resource, @NotNull String[] selectors) {
         return resource.getPath() + ":" + String.join(".", selectors);
     }
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 cbad4f2..769f168 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
@@ -51,7 +51,7 @@
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
-import graphql.schema.GraphQLSchema;
+import graphql.schema.idl.TypeDefinitionRegistry;
 
 import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
 import static org.hamcrest.Matchers.containsString;
@@ -330,28 +330,33 @@
     }
 
     @Test
-    public void testCachedSchemas() throws IOException {
+    public void testCachedTypeRegistry() 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);
+        TypeDefinitionRegistry
+                registry1 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        TypeDefinitionRegistry registry2 =
+                queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        assertEquals(registry1, registry2);
 
         // 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);
+        TypeDefinitionRegistry
+                registry3 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        TypeDefinitionRegistry registry4 =
+                queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        assertEquals(registry1, registry2);
+        assertEquals(registry3, registry4);
+        assertNotEquals(registry1, registry3);
     }
 
     @Test
-    public void testSchemasWithTheCacheDisabled() throws IOException {
+    public void testTypeRegistryWithTheCacheDisabled() throws IOException {
         Map<String, Object> properties = new HashMap<>();
         properties.put("schemaCacheSize", 0);
 
@@ -364,16 +369,20 @@
         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);
+        TypeDefinitionRegistry
+                registry1 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        TypeDefinitionRegistry registry2 =
+                queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        assertNotEquals(registry1, registry2);
 
         // 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);
+        TypeDefinitionRegistry
+                registry3 = queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        TypeDefinitionRegistry registry4 =
+                queryExecutor.getTypeDefinitionRegistry(schemaProvider.getSchema(resource, selectors), resource, selectors);
+        assertNotEquals(registry3, registry4);
     }
 
 }