You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2020/10/12 11:56:52 UTC
[sling-org-apache-sling-graphql-core] branch master updated:
SLING-9796 - Add support for using Unions
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-graphql-core.git
The following commit(s) were added to refs/heads/master by this push:
new 16919c9 SLING-9796 - Add support for using Unions
16919c9 is described below
commit 16919c929e31bef083dd5d9c4314a16f9ada5666
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Mon Oct 12 13:56:44 2020 +0200
SLING-9796 - Add support for using Unions
* applied a slightly modified patch provided by Adrian Kozma
Co-authored-by: Radu Cotescu <ra...@apache.org>
Co-authored-by: Adrian Kozma <ko...@adobe.com>
---
README.md | 57 +++++++-
.../sling/graphql/api/SlingTypeResolver.java | 41 ++++++
.../graphql/api/SlingTypeResolverEnvironment.java | 65 ++++++++++
.../graphql/core/engine/DefaultQueryExecutor.java | 68 ++++++++--
.../core/engine/SlingTypeResolverSelector.java | 144 +++++++++++++++++++++
.../core/engine/SlingTypeResolverWrapper.java | 54 ++++++++
.../engine/TypeResolverEnvironmentWrapper.java | 73 +++++++++++
.../example/resolvers/DoNothingTypeResolver.java | 36 ++++++
.../core/engine/DefaultQueryExecutorTest.java | 42 +++++-
.../core/engine/IntrospectionQueryTest.java | 24 +++-
.../graphql/core/engine/ResourceQueryTestBase.java | 1 +
.../SlingTypeResolverNameValidationTest.java | 65 ++++++++++
.../core/engine/SlingTypeResolverSelectorTest.java | 85 ++++++++++++
.../graphql/core/mocks/DummyTypeResolver.java | 32 +++++
.../apache/sling/graphql/core/mocks/TestUtil.java | 13 ++
.../graphql/core/mocks/TypeSlingResourceDTO.java | 37 ++++++
.../sling/graphql/core/mocks/TypeTestDTO.java | 50 +++++++
.../UnionTypeResolver.java} | 33 ++---
.../core/schema/SchemaDescriptionsTest.java | 5 +
...{test-schema.txt => failing-fetcher-schema.txt} | 31 +----
...schema.txt => failing-type-resolver-schema.txt} | 33 +----
src/test/resources/test-schema.txt | 19 ++-
22 files changed, 919 insertions(+), 89 deletions(-)
diff --git a/README.md b/README.md
index b6159b6..f71a423 100644
--- a/README.md
+++ b/README.md
@@ -100,11 +100,66 @@ The names of those `SlingDataFetcher` services are in the form
<namespace>/<name>
The `sling/` namespace is reserved for `SlingDataFetcher` services
-which hava Java package names that start with `org.apache.sling`.
+which have Java package names that start with `org.apache.sling`.
The `<options>` and `<source>` arguments of the directive can be used by the
`SlingDataFetcher` services to influence their behavior.
+<<<<<<< HEAD
+=======
+### Scripted SlingDataFetchers
+
+Besides Java, `SlingDataFetcher` scripts can be written in any scripting language that supported by the Sling instance's configuration.
+
+Here's an example from the test code. The schema contains the following statement:
+
+ scriptedFetcher (testing : String) : Test @fetcher(name:"scripted/example")
+
+And here's the data fetcher code:
+
+```javascript
+var result = {
+ boolValue: true,
+ resourcePath: "From the test script: " + resource.path,
+ testingArgument: environment.getArgument("testing"),
+ anotherValue: 450 + 1
+};
+
+result;
+```
+
+The data fetcher provider then looks for a script that handles the `graphql/fetchers/scripted/example` resource type with a `fetcher`script name. `graphql/fetchers`is a prefix (hardcoded for now) and `scripted/example` comes from the above schema's `@fetcher` directive.
+
+In that test, the `/graphql/fetchers/scripted/example/fetcher.js` shown above resolves with those requirements, it is executed to retrieve the requested data. That execution happens with a context consisting of the current `SlingDataFetcherEnvironment` under the `environment` key, and the current Sling Resource under the `resource` key, both used in this test script.
+
+## SlingTypeResolver selection with Schema Directives
+
+The GraphQL schemas used by this module can be enhanced using
+[schema directives](http://spec.graphql.org/June2018/#sec-Language.Directives)
+(see also the [Apollo docs](https://www.apollographql.com/docs/graphql-tools/schema-directives/) for how those work)
+that select specific `SlingTypeResolver` services to return the appropriate GraphQL object type using Unions.
+
+Here's a simple example, the test code has more:
+
+ # This directive maps the corresponding type resolver to a given Union
+ directive @resolver(
+ name: String,
+ options: String = "",
+ source: String = ""
+ ) on UNION
+
+ union TestUnion @resolver(name : "test/resolver", source : "TestUnion") = Type_1 | Type_2 | Type_3 | Type_4
+
+The names of those `SlingTypeResolver` services are in the form
+
+ <namespace>/<name>
+
+The `sling/` namespace is reserved for `SlingTypeResolver` services
+which have Java package names that start with `org.apache.sling`.
+
+The `<options>` and `<source>` arguments of the directive can be used by the
+`SlingTypeResolver` services to influence their behavior.
+
## Caching: Persisted queries API
No matter how you decide to create your Sling GraphQL endpoints, you have the option to allow GraphQL clients to use persisted queries.
diff --git a/src/main/java/org/apache/sling/graphql/api/SlingTypeResolver.java b/src/main/java/org/apache/sling/graphql/api/SlingTypeResolver.java
new file mode 100644
index 0000000..19dbc94
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/SlingTypeResolver.java
@@ -0,0 +1,41 @@
+/*
+ * 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.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Resolves the GraphQL object type of a given result item. Services must be registered with a {@link #NAME_SERVICE_PROPERTY} property
+ * with a unique value that's matched with the corresponding {@code @directive} in the GraphQL Schema.
+ */
+@ConsumerType
+public interface SlingTypeResolver<T> {
+
+ /**
+ * Defines the service registration property with which all {@link SlingTypeResolver} services have to be registered. The value should
+ * be namespaced, with namespaces being delimited by the "/" character.
+ */
+ String NAME_SERVICE_PROPERTY = "name";
+
+ @Nullable
+ T getType(@NotNull SlingTypeResolverEnvironment e);
+}
diff --git a/src/main/java/org/apache/sling/graphql/api/SlingTypeResolverEnvironment.java b/src/main/java/org/apache/sling/graphql/api/SlingTypeResolverEnvironment.java
new file mode 100644
index 0000000..9c52196
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/api/SlingTypeResolverEnvironment.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.api;
+
+import org.apache.sling.api.resource.Resource;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Provides contextual information to the {@link SlingTypeResolver}
+ */
+@SuppressWarnings("TypeParameterUnusedInFormals")
+
+@ProviderType
+public interface SlingTypeResolverEnvironment<T> {
+
+ /**
+ * @return the current Sling resource
+ */
+ @Nullable
+ Resource getCurrentResource();
+
+ /**
+ * @return the options, if set by the schema directive
+ */
+ @Nullable
+ String getResolverOptions();
+
+ /**
+ * @return the source, if set by the schema directive
+ */
+ @Nullable
+ String getResolverSource();
+
+ /**
+ * @return the GraphQL result item
+ */
+ @Nullable
+ Object getObject();
+
+ /**
+ * @param name the type name
+ * @return the GraphQL Object Type
+ */
+ @Nullable
+ T getObjectType(@NotNull String name);
+}
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
index b6ecfea..09fafd5 100644
--- a/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
+++ b/src/main/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutor.java
@@ -23,12 +23,16 @@ import java.util.Map;
import javax.script.ScriptException;
+import graphql.language.UnionTypeDefinition;
+import graphql.schema.GraphQLObjectType;
+import graphql.schema.TypeResolver;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.graphql.api.SchemaProvider;
import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingGraphQLException;
import org.apache.sling.graphql.api.engine.QueryExecutor;
import org.apache.sling.graphql.api.engine.ValidationResult;
+import org.apache.sling.graphql.api.SlingTypeResolver;
import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
import org.apache.sling.graphql.core.schema.RankedSchemaProviders;
import org.jetbrains.annotations.NotNull;
@@ -70,6 +74,11 @@ public class DefaultQueryExecutor implements QueryExecutor {
public static final String FETCHER_OPTIONS = "options";
public static final String FETCHER_SOURCE = "source";
+ public static final String RESOLVER_DIRECTIVE = "resolver";
+ public static final String RESOLVER_NAME = "name";
+ public static final String RESOLVER_OPTIONS = "options";
+ public static final String RESOLVER_SOURCE = "source";
+
@Reference
private RankedSchemaProviders schemaProvider;
@@ -77,6 +86,9 @@ public class DefaultQueryExecutor implements QueryExecutor {
private SlingDataFetcherSelector dataFetcherSelector;
@Reference
+ private SlingTypeResolverSelector typeResolverSelector;
+
+ @Reference
private SlingScalarsProvider scalarsProvider;
@Override
@@ -85,8 +97,7 @@ public class DefaultQueryExecutor implements QueryExecutor {
try {
String schemaDef = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
- final GraphQLSchema schema =
- buildSchema(schemaDef, dataFetcherSelector, scalarsProvider, queryResource);
+ final GraphQLSchema schema = buildSchema(schemaDef, queryResource);
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.variables(variables)
@@ -118,7 +129,7 @@ public class DefaultQueryExecutor implements QueryExecutor {
try {
schemaDef = prepareSchemaDefinition(schemaProvider, queryResource, selectors);
LOGGER.debug("Resource {} maps to GQL schema {}", queryResource.getPath(), schemaDef);
- final GraphQLSchema schema = buildSchema(schemaDef, dataFetcherSelector, scalarsProvider, queryResource);
+ final GraphQLSchema schema = buildSchema(schemaDef, queryResource);
final GraphQL graphQL = GraphQL.newGraphQL(schema).build();
LOGGER.debug("Executing query\n[{}]\nat [{}] with variables [{}]", query, queryResource.getPath(), variables);
ExecutionInput ei = ExecutionInput.newExecutionInput()
@@ -147,24 +158,22 @@ public class DefaultQueryExecutor implements QueryExecutor {
}
}
- private GraphQLSchema buildSchema(String sdl, SlingDataFetcherSelector fetchers, SlingScalarsProvider scalarsProvider,
- Resource currentResource) {
+ private GraphQLSchema buildSchema(String sdl, Resource currentResource) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
Iterable<GraphQLScalarType> scalars = scalarsProvider.getCustomScalars(typeRegistry.scalars());
- RuntimeWiring runtimeWiring = buildWiring(typeRegistry, fetchers, scalars, currentResource);
+ RuntimeWiring runtimeWiring = buildWiring(typeRegistry, scalars, currentResource);
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
- private RuntimeWiring buildWiring(TypeDefinitionRegistry typeRegistry, SlingDataFetcherSelector fetchers,
- Iterable<GraphQLScalarType> scalars, Resource r) {
+ private RuntimeWiring buildWiring(TypeDefinitionRegistry typeRegistry, 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 {
- DataFetcher<Object> fetcher = getDataFetcher(field, fetchers, r);
+ DataFetcher<Object> fetcher = getDataFetcher(field, r);
if (fetcher != null) {
typeWiring.dataFetcher(field.getName(), fetcher);
}
@@ -178,6 +187,20 @@ public class DefaultQueryExecutor implements QueryExecutor {
});
}
scalars.forEach(builder::scalar);
+
+ List<UnionTypeDefinition> unionTypes = typeRegistry.getTypes(UnionTypeDefinition.class);
+ for (UnionTypeDefinition type : unionTypes) {
+ try {
+ TypeResolver resolver = getTypeResolver(type, r);
+ if (resolver != null) {
+ builder.type(type.getName(), typeWriting -> typeWriting.typeResolver(resolver));
+ }
+ } catch (SlingGraphQLException e) {
+ throw e;
+ } catch(Exception e) {
+ throw new SlingGraphQLException("Exception while building wiring.", e);
+ }
+ }
return builder.build();
}
@@ -197,7 +220,15 @@ public class DefaultQueryExecutor implements QueryExecutor {
name, SlingDataFetcherSelector.FETCHER_NAME_PATTERN));
}
- private DataFetcher<Object> getDataFetcher(FieldDefinition field, SlingDataFetcherSelector fetchers, Resource currentResource)
+ private @NotNull String validateResolverName(String name) {
+ if (SlingTypeResolverSelector.nameMatchesPattern(name)) {
+ return name;
+ }
+ throw new SlingGraphQLException(String.format("Invalid type resolver name %s, does not match %s",
+ name, SlingTypeResolverSelector.RESOLVER_NAME_PATTERN));
+ }
+
+ private DataFetcher<Object> getDataFetcher(FieldDefinition field, Resource currentResource)
{
DataFetcher<Object> result = null;
final Directive d =field.getDirective(FETCHER_DIRECTIVE);
@@ -205,7 +236,7 @@ public class DefaultQueryExecutor implements QueryExecutor {
final String name = validateFetcherName(getDirectiveArgumentValue(d, FETCHER_NAME));
final String options = getDirectiveArgumentValue(d, FETCHER_OPTIONS);
final String source = getDirectiveArgumentValue(d, FETCHER_SOURCE);
- SlingDataFetcher<Object> f = fetchers.getSlingFetcher(name);
+ SlingDataFetcher<Object> f = dataFetcherSelector.getSlingFetcher(name);
if(f != null) {
result = new SlingDataFetcherWrapper<>(f, currentResource, options, source);
}
@@ -213,6 +244,21 @@ public class DefaultQueryExecutor implements QueryExecutor {
return result;
}
+ private TypeResolver getTypeResolver(UnionTypeDefinition typeDefinition, Resource currentResource) {
+ TypeResolver resolver = null;
+ final Directive d = typeDefinition.getDirective(RESOLVER_DIRECTIVE);
+ if(d != null) {
+ final String name = validateResolverName(getDirectiveArgumentValue(d, RESOLVER_NAME));
+ final String options = getDirectiveArgumentValue(d, RESOLVER_OPTIONS);
+ final String source = getDirectiveArgumentValue(d, RESOLVER_SOURCE);
+ SlingTypeResolver<Object> r = typeResolverSelector.getSlingTypeResolver(name);
+ if(r != null) {
+ resolver = new SlingTypeResolverWrapper(r, currentResource, options, source);
+ }
+ }
+ return resolver;
+ }
+
private @Nullable String prepareSchemaDefinition(@NotNull SchemaProvider schemaProvider,
@NotNull org.apache.sling.api.resource.Resource resource,
@NotNull String[] selectors) throws ScriptException {
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/SlingTypeResolverSelector.java b/src/main/java/org/apache/sling/graphql/core/engine/SlingTypeResolverSelector.java
new file mode 100644
index 0000000..b59cd62
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/engine/SlingTypeResolverSelector.java
@@ -0,0 +1,144 @@
+/*
+ * 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.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.SlingTypeResolver;
+import org.apache.sling.graphql.core.osgi.ServiceReferenceObjectTuple;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.ServiceReference;
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Selects a SlingTypeProvider used to get the corresponding object type,
+ * based on a name specified by a GraphQL schema directive.
+ */
+@Component(service = SlingTypeResolverSelector.class)
+public class SlingTypeResolverSelector {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SlingTypeResolverSelector.class);
+ static final Pattern RESOLVER_NAME_PATTERN = Pattern.compile("\\w+(/\\w+)+");
+
+ private final Map<String, TreeSet<ServiceReferenceObjectTuple<SlingTypeResolver<Object>>>> typeResolvers = new HashMap<>();
+
+ /**
+ * Resolvers which have a name starting with this prefix must be
+ * under the {#link RESERVED_PACKAGE_PREFIX} package.
+ */
+ public static final String RESERVED_NAME_PREFIX = "sling/";
+
+ /**
+ * Package name prefix for resolvers which have names starting
+ * with the {#link RESERVED_NAME_PREFIX}.
+ */
+ public static final String RESERVED_PACKAGE_PREFIX = "org.apache.sling.";
+
+ /**
+ * @return a SlingTypeResolver, or null if none available. First tries to get an
+ * OSGi SlingTypeResolver service, and if not found tries to find a scripted SlingTypeResolver.
+ */
+ @Nullable
+ public SlingTypeResolver<Object> getSlingTypeResolver(@NotNull String name) {
+ TreeSet<ServiceReferenceObjectTuple<SlingTypeResolver<Object>>> resolvers = typeResolvers.get(name);
+ if (resolvers != null && !resolvers.isEmpty()) {
+ return resolvers.last().getServiceObject();
+ }
+ return null;
+ }
+
+ private boolean hasValidName(@NotNull ServiceReference<SlingTypeResolver<Object>> serviceReference,
+ @NotNull SlingTypeResolver<Object> slingTypeResolver) {
+ String name = PropertiesUtil.toString(serviceReference.getProperty(SlingTypeResolver.NAME_SERVICE_PROPERTY), null);
+ if (StringUtils.isNotEmpty(name)) {
+ if (!nameMatchesPattern(name)) {
+ LOGGER.error("Invalid SlingTypeResolver {}: type resolver name is not namespaced (e.g. ns/myTypeResolver)",
+ slingTypeResolver.getClass().getName());
+ return false;
+ }
+ if (name.startsWith(RESERVED_NAME_PREFIX)) {
+ final String className = slingTypeResolver.getClass().getName();
+ if (!slingTypeResolver.getClass().getName().startsWith(RESERVED_PACKAGE_PREFIX)) {
+ LOGGER.error(
+ "Invalid SlingTypeResolver {}: type resolver names starting with '{}' are reserved for Apache Sling Java " +
+ "packages",
+ className, RESERVED_NAME_PREFIX);
+ return false;
+ }
+ }
+ } else {
+ LOGGER.error("Invalid {} implementation: type resolver {} is missing the mandatory value for its {} service property.",
+ SlingTypeResolver.class.getName(), slingTypeResolver.getClass().getName(), SlingTypeResolver.NAME_SERVICE_PROPERTY);
+ return false;
+ }
+ return true;
+ }
+
+ static boolean nameMatchesPattern(String name) {
+ if (StringUtils.isNotEmpty(name)) {
+ return RESOLVER_NAME_PATTERN.matcher(name).matches();
+ }
+ return false;
+ }
+
+ @Reference(
+ service = SlingTypeResolver.class,
+ cardinality = ReferenceCardinality.MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ )
+ private void bindSlingTypeResolver(ServiceReference<SlingTypeResolver<Object>> reference, SlingTypeResolver<Object> slingTypeResolver) {
+ if (hasValidName(reference, slingTypeResolver)) {
+ synchronized (typeResolvers) {
+ String name = (String) reference.getProperty(SlingTypeResolver.NAME_SERVICE_PROPERTY);
+ TreeSet<ServiceReferenceObjectTuple<SlingTypeResolver<Object>>> resolvers = typeResolvers.computeIfAbsent(name,
+ key -> new TreeSet<>());
+ resolvers.add(new ServiceReferenceObjectTuple<>(reference, slingTypeResolver));
+ }
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void unbindSlingTypeResolver(ServiceReference<SlingTypeResolver<Object>> reference) {
+ String name = (String) reference.getProperty(SlingTypeResolver.NAME_SERVICE_PROPERTY);
+ if (StringUtils.isNotEmpty(name)) {
+ synchronized (typeResolvers) {
+ TreeSet<ServiceReferenceObjectTuple<SlingTypeResolver<Object>>> resolvers = typeResolvers.get(name);
+ if (resolvers != null) {
+ Optional<ServiceReferenceObjectTuple<SlingTypeResolver<Object>>> tupleToRemove =
+ resolvers.stream().filter(tuple -> reference.equals(tuple.getServiceReference())).findFirst();
+ tupleToRemove.ifPresent(resolvers::remove);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/SlingTypeResolverWrapper.java b/src/main/java/org/apache/sling/graphql/core/engine/SlingTypeResolverWrapper.java
new file mode 100644
index 0000000..a9976c6
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/engine/SlingTypeResolverWrapper.java
@@ -0,0 +1,54 @@
+/*
+ * 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 graphql.TypeResolutionEnvironment;
+import graphql.schema.GraphQLObjectType;
+import graphql.schema.TypeResolver;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.graphql.api.SlingTypeResolver;
+
+/**
+ * Wraps a SlingTypeResolver to make it usable by graphql-java
+ */
+class SlingTypeResolverWrapper implements TypeResolver {
+
+ private final SlingTypeResolver<Object> resolver;
+ private final Resource currentResource;
+ private final String options;
+ private final String source;
+
+ SlingTypeResolverWrapper(SlingTypeResolver<Object> resolver, Resource currentResource, String options,
+ String source) {
+ this.resolver = resolver;
+ this.currentResource = currentResource;
+ this.options = options;
+ this.source = source;
+ }
+
+ @Override
+ public GraphQLObjectType getType(TypeResolutionEnvironment environment) {
+ Object r = resolver.getType(new TypeResolverEnvironmentWrapper(environment, currentResource, options, source));
+ if (r instanceof GraphQLObjectType) {
+ return (GraphQLObjectType) r;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/graphql/core/engine/TypeResolverEnvironmentWrapper.java b/src/main/java/org/apache/sling/graphql/core/engine/TypeResolverEnvironmentWrapper.java
new file mode 100644
index 0000000..a2a8583
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/engine/TypeResolverEnvironmentWrapper.java
@@ -0,0 +1,73 @@
+/*
+ * 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 graphql.TypeResolutionEnvironment;
+import graphql.schema.GraphQLObjectType;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.graphql.api.SlingTypeResolverEnvironment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Wraps the graphql-java TypeResolverEnvironment to provide
+ * our own SlingTypeResolverEnvironment interface. This avoids
+ * having to expose the graphql-java APIs in our own API.
+ */
+class TypeResolverEnvironmentWrapper implements SlingTypeResolverEnvironment<GraphQLObjectType> {
+ private final TypeResolutionEnvironment env;
+ private final Resource currentResource;
+ private final String options;
+ private final String source;
+
+ TypeResolverEnvironmentWrapper(TypeResolutionEnvironment env, Resource currentResource, String options,
+ String source) {
+ this.env = env;
+ this.currentResource = currentResource;
+ this.options = options;
+ this.source = source;
+ }
+
+ @Override
+ public Resource getCurrentResource() {
+ return currentResource;
+ }
+
+ @Override
+ public String getResolverOptions() {
+ return options;
+ }
+
+ @Override
+ public String getResolverSource() {
+ return source;
+ }
+
+ @Override
+ public Object getObject() {
+ return env.getObject();
+ }
+
+ @Override
+ @Nullable
+ public GraphQLObjectType getObjectType(@NotNull String name) {
+ return env.getSchema().getObjectType(name);
+ }
+}
diff --git a/src/test/java/com/example/resolvers/DoNothingTypeResolver.java b/src/test/java/com/example/resolvers/DoNothingTypeResolver.java
new file mode 100644
index 0000000..8aaeafa
--- /dev/null
+++ b/src/test/java/com/example/resolvers/DoNothingTypeResolver.java
@@ -0,0 +1,36 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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 com.example.resolvers;
+
+import org.apache.sling.graphql.api.SlingTypeResolver;
+import org.apache.sling.graphql.api.SlingTypeResolverEnvironment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** SlingTypeResolver used to test the requirement for sling/* fetcher
+ * names to point to classes under the org.apache.sling package
+ */
+public class DoNothingTypeResolver implements SlingTypeResolver<Object> {
+
+ @Nullable
+ @Override
+ public Object getType(@NotNull SlingTypeResolverEnvironment e) {
+ return null;
+ }
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
index 903f738..f9ab2d9 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/DefaultQueryExecutorTest.java
@@ -18,19 +18,25 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package org.apache.sling.graphql.core.engine;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Hashtable;
+import java.util.List;
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.api.engine.ValidationResult;
import org.apache.sling.graphql.core.mocks.DigestDataFetcher;
+import org.apache.sling.graphql.core.mocks.DummyTypeResolver;
import org.apache.sling.graphql.core.mocks.EchoDataFetcher;
import org.apache.sling.graphql.core.mocks.FailingDataFetcher;
import org.apache.sling.graphql.core.mocks.MockSchemaProvider;
import org.apache.sling.graphql.core.mocks.TestUtil;
+import org.apache.sling.graphql.core.mocks.TypeSlingResourceDTO;
+import org.apache.sling.graphql.core.mocks.TypeTestDTO;
+import org.apache.sling.graphql.core.mocks.UnionTypeResolver;
import org.junit.Test;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
@@ -47,10 +53,19 @@ import static org.junit.Assert.assertTrue;
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);
+ final Dictionary<String, Object> unionData = new Hashtable<>();
+ final List<Object> items = new ArrayList<>();
+ items.add(new TypeTestDTO(true, false, "path/to/resource", "1, 2, 3"));
+ items.add(new TypeSlingResourceDTO(resource.getPath(), resource.getResourceType()));
+ unionData.put("items", items);
+
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "union/resolver", new UnionTypeResolver());
+ TestUtil.registerSlingDataFetcher(context.bundleContext(), "union/fetcher", new EchoDataFetcher(unionData));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "echoNS/echo", new EchoDataFetcher(null));
TestUtil.registerSlingDataFetcher(context.bundleContext(), "failure/fail", new FailingDataFetcher());
TestUtil.registerSlingDataFetcher(context.bundleContext(), "test/static", new EchoDataFetcher(staticData));
@@ -130,17 +145,40 @@ public class DefaultQueryExecutorTest extends ResourceQueryTestBase {
@Test
public void invalidFetcherNamesTest() {
- context.registerService(SchemaProvider.class, new MockSchemaProvider("failing-schema"), Constants.SERVICE_RANKING,
+ context.registerService(SchemaProvider.class, new MockSchemaProvider("failing-fetcher-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, SlingGraphQLException.class, "does not match");
+ TestUtil.assertNestedException(e, SlingGraphQLException.class, "Invalid fetcher name missingSlash");
} finally {
reg.unregister();
}
}
+ @Test
+ public void invalidTypeResolverNamesTest() {
+ context.registerService(SchemaProvider.class, new MockSchemaProvider("failing-type-resolver-schema"), Constants.SERVICE_RANKING,
+ Integer.MAX_VALUE);
+ final ServiceRegistration<?> reg = TestUtil.registerSlingTypeResolver(context.bundleContext(), "missingSlash",
+ new DummyTypeResolver());
+ try {
+ queryJSON("{ currentResource { missingSlash } }", new String[] {});
+ fail("Expected query to fail");
+ } catch(Exception e) {
+ TestUtil.assertNestedException(e, SlingGraphQLException.class, "Invalid type resolver name missingSlash");
+ } finally {
+ reg.unregister();
+ }
+ }
+
+ @Test
+ public void unionFetcherTest() throws Exception {
+ final String json = queryJSON("{ unionFetcher { items { ... on Test { testingArgument } ... on SlingResource { path }} } }");
+ assertThat(json, hasJsonPath("$.data.unionFetcher"));
+ assertThat(json, hasJsonPath("$.data.unionFetcher.items[0].testingArgument", equalTo("1, 2, 3")));
+ assertThat(json, hasJsonPath("$.data.unionFetcher.items[1].path", equalTo(resource.getPath())));
+ }
}
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/IntrospectionQueryTest.java b/src/test/java/org/apache/sling/graphql/core/engine/IntrospectionQueryTest.java
index 4c4d476..227a49f 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/IntrospectionQueryTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/engine/IntrospectionQueryTest.java
@@ -18,15 +18,37 @@
*/
package org.apache.sling.graphql.core.engine;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.contains;
+import org.apache.sling.graphql.core.mocks.EchoDataFetcher;
+import org.apache.sling.graphql.core.mocks.TestUtil;
+import org.apache.sling.graphql.core.mocks.TypeSlingResourceDTO;
+import org.apache.sling.graphql.core.mocks.TypeTestDTO;
+import org.apache.sling.graphql.core.mocks.UnionTypeResolver;
import org.junit.Test;
public class IntrospectionQueryTest extends ResourceQueryTestBase {
-
+
+ @Override
+ protected void setupAdditionalServices() {
+ final Dictionary<String, Object> unionData = new Hashtable<>();
+ final List<Object> items = new ArrayList<>();
+ items.add(new TypeTestDTO(true, false, "path/to/resource", "1, 2, 3"));
+ items.add(new TypeSlingResourceDTO(resource.getPath(), resource.getResourceType()));
+ unionData.put("items", items);
+
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "union/resolver", new UnionTypeResolver());
+ TestUtil.registerSlingDataFetcher(context.bundleContext(), "union/fetcher", new EchoDataFetcher(unionData));
+ }
+
@Test
public void schemaIntrospectionTest() throws Exception {
final String json = queryJSON("{ __schema { types { name } directives { description }}}");
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 eb9090d..3fa7c0f 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
@@ -67,6 +67,7 @@ public abstract class ResourceQueryTestBase {
context.bundleContext().registerService(ServletResolver.class, servletResolver, null);
context.registerInjectActivateService(new SlingDataFetcherSelector());
+ context.registerInjectActivateService(new SlingTypeResolverSelector());
context.registerInjectActivateService(new SlingScalarsProvider());
context.registerInjectActivateService(new RankedSchemaProviders());
context.registerInjectActivateService(new DefaultQueryExecutor());
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/SlingTypeResolverNameValidationTest.java b/src/test/java/org/apache/sling/graphql/core/engine/SlingTypeResolverNameValidationTest.java
new file mode 100644
index 0000000..fbe8273
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/engine/SlingTypeResolverNameValidationTest.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.engine;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class SlingTypeResolverNameValidationTest {
+
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ final List<Object[]> result = new ArrayList<>();
+ result.add(new Object[]{"with/slash", true});
+ result.add(new Object[]{"missingSlash", false});
+ result.add(new Object[]{"", false});
+ result.add(new Object[]{"one/two/three", true});
+ result.add(new Object[]{"one/two/three/four_and/five_and_451_six_6", true});
+ result.add(new Object[]{"uno/1/x42", true});
+ result.add(new Object[]{"uno_due/tre", true});
+ result.add(new Object[]{"the:colon/bad", false});
+ result.add(new Object[]{"/startingslash", false});
+ result.add(new Object[]{"/starting/ending", false});
+ return result;
+ }
+
+ private final String name;
+ private final boolean expectValid;
+
+ public SlingTypeResolverNameValidationTest(String name, Boolean expectValid) {
+ this.name = name;
+ this.expectValid = expectValid;
+ }
+
+ @Test
+ public void testValidation() {
+ final String msg = String.format("Expecting '%s' to be %s", name, expectValid ? "valid" : "invalid");
+ assertEquals(msg, SlingTypeResolverSelector.nameMatchesPattern(name), expectValid);
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/SlingTypeResolverSelectorTest.java b/src/test/java/org/apache/sling/graphql/core/engine/SlingTypeResolverSelectorTest.java
new file mode 100644
index 0000000..db0f254
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/engine/SlingTypeResolverSelectorTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 org.apache.sling.graphql.api.SlingTypeResolver;
+import org.apache.sling.graphql.api.SlingTypeResolverEnvironment;
+import org.apache.sling.graphql.core.mocks.DummyTypeResolver;
+import org.apache.sling.graphql.core.mocks.TestUtil;
+import org.apache.sling.graphql.core.mocks.UnionTypeResolver;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.example.resolvers.DoNothingTypeResolver;
+
+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;
+
+public class SlingTypeResolverSelectorTest {
+
+ @Rule
+ public final OsgiContext context = new OsgiContext();
+
+ private SlingTypeResolverSelector selector;
+
+ @Before
+ public void setup() {
+ context.registerInjectActivateService(new SlingTypeResolverSelector());
+ selector = context.getService(SlingTypeResolverSelector.class);
+
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "sling/union", new UnionTypeResolver());
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "sling/shouldFail", new DoNothingTypeResolver());
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "example/ok", new DoNothingTypeResolver());
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "sling/duplicate", 1, new DummyTypeResolver());
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "sling/duplicate", 0, new UnionTypeResolver());
+ }
+
+ @Test
+ public void acceptableName() {
+ final SlingTypeResolver<Object> sdf = selector.getSlingTypeResolver("example/ok");
+ assertThat(sdf, not(nullValue()));
+ }
+
+ @Test
+ public void reservedNameOk() {
+ final SlingTypeResolver<Object> sdf = selector.getSlingTypeResolver("sling/union");
+ assertThat(sdf, not(nullValue()));
+ }
+
+ @Test
+ public void reservedNameError() {
+ assertNull(selector.getSlingTypeResolver("sling/shouldFail"));
+ }
+
+ @Test
+ public void sameNameTypeResolver() {
+ final SlingTypeResolver<Object> str = selector.getSlingTypeResolver("sling/duplicate");
+ assertNotNull(str);
+ assertEquals(DummyTypeResolver.class, str.getClass());
+ assertNull(str.getType(mock(SlingTypeResolverEnvironment.class)));
+ }
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/mocks/DummyTypeResolver.java b/src/test/java/org/apache/sling/graphql/core/mocks/DummyTypeResolver.java
new file mode 100644
index 0000000..ad44668
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/mocks/DummyTypeResolver.java
@@ -0,0 +1,32 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.mocks;
+
+import org.apache.sling.graphql.api.SlingTypeResolver;
+import org.apache.sling.graphql.api.SlingTypeResolverEnvironment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class DummyTypeResolver implements SlingTypeResolver<Object> {
+
+ @Override
+ public @Nullable Object getType(@NotNull SlingTypeResolverEnvironment e) {
+ return null;
+ }
+}
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 a68d3cb..5a2b415 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
@@ -26,6 +26,7 @@ import java.util.Hashtable;
import org.apache.sling.graphql.api.SlingDataFetcher;
import org.apache.sling.graphql.api.SlingScalarConverter;
+import org.apache.sling.graphql.api.SlingTypeResolver;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
@@ -44,6 +45,18 @@ public class TestUtil {
return bc.registerService(SlingDataFetcher.class, f, props);
}
+ public static ServiceRegistration<?> registerSlingTypeResolver(BundleContext bc, String name, SlingTypeResolver<?> tr) {
+ return registerSlingTypeResolver(bc, name, 0, tr);
+ }
+
+ public static ServiceRegistration<?> registerSlingTypeResolver(BundleContext bc, String name, int serviceRanking,
+ SlingTypeResolver<?> tr) {
+ final Dictionary<String, Object> props = new Hashtable<>();
+ props.put(SlingTypeResolver.NAME_SERVICE_PROPERTY, name);
+ props.put(Constants.SERVICE_RANKING, serviceRanking);
+ return bc.registerService(SlingTypeResolver.class, tr, props);
+ }
+
public static ServiceRegistration<?> registerSlingScalarConverter(BundleContext bc, String name, SlingScalarConverter<?,?> c) {
final Dictionary<String, Object> props = new Hashtable<>();
props.put(SlingScalarConverter.NAME_SERVICE_PROPERTY, name);
diff --git a/src/test/java/org/apache/sling/graphql/core/mocks/TypeSlingResourceDTO.java b/src/test/java/org/apache/sling/graphql/core/mocks/TypeSlingResourceDTO.java
new file mode 100644
index 0000000..fa1339f
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/mocks/TypeSlingResourceDTO.java
@@ -0,0 +1,37 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.mocks;
+
+public class TypeSlingResourceDTO {
+ private final String path;
+ private final String resourceType;
+
+ public TypeSlingResourceDTO(String path, String resourceType) {
+ this.path = path;
+ this.resourceType = resourceType;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getResourceType() {
+ return resourceType;
+ }
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/mocks/TypeTestDTO.java b/src/test/java/org/apache/sling/graphql/core/mocks/TypeTestDTO.java
new file mode 100644
index 0000000..2e087ae
--- /dev/null
+++ b/src/test/java/org/apache/sling/graphql/core/mocks/TypeTestDTO.java
@@ -0,0 +1,50 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.mocks;
+
+public class TypeTestDTO {
+ private final boolean test;
+ private final boolean boolValue;
+ private final String resourcePath;
+ private final String testingArgument;
+
+ public TypeTestDTO(boolean test, boolean boolValue, String resourcePath, String testingArgument) {
+ this.test = test;
+ this.boolValue = boolValue;
+ this.resourcePath = resourcePath;
+ this.testingArgument = testingArgument;
+ }
+
+ public boolean isTest() {
+ return test;
+ }
+
+ public boolean isBoolValue() {
+ return boolValue;
+ }
+
+ public String getResourcePath() {
+ return resourcePath;
+ }
+
+ public String getTestingArgument() {
+ return testingArgument;
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/graphql/core/engine/IntrospectionQueryTest.java b/src/test/java/org/apache/sling/graphql/core/mocks/UnionTypeResolver.java
similarity index 50%
copy from src/test/java/org/apache/sling/graphql/core/engine/IntrospectionQueryTest.java
copy to src/test/java/org/apache/sling/graphql/core/mocks/UnionTypeResolver.java
index 4c4d476..61ccc0c 100644
--- a/src/test/java/org/apache/sling/graphql/core/engine/IntrospectionQueryTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/mocks/UnionTypeResolver.java
@@ -16,25 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.sling.graphql.core.engine;
-import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+package org.apache.sling.graphql.core.mocks;
-import static org.junit.Assert.assertThat;
-import static org.hamcrest.Matchers.contains;
+import org.apache.sling.graphql.api.SlingTypeResolver;
+import org.apache.sling.graphql.api.SlingTypeResolverEnvironment;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
-import org.junit.Test;
+public class UnionTypeResolver implements SlingTypeResolver<Object> {
-public class IntrospectionQueryTest extends ResourceQueryTestBase {
-
- @Test
- public void schemaIntrospectionTest() throws Exception {
- final String json = queryJSON("{ __schema { types { name } directives { description }}}");
- assertThat(json, hasJsonPath("$.data.__schema"));
- assertThat(json, hasJsonPath("$.data.__schema.types"));
- assertThat(json, hasJsonPath("$.data.__schema.types..name"));
- assertThat(json, hasJsonPath("$.data.__schema.directives"));
- assertThat(json, hasJsonPath("$.data.__schema.directives..description"));
+ @Nullable
+ @Override
+ public Object getType(@NotNull SlingTypeResolverEnvironment e) {
+ Object resultItem = e.getObject();
+ if (resultItem instanceof TypeTestDTO) {
+ return e.getObjectType("Test");
+ }
+ if (resultItem instanceof TypeSlingResourceDTO) {
+ return e.getObjectType("SlingResource");
+ }
+ return null;
}
-
}
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 a4147dd..2c093ba 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
@@ -30,7 +30,10 @@ import org.apache.sling.graphql.api.SchemaProvider;
import org.apache.sling.graphql.api.engine.QueryExecutor;
import org.apache.sling.graphql.core.engine.DefaultQueryExecutor;
import org.apache.sling.graphql.core.engine.SlingDataFetcherSelector;
+import org.apache.sling.graphql.core.engine.SlingTypeResolverSelector;
import org.apache.sling.graphql.core.mocks.MockSchemaProvider;
+import org.apache.sling.graphql.core.mocks.TestUtil;
+import org.apache.sling.graphql.core.mocks.UnionTypeResolver;
import org.apache.sling.graphql.core.scalars.SlingScalarsProvider;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.hamcrest.CustomMatcher;
@@ -103,6 +106,8 @@ public class SchemaDescriptionsTest {
final ServletResolver servletResolver = Mockito.mock(ServletResolver.class);
context.bundleContext().registerService(ServletResolver.class, servletResolver, null);
context.registerInjectActivateService(new SlingDataFetcherSelector());
+ context.registerInjectActivateService(new SlingTypeResolverSelector());
+ TestUtil.registerSlingTypeResolver(context.bundleContext(), "union/resolver", new UnionTypeResolver());
context.registerInjectActivateService(new SlingScalarsProvider());
context.registerService(SchemaProvider.class, new MockSchemaProvider("test-schema"));
context.registerInjectActivateService(new RankedSchemaProviders());
diff --git a/src/test/resources/test-schema.txt b/src/test/resources/failing-fetcher-schema.txt
similarity index 56%
copy from src/test/resources/test-schema.txt
copy to src/test/resources/failing-fetcher-schema.txt
index 230bc24..a559d0f 100644
--- a/src/test/resources/test-schema.txt
+++ b/src/test/resources/failing-fetcher-schema.txt
@@ -22,38 +22,11 @@ directive @fetcher(
source : String = ""
) on FIELD_DEFINITION
-# GraphQL Schema used for our tests
+# Schema used to test invalid fetcher names
type Query {
currentResource : SlingResource @fetcher(name:"echoNS/echo")
-
- # Test some static values
- staticContent: Test @fetcher(name:"test/static")
}
-# This should be omitted from the SlingResource type description
-#
-# SlingResource, for our tests
type SlingResource {
- path: String
- resourceType: String
-
- pathMD5: String @fetcher(name:"sling/digest" options:"md5" source:"path")
-
- # SHA256 digest of the path
- pathSHA256: String @fetcher(name:"sling/digest" options:"sha-256" source:"path")
-
- # MD5 digest of the resource type
- resourceTypeMD5: String @fetcher(name:"sling/digest" options:"md5" source:"resourceType")
-
- nullValue: String @fetcher(name:"echoNS/echo" options:"null")
-
- # Failure message
- failure: String @fetcher(name:"failure/fail")
+ missingSlash: String @fetcher(name:"missingSlash")
}
-
-type Test {
- test: Boolean
- boolValue: Boolean
- resourcePath: String
- testingArgument: String
-}
\ No newline at end of file
diff --git a/src/test/resources/test-schema.txt b/src/test/resources/failing-type-resolver-schema.txt
similarity index 59%
copy from src/test/resources/test-schema.txt
copy to src/test/resources/failing-type-resolver-schema.txt
index 230bc24..7451b3a 100644
--- a/src/test/resources/test-schema.txt
+++ b/src/test/resources/failing-type-resolver-schema.txt
@@ -16,11 +16,11 @@
# * under the License.
# This directive maps fields to our Sling data fetchers
-directive @fetcher(
+directive @resolver(
name : String,
options : String = "",
source : String = ""
-) on FIELD_DEFINITION
+) on UNION
# GraphQL Schema used for our tests
type Query {
@@ -28,32 +28,9 @@ type Query {
# Test some static values
staticContent: Test @fetcher(name:"test/static")
-}
-
-# This should be omitted from the SlingResource type description
-#
-# SlingResource, for our tests
-type SlingResource {
- path: String
- resourceType: String
-
- pathMD5: String @fetcher(name:"sling/digest" options:"md5" source:"path")
-
- # SHA256 digest of the path
- pathSHA256: String @fetcher(name:"sling/digest" options:"sha-256" source:"path")
-
- # MD5 digest of the resource type
- resourceTypeMD5: String @fetcher(name:"sling/digest" options:"md5" source:"resourceType")
-
- nullValue: String @fetcher(name:"echoNS/echo" options:"null")
- # Failure message
- failure: String @fetcher(name:"failure/fail")
+ # Test union as return type
+ unionFetcher: Test2 @fetcher(name:"union/fetcher")
}
-type Test {
- test: Boolean
- boolValue: Boolean
- resourcePath: String
- testingArgument: String
-}
\ No newline at end of file
+union AllTypes @resolver(name:"missingSlash" source:"AllTypes") = SlingResource | Test
diff --git a/src/test/resources/test-schema.txt b/src/test/resources/test-schema.txt
index 230bc24..e0853a8 100644
--- a/src/test/resources/test-schema.txt
+++ b/src/test/resources/test-schema.txt
@@ -22,14 +22,26 @@ directive @fetcher(
source : String = ""
) on FIELD_DEFINITION
+# This directive to maps types
+directive @resolver(
+ name : String,
+ options : String = "",
+ source : String = ""
+) on UNION
+
# GraphQL Schema used for our tests
type Query {
currentResource : SlingResource @fetcher(name:"echoNS/echo")
# Test some static values
staticContent: Test @fetcher(name:"test/static")
+
+ # Test union as return type
+ unionFetcher: Test2 @fetcher(name:"union/fetcher")
}
+union AllTypes @resolver(name:"union/resolver" source:"AllTypes") = SlingResource | Test
+
# This should be omitted from the SlingResource type description
#
# SlingResource, for our tests
@@ -56,4 +68,9 @@ type Test {
boolValue: Boolean
resourcePath: String
testingArgument: String
-}
\ No newline at end of file
+}
+
+type Test2 {
+ items: [AllTypes]
+}
+