From 6044017d312d3238b425174b2af932f9e003eb7c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 18 Sep 2020 12:55:19 +0100 Subject: [PATCH] Support for invocation of interceptors --- .../WebFluxGraphQLAutoConfiguration.java | 4 +- .../WebMvcGraphQLAutoConfiguration.java | 4 +- .../graphql/WebFluxGraphQLHandler.java | 41 +++------- .../graphql/WebHandlerSupport.java | 78 +++++++++++++++++++ .../org/springframework/graphql/WebInput.java | 22 ++++-- .../graphql/WebMvcGraphQLHandler.java | 42 +++------- .../springframework/graphql/WebOutput.java | 6 +- 7 files changed, 127 insertions(+), 70 deletions(-) create mode 100644 spring-graphql-web/src/main/java/org/springframework/graphql/WebHandlerSupport.java diff --git a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebFluxGraphQLAutoConfiguration.java b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebFluxGraphQLAutoConfiguration.java index 5f943a54..89a5a445 100644 --- a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebFluxGraphQLAutoConfiguration.java +++ b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebFluxGraphQLAutoConfiguration.java @@ -15,6 +15,8 @@ */ package org.springframework.boot.graphql; +import java.util.Collections; + import graphql.GraphQL; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -39,7 +41,7 @@ public class WebFluxGraphQLAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxGraphQLHandler graphQLHandler(GraphQL.Builder graphQLBuilder) { - return new WebFluxGraphQLHandler(graphQLBuilder); + return new WebFluxGraphQLHandler(graphQLBuilder, Collections.emptyList()); } @Bean diff --git a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebMvcGraphQLAutoConfiguration.java b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebMvcGraphQLAutoConfiguration.java index 3e20ff1c..674ffd8c 100644 --- a/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebMvcGraphQLAutoConfiguration.java +++ b/spring-graphql-web/src/main/java/org/springframework/boot/graphql/WebMvcGraphQLAutoConfiguration.java @@ -15,6 +15,8 @@ */ package org.springframework.boot.graphql; +import java.util.Collections; + import graphql.GraphQL; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -42,7 +44,7 @@ public class WebMvcGraphQLAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcGraphQLHandler graphQLHandler(GraphQL.Builder graphQLBuilder) { - return new WebMvcGraphQLHandler(graphQLBuilder); + return new WebMvcGraphQLHandler(graphQLBuilder, Collections.emptyList()); } @Bean diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java index 601cfdd4..11b60206 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/WebFluxGraphQLHandler.java @@ -15,8 +15,8 @@ */ package org.springframework.graphql; -import graphql.ExecutionInput; -import graphql.ExecutionResult; +import java.util.List; + import graphql.GraphQL; import reactor.core.publisher.Mono; @@ -25,45 +25,24 @@ import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; /** - * GraphQL handler to be exposed as a WebFlux.fn endpoint via + * GraphQL handler to expose as a WebFlux.fn endpoint via * {@link org.springframework.web.reactive.function.server.RouterFunctions}. */ -public class WebFluxGraphQLHandler implements HandlerFunction { +public class WebFluxGraphQLHandler extends WebHandlerSupport implements HandlerFunction { - private final GraphQL graphQL; - public WebFluxGraphQLHandler(GraphQL.Builder graphQLBuilder) { - this.graphQL = graphQLBuilder.build(); + public WebFluxGraphQLHandler(GraphQL.Builder builder, List interceptors) { + super(builder, interceptors); } + public Mono handle(ServerRequest request) { return request.bodyToMono(WebInput.MAP_PARAMETERIZED_TYPE_REF) .flatMap(body -> { - WebInput webInput = new WebInput( - request.uri(), request.headers().asHttpHeaders(), body); - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .query(webInput.getQuery()) - .operationName(webInput.getOperationName()) - .variables(webInput.getVariables()) - .build(); - - // Invoke GraphQLInterceptor's preHandle here - return extendInput(executionInput, webInput); + WebInput webInput = new WebInput(request.uri(), request.headers().asHttpHeaders(), body); + return executeQuery(webInput); }) - .flatMap(executionInput -> { - // Invoke handleResult here - return execute(executionInput); - }) - .flatMap(result -> ServerResponse.ok().bodyValue(result.toSpecification())); - } - - protected Mono extendInput(ExecutionInput executionInput, WebInput webInput) { - return Mono.just(executionInput); - } - - protected Mono execute(ExecutionInput input) { - return Mono.fromFuture(graphQL.executeAsync(input)); + .flatMap(output -> ServerResponse.ok().bodyValue(output.toSpecification())); } } diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebHandlerSupport.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebHandlerSupport.java new file mode 100644 index 00000000..45147f64 --- /dev/null +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/WebHandlerSupport.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020-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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import reactor.core.publisher.Mono; + +import org.springframework.util.CollectionUtils; + +/** + * Base class for GraphQL over HTTP handlers. + */ +public abstract class WebHandlerSupport { + + private final GraphQL graphQL; + + private final List interceptors; + + + public WebHandlerSupport(GraphQL.Builder builder, List interceptors) { + this.graphQL = builder.build(); + this.interceptors = (!CollectionUtils.isEmpty(interceptors) ? + Collections.unmodifiableList(new ArrayList<>(interceptors)) : Collections.emptyList()); + } + + + public GraphQL getGraphQL() { + return this.graphQL; + } + + public List getInterceptors() { + return this.interceptors; + } + + + protected Mono executeQuery(WebInput webInput) { + return createInputChain(webInput).flatMap(executionInput -> { + Mono resultMono = Mono.fromFuture(getGraphQL().executeAsync(executionInput)); + return createOutputChain(resultMono); + }); + } + + protected Mono createInputChain(WebInput webInput) { + Mono preHandleMono = Mono.just(webInput.toExecutionInput()); + for (WebInterceptor interceptor : this.interceptors) { + preHandleMono = preHandleMono.flatMap(input -> interceptor.preHandle(input, webInput)); + } + return preHandleMono; + } + + protected Mono createOutputChain(Mono resultMono) { + Mono outputMono = resultMono.map(WebOutput::new); + for (WebInterceptor interceptor : this.interceptors) { + outputMono = outputMono.flatMap(interceptor::postHandle); + } + return outputMono; + } + +} diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebInput.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebInput.java index 7d896bc4..f6a70f5d 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/WebInput.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/WebInput.java @@ -17,8 +17,12 @@ package org.springframework.graphql; import java.net.URI; import java.util.Collections; +import java.util.List; import java.util.Map; +import graphql.ExecutionInput; +import reactor.core.publisher.Mono; + import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; @@ -50,7 +54,7 @@ public class WebInput { @SuppressWarnings("unchecked") - WebInput(URI uri, HttpHeaders headers, Map body) { + public WebInput(URI uri, HttpHeaders headers, Map body) { this.uri = UriComponentsBuilder.fromUri(uri).build(true); this.headers = headers; this.query = getAndValidateQuery(body); @@ -71,21 +75,29 @@ public class WebInput { return this.uri; } - public HttpHeaders getHeaders() { + public HttpHeaders headers() { return this.headers; } - public String getQuery() { + public String query() { return this.query; } @Nullable - public String getOperationName() { + public String operationName() { return this.operationName; } - public Map getVariables() { + public Map variables() { return this.variables; } + public ExecutionInput toExecutionInput() { + return ExecutionInput.newExecutionInput() + .query(query()) + .operationName(operationName()) + .variables(variables()) + .build(); + } + } \ No newline at end of file diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java index f9655db4..5acddbb3 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/WebMvcGraphQLHandler.java @@ -16,11 +16,11 @@ package org.springframework.graphql; import java.io.IOException; +import java.util.List; import java.util.Map; import javax.servlet.ServletException; -import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.GraphQL; import reactor.core.publisher.Mono; @@ -32,17 +32,17 @@ import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; /** - * GraphQL handler to be exposed as a WebMvc.fn endpoint via + * GraphQL handler to expose as a WebMvc.fn endpoint via * {@link org.springframework.web.servlet.function.RouterFunctions}. */ -public class WebMvcGraphQLHandler implements HandlerFunction { +public class WebMvcGraphQLHandler extends WebHandlerSupport implements HandlerFunction { - private final GraphQL graphQL; - public WebMvcGraphQLHandler(GraphQL.Builder graphQL) { - this.graphQL = graphQL.build(); + public WebMvcGraphQLHandler(GraphQL.Builder builder, List interceptors) { + super(builder, interceptors); } + /** * {@inheritDoc} * @@ -50,38 +50,18 @@ public class WebMvcGraphQLHandler implements HandlerFunction { * e.g. {@link HttpMediaTypeNotSupportedException}. */ public ServerResponse handle(ServerRequest request) throws ServletException { - WebInput webInput = createWebInput(request); - - ExecutionInput executionInput = ExecutionInput.newExecutionInput() - .query(webInput.getQuery()) - .operationName(webInput.getOperationName()) - .variables(webInput.getVariables()) - .build(); - - Mono> body = extendInput(executionInput, webInput) - .flatMap(this::execute) - .map(ExecutionResult::toSpecification); - - return ServerResponse.ok().body(body); + WebInput webInput = new WebInput(request.uri(), request.headers().asHttpHeaders(), readBody(request)); + Mono outputMono = executeQuery(webInput); + return ServerResponse.ok().body(outputMono.map(ExecutionResult::toSpecification)); } - private static WebInput createWebInput(ServerRequest request) throws ServletException { - Map body; + private static Map readBody(ServerRequest request) throws ServletException { try { - body = request.body(WebInput.MAP_PARAMETERIZED_TYPE_REF); + return request.body(WebInput.MAP_PARAMETERIZED_TYPE_REF); } catch (IOException ex) { throw new ServerWebInputException("I/O error while reading request body", null, ex); } - return new WebInput(request.uri(), request.headers().asHttpHeaders(), body); - } - - protected Mono extendInput(ExecutionInput executionInput, WebInput webInput) { - return Mono.just(executionInput); - } - - protected Mono execute(ExecutionInput input) { - return Mono.fromFuture(this.graphQL.executeAsync(input)); } } \ No newline at end of file diff --git a/spring-graphql-web/src/main/java/org/springframework/graphql/WebOutput.java b/spring-graphql-web/src/main/java/org/springframework/graphql/WebOutput.java index d59220e5..849c1eea 100644 --- a/spring-graphql-web/src/main/java/org/springframework/graphql/WebOutput.java +++ b/spring-graphql-web/src/main/java/org/springframework/graphql/WebOutput.java @@ -26,12 +26,16 @@ import graphql.GraphQLError; import org.springframework.lang.Nullable; +/** + * Simple wrapper around a GraphQL {@link ExecutionResult} that allows + * {@link #transform(Consumer) transformation} via a {@link Builder Builder}. + */ public class WebOutput implements ExecutionResult { private final ExecutionResult executionResult; - WebOutput(ExecutionResult executionResult) { + public WebOutput(ExecutionResult executionResult) { this.executionResult = executionResult; }