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 2020/10/08 14:48:11 UTC

[sling-org-apache-sling-graphql-core] 01/01: SLING-9800 - Extract a service to be able to execute GraphQL queries directly

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

radu pushed a commit to branch issue/SLING-9800
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git

commit a897812104b93ba852116cd541ff201c20329f43
Author: Radu Cotescu <co...@adobe.com>
AuthorDate: Thu Oct 8 16:45:50 2020 +0200

    SLING-9800 - Extract a service to be able to execute GraphQL queries directly
    
    * defined a new service - QueryExecutor - to execute GraphQL queries
    * switched the GraphQLServlet and GraphQLScriptEngine to use the new service
    * cleaned code interacting with the OSGi service registry
    * made handling of OSGi services consistent, by using the service with
    the highest ranking which matches the filter
---
 pom.xml                                            |  24 ++-
 .../apache/sling/graphql/api/SlingDataFetcher.java |   4 +
 .../sling/graphql/api/SlingGraphQLException.java   |  43 +++++
 .../sling/graphql/api/engine/QueryExecutor.java    |  61 +++++++
 .../sling/graphql/api/engine/package-info.java     |  22 +++
 .../org/apache/sling/graphql/api/package-info.java |   4 +-
 .../core/cache/SimpleGraphQLCacheProvider.java     |   2 +-
 ...esourceQuery.java => DefaultQueryExecutor.java} | 180 +++++++++++----------
 .../core/engine/ScriptedDataFetcherProvider.java   |  41 +----
 .../core/engine/SlingDataFetcherSelector.java      | 141 ++++++++++------
 .../sling/graphql/core/json/JsonSerializer.java    |  68 --------
 .../core/osgi/ServiceReferenceObjectTuple.java     |  65 ++++++++
 .../graphql/core/scalars/SlingScalarsProvider.java |  74 +++++----
 .../graphql/core/schema/DefaultSchemaProvider.java |   2 +-
 .../graphql/core/schema/RankedSchemaProviders.java |   2 +-
 .../core/scripting/GraphQLScriptEngine.java        |  18 +--
 .../core/scripting/GraphQLScriptEngineFactory.java |  25 +--
 .../sling/graphql/core/servlet/GraphQLServlet.java |  42 ++---
 .../sling/graphql/core/servlet/QueryParser.java    |  49 +++---
 ...ueryTest.java => DefaultQueryExecutorTest.java} |  79 ++++-----
 .../graphql/core/engine/ResourceQueryTestBase.java |  32 ++--
 .../engine/SlingDataFetcherNameValidationTest.java |  10 +-
 .../core/engine/SlingDataFetcherSelectorTest.java  |  31 ++--
 .../graphql/core/it/GraphQLCoreTestSupport.java    |  41 +++--
 .../sling/graphql/core/it/GraphQLServletIT.java    |   4 +-
 .../sling/graphql/core/it/ServerSideQueryIT.java   |   4 +-
 .../graphql/core/json/JsonSerializerTest.java      |  86 ----------
 .../apache/sling/graphql/core/mocks/TestUtil.java  |  10 +-
 .../core/osgi/ServiceReferenceObjectTupleTest.java |  80 +++++++++
 .../core/schema/RankedSchemaProvidersTest.java     |  17 +-
 .../core/schema/SchemaDescriptionsTest.java        |  38 +++--
 .../graphql/core/servlet/GraphQLServletTest.java   |  35 +---
 32 files changed, 729 insertions(+), 605 deletions(-)

diff --git a/pom.xml b/pom.xml
index bcc8f94..c9472ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -159,6 +159,24 @@
     </dependency>
     <dependency>
       <groupId>org.apache.sling</groupId>
+      <artifactId>org.apache.sling.commons.johnzon</artifactId>
+      <version>1.2.4</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-core</artifactId>
+      <version>1.2.8</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-mapper</artifactId>
+      <version>1.2.8</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.sling</groupId>
       <artifactId>org.apache.sling.resource.presence</artifactId>
       <version>0.0.2</version>
       <scope>test</scope>
@@ -188,12 +206,6 @@
       <scope>provided</scope>
     </dependency>
     <dependency>
-      <groupId>com.cedarsoftware</groupId>
-      <artifactId>json-io</artifactId>
-      <version>4.12.0</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>javax.servlet-api</artifactId>
       <scope>provided</scope>
diff --git a/src/main/java/org/apache/sling/graphql/api/SlingDataFetcher.java b/src/main/java/org/apache/sling/graphql/api/SlingDataFetcher.java
index 142f2ae..371ff36 100644
--- a/src/main/java/org/apache/sling/graphql/api/SlingDataFetcher.java
+++ b/src/main/java/org/apache/sling/graphql/api/SlingDataFetcher.java
@@ -30,6 +30,10 @@ import org.osgi.annotation.versioning.ConsumerType;
  */
 @ConsumerType
 public interface SlingDataFetcher<T> {
+    /**
+     * Defines the service registration property with which all {@code SlingDataFetcher} services have to be registered. The value should
+     * be namespaced, with namespaces being delimited by the "/" character.
+     */
     String NAME_SERVICE_PROPERTY = "name";
 
     @Nullable
diff --git a/src/main/java/org/apache/sling/graphql/api/SlingGraphQLException.java b/src/main/java/org/apache/sling/graphql/api/SlingGraphQLException.java
new file mode 100644
index 0000000..d447ba2
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/SlingGraphQLException.java
@@ -0,0 +1,43 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.api;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * The {@code SlingGraphQLException} defines the class of errors that can be thrown by the {@code org.apache.sling.graphql.core} bundle.
+ */
+@ProviderType
+public class SlingGraphQLException extends RuntimeException {
+
+    /**
+     * Creates a {@code SlingGraphQLException} without a known cause.
+     */
+    public SlingGraphQLException(String message) {
+        this(message, null);
+    }
+
+    /**
+     * Creates a {@code SlingGraphQLException} with a known cause.
+     */
+    public SlingGraphQLException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/graphql/api/engine/QueryExecutor.java b/src/main/java/org/apache/sling/graphql/api/engine/QueryExecutor.java
new file mode 100644
index 0000000..29e49ea
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/engine/QueryExecutor.java
@@ -0,0 +1,61 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.api.engine;
+
+import java.util.Map;
+
+import javax.json.JsonObject;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.graphql.api.SlingGraphQLException;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * A {@code QueryExecutor} service allows consumers to validate and execute GraphQL queries directly.
+ */
+@ProviderType
+public interface QueryExecutor {
+
+    /**
+     * Validates the passed {@code query} and {@code variables}, by checking if the query obeys the known schemas.
+     *
+     * @param query         the query
+     * @param variables     the query's variables; can be an empty {@link Map} if the query doesn't accept variables
+     * @param queryResource the current resource, used as the root for the query
+     * @param selectors     potential selectors used to select the schema applicable to the passed {@code query}
+     * @return {code true} if the {@code query} is valid, {@code false} otherwise
+     */
+    boolean isValid(@NotNull String query, @NotNull Map<String, Object> variables, @NotNull Resource queryResource,
+                    @NotNull String[] selectors);
+
+    /**
+     * Executes the passed {@code query}.
+     *
+     * @param query         the query
+     * @param variables     the query's variables; can be an empty {@link Map} if the query doesn't accept variables
+     * @param queryResource the current resource, used as the root for the query
+     * @param selectors     potential selectors used to select the schema applicable to the passed {@code query}
+     * @return a {@link JsonObject} representing the query's result
+     * @throws SlingGraphQLException if the execution of the query leads to any issues
+     */
+    @NotNull
+    JsonObject execute(@NotNull String query, @NotNull Map<String, Object> variables, @NotNull Resource queryResource,
+                       @NotNull String[] selectors);
+}
diff --git a/src/main/java/org/apache/sling/graphql/api/engine/package-info.java b/src/main/java/org/apache/sling/graphql/api/engine/package-info.java
new file mode 100644
index 0000000..7e7ccb7
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/engine/package-info.java
@@ -0,0 +1,22 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+@Version("0.0.1")
+package org.apache.sling.graphql.api.engine;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/graphql/api/package-info.java b/src/main/java/org/apache/sling/graphql/api/package-info.java
index 496bec5..e5d4b27 100644
--- a/src/main/java/org/apache/sling/graphql/api/package-info.java
+++ b/src/main/java/org/apache/sling/graphql/api/package-info.java
@@ -21,6 +21,6 @@
   * This package contains APIs which are independent of
   * a specific implementation of the underlying graphQL engine.
   */
-@Version("3.1.0")
+@Version("3.2.0")
 package org.apache.sling.graphql.api;
-import org.osgi.annotation.versioning.Version;
\ No newline at end of file
+import org.osgi.annotation.versioning.Version;
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 97a6092..a2411a3 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
@@ -37,7 +37,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.core.engine.SlingGraphQLException;
+import org.apache.sling.graphql.api.SlingGraphQLException;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.BundleContext;
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/GraphQLResourceQuery.java b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
similarity index 52%
rename from src/main/java/org/apache/sling/graphql/core/engine/GraphQLResourceQuery.java
rename to src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
index 2175f9d..2b87dab 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/GraphQLResourceQuery.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
@@ -1,43 +1,48 @@
-/*
- * 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.
- */
-
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.engine;
 
-import java.io.IOException;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Pattern;
 
+import javax.json.Json;
+import javax.json.JsonObject;
 import javax.script.ScriptException;
 
 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.engine.QueryExecutor;
 import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
+import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import graphql.ExecutionInput;
 import graphql.ExecutionResult;
 import graphql.GraphQL;
+import graphql.GraphQLError;
 import graphql.ParseAndValidate;
 import graphql.language.Argument;
 import graphql.language.Directive;
@@ -52,76 +57,81 @@ import graphql.schema.idl.SchemaGenerator;
 import graphql.schema.idl.SchemaParser;
 import graphql.schema.idl.TypeDefinitionRegistry;
 
-/**
- * Run a GraphQL query in the context of a Sling Resource
- */
-public class GraphQLResourceQuery {
+@Component(
+        service = QueryExecutor.class
+)
+public class DefaultQueryExecutor implements QueryExecutor {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultQueryExecutor.class);
 
     public static final String FETCHER_DIRECTIVE = "fetcher";
     public static final String FETCHER_NAME = "name";
     public static final String FETCHER_OPTIONS = "options";
     public static final String FETCHER_SOURCE = "source";
-    private static final Pattern FETCHER_NAME_PATTERN = Pattern.compile("\\w+(/\\w+)+");
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(GraphQLResourceQuery.class);
+    @Reference
+    private RankedSchemaProviders schemaProvider;
 
-    private GraphQLResourceQuery() {}
+    @Reference
+    private SlingDataFetcherSelector dataFetcherSelector;
 
-    public static ExecutionResult executeQuery(@NotNull SchemaProvider schemaProvider,
-                                               @NotNull SlingDataFetcherSelector fetchersSelector,
-                                               @NotNull SlingScalarsProvider scalarsProvider,
-                                               @NotNull Resource r,
-                                               @NotNull String[] requestSelectors,
-                                               @NotNull String query, @NotNull Map<String, Object> variables) throws ScriptException {
-        String schemaDef = null;
+    @Reference
+    private SlingScalarsProvider scalarsProvider;
+
+    @Override
+    public boolean isValid(@NotNull String query, @NotNull Map<String, Object> variables, @NotNull Resource queryResource,
+                           @NotNull String[] selectors) {
         try {
-            schemaDef = prepareSchemaDefinition(schemaProvider, r, requestSelectors);
-            LOGGER.debug("Resource {} maps to GQL schema {}", r.getPath(), schemaDef);
-            final GraphQLSchema schema = buildSchema(schemaDef, fetchersSelector, scalarsProvider, r);
-            final GraphQL graphQL = GraphQL.newGraphQL(schema).build();
-            LOGGER.debug("Executing query\n[{}]\nat [{}] with variables [{}]", query, r.getPath(), variables);
-            ExecutionInput ei = ExecutionInput.newExecutionInput()
+            String schemaDef = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
+            LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
+            final GraphQLSchema schema =
+                    buildSchema(schemaDef, dataFetcherSelector, scalarsProvider, queryResource);
+            ExecutionInput executionInput = ExecutionInput.newExecutionInput()
                     .query(query)
                     .variables(variables)
                     .build();
-            final ExecutionResult result = graphQL.execute(ei);
-            LOGGER.debug("ExecutionResult.isDataPresent={}", result.isDataPresent());
-            return result;
-        } catch (ScriptException e) {
-            throw e;
-        } catch(Exception e) {
-            final ScriptException up = new ScriptException(
-                String.format("Query failed for Resource %s: schema=%s, query=%s", r.getPath(), schemaDef, query));
-            up.initCause(e);
-            LOGGER.info("GraphQL Query Exception", up);
-            throw up;                
+            return !ParseAndValidate.parseAndValidate(schema, executionInput).isFailure();
+        } catch (Exception e) {
+            LOGGER.error(String.format("Invalid query: %s.", query), e);
+            return false;
         }
     }
 
-    public static boolean isQueryValid(@NotNull SchemaProvider schemaProvider,
-                                       @NotNull SlingDataFetcherSelector fetchersSelector,
-                                       @NotNull SlingScalarsProvider scalarsProvider,
-                                       @NotNull Resource r,
-                                       @NotNull String[] requestSelectors,
-                                       @NotNull String query, Map<String, Object> variables) {
-
+    @Override
+    public @NotNull JsonObject execute(@NotNull String query, @NotNull Map<String, Object> variables, @NotNull Resource queryResource,
+                                       @NotNull String[] selectors) {
+        String schemaDef = null;
         try {
-            String schemaDef = prepareSchemaDefinition(schemaProvider, r, requestSelectors);
-            LOGGER.debug("Resource {} maps to GQL schema {}", r.getPath(), schemaDef);
-            final GraphQLSchema schema =
-                    buildSchema(schemaDef, fetchersSelector, scalarsProvider, r);
-            ExecutionInput executionInput = ExecutionInput.newExecutionInput()
+            schemaDef = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
+            LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
+            final GraphQLSchema schema = buildSchema(schemaDef, dataFetcherSelector, scalarsProvider, queryResource);
+            final GraphQL graphQL = GraphQL.newGraphQL(schema).build();
+            LOGGER.debug("Executing query\n[{}]\nat [{}] with variables [{}]", query, queryResource.getPath(), variables);
+            ExecutionInput ei = ExecutionInput.newExecutionInput()
                     .query(query)
                     .variables(variables)
                     .build();
-            return !ParseAndValidate.parseAndValidate(schema, executionInput).isFailure();
+            final ExecutionResult result = graphQL.execute(ei);
+            if (!result.getErrors().isEmpty()) {
+                StringBuilder errors = new StringBuilder();
+                for (GraphQLError error : result.getErrors()) {
+                    errors.append("Error type: ").append(error.getErrorType().toString()).append("; error message: ").append(error.getMessage()).append(System.lineSeparator());
+                }
+                throw new SlingGraphQLException(String.format("Query failed for Resource %s: schema=%s, query=%s%nErrors:%n%s",
+                        queryResource.getPath(), schemaDef, query, errors.toString()));
+            }
+            LOGGER.debug("ExecutionResult.isDataPresent={}", result.isDataPresent());
+            Map<String, Object> resultAsMap = result.toSpecification();
+            return Json.createObjectBuilder(resultAsMap).build().asJsonObject();
+        } catch (SlingGraphQLException e) {
+            throw e;
         } catch (Exception e) {
-            LOGGER.error(String.format("Invalid query: %s.", query), e);
-            return false;
+            throw new SlingGraphQLException(
+                    String.format("Query failed for Resource %s: schema=%s, query=%s", queryResource.getPath(), schemaDef, query), e);
         }
     }
 
-    private static GraphQLSchema buildSchema(String sdl, SlingDataFetcherSelector fetchers, SlingScalarsProvider scalarsProvider,
+    private GraphQLSchema buildSchema(String sdl, SlingDataFetcherSelector fetchers, SlingScalarsProvider scalarsProvider,
                                              Resource currentResource) {
         TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
         Iterable<GraphQLScalarType> scalars = scalarsProvider.getCustomScalars(typeRegistry.scalars());
@@ -130,12 +140,11 @@ public class GraphQLResourceQuery {
         return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
     }
 
-    private static RuntimeWiring buildWiring(TypeDefinitionRegistry typeRegistry, SlingDataFetcherSelector fetchers,
-                                       Iterable<GraphQLScalarType> scalars, Resource r) {
+    private RuntimeWiring buildWiring(TypeDefinitionRegistry typeRegistry, SlingDataFetcherSelector fetchers,
+                                             Iterable<GraphQLScalarType> scalars, Resource r) {
         List<ObjectTypeDefinition> types = typeRegistry.getTypes(ObjectTypeDefinition.class);
         RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
         for (ObjectTypeDefinition type : types) {
-
             builder.type(type.getName(), typeWiring -> {
                 for (FieldDefinition field : type.getFieldDefinitions()) {
                     try {
@@ -143,8 +152,10 @@ public class GraphQLResourceQuery {
                         if (fetcher != null) {
                             typeWiring.dataFetcher(field.getName(), fetcher);
                         }
-                    } catch(IOException e) {
-                        throw new RuntimeException("Exception while building wiring", e);
+                    } catch (SlingGraphQLException e) {
+                        throw  e;
+                    } catch (Exception e) {
+                        throw new SlingGraphQLException("Exception while building wiring.", e);
                     }
                 }
                 return typeWiring;
@@ -154,7 +165,7 @@ public class GraphQLResourceQuery {
         return builder.build();
     }
 
-    private static String getDirectiveArgumentValue(Directive d, String name) {
+    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();
@@ -162,19 +173,16 @@ public class GraphQLResourceQuery {
         return null;
     }
 
-    static String validateFetcherName(String name) throws IOException {
-        if(name == null) {
-            throw new IOException(FETCHER_NAME + " cannot be null");
-        }
-        if(!FETCHER_NAME_PATTERN.matcher(name).matches()) {
-            throw new IOException(String.format("Invalid fetcher name %s, does not match %s", 
-                name, FETCHER_NAME_PATTERN));
+    private @NotNull String validateFetcherName(String name) {
+        if (SlingDataFetcherSelector.nameMatchesPattern(name)) {
+            return name;
         }
-        return name;
+        throw new SlingGraphQLException(String.format("Invalid fetcher name %s, does not match %s",
+                name, SlingDataFetcherSelector.FETCHER_NAME_PATTERN));
     }
 
-    private static DataFetcher<Object> getDataFetcher(FieldDefinition field,
-        SlingDataFetcherSelector fetchers, Resource currentResource) throws IOException {
+    private DataFetcher<Object> getDataFetcher(FieldDefinition field, SlingDataFetcherSelector fetchers, Resource currentResource)
+            {
         DataFetcher<Object> result = null;
         final Directive d =field.getDirective(FETCHER_DIRECTIVE);
         if(d != null) {
@@ -189,8 +197,8 @@ public class GraphQLResourceQuery {
         return result;
     }
 
-    private static @Nullable String prepareSchemaDefinition(@NotNull SchemaProvider schemaProvider,
-                                                            @NotNull Resource resource,
+    private @Nullable String prepareSchemaDefinition(@NotNull SchemaProvider schemaProvider,
+                                                            @NotNull org.apache.sling.api.resource.Resource resource,
                                                             @NotNull String[] selectors) throws ScriptException {
         try {
             return schemaProvider.getSchema(resource, selectors);
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/ScriptedDataFetcherProvider.java b/src/main/java/org/apache/sling/graphql/core/engine/ScriptedDataFetcherProvider.java
index a4b81f0..2cf294f 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/ScriptedDataFetcherProvider.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/ScriptedDataFetcherProvider.java
@@ -22,10 +22,8 @@ package org.apache.sling.graphql.core.engine;
 
 import javax.servlet.Servlet;
 
-import org.apache.sling.api.resource.AbstractResource;
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceMetadata;
-import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.SyntheticResource;
 import org.apache.sling.api.scripting.SlingScript;
 import org.apache.sling.api.servlets.ServletResolver;
 import org.apache.sling.graphql.api.SlingDataFetcher;
@@ -45,43 +43,10 @@ public class ScriptedDataFetcherProvider {
     @Reference
     private ServletResolver servletResolver;
 
-    private static class FakeResource extends AbstractResource {
-
-        private final String resourceType;
-
-        FakeResource(String resourceType) {
-            this.resourceType = resourceType;
-        }
-
-        @Override
-        public String getPath() {
-            return "FAKE_RESOURCE_PATH";
-        }
-
-        @Override
-        public String getResourceType() {
-            return resourceType;
-        }
-
-        @Override
-        public String getResourceSuperType() {
-            return null;
-        }
-
-        @Override
-        public ResourceMetadata getResourceMetadata() {
-            throw new UnsupportedOperationException("Shouldn't be needed");
-        }
-
-        @Override
-        public ResourceResolver getResourceResolver() {
-            throw new UnsupportedOperationException("Shouldn't be needed");
-        }
-    }
-
     @Nullable
     SlingDataFetcher<Object> getDataFetcher(@NotNull String name) {
-        final Resource r = new FakeResource(FAKE_RESOURCE_TYPE_PREFIX + name);
+        final Resource r = new SyntheticResource(null,
+                "FAKE_RESOURCE_PATH", FAKE_RESOURCE_TYPE_PREFIX + name);
         final Servlet s = servletResolver.resolveServlet(r, SCRIPT_NAME);
         if(s instanceof SlingScript) {
             return new SlingScriptWrapper((SlingScript)s);
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelector.java b/src/main/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelector.java
index a4666bc..def4e91 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelector.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelector.java
@@ -19,24 +19,39 @@
 
 package org.apache.sling.graphql.core.engine;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.graphql.api.SlingDataFetcher;
+import org.apache.sling.graphql.core.osgi.ServiceReferenceObjectTuple;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
-import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
-
-import java.io.IOException;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /** Selects a SlingDataFetcher used to retrieve data, based
  *  on a name specified by a GraphQL schema directive.
  */
-@Component(service=SlingDataFetcherSelector.class)
+@Component(
+        service=SlingDataFetcherSelector.class
+)
 public class SlingDataFetcherSelector {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(SlingDataFetcherSelector.class);
+    static final Pattern FETCHER_NAME_PATTERN = Pattern.compile("\\w+(/\\w+)+");
+
+    private final Map<String, TreeSet<ServiceReferenceObjectTuple<SlingDataFetcher<Object>>>> dataFetchers = new HashMap<>();
+
     /** Fetchers which have a name starting with this prefix must be
      *  under the {#link RESERVED_PACKAGE_PREFIX} package.
      */
@@ -47,62 +62,92 @@ public class SlingDataFetcherSelector {
      */
     public static final String RESERVED_PACKAGE_PREFIX = "org.apache.sling.";
 
-    private BundleContext bundleContext;
-
     @Reference
     private ScriptedDataFetcherProvider scriptedDataFetcherProvider;
 
-    @Activate
-    public void activate(BundleContext ctx) {
-        bundleContext = ctx;
+    /** @return a SlingDataFetcher, or null if none available. First tries to get an
+     *  OSGi SlingDataFetcher service, and if not found tries to find a scripted SlingDataFetcher.
+     */
+    @Nullable
+    public SlingDataFetcher<Object> getSlingFetcher(@NotNull String name) {
+        SlingDataFetcher<Object> result = getOsgiServiceFetcher(name);
+        if(result == null) {
+            result = scriptedDataFetcherProvider.getDataFetcher(name);
+        }
+        return result;
     }
 
-    /** Return a SlingFetcher from the available OSGi services, if there's one
-     *  registered with the supplied name.
-      */
-      @SuppressWarnings("unchecked")
-      private SlingDataFetcher<Object> getOsgiServiceFetcher(@NotNull String name) throws IOException {
-        SlingDataFetcher<Object> result = null;
-        final String filter = String.format("(%s=%s)", SlingDataFetcher.NAME_SERVICE_PROPERTY, name);
-        ServiceReference<?>[] refs= null;
-        try {
-            refs = bundleContext.getServiceReferences(SlingDataFetcher.class.getName(), filter);
-        } catch(InvalidSyntaxException ise) {
-            throw new IOException("Invalid OSGi filter syntax", ise);
+    /**
+     * Returns a SlingFetcher from the available OSGi services, if there's one registered with the supplied name.
+     */
+    private SlingDataFetcher<Object> getOsgiServiceFetcher(@NotNull String name) {
+        TreeSet<ServiceReferenceObjectTuple<SlingDataFetcher<Object>>> fetcherSet = dataFetchers.get(name);
+        if (fetcherSet != null && !fetcherSet.isEmpty()) {
+            return fetcherSet.last().getServiceObject();
         }
-        if(refs != null) {
-            // SlingFetcher services must have a unique name
-            if(refs.length > 1) {
-                throw new IOException(String.format("Got %d services for %s, expected just one", refs.length, filter));
+        return null;
+    }
+
+    private boolean hasValidName(@NotNull ServiceReference<SlingDataFetcher<Object>> serviceReference, @NotNull SlingDataFetcher<Object> fetcher) {
+        String name = PropertiesUtil.toString(serviceReference.getProperty(SlingDataFetcher.NAME_SERVICE_PROPERTY), null);
+        if (StringUtils.isNotEmpty(name)) {
+            if (!nameMatchesPattern(name)) {
+                LOGGER.error("Invalid SlingDataFetcher {}: fetcher name is not namespaced (e.g. ns/myFetcher)",
+                        fetcher.getClass().getName());
+                return false;
+            }
+            if (name.startsWith(RESERVED_NAME_PREFIX)) {
+                final String className = fetcher.getClass().getName();
+                if (!fetcher.getClass().getName().startsWith(RESERVED_PACKAGE_PREFIX)) {
+                    LOGGER.error(
+                            "Invalid SlingDataFetcher {}: fetcher names starting with '{}' are reserved for Apache Sling Java packages",
+                            className, RESERVED_NAME_PREFIX);
+                    return false;
+                }
             }
-            result = (SlingDataFetcher<Object>)bundleContext.getService(refs[0]);
-            validateResult(name, result);
+        } else {
+            LOGGER.error("Invalid {} implementation: fetcher {} is missing the mandatory value for its {} service property.",
+                    SlingDataFetcher.class.getName(), fetcher.getClass().getName(), SlingDataFetcher.NAME_SERVICE_PROPERTY);
+            return false;
         }
-        return result;
+        return true;
+    }
+
+    static boolean nameMatchesPattern(String name) {
+        if (StringUtils.isNotEmpty(name)) {
+            return FETCHER_NAME_PATTERN.matcher(name).matches();
+        }
+        return false;
     }
 
-    private void validateResult(String name, SlingDataFetcher<?> fetcher) throws IOException {
-        if(name.startsWith(RESERVED_NAME_PREFIX)) {
-            final String className = fetcher.getClass().getName();
-            if(!fetcher.getClass().getName().startsWith(RESERVED_PACKAGE_PREFIX)) {
-                throw new IOException(
-                    String.format(
-                        "Invalid SlingDataFetcher %s:"
-                        + " fetcher names starting with '%s' are reserved for Apache Sling Java packages", 
-                        className, RESERVED_NAME_PREFIX));
+    @Reference(
+            service = SlingDataFetcher.class,
+            cardinality = ReferenceCardinality.MULTIPLE,
+            policy = ReferencePolicy.DYNAMIC
+    )
+    private void bindSlingDataFetcher(ServiceReference<SlingDataFetcher<Object>> reference, SlingDataFetcher<Object> slingDataFetcher) {
+        if (hasValidName(reference, slingDataFetcher)) {
+            synchronized (dataFetchers) {
+                String name = (String) reference.getProperty(SlingDataFetcher.NAME_SERVICE_PROPERTY);
+                TreeSet<ServiceReferenceObjectTuple<SlingDataFetcher<Object>>> fetchers = dataFetchers.computeIfAbsent(name,
+                        key -> new TreeSet<>());
+                fetchers.add(new ServiceReferenceObjectTuple<>(reference, slingDataFetcher));
             }
         }
     }
 
-    /** @return a SlingDataFetcher, or null if none available. First tries to get an
-     *  OSGi SlingDataFetcher service, and if not found tries to find a scripted SlingDataFetcher.
-     */
-    @Nullable
-    public SlingDataFetcher<Object> getSlingFetcher(@NotNull String name) throws IOException {
-        SlingDataFetcher<Object> result = getOsgiServiceFetcher(name);
-        if(result == null) {
-            result = scriptedDataFetcherProvider.getDataFetcher(name);
+    @SuppressWarnings("unused")
+    private void unbindSlingDataFetcher(ServiceReference<SlingDataFetcher<Object>> reference) {
+        synchronized (dataFetchers) {
+            String name = (String) reference.getProperty(SlingDataFetcher.NAME_SERVICE_PROPERTY);
+            if (StringUtils.isNotEmpty(name)) {
+                TreeSet<ServiceReferenceObjectTuple<SlingDataFetcher<Object>>> fetchers = dataFetchers.get(name);
+                if (fetchers != null) {
+                    Optional<ServiceReferenceObjectTuple<SlingDataFetcher<Object>>> tupleToRemove =
+                            fetchers.stream().filter(tuple -> reference.equals(tuple.getServiceReference())).findFirst();
+                    tupleToRemove.ifPresent(fetchers::remove);
+                }
+            }
         }
-        return result;
     }
 }
diff --git a/src/main/java/org/apache/sling/graphql/core/json/JsonSerializer.java b/src/main/java/org/apache/sling/graphql/core/json/JsonSerializer.java
deleted file mode 100644
index 46e8ad2..0000000
--- a/src/main/java/org/apache/sling/graphql/core/json/JsonSerializer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-
-/*
- * 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.json;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.cedarsoftware.util.io.JsonReader;
-import com.cedarsoftware.util.io.JsonWriter;
-
-import org.apache.commons.io.output.WriterOutputStream;
-
-import graphql.ExecutionResult;
-
-/** All conversions between JSON and Objects should
- *  happen in this class, in case we want to replace
- *  the underlying converter library later */
-public class JsonSerializer {
-
-    public static final Map<String, Object> WRITER_OPTIONS;
-
-    static {
-        HashMap<String, Object> writerOptions = new HashMap<>();
-        writerOptions.put(JsonWriter.TYPE, false);
-        WRITER_OPTIONS = Collections.unmodifiableMap(writerOptions);
-    }
-
-    public void sendJSON(Writer out, ExecutionResult result) throws IOException {
-        final Object data = result.toSpecification();
-        if (data == null) {
-            throw new IOException("No data");
-        }
-        try(JsonWriter w = new JsonWriter(new WriterOutputStream(out), WRITER_OPTIONS)) {
-            w.write(data);
-        }
-    }
-
-    public String toJSON(Object data) {
-        return JsonWriter.objectToJson(data, WRITER_OPTIONS);
-    }
-
-    @SuppressWarnings("unchecked")
-    public Map<String, Object> jsonToMaps(InputStream input) {
-        return JsonReader.jsonToMaps(input, null);
-    }
-}
diff --git a/src/main/java/org/apache/sling/graphql/core/osgi/ServiceReferenceObjectTuple.java b/src/main/java/org/apache/sling/graphql/core/osgi/ServiceReferenceObjectTuple.java
new file mode 100644
index 0000000..49d05a2
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/osgi/ServiceReferenceObjectTuple.java
@@ -0,0 +1,65 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.osgi;
+
+import java.util.Objects;
+
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.ServiceReference;
+
+public class ServiceReferenceObjectTuple<T> implements Comparable<ServiceReferenceObjectTuple<T>> {
+
+    private final ServiceReference<T> serviceReference;
+    private final T serviceObject;
+
+    public ServiceReferenceObjectTuple(@NotNull ServiceReference<T> serviceReference, @NotNull T serviceObject) {
+        this.serviceReference = serviceReference;
+        this.serviceObject = serviceObject;
+    }
+
+    public ServiceReference<T> getServiceReference() {
+        return serviceReference;
+    }
+
+    public T getServiceObject() {
+        return serviceObject;
+    }
+
+    @Override
+    public int compareTo(@NotNull ServiceReferenceObjectTuple<T> o) {
+        return serviceReference.compareTo(o.serviceReference);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(serviceReference, serviceObject);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof ServiceReferenceObjectTuple) {
+            ServiceReferenceObjectTuple<T> other = (ServiceReferenceObjectTuple<T>) obj;
+            if (this == other) {
+                return true;
+            }
+            return Objects.equals(serviceReference, other.serviceReference) && Objects.equals(serviceObject, other.serviceObject);
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/sling/graphql/core/scalars/SlingScalarsProvider.java b/src/main/java/org/apache/sling/graphql/core/scalars/SlingScalarsProvider.java
index a78fff4..eb181fd 100644
--- a/src/main/java/org/apache/sling/graphql/core/scalars/SlingScalarsProvider.java
+++ b/src/main/java/org/apache/sling/graphql/core/scalars/SlingScalarsProvider.java
@@ -20,18 +20,23 @@
 
 package org.apache.sling.graphql.core.scalars;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.TreeSet;
 import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.graphql.api.SlingScalarConverter;
-import org.apache.sling.graphql.core.engine.SlingGraphQLException;
-import org.osgi.framework.BundleContext;
+import org.apache.sling.graphql.api.SlingGraphQLException;
+import org.apache.sling.graphql.core.osgi.ServiceReferenceObjectTuple;
 import org.osgi.framework.Constants;
-import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
-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.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
 
 import graphql.language.ScalarTypeDefinition;
 import graphql.schema.GraphQLScalarType;
@@ -45,41 +50,50 @@ import graphql.schema.idl.ScalarInfo;
     Constants.SERVICE_VENDOR + "=The Apache Software Foundation" })
 public class SlingScalarsProvider {
 
-    private BundleContext bundleContext;
-    
-    @Activate
-    public void activate(BundleContext ctx) {
-        bundleContext = ctx;
+    private final Map<String, TreeSet<ServiceReferenceObjectTuple<SlingScalarConverter<Object, Object>>>> scalars = new HashMap<>();
+
+    @Reference(
+            service = SlingScalarConverter.class,
+            cardinality = ReferenceCardinality.MULTIPLE,
+            policy = ReferencePolicy.DYNAMIC
+    )
+    private void bindSlingScalarConverter(ServiceReference<SlingScalarConverter<Object, Object>> serviceReference,
+                                          SlingScalarConverter<Object, Object> scalarConverter) {
+        String name = (String) serviceReference.getProperty(SlingScalarConverter.NAME_SERVICE_PROPERTY);
+        if (StringUtils.isNotEmpty(name)) {
+            synchronized (scalars) {
+                TreeSet<ServiceReferenceObjectTuple<SlingScalarConverter<Object, Object>>> set =
+                        scalars.computeIfAbsent(name, key -> new TreeSet<>());
+                set.add(new ServiceReferenceObjectTuple<>(serviceReference, scalarConverter));
+            }
+        }
     }
 
-    @SuppressWarnings("unchecked")
-    private GraphQLScalarType getScalar(String name) {
+    @SuppressWarnings("unused")
+    private void unbindSlingScalarConverter(ServiceReference<SlingScalarConverter<Object, Object>> serviceReference) {
+        String name = (String) serviceReference.getProperty(SlingScalarConverter.NAME_SERVICE_PROPERTY);
+        if (StringUtils.isNotEmpty(name)) {
+            synchronized (scalars) {
+                TreeSet<ServiceReferenceObjectTuple<SlingScalarConverter<Object, Object>>> set = scalars.get(name);
+                if (set != null) {
+                    Optional<ServiceReferenceObjectTuple<SlingScalarConverter<Object, Object>>> tupleToRemove =
+                            set.stream().filter(tuple -> serviceReference.equals(tuple.getServiceReference())).findFirst();
+                    tupleToRemove.ifPresent(set::remove);
+                }
+            }
+        }
+    }
 
+    private GraphQLScalarType getScalar(String name) {
         // Ignore standard scalars
         if(ScalarInfo.isGraphqlSpecifiedScalar(name)) {
             return null;
         }
-
-        SlingScalarConverter<Object, Object> converter = null;
-        final String filter = String.format("(%s=%s)", SlingScalarConverter.NAME_SERVICE_PROPERTY, name);
-        ServiceReference<?>[] refs= null;
-        try {
-            refs = bundleContext.getServiceReferences(SlingScalarConverter.class.getName(), filter);
-        } catch(InvalidSyntaxException ise) {
-            throw new SlingGraphQLException("Invalid OSGi filter syntax:" + filter);
-        }
-        if(refs != null) {
-            // SlingScalarConverter services must have a unique name for now
-            // (we might use a namespacing @directive in the schema to allow multiple ones with the same name)
-            if(refs.length > 1) {
-                throw new SlingGraphQLException(String.format("Got %d services for %s, expected just one", refs.length, filter));
-            }
-            converter = (SlingScalarConverter<Object, Object>)bundleContext.getService(refs[0]);
-        }
-
-        if(converter == null) {
+        TreeSet<ServiceReferenceObjectTuple<SlingScalarConverter<Object, Object>>> set = scalars.get(name);
+        if (set == null || set.isEmpty()) {
             throw new SlingGraphQLException("SlingScalarConverter with name '" + name + "' not found");
         }
+        SlingScalarConverter<Object, Object> converter = set.last().getServiceObject();
 
         return GraphQLScalarType.newScalar()
             .name(name)
diff --git a/src/main/java/org/apache/sling/graphql/core/schema/DefaultSchemaProvider.java b/src/main/java/org/apache/sling/graphql/core/schema/DefaultSchemaProvider.java
index 9464ea3..bcf7bf8 100644
--- a/src/main/java/org/apache/sling/graphql/core/schema/DefaultSchemaProvider.java
+++ b/src/main/java/org/apache/sling/graphql/core/schema/DefaultSchemaProvider.java
@@ -44,7 +44,7 @@ public class DefaultSchemaProvider implements SchemaProvider {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
-    public static final int SERVICE_RANKING = Integer.MAX_VALUE - 100;
+    public static final int SERVICE_RANKING =  Integer.MIN_VALUE + 1000;
     public static final String SCHEMA_EXTENSION = "GQLschema";
     public static final String DEFAULT_SCHEMA = "";
 
diff --git a/src/main/java/org/apache/sling/graphql/core/schema/RankedSchemaProviders.java b/src/main/java/org/apache/sling/graphql/core/schema/RankedSchemaProviders.java
index dd856ac..b67de45 100644
--- a/src/main/java/org/apache/sling/graphql/core/schema/RankedSchemaProviders.java
+++ b/src/main/java/org/apache/sling/graphql/core/schema/RankedSchemaProviders.java
@@ -43,7 +43,7 @@ import org.osgi.service.component.annotations.ReferencePolicyOption;
 @Component(service = RankedSchemaProviders.class)
 public class RankedSchemaProviders implements SchemaProvider {
 
-    final RankedServices<SchemaProvider> providers = new RankedServices<>(Order.ASCENDING);
+    final RankedServices<SchemaProvider> providers = new RankedServices<>(Order.DESCENDING);
 
     @Override
     public @Nullable String getSchema(@NotNull final Resource r, @Nullable final String[] selectors) throws IOException {
diff --git a/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngine.java b/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngine.java
index 85681a4..5f4ac7f 100644
--- a/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngine.java
+++ b/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngine.java
@@ -1,4 +1,3 @@
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -28,6 +27,9 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonWriter;
 import javax.script.AbstractScriptEngine;
 import javax.script.Bindings;
 import javax.script.ScriptContext;
@@ -37,16 +39,10 @@ import javax.script.ScriptException;
 import org.apache.commons.io.IOUtils;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.scripting.SlingBindings;
-import org.apache.sling.graphql.core.engine.GraphQLResourceQuery;
-import org.apache.sling.graphql.core.json.JsonSerializer;
-
-import graphql.ExecutionResult;
 
 public class GraphQLScriptEngine extends AbstractScriptEngine {
 
     private final GraphQLScriptEngineFactory factory;
-    private final JsonSerializer jsonSerializer = new JsonSerializer();
-    public static final int JSON_INDENT_SPACES = 2;
 
     public GraphQLScriptEngine(GraphQLScriptEngineFactory factory) {
         this.factory = factory;
@@ -59,15 +55,13 @@ public class GraphQLScriptEngine extends AbstractScriptEngine {
 
     @Override
     public Object eval(Reader reader, ScriptContext context) throws ScriptException {
-        try {
+        try (JsonWriter writer = Json.createWriter((PrintWriter) context.getBindings(ScriptContext.ENGINE_SCOPE).get(SlingBindings.OUT))) {
 
             final Resource resource = (Resource) context.getBindings(ScriptContext.ENGINE_SCOPE)
                     .get(SlingBindings.RESOURCE);
             final String [] selectors = getRequestSelectors(resource);
-            final ExecutionResult result = GraphQLResourceQuery.executeQuery(factory.getSchemaProviders(), factory.getdataFetcherSelector(),
-                    factory.getScalarsProvider(), resource, selectors, IOUtils.toString(reader), Collections.emptyMap());
-            final PrintWriter out = (PrintWriter) context.getBindings(ScriptContext.ENGINE_SCOPE).get(SlingBindings.OUT);
-            jsonSerializer.sendJSON(out, result);
+            JsonObject json = factory.getQueryExecutor().execute(IOUtils.toString(reader), Collections.emptyMap(), resource, selectors);
+            writer.writeObject(json);
         } catch(Exception e) {
             throw new ScriptException(e);
         }
diff --git a/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngineFactory.java b/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngineFactory.java
index 72e1d75..59c4294 100644
--- a/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngineFactory.java
+++ b/src/main/java/org/apache/sling/graphql/core/scripting/GraphQLScriptEngineFactory.java
@@ -22,10 +22,8 @@ package org.apache.sling.graphql.core.scripting;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 
+import org.apache.sling.graphql.api.engine.QueryExecutor;
 import org.apache.sling.scripting.api.AbstractScriptEngineFactory;
-import org.apache.sling.graphql.core.engine.SlingDataFetcherSelector;
-import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
-import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Activate;
@@ -51,13 +49,7 @@ public class GraphQLScriptEngineFactory extends AbstractScriptEngineFactory {
     public static final String LANGUAGE_VERSION = "Sling:GraphQL:0.1";
 
     @Reference
-    private RankedSchemaProviders schemaProviders;
-
-    @Reference
-    private SlingDataFetcherSelector dataFetcherSelector;
-
-    @Reference
-    private SlingScalarsProvider scalarsProvider;
+    private QueryExecutor queryExecutor;
 
     @Activate
     private void activate(final GraphQLScriptEngineFactoryConfiguration config, final BundleContext ctx) {
@@ -81,16 +73,7 @@ public class GraphQLScriptEngineFactory extends AbstractScriptEngineFactory {
         return new GraphQLScriptEngine(this);
     }
 
-    RankedSchemaProviders getSchemaProviders() {
-        return schemaProviders;
+    QueryExecutor getQueryExecutor() {
+        return queryExecutor;
     }
-
-    SlingDataFetcherSelector getdataFetcherSelector() {
-        return dataFetcherSelector;
-    }
-
-    SlingScalarsProvider getScalarsProvider() {
-        return scalarsProvider;
-    }
-
 }
diff --git a/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java b/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
index 3d2daca..49bd8b4 100644
--- a/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
+++ b/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
@@ -25,6 +25,9 @@ import java.util.Arrays;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonWriter;
 import javax.servlet.Servlet;
 import javax.servlet.http.HttpServletResponse;
 
@@ -38,11 +41,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.engine.GraphQLResourceQuery;
-import org.apache.sling.graphql.core.engine.SlingDataFetcherSelector;
-import org.apache.sling.graphql.core.json.JsonSerializer;
-import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
-import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
+import org.apache.sling.graphql.api.engine.QueryExecutor;
 import org.jetbrains.annotations.NotNull;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
@@ -56,7 +55,6 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.MetricRegistry;
-import graphql.ExecutionResult;
 
 /** Servlet that can be activated to implement the standard
  *  GraphQL "protocol" as per https://graphql.org/learn/serving-over-http/
@@ -124,13 +122,7 @@ public class GraphQLServlet extends SlingAllMethodsServlet {
     }
 
     @Reference
-    private RankedSchemaProviders schemaProviders;
-
-    @Reference
-    private SlingDataFetcherSelector dataFetcherSelector;
-
-    @Reference
-    private SlingScalarsProvider scalarsProvider;
+    private QueryExecutor queryExecutor;
 
     @Reference
     private GraphQLCacheProvider cacheProvider;
@@ -144,7 +136,6 @@ public class GraphQLServlet extends SlingAllMethodsServlet {
     private String suffixPersisted;
     private Pattern patternGetPersistedQuery;
     private int cacheControlMaxAge;
-    private final JsonSerializer jsonSerializer = new JsonSerializer();
 
     private Counter cacheHits;
     private Counter cacheMisses;
@@ -286,9 +277,8 @@ public class GraphQLServlet extends SlingAllMethodsServlet {
             throws IOException {
         String rawQuery = IOUtils.toString(request.getReader());
         QueryParser.Result query = QueryParser.fromJSON(rawQuery);
-        if (GraphQLResourceQuery.isQueryValid(schemaProviders, dataFetcherSelector, scalarsProvider, request.getResource(),
-                request.getRequestPathInfo().getSelectors(), query.getQuery(), query.getVariables())) {
-
+        if (queryExecutor
+                .isValid(query.getQuery(), query.getVariables(), request.getResource(), request.getRequestPathInfo().getSelectors())) {
             String hash = cacheProvider.cacheQuery(rawQuery, request.getResource().getResourceType(),
                     request.getRequestPathInfo().getSelectorString());
             if (hash != null) {
@@ -316,10 +306,10 @@ public class GraphQLServlet extends SlingAllMethodsServlet {
             return;
         }
 
-        try {
-            final ExecutionResult executionResult = GraphQLResourceQuery.executeQuery(schemaProviders, dataFetcherSelector, scalarsProvider,
-                resource, request.getRequestPathInfo().getSelectors(), query, result.getVariables());
-            jsonSerializer.sendJSON(response.getWriter(), executionResult);
+        try (JsonWriter writer = Json.createWriter(response.getWriter())) {
+            final JsonObject json = queryExecutor.execute(query, result.getVariables(), resource,
+                    request.getRequestPathInfo().getSelectors());
+            writer.writeObject(json);
         } catch(Exception ex) {
             throw new IOException(ex);
         }
@@ -328,12 +318,12 @@ public class GraphQLServlet extends SlingAllMethodsServlet {
     private void execute(@NotNull String persistedQuery, SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
         response.setContentType("application/json");
         response.setCharacterEncoding("UTF-8");
-        try {
+        try (JsonWriter writer = Json.createWriter(response.getWriter())) {
             final QueryParser.Result result = QueryParser.fromJSON(persistedQuery);
-            final ExecutionResult executionResult = GraphQLResourceQuery.executeQuery(schemaProviders, dataFetcherSelector, scalarsProvider,
-                    request.getResource(), request.getRequestPathInfo().getSelectors(), result.getQuery(), result.getVariables());
-            jsonSerializer.sendJSON(response.getWriter(), executionResult);
-        } catch(Exception ex) {
+            final JsonObject json = queryExecutor
+                    .execute(result.getQuery(), result.getVariables(), request.getResource(), request.getRequestPathInfo().getSelectors());
+            writer.writeObject(json);
+        } catch (Exception ex) {
             throw new IOException(ex);
         }
     }
diff --git a/src/main/java/org/apache/sling/graphql/core/servlet/QueryParser.java b/src/main/java/org/apache/sling/graphql/core/servlet/QueryParser.java
index d5482bf..5a8585d 100644
--- a/src/main/java/org/apache/sling/graphql/core/servlet/QueryParser.java
+++ b/src/main/java/org/apache/sling/graphql/core/servlet/QueryParser.java
@@ -20,14 +20,17 @@
 package org.apache.sling.graphql.core.servlet;
 
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
+import java.io.StringReader;
 import java.util.Collections;
 import java.util.Map;
 
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.input.ReaderInputStream;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
+import org.apache.johnzon.mapper.Mapper;
+import org.apache.johnzon.mapper.MapperBuilder;
 import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.graphql.core.json.JsonSerializer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -57,21 +60,23 @@ public class QueryParser {
     }
 
     private static final String MIME_TYPE_JSON = "application/json";
-    private static final JsonSerializer jsonSerializer = new JsonSerializer();
     private static final String JSON_KEY_QUERY = "query";
     private static final String JSON_KEY_VARIABLES = "variables";
+    private static final Mapper MAPPER = new MapperBuilder().build();
 
     @Nullable
     public static Result fromRequest(@NotNull SlingHttpServletRequest request) throws IOException {
         String query = null;
         Map<String, Object> variables = null;
         if (request.getMethod().equalsIgnoreCase("POST") && MIME_TYPE_JSON.equals(request.getContentType())) {
-            Map<String, Object> requestJson = getInputJson(request);
-            query = (String) requestJson.get(JSON_KEY_QUERY);
-            if (query != null) {
+            try (JsonReader reader = Json.createReader(request.getReader())) {
+                JsonObject input = reader.readObject();
+                query = input.getString(JSON_KEY_QUERY);
                 query = query.replace("\\n", "\n");
+                if (input.containsKey(JSON_KEY_VARIABLES)) {
+                    variables = MAPPER.readObject(input.get(JSON_KEY_VARIABLES), Map.class);
+                }
             }
-            variables = (Map<String, Object>) requestJson.get(JSON_KEY_VARIABLES);
         }
 
         if (query == null) {
@@ -88,21 +93,19 @@ public class QueryParser {
     }
 
     public static Result fromJSON(String json) throws IOException {
-        Map<String, Object> jsonMap = jsonSerializer.jsonToMaps(IOUtils.toInputStream(json, StandardCharsets.UTF_8));
-        String query = (String) jsonMap.get(JSON_KEY_QUERY);
-        if (query != null) {
-            Map<String, Object> variables = (Map<String, Object>) jsonMap.get(JSON_KEY_VARIABLES);
-            if (variables == null) {
-                variables = Collections.emptyMap();
+        try (JsonReader reader = Json.createReader(new StringReader(json))) {
+            JsonObject jsonInput = reader.readObject();
+            String query = jsonInput.getString(JSON_KEY_QUERY);
+            if (query != null) {
+                Map<String, Object> variables = null;
+                if (jsonInput.containsKey(JSON_KEY_VARIABLES)) {
+                     variables= MAPPER.readObject(jsonInput.get(JSON_KEY_VARIABLES), Map.class);
+                } else {
+                    variables = Collections.emptyMap();
+                }
+                return new Result(query, variables);
             }
-            return new Result(query, variables);
+            throw new IOException("The provided JSON structure does not contain a query.");
         }
-        throw new IOException("The provided JSON structure does not contain a query.");
-
-    }
-
-    private static Map<String, Object> getInputJson(SlingHttpServletRequest req) throws IOException {
-        return jsonSerializer.jsonToMaps(new ReaderInputStream(req.getReader()));
     }
-
 }
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/GraphQLResourceQueryTest.java b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
similarity index 71%
rename from src/test/java/org/apache/sling/graphql/core/engine/GraphQLResourceQueryTest.java
rename to src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
index 251e9d2..41a0e4b 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/GraphQLResourceQueryTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
@@ -1,46 +1,49 @@
-/*
- * 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.
- */
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.engine;
 
-import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
-
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.nullValue;
-
-import java.io.IOException;
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Hashtable;
 
+import org.apache.sling.graphql.api.SchemaProvider;
+import org.apache.sling.graphql.api.SlingGraphQLException;
+import org.apache.sling.graphql.api.engine.QueryExecutor;
 import org.apache.sling.graphql.core.mocks.DigestDataFetcher;
 import org.apache.sling.graphql.core.mocks.EchoDataFetcher;
 import org.apache.sling.graphql.core.mocks.FailingDataFetcher;
 import org.apache.sling.graphql.core.mocks.MockSchemaProvider;
 import org.apache.sling.graphql.core.mocks.TestUtil;
 import org.junit.Test;
+import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
 
-public class GraphQLResourceQueryTest extends ResourceQueryTestBase {
-    
+import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class DefaultQueryExecutorTest extends ResourceQueryTestBase {
     protected void setupAdditionalServices() {
         final Dictionary<String, Object> staticData = new Hashtable<>();
         staticData.put("test", true);
@@ -51,7 +54,7 @@ public class GraphQLResourceQueryTest extends ResourceQueryTestBase {
         TestUtil.registerSlingDataFetcher(context.bundleContext(), "test/fortyTwo", new EchoDataFetcher(42));
         TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/digest", new DigestDataFetcher());
     }
-    
+
     @Test
     public void basicTest() throws Exception {
         final String json = queryJSON("{ currentResource { path resourceType } }");
@@ -90,13 +93,14 @@ public class GraphQLResourceQueryTest extends ResourceQueryTestBase {
     }
 
     @Test
-    public void dataFetcherFailureTest() throws Exception {
+    public void dataFetcherFailureTest() {
         try {
             final String stmt = "{ currentResource { failure } }";
-            GraphQLResourceQuery.executeQuery(schemaProvider, dataFetchersSelector, scalarsProvider, resource, new String[] {}, stmt,
-                    Collections.emptyMap());
+            QueryExecutor queryExecutor = context.getService(QueryExecutor.class);
+            assertNotNull(queryExecutor);
+            queryExecutor.execute(stmt, Collections.emptyMap(), resource, new String[] {});
         } catch(RuntimeException rex) {
-            assertThat(rex.getMessage(), equalTo("FailureDataFetcher"));
+            assertThat(rex.getMessage(), containsString("DataFetchingException; error message: Exception while fetching data (/currentResource/failure) : FailingDataFetcher"));
         }
     }
 
@@ -111,14 +115,15 @@ public class GraphQLResourceQueryTest extends ResourceQueryTestBase {
     }
 
     @Test
-    public void invalidFetcherNamesTest() throws Exception {
-        schemaProvider = new MockSchemaProvider("failing-schema");
+    public void invalidFetcherNamesTest() {
+        context.registerService(SchemaProvider.class, new MockSchemaProvider("failing-schema"), Constants.SERVICE_RANKING,
+                Integer.MAX_VALUE);
         final ServiceRegistration<?> reg = TestUtil.registerSlingDataFetcher(context.bundleContext(), "missingSlash", new EchoDataFetcher(42));
         try {
             queryJSON("{ currentResource { missingSlash } }", new String[] {});
             fail("Expected query to fail");
         } catch(Exception e) {
-            TestUtil.assertNestedException(e, IOException.class, "does not match");
+            TestUtil.assertNestedException(e, SlingGraphQLException.class, "does not match");
         } finally {
             reg.unregister();
         }
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/ResourceQueryTestBase.java b/src/test/java/org/apache/sling/graphql/core/engine/ResourceQueryTestBase.java
index d9bfb0a..c5b3095 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/ResourceQueryTestBase.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/ResourceQueryTestBase.java
@@ -18,29 +18,28 @@
  */
 package org.apache.sling.graphql.core.engine;
 
-import static org.junit.Assert.assertTrue;
-
 import java.util.Collections;
 import java.util.UUID;
 
+import javax.json.JsonObject;
+
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.servlets.ServletResolver;
 import org.apache.sling.graphql.api.SchemaProvider;
-import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
-import org.apache.sling.graphql.core.json.JsonSerializer;
+import org.apache.sling.graphql.api.engine.QueryExecutor;
 import org.apache.sling.graphql.core.mocks.MockSchemaProvider;
 import org.apache.sling.graphql.core.mocks.MockScriptServlet;
 import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
+import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 import org.junit.Before;
 import org.junit.Rule;
 import org.mockito.Mockito;
 
-import graphql.ExecutionResult;
+import static org.junit.Assert.assertNotNull;
 
 public abstract class ResourceQueryTestBase {
-    protected SchemaProvider schemaProvider;
-    protected SlingDataFetcherSelector dataFetchersSelector;
-    protected SlingScalarsProvider scalarsProvider;
+
     protected Resource resource;
 
     @Rule
@@ -48,7 +47,8 @@ public abstract class ResourceQueryTestBase {
 
     @Before
     public void setup() {
-        schemaProvider = new MockSchemaProvider(getTestSchemaName());
+        SchemaProvider schemaProvider = new MockSchemaProvider(getTestSchemaName());
+        context.registerService(SchemaProvider.class, schemaProvider);
         final String resourceType = "RT-" + UUID.randomUUID();
         final String path = "/some/path/" + UUID.randomUUID();
         resource = Mockito.mock(Resource.class);
@@ -66,20 +66,20 @@ public abstract class ResourceQueryTestBase {
 
         context.registerInjectActivateService(new ScriptedDataFetcherProvider());
         context.registerInjectActivateService(new SlingDataFetcherSelector());
-        dataFetchersSelector = context.getService(SlingDataFetcherSelector.class);
         context.registerInjectActivateService(new SlingScalarsProvider());
-        scalarsProvider = context.getService(SlingScalarsProvider.class);
+        context.registerInjectActivateService(new RankedSchemaProviders());
+        context.registerInjectActivateService(new DefaultQueryExecutor());
     }
 
     protected String queryJSON(String stmt) throws Exception {
         return queryJSON(stmt, new String[]{});
     }
 
-    protected String queryJSON(String stmt, String [] selectors) throws Exception {
-        final ExecutionResult result = GraphQLResourceQuery.executeQuery(schemaProvider,
-            dataFetchersSelector, scalarsProvider, resource, selectors, stmt, Collections.emptyMap());
-        assertTrue("Expecting no errors: " + result.getErrors(), result.getErrors().isEmpty());
-        return new JsonSerializer().toJSON(result);
+    protected String queryJSON(String stmt, String [] selectors) {
+        final QueryExecutor queryExecutor = context.getService(QueryExecutor.class);
+        assertNotNull(queryExecutor);
+        JsonObject json = queryExecutor.execute(stmt, Collections.emptyMap(), resource, selectors);
+        return json.toString();
     }
 
     protected void setupAdditionalServices() {
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherNameValidationTest.java b/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherNameValidationTest.java
index bd14552..45508c4 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherNameValidationTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherNameValidationTest.java
@@ -59,14 +59,8 @@ public class SlingDataFetcherNameValidationTest {
 
     @Test
     public void testValidation() {
-        boolean valid = true;
-        try {
-            GraphQLResourceQuery.validateFetcherName(name);
-        } catch(IOException ioe) {
-            valid = false;
-        }
         final String msg = String.format("Expecting '%s' to be %s", name, expectValid ? "valid" : "invalid");
-        assertEquals(msg, valid, expectValid);
+        assertEquals(msg, SlingDataFetcherSelector.nameMatchesPattern(name), expectValid);
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelectorTest.java b/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelectorTest.java
index 3795e02..bc81723 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelectorTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/SlingDataFetcherSelectorTest.java
@@ -20,14 +20,19 @@ package org.apache.sling.graphql.core.engine;
 
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
 
 import java.io.IOException;
 
 import com.example.fetchers.DoNothingFetcher;
 
 import org.apache.sling.graphql.api.SlingDataFetcher;
+import org.apache.sling.graphql.api.SlingDataFetcherEnvironment;
 import org.apache.sling.graphql.core.mocks.DigestDataFetcher;
 import org.apache.sling.graphql.core.mocks.EchoDataFetcher;
 import org.apache.sling.graphql.core.mocks.TestUtil;
@@ -46,7 +51,7 @@ public class SlingDataFetcherSelectorTest {
 
     @Before
     public void setup() {
-        final ScriptedDataFetcherProvider sdfp = Mockito.mock(ScriptedDataFetcherProvider.class);
+        final ScriptedDataFetcherProvider sdfp = mock(ScriptedDataFetcherProvider.class);
         context.bundleContext().registerService(ScriptedDataFetcherProvider.class, sdfp, null);
         context.registerInjectActivateService(new SlingDataFetcherSelector());
         selector = context.getService(SlingDataFetcherSelector.class);
@@ -54,8 +59,9 @@ public class SlingDataFetcherSelectorTest {
         TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/digest", new DigestDataFetcher());
         TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/shouldFail", new DoNothingFetcher());
         TestUtil.registerSlingDataFetcher(context.bundleContext(), "example/ok", new DoNothingFetcher());
-        TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/duplicate", new EchoDataFetcher(451));
-        TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/duplicate", new DigestDataFetcher());
+        TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/duplicate", 0, new DigestDataFetcher());
+        TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/duplicate", 10, new EchoDataFetcher(451));
+        TestUtil.registerSlingDataFetcher(context.bundleContext(), "sling/duplicate", 5, new EchoDataFetcher(452));
     }
 
     @Test
@@ -71,18 +77,15 @@ public class SlingDataFetcherSelectorTest {
     }
 
     @Test
-    public void reservedNameError() throws Exception {
-        try {
-            selector.getSlingFetcher("sling/shouldFail");
-            fail("Expected getSlingFetcher to fail");
-        } catch(Exception e) {
-            TestUtil.assertNestedException(e, IOException.class, DoNothingFetcher.class.getName());
-            TestUtil.assertNestedException(e, IOException.class, "starting with 'sling/' are reserved for Apache Sling");
-        }
+    public void reservedNameError() {
+        assertNull(selector.getSlingFetcher("sling/shouldFail"));
     }
 
-    @Test(expected=IOException.class)
-    public void duplicateFetcherError() throws Exception {
+    @Test
+    public void sameNameFetcher() throws Exception {
         final SlingDataFetcher<Object> sdf = selector.getSlingFetcher("sling/duplicate");
+        assertNotNull(sdf);
+        assertEquals(EchoDataFetcher.class, sdf.getClass());
+        assertEquals(451, sdf.get(mock(SlingDataFetcherEnvironment.class)));
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java b/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java
index 71c948a..9b4712c 100644
--- a/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java
+++ b/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java
@@ -18,15 +18,24 @@
  */
 package org.apache.sling.graphql.core.it;
 
-import javax.inject.Inject;
+import java.io.Reader;
+import java.io.StringReader;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
-import com.cedarsoftware.util.io.JsonWriter;
+import javax.inject.Inject;
 
-import org.apache.commons.io.output.WriterOutputStream;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.johnzon.mapper.Mapper;
+import org.apache.johnzon.mapper.MapperBuilder;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.graphql.core.json.JsonSerializer;
+import org.apache.sling.engine.SlingRequestProcessor;
 import org.apache.sling.graphql.core.mocks.TestDataFetcherComponent;
 import org.apache.sling.servlethelpers.MockSlingHttpServletResponse;
 import org.apache.sling.servlethelpers.internalrequests.SlingInternalRequest;
@@ -39,32 +48,20 @@ import org.ops4j.pax.exam.options.ModifiableCompositeOption;
 import org.ops4j.pax.exam.options.extra.VMOption;
 import org.ops4j.pax.tinybundles.core.TinyBundle;
 import org.osgi.framework.Constants;
-import org.apache.sling.engine.SlingRequestProcessor;
 
 import static org.apache.sling.testing.paxexam.SlingOptions.slingCommonsMetrics;
-import static org.junit.Assert.fail;
 import static org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
 import static org.apache.sling.testing.paxexam.SlingOptions.slingResourcePresence;
 import static org.apache.sling.testing.paxexam.SlingOptions.slingScripting;
 import static org.apache.sling.testing.paxexam.SlingOptions.slingScriptingJsp;
+import static org.junit.Assert.fail;
 import static org.ops4j.pax.exam.CoreOptions.composite;
 import static org.ops4j.pax.exam.CoreOptions.junitBundles;
 import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
-import static org.ops4j.pax.exam.CoreOptions.vmOption;
-import static org.ops4j.pax.exam.CoreOptions.when;
 import static org.ops4j.pax.exam.CoreOptions.streamBundle;
+import static org.ops4j.pax.exam.CoreOptions.when;
 import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
 
-import java.io.Reader;
-import java.io.StringReader;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
 public abstract class GraphQLCoreTestSupport extends TestSupport {
 
     private final static int STARTUP_WAIT_SECONDS = 30;
@@ -95,12 +92,13 @@ public abstract class GraphQLCoreTestSupport extends TestSupport {
             slingQuickstart(),
             graphQLJava(),
             testBundle("bundle.filename"),
-            buildBundleWithExportedPackages(JsonSerializer.class, WriterOutputStream.class),
             newConfiguration("org.apache.sling.jcr.base.internal.LoginAdminWhitelist")
                 .put("whitelist.bundles.regexp", "^PAXEXAM.*$")
                 .asOption(),
             mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.servlet-helpers").versionAsInProject(),
-            mavenBundle().groupId("com.cedarsoftware").artifactId("json-io").versionAsInProject(),
+            mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.commons.johnzon").versionAsInProject(),
+            mavenBundle().groupId("org.apache.johnzon").artifactId("johnzon-core").versionAsInProject(),
+            mavenBundle().groupId("org.apache.johnzon").artifactId("johnzon-mapper").versionAsInProject(),
             slingResourcePresence(),
             slingCommonsMetrics(),
             jsonPath(),
@@ -227,7 +225,8 @@ public abstract class GraphQLCoreTestSupport extends TestSupport {
     }
 
     protected String toJSON(Object source) {
-        return JsonWriter.objectToJson(source, JsonSerializer.WRITER_OPTIONS);
+        Mapper mapper = new MapperBuilder().build();
+        return mapper.toStructure(source).toString();
     }
 
     protected Map<String, Object> toMap(String ...keyValuePairs) {
diff --git a/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java b/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java
index c685c88..a185058 100644
--- a/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java
+++ b/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java
@@ -250,8 +250,8 @@ public class GraphQLServletIT extends GraphQLCoreTestSupport {
 
     @Test
     public void testMultipleSchemaProviders() throws Exception {
-        new ReplacingSchemaProvider("currentResource", "REPLACED").register(bundleContext, defaultSchemaProvider, 1);
-        new ReplacingSchemaProvider("currentResource", "NOT_THIS_ONE").register(bundleContext, defaultSchemaProvider, Integer.MAX_VALUE);
+        new ReplacingSchemaProvider("currentResource", "REPLACED").register(bundleContext, defaultSchemaProvider, Integer.MAX_VALUE);
+        new ReplacingSchemaProvider("currentResource", "NOT_THIS_ONE").register(bundleContext, defaultSchemaProvider, 1);
         final String json = getContent("/graphql/two.gql", "query", "{ REPLACED { resourceType name } }");
         assertThat(json, hasJsonPath("$.data.REPLACED.resourceType", equalTo("graphql/test/two")));
         assertThat(json, hasJsonPath("$.data.REPLACED.name", equalTo("two")));
diff --git a/src/test/java/org/apache/sling/graphql/core/it/ServerSideQueryIT.java b/src/test/java/org/apache/sling/graphql/core/it/ServerSideQueryIT.java
index a1509b5..e8965dd 100644
--- a/src/test/java/org/apache/sling/graphql/core/it/ServerSideQueryIT.java
+++ b/src/test/java/org/apache/sling/graphql/core/it/ServerSideQueryIT.java
@@ -84,8 +84,8 @@ public class ServerSideQueryIT extends GraphQLCoreTestSupport {
 
     @Test
     public void testMultipleSchemaProviders() throws Exception {
-        new ReplacingSchemaProvider("scriptedSchemaResource", "REPLACED").register(bundleContext, defaultSchemaProvider, 1);
-        new ReplacingSchemaProvider("scriptedSchemaResource", "NOT_THIS_ONE").register(bundleContext, defaultSchemaProvider, Integer.MAX_VALUE);
+        new ReplacingSchemaProvider("scriptedSchemaResource", "REPLACED").register(bundleContext, defaultSchemaProvider, Integer.MAX_VALUE);
+        new ReplacingSchemaProvider("scriptedSchemaResource", "NOT_THIS_ONE").register(bundleContext, defaultSchemaProvider, 1);
         assertDefaultContent(".REPLACED", "REPLACED");
     }
 
diff --git a/src/test/java/org/apache/sling/graphql/core/json/JsonSerializerTest.java b/src/test/java/org/apache/sling/graphql/core/json/JsonSerializerTest.java
deleted file mode 100644
index c2b9d5b..0000000
--- a/src/test/java/org/apache/sling/graphql/core/json/JsonSerializerTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.json;
-
-import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.Matchers.nullValue;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import graphql.ExecutionResult;
-
-public class JsonSerializerTest {
-    private final JsonSerializer serializer = new JsonSerializer();
-
-    private static final Map<String, Object> TEST_MAP = toMap("A", "bc", "farenheit", 451, "really", true, "itsNothing",
-            null, "map", toMap("one", 1, "two", toMap("three", 3)));
-
-    private static Map<String, Object> toMap(Object... keyValuePairs) {
-        final Map<String, Object> result = new HashMap<>();
-        for (int i = 0; i < keyValuePairs.length; i += 2) {
-            result.put(keyValuePairs[i].toString(), keyValuePairs[i + 1]);
-        }
-        return result;
-    }
-
-    private void assertHasTestData(String json) {
-        assertThat(json, hasJsonPath("A", equalTo("bc")));
-        assertThat(json, hasJsonPath("farenheit", equalTo(451)));
-        assertThat(json, hasJsonPath("really", equalTo(true)));
-        assertThat(json, hasJsonPath("itsNothing", equalTo(null)));
-        assertThat(json, hasJsonPath("map.one", equalTo(1)));
-        assertThat(json, hasJsonPath("map.two.three", equalTo(3)));
-    }
-
-    @Test
-    public void testSendJSON() throws IOException {
-        final ExecutionResult r = Mockito.mock(ExecutionResult.class);
-        Mockito.when(r.toSpecification()).thenReturn(TEST_MAP);
-        final StringWriter w = new StringWriter();
-        serializer.sendJSON(w, r);
-        assertHasTestData(w.toString());
-    }
-
-    @Test
-    public void testToJSON() {
-        assertHasTestData(serializer.toJSON(TEST_MAP));
-    }
-
-    @Test
-    public void testToMap() throws UnsupportedEncodingException {
-        final String json = serializer.toJSON(TEST_MAP);
-        final Map<String, Object> map = serializer.jsonToMaps(new ByteArrayInputStream(json.getBytes("UTF-8")));
-        assertThat(map.get("A"), is("bc"));
-        assertThat(map.get("farenheit"), is(451L));
-        assertThat(map.get("really"), is(true));
-        assertThat(map.get("itsNothing"), nullValue());
-        assertThat(map.get("map").toString(), equalTo("{one=1, two={three=3}}"));
-    }
-}
diff --git a/src/test/java/org/apache/sling/graphql/core/mocks/TestUtil.java b/src/test/java/org/apache/sling/graphql/core/mocks/TestUtil.java
index 2f85566..a68d3cb 100644
--- a/src/test/java/org/apache/sling/graphql/core/mocks/TestUtil.java
+++ b/src/test/java/org/apache/sling/graphql/core/mocks/TestUtil.java
@@ -27,12 +27,20 @@ import java.util.Hashtable;
 import org.apache.sling.graphql.api.SlingDataFetcher;
 import org.apache.sling.graphql.api.SlingScalarConverter;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
 
 public class TestUtil {
+
     public static ServiceRegistration<?> registerSlingDataFetcher(BundleContext bc, String name, SlingDataFetcher<?> f) {
+        return registerSlingDataFetcher(bc, name, 0, f);
+    }
+
+    public static ServiceRegistration<?> registerSlingDataFetcher(BundleContext bc, String name, int serviceRanking,
+                                                                  SlingDataFetcher<?> f) {
         final Dictionary<String, Object> props = new Hashtable<>();
         props.put(SlingDataFetcher.NAME_SERVICE_PROPERTY, name);
+        props.put(Constants.SERVICE_RANKING, serviceRanking);
         return bc.registerService(SlingDataFetcher.class, f, props);
     }
 
@@ -56,4 +64,4 @@ public class TestUtil {
                 clazz.getName(), messageContains));
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/osgi/ServiceReferenceObjectTupleTest.java b/src/test/java/org/apache/sling/graphql/core/osgi/ServiceReferenceObjectTupleTest.java
new file mode 100644
index 0000000..c0d57b5
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/osgi/ServiceReferenceObjectTupleTest.java
@@ -0,0 +1,80 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.osgi;
+
+import java.util.HashMap;
+import java.util.TreeSet;
+
+import javax.servlet.Servlet;
+
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Rule;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class ServiceReferenceObjectTupleTest {
+
+    @Rule
+    public OsgiContext osgiContext = new OsgiContext();
+
+    @Test
+    public void testEqualsAndOrdering() throws InvalidSyntaxException {
+        Servlet servlet1 = mock(Servlet.class);
+        Servlet servlet2 = mock(Servlet.class);
+        osgiContext.registerService(Servlet.class, servlet1, Constants.SERVICE_RANKING, 1);
+        osgiContext.registerService(Servlet.class, servlet2, Constants.SERVICE_RANKING, 2);
+
+        ServiceReference<Servlet> sr1 =
+                osgiContext.bundleContext().getServiceReferences(Servlet.class, "(service.ranking=1)").stream().findFirst().orElse(null);
+
+        ServiceReference<Servlet> sr2 =
+                osgiContext.bundleContext().getServiceReferences(Servlet.class, "(service.ranking=2)").stream().findFirst().orElse(null);
+
+        ServiceReference<Servlet> sr3 =
+                osgiContext.bundleContext().getServiceReferences(Servlet.class, "(service.ranking=1)").stream().findFirst().orElse(null);
+
+        ServiceReferenceObjectTuple<Servlet> t1 = new ServiceReferenceObjectTuple(sr1, servlet1);
+        ServiceReferenceObjectTuple<Servlet> t3 = new ServiceReferenceObjectTuple(sr1, servlet1);
+        ServiceReferenceObjectTuple<Servlet> t2 = new ServiceReferenceObjectTuple(sr2, servlet2);
+
+        assertNotEquals(t1, t2);
+        assertEquals(t1, t3);
+
+        TreeSet<ServiceReferenceObjectTuple<Servlet>> treeSet = new TreeSet<>();
+        assertTrue(treeSet.add(t1));
+        assertTrue(treeSet.add(t2));
+        assertFalse(treeSet.add(t3));
+
+        HashMap<ServiceReferenceObjectTuple<Servlet>, Integer> hashMap = new HashMap<>();
+        assertNull(hashMap.put(t1, 1));
+        assertNull(hashMap.put(t2, 2));
+        Integer previous = hashMap.put(t3, 3);
+        assertTrue(previous != null && previous == 1);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/schema/RankedSchemaProvidersTest.java b/src/test/java/org/apache/sling/graphql/core/schema/RankedSchemaProvidersTest.java
index 2e83ae7..15dd73b 100644
--- a/src/test/java/org/apache/sling/graphql/core/schema/RankedSchemaProvidersTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/schema/RankedSchemaProvidersTest.java
@@ -80,26 +80,29 @@ public class RankedSchemaProvidersTest {
     public void providerPriorities() throws IOException {
         assertProvider("Beginning", DEFAULT_SCHEMA_PROVIDER_OUTPUT);
 
-        registerProvider("Y", DEFAULT_SERVICE_RANKING + 1);
-        assertProvider("After Y", DEFAULT_SCHEMA_PROVIDER_OUTPUT);
+        final ServiceRegistration<?> y = registerProvider("Y", DEFAULT_SERVICE_RANKING + 1);
+        assertProvider("After Default", "Y");
 
         final ServiceRegistration<?> z = registerProvider("Z", DEFAULT_SERVICE_RANKING - 1);
-        assertProvider("After Z", "Z");
+        assertProvider("Before Default", "Y");
 
         final ServiceRegistration<?> a = registerProvider("A", 1);
         assertProvider("After A", "A");
 
         final ServiceRegistration<?> b = registerProvider("B", 2);
-        assertProvider("After B", "A");
+        assertProvider("After B", "B");
 
         a.unregister();
         assertProvider("After removing A", "B");
 
         b.unregister();
-        assertProvider("After removing B", "Z");
+        assertProvider("After removing B", "Y");
 
         z.unregister();
-        assertProvider("After removing Z", DEFAULT_SCHEMA_PROVIDER_OUTPUT);
+        assertProvider("After removing Z", "Y");
+
+        y.unregister();
+        assertProvider("After removing Y", DEFAULT_SCHEMA_PROVIDER_OUTPUT);
     }
 
     @Test
@@ -110,4 +113,4 @@ public class RankedSchemaProvidersTest {
         registerProvider("A", 1);
         assertProvider("After A", "A");
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/schema/SchemaDescriptionsTest.java b/src/test/java/org/apache/sling/graphql/core/schema/SchemaDescriptionsTest.java
index 82cbf66..0b247a2 100644
--- a/src/test/java/org/apache/sling/graphql/core/schema/SchemaDescriptionsTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/schema/SchemaDescriptionsTest.java
@@ -18,34 +18,34 @@
  */
 package org.apache.sling.graphql.core.schema;
 
-import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
-
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
 import java.util.Collections;
 import java.util.UUID;
 
+import javax.json.JsonObject;
+
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.servlets.ServletResolver;
 import org.apache.sling.graphql.api.SchemaProvider;
-import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
-import org.hamcrest.CustomMatcher;
-import org.hamcrest.Matcher;
-import org.apache.sling.graphql.core.engine.GraphQLResourceQuery;
+import org.apache.sling.graphql.api.engine.QueryExecutor;
+import org.apache.sling.graphql.core.engine.DefaultQueryExecutor;
 import org.apache.sling.graphql.core.engine.ScriptedDataFetcherProvider;
 import org.apache.sling.graphql.core.engine.SlingDataFetcherSelector;
-import org.apache.sling.graphql.core.json.JsonSerializer;
 import org.apache.sling.graphql.core.mocks.MockSchemaProvider;
 import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.hamcrest.CustomMatcher;
+import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mockito;
 
-import graphql.ExecutionResult;
 import net.minidev.json.JSONArray;
 
+import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
 /**
  * Test the fields descriptions which are part of the schema as per
  * http://spec.graphql.org/June2018/#sec-Descriptions
@@ -55,9 +55,6 @@ import net.minidev.json.JSONArray;
  * containing just a hash character.
  */
 public class SchemaDescriptionsTest {
-    private SchemaProvider schemaProvider = new MockSchemaProvider("test-schema");
-    private SlingDataFetcherSelector dataFetchersSelector;
-    private SlingScalarsProvider scalarsProvider;
     private Resource resource;
     private String schemaJson;
 
@@ -107,17 +104,18 @@ public class SchemaDescriptionsTest {
         context.bundleContext().registerService(ServletResolver.class, servletResolver, null);
         context.registerInjectActivateService(new ScriptedDataFetcherProvider());
         context.registerInjectActivateService(new SlingDataFetcherSelector());
-        dataFetchersSelector = context.getService(SlingDataFetcherSelector.class);
         context.registerInjectActivateService(new SlingScalarsProvider());
-        scalarsProvider = context.getService(SlingScalarsProvider.class);
+        context.registerService(SchemaProvider.class, new MockSchemaProvider("test-schema"));
+        context.registerInjectActivateService(new RankedSchemaProviders());
+        context.registerInjectActivateService(new DefaultQueryExecutor());
         schemaJson = queryJSON(SCHEMA_QUERY);
     }
 
     private String queryJSON(String stmt) throws Exception {
-        final ExecutionResult result = GraphQLResourceQuery.executeQuery(schemaProvider,
-            dataFetchersSelector, scalarsProvider, resource, new String[] {}, stmt, Collections.emptyMap());
-        assertTrue("Expecting no errors: " + result.getErrors(), result.getErrors().isEmpty());
-        return new JsonSerializer().toJSON(result);
+        QueryExecutor queryExecutor = context.getService(QueryExecutor.class);
+        assertNotNull(queryExecutor);
+        JsonObject json = queryExecutor.execute(stmt, Collections.emptyMap(), resource, new String[] {});
+        return json.toString();
     }
 
     private void assertTypeDescription(String typeName, String expected) {
diff --git a/src/test/java/org/apache/sling/graphql/core/servlet/GraphQLServletTest.java b/src/test/java/org/apache/sling/graphql/core/servlet/GraphQLServletTest.java
index 100bdc0..0c1ab0c 100644
--- a/src/test/java/org/apache/sling/graphql/core/servlet/GraphQLServletTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/servlet/GraphQLServletTest.java
@@ -30,12 +30,8 @@ import org.apache.sling.api.servlets.ServletResolverConstants;
 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.SchemaProvider;
+import org.apache.sling.graphql.api.engine.QueryExecutor;
 import org.apache.sling.graphql.core.cache.SimpleGraphQLCacheProvider;
-import org.apache.sling.graphql.core.engine.GraphQLResourceQuery;
-import org.apache.sling.graphql.core.engine.SlingDataFetcherSelector;
-import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
-import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.apache.sling.testing.mock.sling.servlet.MockRequestPathInfo;
 import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
@@ -44,7 +40,6 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.MockedStatic;
 import org.mockito.junit.MockitoJUnitRunner;
 
 import com.codahale.metrics.MetricRegistry;
@@ -52,9 +47,7 @@ import com.codahale.metrics.MetricRegistry;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -66,30 +59,23 @@ public class GraphQLServletTest {
     @Before
     public void setUp() {
         MetricsService metricsService = mock(MetricsService.class);
-        when(metricsService.counter(anyString())).thenReturn(mock(Counter.class));
+        when(metricsService.counter(any(String.class))).thenReturn(mock(Counter.class));
 
         Timer timer = mock(Timer.class);
         when(timer.time()).thenReturn(mock(Timer.Context.class));
-        when(metricsService.timer(anyString())).thenReturn(timer);
+        when(metricsService.timer(any(String.class))).thenReturn(timer);
         context.registerService(MetricsService.class, metricsService);
 
         MetricRegistry metricRegistry = mock(MetricRegistry.class);
         context.registerService(MetricRegistry.class, metricRegistry, "name", "sling");
+
+        QueryExecutor queryExecutor = mock(QueryExecutor.class);
+        when(queryExecutor.isValid(any(String.class), any(Map.class), any(Resource.class), any(String[].class))).thenReturn(true);
+        context.registerService(QueryExecutor.class, queryExecutor);
     }
 
     @Test
     public void testCachingErrors() throws IOException {
-        try (MockedStatic<GraphQLResourceQuery> graphQLResourceQueryMockedStatic = mockStatic(GraphQLResourceQuery.class)) {
-            graphQLResourceQueryMockedStatic.when(() -> GraphQLResourceQuery.isQueryValid(any(SchemaProvider.class),
-                    any(SlingDataFetcherSelector.class), any(SlingScalarsProvider.class), any(Resource.class), any(String[].class),
-                    anyString(), any(Map.class))).thenReturn(true);
-            RankedSchemaProviders rankedSchemaProviders = mock(RankedSchemaProviders.class);
-            context.registerService(rankedSchemaProviders);
-            SlingDataFetcherSelector slingDataFetcherSelector = mock(SlingDataFetcherSelector.class);
-            context.registerService(slingDataFetcherSelector);
-            SlingScalarsProvider slingScalarsProvider = mock(SlingScalarsProvider.class);
-            context.registerService(slingScalarsProvider);
-
             context.registerInjectActivateService(new SimpleGraphQLCacheProvider(), "maxMemory", 10);
 
             context.registerInjectActivateService(new GraphQLServlet(), ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES, "a/b/c",
@@ -114,17 +100,10 @@ public class GraphQLServletTest {
             servlet.doPost(request, response);
 
             assertEquals(500, response.getStatus());
-        }
     }
 
     @Test
     public void testDisabledSuffix() throws IOException {
-        RankedSchemaProviders rankedSchemaProviders = mock(RankedSchemaProviders.class);
-        context.registerService(rankedSchemaProviders);
-        SlingDataFetcherSelector slingDataFetcherSelector = mock(SlingDataFetcherSelector.class);
-        context.registerService(slingDataFetcherSelector);
-        SlingScalarsProvider slingScalarsProvider = mock(SlingScalarsProvider.class);
-        context.registerService(slingScalarsProvider);
         context.registerInjectActivateService(new SimpleGraphQLCacheProvider());
         context.registerInjectActivateService(new GraphQLServlet(), ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES, "a/b/c",
                 "persistedQueries.suffix", "");