Add RuntimeWiringConfigurer

Closes gh-107
This commit is contained in:
Rossen Stoyanchev
2021-08-06 17:51:07 +01:00
parent 8db2bf9680
commit 3567a062ab
17 changed files with 78 additions and 81 deletions

View File

@@ -36,6 +36,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for creating a
@@ -57,14 +58,14 @@ public class GraphQlAutoConfiguration {
ObjectProvider<DataFetcherExceptionResolver> exceptionResolversProvider,
ObjectProvider<Instrumentation> instrumentationsProvider,
ObjectProvider<GraphQlSourceBuilderCustomizer> sourceCustomizers,
ObjectProvider<RuntimeWiringBuilderCustomizer> wiringCustomizers) throws IOException {
ObjectProvider<RuntimeWiringConfigurer> wiringConfigurers) throws IOException {
List<Resource> schemaResources = resolveSchemaResources(resourcePatternResolver, properties.getSchema().getLocations());
GraphQlSource.Builder builder = GraphQlSource.builder()
.schemaResources(schemaResources.toArray(new Resource[0]))
.exceptionResolvers(exceptionResolversProvider.orderedStream().collect(Collectors.toList()))
.instrumentation(instrumentationsProvider.orderedStream().collect(Collectors.toList()));
wiringCustomizers.orderedStream().forEach((customizer) -> builder.configureRuntimeWiring(customizer::customize));
wiringConfigurers.orderedStream().forEach(builder::runtimeWiringConfigurer);
sourceCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}

View File

@@ -36,7 +36,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.data.method.AnnotatedDataFetcherRegistrar;
import org.springframework.graphql.data.method.AnnotatedDataFetcherConfigurer;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInterceptor;
@@ -75,17 +75,12 @@ public class GraphQlWebFluxAutoConfiguration {
private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class);
@Bean
public AnnotatedDataFetcherRegistrar dataFetcherRegistrar(ServerCodecConfigurer configurer) {
AnnotatedDataFetcherRegistrar registrar = new AnnotatedDataFetcherRegistrar();
public AnnotatedDataFetcherConfigurer annotatedDataFetcherConfigurer(ServerCodecConfigurer configurer) {
AnnotatedDataFetcherConfigurer registrar = new AnnotatedDataFetcherConfigurer();
registrar.setServerCodecConfigurer(configurer);
return registrar;
}
@Bean
public RuntimeWiringBuilderCustomizer annotatedDataFetcherRuntimeWiringCustomizer(AnnotatedDataFetcherRegistrar registrar) {
return registrar::register;
}
@Bean
@ConditionalOnBean(GraphQlService.class)
@ConditionalOnMissingBean

View File

@@ -40,7 +40,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.data.method.AnnotatedDataFetcherRegistrar;
import org.springframework.graphql.data.method.AnnotatedDataFetcherConfigurer;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.execution.ThreadLocalAccessor;
import org.springframework.graphql.web.WebGraphQlHandler;
@@ -83,8 +83,8 @@ public class GraphQlWebMvcAutoConfiguration {
@Bean
public AnnotatedDataFetcherRegistrar dataFetcherRegistrar(HttpMessageConverters converters) {
AnnotatedDataFetcherRegistrar registrar = new AnnotatedDataFetcherRegistrar();
public AnnotatedDataFetcherConfigurer annotatedDataFetcherConfigurer(HttpMessageConverters converters) {
AnnotatedDataFetcherConfigurer registrar = new AnnotatedDataFetcherConfigurer();
registrar.setJsonMessageConverter(getJsonConverter(converters));
return registrar;
}
@@ -98,11 +98,6 @@ public class GraphQlWebMvcAutoConfiguration {
.orElseThrow(() -> new IllegalStateException("No JSON converter"));
}
@Bean
public RuntimeWiringBuilderCustomizer annotatedDataFetcherRuntimeWiringCustomizer(AnnotatedDataFetcherRegistrar registrar) {
return registrar::register;
}
@Bean
@ConditionalOnBean(GraphQlService.class)
@ConditionalOnMissingBean

View File

@@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfigurat
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.web.WebInterceptor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -161,9 +162,9 @@ class GraphQlWebFluxAutoConfigurationTests {
static class DataFetchersConfiguration {
@Bean
RuntimeWiringBuilderCustomizer bookDataFetcher() {
return (runtimeWiring) ->
runtimeWiring.type(TypeRuntimeWiring.newTypeWiring("Query")
RuntimeWiringConfigurer bookDataFetcher() {
return (builder) -> builder.type(
TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", GraphQlDataFetchers.getBookByIdDataFetcher()));
}

View File

@@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguratio
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.web.WebInterceptor;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
@@ -147,9 +148,10 @@ class GraphQlWebMvcAutoConfigurationTests {
static class DataFetchersConfiguration {
@Bean
RuntimeWiringBuilderCustomizer bookDataFetcher() {
return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", GraphQlDataFetchers.getBookByIdDataFetcher()));
RuntimeWiringConfigurer bookDataFetcher() {
return (builder) -> builder.type(
TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", GraphQlDataFetchers.getBookByIdDataFetcher()));
}
}

View File

@@ -37,8 +37,8 @@ import org.springframework.graphql.boot.GraphQlAutoConfiguration;
import org.springframework.graphql.boot.GraphQlDataFetchers;
import org.springframework.graphql.boot.GraphQlServiceAutoConfiguration;
import org.springframework.graphql.boot.GraphQlWebFluxAutoConfiguration;
import org.springframework.graphql.boot.RuntimeWiringBuilderCustomizer;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.security.ReactiveSecurityDataFetcherExceptionResolver;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
@@ -143,9 +143,10 @@ class GraphQlWebFluxSecurityAutoConfigurationTests {
static class DataFetchersConfiguration {
@Bean
RuntimeWiringBuilderCustomizer bookDataFetcher(BookService bookService) {
return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", environment -> bookService.getBookdById(environment.getArgument("id"))));
RuntimeWiringConfigurer bookDataFetcher(BookService bookService) {
return (builder) -> builder.type(
TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", env -> bookService.getBookdById(env.getArgument("id"))));
}
@Bean

View File

@@ -33,8 +33,8 @@ import org.springframework.graphql.boot.GraphQlAutoConfiguration;
import org.springframework.graphql.boot.GraphQlDataFetchers;
import org.springframework.graphql.boot.GraphQlServiceAutoConfiguration;
import org.springframework.graphql.boot.GraphQlWebMvcAutoConfiguration;
import org.springframework.graphql.boot.RuntimeWiringBuilderCustomizer;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.graphql.security.SecurityContextThreadLocalAccessor;
import org.springframework.graphql.security.SecurityDataFetcherExceptionResolver;
import org.springframework.http.MediaType;
@@ -148,9 +148,10 @@ class GraphQlWebMvcSecurityAutoConfigurationTests {
static class DataFetchersConfiguration {
@Bean
RuntimeWiringBuilderCustomizer bookDataFetcher(BookService bookService) {
return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", environment -> bookService.getBookdById(environment.getArgument("id"))));
RuntimeWiringConfigurer bookDataFetcher(BookService bookService) {
return (builder) -> builder.type(
TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", env -> bookService.getBookdById(env.getArgument("id"))));
}
@Bean

View File

@@ -116,13 +116,13 @@ spring.graphql.schema.printer.enabled=false
[[boot-graphql-datafetcher]]
== `DataFetcher` Registration
You can declare `RuntimeWiringBuilderCustomizer` beans in your Spring config and use those to
You can declare `RuntimeWiringConfigurer` beans in your Spring config and use those to
register data fetchers, type resolvers, and more with the GraphQL engine:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class PersonDataWiring implements RuntimeWiringBuilderCustomizer {
public class PersonDataWiring implements RuntimeWiringConfigurer {
private final PersonService service;
@@ -131,7 +131,7 @@ public class PersonDataWiring implements RuntimeWiringBuilderCustomizer {
}
@Override
public void customize(RuntimeWiring.Builder builder) {
public void configure(RuntimeWiring.Builder builder) {
builder.type("Query", wiring ->
wiring.dataFetcher("people", env -> this.service.findAll()));
}

View File

@@ -49,6 +49,7 @@ import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
import org.springframework.graphql.data.method.annotation.support.InputArgumentMethodArgumentResolver;
import org.springframework.graphql.data.method.annotation.support.DataFetchingEnvironmentMethodArgumentResolver;
import org.springframework.graphql.data.method.annotation.support.SourceMethodArgumentResolver;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
import org.springframework.http.MediaType;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter;
@@ -60,14 +61,17 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
*
* A {@link RuntimeWiringConfigurer} that detects {@link SchemaMapping @SchemaMapping}
* annotated handler methods in {@link GraphQlController @GraphQlController}
* classes and registers them as {@link DataFetcher}s.
*
* @author Rossen Stoyanchev
* @since 1.0.0
*/
public class AnnotatedDataFetcherRegistrar implements ApplicationContextAware, InitializingBean {
public class AnnotatedDataFetcherConfigurer
implements ApplicationContextAware, InitializingBean, RuntimeWiringConfigurer {
private final static Log logger = LogFactory.getLog(AnnotatedDataFetcherRegistrar.class);
private final static Log logger = LogFactory.getLog(AnnotatedDataFetcherConfigurer.class);
/**
@@ -175,7 +179,8 @@ public class AnnotatedDataFetcherRegistrar implements ApplicationContextAware, I
}
public void register(RuntimeWiring.Builder builder) {
@Override
public void configure(RuntimeWiring.Builder builder) {
Assert.state(this.argumentResolvers != null, "`argumentResolvers` not initialized");
Assert.state(this.applicationContext != null, "ApplicationContext is required");

View File

@@ -51,8 +51,7 @@ class DefaultGraphQlSourceBuilder implements GraphQlSource.Builder {
private final List<Resource> schemaResources = new ArrayList<>();
private Consumer<RuntimeWiring.Builder> runtimeWiringConfigurers = (builder) -> {
};
private final List<RuntimeWiringConfigurer> runtimeWiringConfigurers = new ArrayList<>();
private final List<DataFetcherExceptionResolver> exceptionResolvers = new ArrayList<>();
@@ -71,9 +70,8 @@ class DefaultGraphQlSourceBuilder implements GraphQlSource.Builder {
}
@Override
public GraphQlSource.Builder configureRuntimeWiring(Consumer<RuntimeWiring.Builder> configurer) {
Assert.notNull(configurer, "RuntimeWiring configurer is required");
this.runtimeWiringConfigurers = this.runtimeWiringConfigurers.andThen(configurer);
public GraphQlSource.Builder runtimeWiringConfigurer(RuntimeWiringConfigurer configurer) {
this.runtimeWiringConfigurers.add(configurer);
return this;
}
@@ -107,9 +105,10 @@ class DefaultGraphQlSourceBuilder implements GraphQlSource.Builder {
.map(this::parseSchemaResource).reduce(TypeDefinitionRegistry::merge)
.orElseThrow(() -> new IllegalArgumentException("'schemaResources' should not be empty"));
RuntimeWiring.Builder runtimeWiring = RuntimeWiring.newRuntimeWiring();
this.runtimeWiringConfigurers.accept(runtimeWiring);
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(registry, runtimeWiring.build());
RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring();
this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(runtimeWiringBuilder));
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(registry, runtimeWiringBuilder.build());
schema = applyTypeVisitors(schema);
GraphQL.Builder builder = GraphQL.newGraphQL(schema);

View File

@@ -79,15 +79,14 @@ public interface GraphQlSource {
Builder schemaResources(Resource... resources);
/**
* Configure consumers that will be given access to the {@link RuntimeWiring.Builder} used to
* build the {@link RuntimeWiring}. A {@link RuntimeWiring#newRuntimeWiring() default builder}
* instance is created as a starting point.
* @param configurer the runtime wiring configurer
* Add a component that is given access to the {@link RuntimeWiring.Builder}
* used to register {@link graphql.schema.DataFetcher}s, custom scalar
* types, type resolvers, and more.
* @param configurer the configurer to apply
* @return the current builder
* @see graphql.schema.idl.SchemaGenerator#makeExecutableSchema(TypeDefinitionRegistry,
* RuntimeWiring)
* @see graphql.schema.idl.SchemaGenerator#makeExecutableSchema(TypeDefinitionRegistry, RuntimeWiring)
*/
Builder configureRuntimeWiring(Consumer<RuntimeWiring.Builder> configurer);
Builder runtimeWiringConfigurer(RuntimeWiringConfigurer configurer);
/**
* Add {@link DataFetcherExceptionResolver}'s to use for resolving exceptions from

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 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.
@@ -13,26 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.boot;
package org.springframework.graphql.execution;
import graphql.schema.idl.RuntimeWiring;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link RuntimeWiring} via a {@link RuntimeWiring.Builder} whilst retaining default
* auto-configuration.
* Component used to apply changes to the {@link RuntimeWiring.Builder} instance
* used in {@link GraphQlSource.Builder}.
*
* @author Brian Clozel
* @author Rossen Stoyanchev
* @since 1.0.0
*/
@FunctionalInterface
public interface RuntimeWiringBuilderCustomizer {
public interface RuntimeWiringConfigurer {
/**
* Customize the {@link RuntimeWiring.Builder} instance.
* @param builder builder the builder to customize
* Apply changes to the {@link RuntimeWiring.Builder} such as registering
* {@link graphql.schema.DataFetcher}s, custom scalar types, and more.
* @param builder the builder to configure
*/
void customize(RuntimeWiring.Builder builder);
void configure(RuntimeWiring.Builder builder);
}

View File

@@ -54,7 +54,7 @@ public abstract class GraphQlTestUtils {
return GraphQlSource.builder()
.schemaResources(new ByteArrayResource(schemaContent.getBytes(StandardCharsets.UTF_8)))
.configureRuntimeWiring(wiring -> wiring.type(typeName, (builder) -> builder.dataFetcher(fieldName, fetcher)));
.runtimeWiringConfigurer(wiring -> wiring.type(typeName, (builder) -> builder.dataFetcher(fieldName, fetcher)));
}
}

View File

@@ -37,10 +37,10 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link AnnotatedDataFetcherRegistrar}.
* Unit tests for {@link AnnotatedDataFetcherConfigurer}.
* @author Rossen Stoyanchev
*/
public class AnnotatedDataFetcherRegistrarTests {
public class AnnotatedDataFetcherConfigurerTests {
@Test
void registerWithDefaultCoordinates() {
@@ -81,13 +81,13 @@ public class AnnotatedDataFetcherRegistrarTests {
appContext.registerBean(handlerType);
appContext.refresh();
AnnotatedDataFetcherRegistrar registrar = new AnnotatedDataFetcherRegistrar();
registrar.setJsonMessageConverter(new MappingJackson2HttpMessageConverter());
registrar.setApplicationContext(appContext);
registrar.afterPropertiesSet();
AnnotatedDataFetcherConfigurer configurer = new AnnotatedDataFetcherConfigurer();
configurer.setJsonMessageConverter(new MappingJackson2HttpMessageConverter());
configurer.setApplicationContext(appContext);
configurer.afterPropertiesSet();
RuntimeWiring.Builder wiringBuilder = RuntimeWiring.newRuntimeWiring();
registrar.register(wiringBuilder);
configurer.configure(wiringBuilder);
return wiringBuilder;
}

View File

@@ -180,14 +180,14 @@ public class AnnotatedDataFetcherInvocationTests {
applicationContext.registerBean(beanClass);
applicationContext.refresh();
AnnotatedDataFetcherRegistrar registrar = new AnnotatedDataFetcherRegistrar();
registrar.setApplicationContext(applicationContext);
registrar.setServerCodecConfigurer(ServerCodecConfigurer.create());
registrar.afterPropertiesSet();
AnnotatedDataFetcherConfigurer configurer = new AnnotatedDataFetcherConfigurer();
configurer.setApplicationContext(applicationContext);
configurer.setServerCodecConfigurer(ServerCodecConfigurer.create());
configurer.afterPropertiesSet();
GraphQlSource graphQlSource = GraphQlSource.builder()
.schemaResources(new ClassPathResource("books/schema.graphqls"))
.configureRuntimeWiring(registrar::register)
.runtimeWiringConfigurer(configurer::configure)
.build();
return graphQlSource.graphQl();

View File

@@ -282,7 +282,7 @@ class QuerydslDataFetcherTests {
if (configurer != null) {
TypeRuntimeWiring.Builder typeBuilder = TypeRuntimeWiring.newTypeWiring("Query");
configurer.accept(typeBuilder);
graphQlSourceBuilder.configureRuntimeWiring(wiring -> wiring.type(typeBuilder));
graphQlSourceBuilder.runtimeWiringConfigurer(wiring -> wiring.type(typeBuilder));
}
GraphQLTypeVisitor visitor = QuerydslDataFetcher.registrationTypeVisitor(

View File

@@ -69,7 +69,7 @@ public abstract class BookTestUtils {
private static GraphQlSource graphQlSource() {
return GraphQlSource.builder()
.schemaResources(new ClassPathResource("books/schema.graphqls"))
.configureRuntimeWiring(builder -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.runtimeWiringConfigurer(builder -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", (env) -> {
Long id = Long.parseLong(env.getArgument("id"));
return BookSource.getBook(id);