From e5a92a310d99c1940b00e558764c09c3114a7e87 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 28 Apr 2021 17:32:35 +0100 Subject: [PATCH] Introduce GraphQLSource A pluggable abstraction to encapsulate the way GraphQL is initialized and the way it is sourced at runtime. Closes gh-48 --- .../boot/GraphQLAutoConfiguration.java | 65 ++------ .../boot/MissingGraphQLSchemaException.java | 41 ----- .../boot/WebFluxGraphQLAutoConfiguration.java | 7 +- .../boot/WebMvcGraphQLAutoConfiguration.java | 7 +- .../boot/GraphQLAutoConfigurationTests.java | 31 ++-- .../support/DefaultGraphQLSourceBuilder.java | 145 ++++++++++++++++++ .../graphql/support/GraphQLSource.java | 101 ++++++++++++ .../ReactorDataFetcherAdapter.java | 4 +- .../{data => support}/package-info.java | 4 +- .../web/AbstractWebGraphQLService.java | 2 +- .../graphql/web/DefaultWebGraphQLService.java | 10 +- .../ReactorDataFetcherAdapterTests.java | 2 +- .../web/DefaultWebGraphQLServiceTests.java | 23 +-- .../webflux/GraphQLWebSocketHandlerTests.java | 24 +-- .../webmvc/GraphQLWebSocketHandlerTests.java | 24 +-- 15 files changed, 315 insertions(+), 175 deletions(-) delete mode 100644 graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/MissingGraphQLSchemaException.java create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/support/DefaultGraphQLSourceBuilder.java create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/support/GraphQLSource.java rename spring-graphql/src/main/java/org/springframework/graphql/{data => support}/ReactorDataFetcherAdapter.java (97%) rename spring-graphql/src/main/java/org/springframework/graphql/{data => support}/package-info.java (87%) rename spring-graphql/src/test/java/org/springframework/graphql/{data => support}/ReactorDataFetcherAdapterTests.java (99%) diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLAutoConfiguration.java index b51d53d6..d47f4a0c 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQLAutoConfiguration.java @@ -15,19 +15,11 @@ */ package org.springframework.graphql.boot; -import java.io.IOException; -import java.util.List; import java.util.stream.Collectors; import graphql.GraphQL; -import graphql.execution.instrumentation.ChainedInstrumentation; import graphql.execution.instrumentation.Instrumentation; -import graphql.schema.GraphQLSchema; -import graphql.schema.SchemaTransformer; import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -35,31 +27,23 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; -import org.springframework.graphql.data.ReactorDataFetcherAdapter; +import org.springframework.graphql.support.GraphQLSource; @Configuration @ConditionalOnClass(GraphQL.class) -@ConditionalOnMissingBean(GraphQL.class) +@ConditionalOnMissingBean(GraphQLSource.class) @EnableConfigurationProperties(GraphQLProperties.class) public class GraphQLAutoConfiguration { - - @Configuration - static class GraphQLConfiguration { - - @Bean - public GraphQL graphQL(GraphQL.Builder builder) { - return builder.build(); - } - + @Bean + public GraphQLSource graphQLSource(GraphQLSource.Builder builder) { + return builder.build(); } - @Configuration - @ConditionalOnMissingBean(GraphQL.Builder.class) - static class GraphQLBuilderConfiguration { + @ConditionalOnMissingBean(GraphQLSource.Builder.class) + static class GraphQLSourceConfiguration { @Bean @ConditionalOnMissingBean @@ -70,36 +54,17 @@ public class GraphQLAutoConfiguration { } @Bean - public GraphQL.Builder graphQLBuilder(GraphQLProperties properties, RuntimeWiring runtimeWiring, - ResourceLoader resourceLoader, - ObjectProvider instrumentationsProvider) { + public GraphQLSource.Builder graphQLSourceBuilder( + GraphQLProperties properties, RuntimeWiring runtimeWiring, + ResourceLoader resourceLoader, ObjectProvider instrumentationsProvider) { - Resource schemaResource = resourceLoader.getResource(properties.getSchemaLocation()); - GraphQLSchema schema = buildSchema(schemaResource, runtimeWiring); - schema = SchemaTransformer.transformSchema(schema, ReactorDataFetcherAdapter.TYPE_VISITOR); + String schemaLocation = properties.getSchemaLocation(); - GraphQL.Builder builder = GraphQL.newGraphQL(schema); - List instrumentations = instrumentationsProvider.orderedStream().collect(Collectors.toList()); - if (!instrumentations.isEmpty()) { - builder = builder.instrumentation(new ChainedInstrumentation(instrumentations)); - } - return builder; + return GraphQLSource.builder() + .schemaResource(resourceLoader.getResource(schemaLocation)) + .runtimeWiring(runtimeWiring) + .instrumentation(instrumentationsProvider.orderedStream().collect(Collectors.toList())); } - - private GraphQLSchema buildSchema(Resource schemaResource, RuntimeWiring runtimeWiring) { - if (!schemaResource.exists()) { - throw new MissingGraphQLSchemaException(schemaResource); - } - try { - TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaResource.getInputStream()); - SchemaGenerator schemaGenerator = new SchemaGenerator(); - return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); - } - catch (IOException exc) { - throw new MissingGraphQLSchemaException(exc, schemaResource); - } - } - } } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/MissingGraphQLSchemaException.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/MissingGraphQLSchemaException.java deleted file mode 100644 index 31e1ba64..00000000 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/MissingGraphQLSchemaException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2020 the original author or authors. - * - * Licensed 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 - * - * https://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.springframework.graphql.boot; - -import org.springframework.core.io.Resource; - -/** - * Exception thrown when no GraphQL schema is available. - */ -public class MissingGraphQLSchemaException extends RuntimeException { - - private final Resource schemaResource; - - public MissingGraphQLSchemaException(Throwable cause, Resource schemaResource) { - super(cause); - this.schemaResource = schemaResource; - } - - MissingGraphQLSchemaException(Resource schemaResource) { - super("Schema resource " + schemaResource.toString() + " does not exist"); - this.schemaResource = schemaResource; - } - - public Resource getSchemaResource() { - return this.schemaResource; - } -} diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java index 87b4e8d0..f27f03ae 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebFluxGraphQLAutoConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.graphql.support.GraphQLSource; import org.springframework.graphql.web.DefaultWebGraphQLService; import org.springframework.graphql.web.WebGraphQLService; import org.springframework.graphql.web.WebInterceptor; @@ -54,7 +55,7 @@ import static org.springframework.web.reactive.function.server.RequestPredicates @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @ConditionalOnClass(GraphQL.class) -@ConditionalOnBean(GraphQL.class) +@ConditionalOnBean(GraphQLSource.class) @AutoConfigureAfter(GraphQLAutoConfiguration.class) public class WebFluxGraphQLAutoConfiguration { @@ -63,8 +64,8 @@ public class WebFluxGraphQLAutoConfiguration { @Bean @ConditionalOnMissingBean - public WebGraphQLService webGraphQLService(GraphQL graphQL, ObjectProvider interceptors) { - DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQL); + public WebGraphQLService webGraphQLService(GraphQLSource graphQLSource, ObjectProvider interceptors) { + DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQLSource); handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList())); return handler; } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java index f6405f17..3a0f4f30 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/WebMvcGraphQLAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; +import org.springframework.graphql.support.GraphQLSource; import org.springframework.graphql.web.DefaultWebGraphQLService; import org.springframework.graphql.web.WebGraphQLService; import org.springframework.graphql.web.WebInterceptor; @@ -61,7 +62,7 @@ import static org.springframework.web.servlet.function.RequestPredicates.content @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(GraphQL.class) -@ConditionalOnBean(GraphQL.class) +@ConditionalOnBean(GraphQLSource.class) @AutoConfigureAfter(GraphQLAutoConfiguration.class) public class WebMvcGraphQLAutoConfiguration { @@ -70,8 +71,8 @@ public class WebMvcGraphQLAutoConfiguration { @Bean @ConditionalOnMissingBean - public WebGraphQLService webGraphQLService(GraphQL graphQL, ObjectProvider interceptors) { - DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQL); + public WebGraphQLService webGraphQLService(GraphQLSource graphQLSource, ObjectProvider interceptors) { + DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQLSource); handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList())); return handler; } diff --git a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQLAutoConfigurationTests.java b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQLAutoConfigurationTests.java index 4f6aa867..483c854a 100644 --- a/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQLAutoConfigurationTests.java +++ b/graphql-spring-boot-starter/src/test/java/org/springframework/graphql/boot/GraphQLAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,15 @@ package org.springframework.graphql.boot; -import graphql.GraphQL; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLSchema; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.graphql.support.GraphQLSource; -import static graphql.Scalars.GraphQLString; -import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; -import static graphql.schema.GraphQLObjectType.newObject; import static org.assertj.core.api.Assertions.assertThat; /** @@ -44,7 +40,7 @@ class GraphQLAutoConfigurationTests { void shouldFailWhenSchemaFileIsMissing() { contextRunner.run((context) -> { assertThat(context).hasFailed(); - assertThat(context).getFailure().getRootCause().isInstanceOf(MissingGraphQLSchemaException.class); + assertThat(context).getFailure().getRootCause().hasMessage("'schemaResource' does not exist"); }); } @@ -52,9 +48,7 @@ class GraphQLAutoConfigurationTests { void shouldCreateBuilderWithSdl() { contextRunner .withPropertyValues("spring.graphql.schema-location:classpath:books/schema.graphqls") - .run((context) -> { - assertThat(context).hasSingleBean(GraphQL.Builder.class); - }); + .run((context) -> assertThat(context).hasSingleBean(GraphQLSource.class)); } @Test @@ -63,8 +57,8 @@ class GraphQLAutoConfigurationTests { .withPropertyValues("spring.graphql.schema-location:classpath:books/schema.graphqls") .withUserConfiguration(CustomGraphQLBuilderConfiguration.class) .run((context) -> { - assertThat(context).hasBean("customGraphQLBuilder"); - assertThat(context).hasSingleBean(GraphQL.Builder.class); + assertThat(context).hasBean("customGraphQLSourceBuilder"); + assertThat(context).hasSingleBean(GraphQLSource.Builder.class); }); } @@ -72,15 +66,8 @@ class GraphQLAutoConfigurationTests { static class CustomGraphQLBuilderConfiguration { @Bean - public GraphQL.Builder customGraphQLBuilder() { - GraphQLObjectType queryType = newObject() - .name("helloWorldQuery") - .field(newFieldDefinition() - .type(GraphQLString) - .name("hello")) - .build(); - GraphQLSchema schema = GraphQLSchema.newSchema().query(queryType).build(); - return GraphQL.newGraphQL(schema); + public GraphQLSource.Builder customGraphQLSourceBuilder() { + return GraphQLSource.builder().schemaResource(new ClassPathResource("books/schema.graphqls")); } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/support/DefaultGraphQLSourceBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/support/DefaultGraphQLSourceBuilder.java new file mode 100644 index 00000000..f468f583 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/support/DefaultGraphQLSourceBuilder.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.graphql.support; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import graphql.GraphQL; +import graphql.execution.instrumentation.ChainedInstrumentation; +import graphql.execution.instrumentation.Instrumentation; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.SchemaTransformer; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Default implementation of {@link GraphQLSource.Builder} that initializes a + * {@link GraphQL} instance and wraps it with a {@link GraphQLSource} that + * returns it. + */ +class DefaultGraphQLSourceBuilder implements GraphQLSource.Builder { + + @Nullable + private Resource schemaResource; + + private RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build(); + + private final List typeVisitors = new ArrayList<>(); + + private final List instrumentations = new ArrayList<>(); + + private Consumer graphQLConfigurers = builder -> {}; + + + DefaultGraphQLSourceBuilder() { + this.typeVisitors.add(ReactorDataFetcherAdapter.TYPE_VISITOR); + } + + + @Override + public GraphQLSource.Builder schemaResource(Resource resource) { + this.schemaResource = resource; + return this; + } + + @Override + public GraphQLSource.Builder runtimeWiring(RuntimeWiring runtimeWiring) { + Assert.notNull(runtimeWiring, "RuntimeWiring is required"); + this.runtimeWiring = runtimeWiring; + return this; + } + + @Override + public GraphQLSource.Builder typeVisitors(List typeVisitors) { + this.typeVisitors.addAll(typeVisitors); + return this; + } + + @Override + public GraphQLSource.Builder instrumentation(List instrumentations) { + this.instrumentations.addAll(instrumentations); + return this; + } + + @Override + public GraphQLSource.Builder configureGraphQL(Consumer configurer) { + this.graphQLConfigurers = this.graphQLConfigurers.andThen(configurer); + return this; + } + + @Override + public GraphQLSource build() { + TypeDefinitionRegistry registry = parseSchemaResource(); + + GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(registry, this.runtimeWiring); + for (GraphQLTypeVisitor visitor : this.typeVisitors) { + schema = SchemaTransformer.transformSchema(schema, visitor); + } + + GraphQL.Builder builder = GraphQL.newGraphQL(schema); + if (!this.instrumentations.isEmpty()) { + builder = builder.instrumentation(new ChainedInstrumentation(this.instrumentations)); + } + this.graphQLConfigurers.accept(builder); + GraphQL graphQL = builder.build(); + + return new CachedGraphQLSource(graphQL); + } + + private TypeDefinitionRegistry parseSchemaResource() { + Assert.notNull(this.schemaResource, "'schemaResource' not provided"); + Assert.isTrue(this.schemaResource.exists(), "'schemaResource' does not exist"); + try { + try (InputStream inputStream = this.schemaResource.getInputStream()) { + return new SchemaParser().parse(inputStream); + } + } + catch (IOException ex) { + throw new IllegalArgumentException( + "Failed to load resourceLocation " + this.schemaResource.toString()); + } + } + + + /** + * GraphQLSource that returns the built GraphQL instance. + */ + private static class CachedGraphQLSource implements GraphQLSource { + + private final GraphQL graphQL; + + CachedGraphQLSource(GraphQL graphQL) { + this.graphQL = graphQL; + } + + @Override + public GraphQL graphQL() { + return this.graphQL; + } + } + +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/support/GraphQLSource.java b/spring-graphql/src/main/java/org/springframework/graphql/support/GraphQLSource.java new file mode 100644 index 00000000..706be72d --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/support/GraphQLSource.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.graphql.support; + +import java.io.File; +import java.util.List; +import java.util.function.Consumer; + +import graphql.GraphQL; +import graphql.execution.instrumentation.Instrumentation; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.TypeDefinitionRegistry; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; + +/** + * Strategy to resolve the {@link GraphQL} instance to use. + * + *

This contract also includes a {@link GraphQLSource} builder encapsulating + * the initialization of the {@link GraphQL} instance and associated + * {@link graphql.schema.GraphQLSchema}. + */ +public interface GraphQLSource { + + + /** + * Return the {@link GraphQL} to use. This can be a cached instance or a + * different one from time to time (e.g. based on a reloaded schema). + */ + GraphQL graphQL(); + + + /** + * Return a builder for a {@link GraphQLSource} given input for the + * initialization of {@link GraphQL} and {@link graphql.schema.GraphQLSchema}. + */ + static Builder builder() { + return new DefaultGraphQLSourceBuilder(); + } + + + /** + * Builder for a {@link GraphQLSource}. + */ + interface Builder { + + /** + * Provide the resource for the GraphQL {@literal ".schema"} file to parse. + * @see graphql.schema.idl.SchemaParser#parse(File) + */ + Builder schemaResource(Resource resource); + + /** + * Set a {@link RuntimeWiring} to contribute data fetchers and more. + * @see graphql.schema.idl.SchemaGenerator#makeExecutableSchema(TypeDefinitionRegistry, RuntimeWiring) + */ + Builder runtimeWiring(RuntimeWiring runtimeWiring); + + /** + * Add {@link GraphQLTypeVisitor}s to transform the underlying + * {@link graphql.schema.GraphQLSchema} with. + * @see graphql.schema.SchemaTransformer#transformSchema(GraphQLSchema, GraphQLTypeVisitor) + */ + Builder typeVisitors(List typeVisitors); + + /** + * Provide {@link Instrumentation} components to instrument the execution + * of GraphQL queries. + * @see graphql.GraphQL.Builder#instrumentation(Instrumentation) + */ + Builder instrumentation(List instrumentations); + + /** + * Configure consumers to be given access to the {@link GraphQL.Builder} + * used to build {@link GraphQL}. + */ + Builder configureGraphQL(Consumer configurer); + + /** + * Build the {@link GraphQLSource}. + */ + GraphQLSource build(); + } + +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/ReactorDataFetcherAdapter.java b/spring-graphql/src/main/java/org/springframework/graphql/support/ReactorDataFetcherAdapter.java similarity index 97% rename from spring-graphql/src/main/java/org/springframework/graphql/data/ReactorDataFetcherAdapter.java rename to spring-graphql/src/main/java/org/springframework/graphql/support/ReactorDataFetcherAdapter.java index 91b59de0..b985265a 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/ReactorDataFetcherAdapter.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/support/ReactorDataFetcherAdapter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.graphql.data; +package org.springframework.graphql.support; import java.lang.reflect.Method; @@ -34,6 +34,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.context.ContextView; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -87,6 +88,7 @@ public class ReactorDataFetcherAdapter implements DataFetcher { return value; } + @Nullable private ContextView getReactorContext(DataFetchingEnvironment environment) { GraphQLContext graphQLContext = environment.getContext(); return graphQLContext.get(REACTOR_CONTEXT_KEY); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/package-info.java b/spring-graphql/src/main/java/org/springframework/graphql/support/package-info.java similarity index 87% rename from spring-graphql/src/main/java/org/springframework/graphql/data/package-info.java rename to spring-graphql/src/main/java/org/springframework/graphql/support/package-info.java index 080057b7..54a9398c 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/package-info.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/support/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2020 the original author or authors. + * Copyright 2020-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ @NonNullApi @NonNullFields -package org.springframework.graphql.data; +package org.springframework.graphql.support; import org.springframework.lang.NonNullApi; import org.springframework.lang.NonNullFields; diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/AbstractWebGraphQLService.java b/spring-graphql/src/main/java/org/springframework/graphql/web/AbstractWebGraphQLService.java index bfb7d5e1..8f22a05c 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/AbstractWebGraphQLService.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/AbstractWebGraphQLService.java @@ -23,7 +23,7 @@ import graphql.ExecutionInput; import graphql.ExecutionResult; import reactor.core.publisher.Mono; -import org.springframework.graphql.data.ReactorDataFetcherAdapter; +import org.springframework.graphql.support.ReactorDataFetcherAdapter; /** * Base class for {@link WebGraphQLService} implementations, providing support diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/DefaultWebGraphQLService.java b/spring-graphql/src/main/java/org/springframework/graphql/web/DefaultWebGraphQLService.java index 6935b1fb..c7d532b4 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/DefaultWebGraphQLService.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/DefaultWebGraphQLService.java @@ -21,23 +21,25 @@ import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.GraphQL; +import org.springframework.graphql.support.GraphQLSource; + /** * Extension of {@link AbstractWebGraphQLService} that executes GraphQL queries * through the {@link GraphQL} instance it is configured with. */ public class DefaultWebGraphQLService extends AbstractWebGraphQLService { - private final GraphQL graphQL; + private final GraphQLSource graphQLSource; - public DefaultWebGraphQLService(GraphQL graphQL) { - this.graphQL = graphQL; + public DefaultWebGraphQLService(GraphQLSource graphQLSource) { + this.graphQLSource = graphQLSource; } @Override protected CompletableFuture executeInternal(ExecutionInput input) { - return this.graphQL.executeAsync(input); + return this.graphQLSource.graphQL().executeAsync(input); } } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/ReactorDataFetcherAdapterTests.java b/spring-graphql/src/test/java/org/springframework/graphql/support/ReactorDataFetcherAdapterTests.java similarity index 99% rename from spring-graphql/src/test/java/org/springframework/graphql/data/ReactorDataFetcherAdapterTests.java rename to spring-graphql/src/test/java/org/springframework/graphql/support/ReactorDataFetcherAdapterTests.java index ea1d4cf1..eaf2f9d4 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/ReactorDataFetcherAdapterTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/support/ReactorDataFetcherAdapterTests.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.graphql.data; +package org.springframework.graphql.support; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/DefaultWebGraphQLServiceTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/DefaultWebGraphQLServiceTests.java index 5cf2e5f6..d197f0e3 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/DefaultWebGraphQLServiceTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/DefaultWebGraphQLServiceTests.java @@ -15,7 +15,6 @@ */ package org.springframework.graphql.web; -import java.io.File; import java.net.URI; import java.time.Duration; import java.util.Arrays; @@ -24,17 +23,13 @@ import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import graphql.ExecutionInput; -import graphql.GraphQL; -import graphql.schema.GraphQLSchema; import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; +import org.springframework.core.io.ClassPathResource; +import org.springframework.graphql.support.GraphQLSource; import org.springframework.http.HttpHeaders; -import org.springframework.util.ResourceUtils; import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring; import static org.assertj.core.api.Assertions.assertThat; @@ -64,7 +59,7 @@ public class DefaultWebGraphQLServiceTests { Map body = mapper.reader().readValue("{\"query\": \"" + query + "\"}", Map.class); WebInput webInput = new WebInput(URI.create("/graphql"), new HttpHeaders(), body, "1"); - DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(createGraphQL()); + DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(createGraphQLSource()); requestHandler.setInterceptors(interceptors); WebOutput webOutput = requestHandler.execute(webInput).block(); @@ -75,17 +70,15 @@ public class DefaultWebGraphQLServiceTests { } - private static GraphQL createGraphQL() throws Exception { + private static GraphQLSource createGraphQLSource() { RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() .type(newTypeWiring("Query").dataFetcher("bookById", GraphQLDataFetchers.getBookByIdDataFetcher())) .build(); - File file = ResourceUtils.getFile("classpath:books/schema.graphqls"); - TypeDefinitionRegistry registry = new SchemaParser().parse(file); - SchemaGenerator generator = new SchemaGenerator(); - GraphQLSchema schema = generator.makeExecutableSchema(registry, runtimeWiring); - - return GraphQL.newGraphQL(schema).build(); + return GraphQLSource.builder() + .schemaResource(new ClassPathResource("books/schema.graphqls")) + .runtimeWiring(runtimeWiring) + .build(); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQLWebSocketHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQLWebSocketHandlerTests.java index 59fc25ee..40ae6c45 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQLWebSocketHandlerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQLWebSocketHandlerTests.java @@ -15,7 +15,6 @@ */ package org.springframework.graphql.web.webflux; -import java.io.File; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -25,12 +24,7 @@ import java.util.List; import java.util.Map; import java.util.function.BiConsumer; -import graphql.GraphQL; -import graphql.schema.GraphQLSchema; import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -38,9 +32,11 @@ import reactor.core.publisher.Mono; import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.graphql.support.GraphQLSource; import org.springframework.graphql.web.ConsumeOneAndNeverCompleteInterceptor; import org.springframework.graphql.web.DefaultWebGraphQLService; import org.springframework.graphql.web.GraphQLDataFetchers; @@ -49,7 +45,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.lang.Nullable; -import org.springframework.util.ResourceUtils; import org.springframework.web.reactive.socket.CloseStatus; import org.springframework.web.reactive.socket.HandshakeInfo; import org.springframework.web.reactive.socket.WebSocketMessage; @@ -278,9 +273,7 @@ public class GraphQLWebSocketHandlerTests { private GraphQLWebSocketHandler initWebSocketHandler( @Nullable List interceptors, @Nullable Duration initTimeoutDuration) throws Exception { - GraphQL graphQL = initGraphQL(); - - DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(graphQL); + DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(initGraphQLSource()); if (interceptors != null) { requestHandler.setInterceptors(interceptors); } @@ -290,17 +283,16 @@ public class GraphQLWebSocketHandlerTests { (initTimeoutDuration != null ? initTimeoutDuration : Duration.ofSeconds(60))); } - private static GraphQL initGraphQL() throws Exception { - File schemaFile = ResourceUtils.getFile("classpath:books/schema.graphqls"); - TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schemaFile); - + private static GraphQLSource initGraphQLSource() { RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring(); builder.type(newTypeWiring("Query").dataFetcher("bookById", GraphQLDataFetchers.getBookByIdDataFetcher())); builder.type(newTypeWiring("Subscription").dataFetcher("bookSearch", GraphQLDataFetchers.getBooksOnSale())); RuntimeWiring runtimeWiring = builder.build(); - GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); - return GraphQL.newGraphQL(schema).build(); + return GraphQLSource.builder() + .schemaResource(new ClassPathResource("books/schema.graphqls")) + .runtimeWiring(runtimeWiring) + .build(); } private static WebSocketMessage toWebSocketMessage(String data) { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQLWebSocketHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQLWebSocketHandlerTests.java index 7513aec5..316dc88f 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQLWebSocketHandlerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQLWebSocketHandlerTests.java @@ -16,7 +16,6 @@ package org.springframework.graphql.web.webmvc; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.time.Duration; @@ -27,15 +26,12 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; -import graphql.GraphQL; -import graphql.schema.GraphQLSchema; import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; +import org.springframework.core.io.ClassPathResource; +import org.springframework.graphql.support.GraphQLSource; import org.springframework.graphql.web.ConsumeOneAndNeverCompleteInterceptor; import org.springframework.graphql.web.DefaultWebGraphQLService; import org.springframework.graphql.web.GraphQLDataFetchers; @@ -45,7 +41,6 @@ import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.lang.Nullable; -import org.springframework.util.ResourceUtils; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketMessage; @@ -262,9 +257,7 @@ public class GraphQLWebSocketHandlerTests { @Nullable List interceptors, @Nullable Duration initTimeoutDuration) { try { - GraphQL graphQL = initGraphQL(); - - DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(graphQL); + DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(initGraphQLSource()); if (interceptors != null) { requestHandler.setInterceptors(interceptors); } @@ -277,17 +270,16 @@ public class GraphQLWebSocketHandlerTests { } } - private static GraphQL initGraphQL() throws Exception { - File schemaFile = ResourceUtils.getFile("classpath:books/schema.graphqls"); - TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(schemaFile); - + private static GraphQLSource initGraphQLSource() throws Exception { RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring(); builder.type(newTypeWiring("Query").dataFetcher("bookById", GraphQLDataFetchers.getBookByIdDataFetcher())); builder.type(newTypeWiring("Subscription").dataFetcher("bookSearch", GraphQLDataFetchers.getBooksOnSale())); RuntimeWiring runtimeWiring = builder.build(); - GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); - return GraphQL.newGraphQL(schema).build(); + return GraphQLSource.builder() + .schemaResource(new ClassPathResource("books/schema.graphqls")) + .runtimeWiring(runtimeWiring) + .build(); } private void assertMessageType(WebSocketMessage message, String messageType) {