Introduce GraphQLSource

A pluggable abstraction to encapsulate the way GraphQL is initialized and
the way it is sourced at runtime.

Closes gh-48
This commit is contained in:
Rossen Stoyanchev
2021-04-28 17:32:35 +01:00
parent f3d941c0f4
commit e5a92a310d
15 changed files with 315 additions and 175 deletions

View File

@@ -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<Instrumentation> instrumentationsProvider) {
public GraphQLSource.Builder graphQLSourceBuilder(
GraphQLProperties properties, RuntimeWiring runtimeWiring,
ResourceLoader resourceLoader, ObjectProvider<Instrumentation> 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<Instrumentation> 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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<WebInterceptor> interceptors) {
DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQL);
public WebGraphQLService webGraphQLService(GraphQLSource graphQLSource, ObjectProvider<WebInterceptor> interceptors) {
DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQLSource);
handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList()));
return handler;
}

View File

@@ -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<WebInterceptor> interceptors) {
DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQL);
public WebGraphQLService webGraphQLService(GraphQLSource graphQLSource, ObjectProvider<WebInterceptor> interceptors) {
DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQLSource);
handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList()));
return handler;
}

View File

@@ -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"));
}
}