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 <[email protected]> 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", "");
