From 43806113473acaee39a1b84910139a5a8b1ad6f9 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Fri, 15 Nov 2019 12:56:13 +0100 Subject: [PATCH 01/26] Extracted http request handler --- build.gradle | 9 +- gradle/wrapper/gradle-wrapper.properties | 6 +- .../servlet/AbstractGraphQLHttpServlet.java | 933 ++++++++---------- .../servlet/BatchedQueryResponseWriter.java | 40 + .../servlet/DecoratedExecutionResult.java | 53 + .../servlet/ErrorQueryResponseWriter.java | 19 + .../servlet/ExecutionResultSubscriber.java | 62 ++ .../servlet/GraphQLBatchedQueryResult.java | 24 + .../servlet/GraphQLErrorQueryResult.java | 29 + .../servlet/GraphQLInvocationInputParser.java | 63 ++ .../servlet/GraphQLQueryProcessor.java | 9 + .../graphql/servlet/GraphQLQueryResult.java | 31 + .../servlet/GraphQLSingleQueryResult.java | 22 + .../servlet/HttpGetRequestHandler.java | 69 ++ .../graphql/servlet/HttpRequestHandler.java | 26 + .../graphql/servlet/QueryResponseWriter.java | 31 + ...SingleAsynchronousQueryResponseWriter.java | 65 ++ .../servlet/SingleQueryResponseWriter.java | 25 + .../graphql/servlet/StaticDataPublisher.java | 14 + .../servlet/SubscriptionAsyncListener.java | 33 + .../servlet/context/ContextSetting.java | 4 +- .../servlet/core/GraphQLObjectMapper.java | 1 + .../servlet/core/GraphQLQueryInvoker.java | 264 ++--- .../servlet/core/internal/GraphQLRequest.java | 5 + .../input/GraphQLBatchedInvocationInput.java | 6 +- .../servlet/input/GraphQLInvocationInput.java | 5 + .../input/GraphQLInvocationInputFactory.java | 1 + .../input/GraphQLSingleInvocationInput.java | 2 +- .../input/PerQueryBatchedInvocationInput.java | 15 +- .../PerRequestBatchedInvocationInput.java | 15 +- 30 files changed, 1204 insertions(+), 677 deletions(-) create mode 100644 src/main/java/graphql/servlet/BatchedQueryResponseWriter.java create mode 100644 src/main/java/graphql/servlet/DecoratedExecutionResult.java create mode 100644 src/main/java/graphql/servlet/ErrorQueryResponseWriter.java create mode 100644 src/main/java/graphql/servlet/ExecutionResultSubscriber.java create mode 100644 src/main/java/graphql/servlet/GraphQLBatchedQueryResult.java create mode 100644 src/main/java/graphql/servlet/GraphQLErrorQueryResult.java create mode 100644 src/main/java/graphql/servlet/GraphQLInvocationInputParser.java create mode 100644 src/main/java/graphql/servlet/GraphQLQueryProcessor.java create mode 100644 src/main/java/graphql/servlet/GraphQLQueryResult.java create mode 100644 src/main/java/graphql/servlet/GraphQLSingleQueryResult.java create mode 100644 src/main/java/graphql/servlet/HttpGetRequestHandler.java create mode 100644 src/main/java/graphql/servlet/HttpRequestHandler.java create mode 100644 src/main/java/graphql/servlet/QueryResponseWriter.java create mode 100644 src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java create mode 100644 src/main/java/graphql/servlet/SingleQueryResponseWriter.java create mode 100644 src/main/java/graphql/servlet/StaticDataPublisher.java create mode 100644 src/main/java/graphql/servlet/SubscriptionAsyncListener.java create mode 100644 src/main/java/graphql/servlet/input/GraphQLInvocationInput.java diff --git a/build.gradle b/build.gradle index d25800cf..a7e908c6 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ plugins { id "com.jfrog.bintray" version "1.8.4" id "com.jfrog.artifactory" version "4.8.1" id 'net.researchgate.release' version '2.7.0' + id 'io.franzbecker.gradle-lombok' version '3.1.0' } apply plugin: 'java' @@ -25,10 +26,6 @@ repositories { mavenCentral() } -configurations.all { - exclude group:"org.projectlombok", module: "lombok" -} - dependencies { compile 'org.slf4j:slf4j-api:1.7.21' @@ -210,3 +207,7 @@ idea { wrapper { gradleVersion = '4.10.3' } + +lombok { + version = '1.18.10' +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 310a46a5..d4c1dfdb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jun 20 12:32:36 CEST 2019 +#Thu Nov 14 18:53:34 CET 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java index f59fa38e..448181c3 100644 --- a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java @@ -1,5 +1,8 @@ package graphql.servlet; +import static graphql.servlet.HttpRequestHandler.APPLICATION_GRAPHQL; +import static graphql.servlet.HttpRequestHandler.STATUS_BAD_REQUEST; + import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import graphql.ExecutionResult; @@ -15,13 +18,30 @@ import graphql.servlet.core.GraphQLServletListener; import graphql.servlet.core.internal.GraphQLRequest; import graphql.servlet.core.internal.VariableMapper; -import graphql.servlet.input.*; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import graphql.servlet.input.BatchInputPreProcessResult; +import graphql.servlet.input.BatchInputPreProcessor; +import graphql.servlet.input.GraphQLBatchedInvocationInput; +import graphql.servlet.input.GraphQLInvocationInputFactory; +import graphql.servlet.input.GraphQLSingleInvocationInput; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -30,567 +50,420 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; -import java.io.*; -import java.util.*; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; /** * @author Andrew Potter */ +@Slf4j public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements Servlet, GraphQLMBean { - private static final Logger log = LoggerFactory.getLogger(AbstractGraphQLHttpServlet.class); - - private static final String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8"; - private static final String APPLICATION_EVENT_STREAM_UTF8 = "text/event-stream;charset=UTF-8"; - private static final String APPLICATION_GRAPHQL = "application/graphql"; - private static final int STATUS_OK = 200; - private static final int STATUS_BAD_REQUEST = 400; - - private static final GraphQLRequest INTROSPECTION_REQUEST = new GraphQLRequest(IntrospectionQuery.INTROSPECTION_QUERY, new HashMap<>(), null); - private static final String[] MULTIPART_KEYS = new String[]{"operations", "graphql", "query"}; - - private GraphQLConfiguration configuration; - - /** - * @deprecated override {@link #getConfiguration()} instead - */ - @Deprecated - protected abstract GraphQLQueryInvoker getQueryInvoker(); - - /** - * @deprecated override {@link #getConfiguration()} instead - */ - @Deprecated - protected abstract GraphQLInvocationInputFactory getInvocationInputFactory(); - - /** - * @deprecated override {@link #getConfiguration()} instead - */ - @Deprecated - protected abstract GraphQLObjectMapper getGraphQLObjectMapper(); - - /** - * @deprecated override {@link #getConfiguration()} instead - */ - @Deprecated - protected abstract boolean isAsyncServletMode(); - - protected GraphQLConfiguration getConfiguration() { - return GraphQLConfiguration.with(getInvocationInputFactory()) - .with(getQueryInvoker()) - .with(getGraphQLObjectMapper()) - .with(isAsyncServletMode()) - .with(listeners) - .build(); - } - - /** - * @deprecated use {@link #getConfiguration()} instead - */ - @Deprecated - private final List listeners; - - private HttpRequestHandler getHandler; - private HttpRequestHandler postHandler; - - public AbstractGraphQLHttpServlet() { - this(null); + private static final String[] MULTIPART_KEYS = new String[]{"operations", "graphql", "query"}; + /** + * @deprecated use {@link #getConfiguration()} instead + */ + @Deprecated + private final List listeners; + private GraphQLConfiguration configuration; + private HttpRequestHandler getHandler; + private HttpRequestHandler postHandler; + + public AbstractGraphQLHttpServlet() { + this(null); + } + + public AbstractGraphQLHttpServlet(List listeners) { + this.listeners = listeners != null ? new ArrayList<>(listeners) : new ArrayList<>(); + } + + /** + * @deprecated override {@link #getConfiguration()} instead + */ + @Deprecated + protected abstract GraphQLQueryInvoker getQueryInvoker(); + + /** + * @deprecated override {@link #getConfiguration()} instead + */ + @Deprecated + protected abstract GraphQLInvocationInputFactory getInvocationInputFactory(); + + /** + * @deprecated override {@link #getConfiguration()} instead + */ + @Deprecated + protected abstract GraphQLObjectMapper getGraphQLObjectMapper(); + + /** + * @deprecated override {@link #getConfiguration()} instead + */ + @Deprecated + protected abstract boolean isAsyncServletMode(); + + protected GraphQLConfiguration getConfiguration() { + return GraphQLConfiguration.with(getInvocationInputFactory()) + .with(getQueryInvoker()) + .with(getGraphQLObjectMapper()) + .with(isAsyncServletMode()) + .with(listeners) + .build(); + } + + @Override + public void init() { + if (configuration != null) { + return; } + this.configuration = getConfiguration(); + this.getHandler = new HttpGetRequestHandler(configuration); + + this.postHandler = (request, response) -> { + GraphQLInvocationInputFactory invocationInputFactory = configuration.getInvocationInputFactory(); + GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper(); + GraphQLQueryInvoker queryInvoker = configuration.getQueryInvoker(); + + try { + if (APPLICATION_GRAPHQL.equals(request.getContentType())) { + String query = CharStreams.toString(request.getReader()); + query(queryInvoker, graphQLObjectMapper, + invocationInputFactory.create(new GraphQLRequest(query, null, null), request, response), + request, response); + } else if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data") + && !request.getParts().isEmpty()) { + final Map> fileItems = request.getParts() + .stream() + .collect(Collectors.groupingBy(Part::getName)); + + for (String key : MULTIPART_KEYS) { + // Check to see if there is a part under the key we seek + if (!fileItems.containsKey(key)) { + continue; + } - public AbstractGraphQLHttpServlet(List listeners) { - this.listeners = listeners != null ? new ArrayList<>(listeners) : new ArrayList<>(); - } + final Optional queryItem = getFileItem(fileItems, key); + if (!queryItem.isPresent()) { + // If there is a part, but we don't see an item, then break and return BAD_REQUEST + break; + } - @Override - public void init() { - this.configuration = getConfiguration(); + InputStream inputStream = asMarkableInputStream(queryItem.get().getInputStream()); - this.getHandler = (request, response) -> { - GraphQLInvocationInputFactory invocationInputFactory = configuration.getInvocationInputFactory(); - GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper(); - GraphQLQueryInvoker queryInvoker = configuration.getQueryInvoker(); + final Optional>> variablesMap = + getFileItem(fileItems, "map").map(graphQLObjectMapper::deserializeMultipartMap); - String path = request.getPathInfo(); - if (path == null) { - path = request.getServletPath(); - } - if (path.contentEquals("/schema.json")) { - query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(INTROSPECTION_REQUEST, request, response), - request, response); + if (isBatchedQuery(inputStream)) { + List graphQLRequests = + graphQLObjectMapper.readBatchedGraphQLRequest(inputStream); + variablesMap.ifPresent(map -> graphQLRequests.forEach(r -> mapMultipartVariables(r, map, fileItems))); + GraphQLBatchedInvocationInput batchedInvocationInput = invocationInputFactory + .create(configuration.getContextSetting(), + graphQLRequests, request, response); + queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration); + return; } else { - String query = request.getParameter("query"); - if (query != null) { - - if (isBatchedQuery(query)) { - List requests = graphQLObjectMapper.readBatchedGraphQLRequest(query); - GraphQLBatchedInvocationInput batchedInvocationInput = - invocationInputFactory.createReadOnly(configuration.getContextSetting(), requests, request, response); - queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration); - } else { - final Map variables = new HashMap<>(); - if (request.getParameter("variables") != null) { - variables.putAll(graphQLObjectMapper.deserializeVariables(request.getParameter("variables"))); - } - - String operationName = request.getParameter("operationName"); - - query(queryInvoker, graphQLObjectMapper, - invocationInputFactory.createReadOnly(new GraphQLRequest(query, variables, operationName), request, response), - request, response); - } - } else { - response.setStatus(STATUS_BAD_REQUEST); - log.info("Bad GET request: path was not \"/schema.json\" or no query variable named \"query\" given"); - } + GraphQLRequest graphQLRequest; + if ("query".equals(key)) { + graphQLRequest = buildRequestFromQuery(inputStream, graphQLObjectMapper, fileItems); + } else { + graphQLRequest = graphQLObjectMapper.readGraphQLRequest(inputStream); + } + + variablesMap.ifPresent(m -> mapMultipartVariables(graphQLRequest, m, fileItems)); + GraphQLSingleInvocationInput invocationInput = + invocationInputFactory.create(graphQLRequest, request, response); + query(queryInvoker, graphQLObjectMapper, invocationInput, request, response); + return; } - }; - - this.postHandler = (request, response) -> { - GraphQLInvocationInputFactory invocationInputFactory = configuration.getInvocationInputFactory(); - GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper(); - GraphQLQueryInvoker queryInvoker = configuration.getQueryInvoker(); - - try { - if (APPLICATION_GRAPHQL.equals(request.getContentType())) { - String query = CharStreams.toString(request.getReader()); - query(queryInvoker, graphQLObjectMapper, - invocationInputFactory.create(new GraphQLRequest(query, null, null), request, response), - request, response); - } else if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data") && !request.getParts().isEmpty()) { - final Map> fileItems = request.getParts() - .stream() - .collect(Collectors.groupingBy(Part::getName)); - - for (String key : MULTIPART_KEYS) { - // Check to see if there is a part under the key we seek - if (!fileItems.containsKey(key)) { - continue; - } - - final Optional queryItem = getFileItem(fileItems, key); - if (!queryItem.isPresent()) { - // If there is a part, but we don't see an item, then break and return BAD_REQUEST - break; - } - - InputStream inputStream = asMarkableInputStream(queryItem.get().getInputStream()); - - final Optional>> variablesMap = - getFileItem(fileItems, "map").map(graphQLObjectMapper::deserializeMultipartMap); - - if (isBatchedQuery(inputStream)) { - List graphQLRequests = - graphQLObjectMapper.readBatchedGraphQLRequest(inputStream); - variablesMap.ifPresent(map -> graphQLRequests.forEach(r -> mapMultipartVariables(r, map, fileItems))); - GraphQLBatchedInvocationInput batchedInvocationInput = invocationInputFactory.create(configuration.getContextSetting(), - graphQLRequests, request, response); - queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration); - return; - } else { - GraphQLRequest graphQLRequest; - if ("query".equals(key)) { - graphQLRequest = buildRequestFromQuery(inputStream, graphQLObjectMapper, fileItems); - } else { - graphQLRequest = graphQLObjectMapper.readGraphQLRequest(inputStream); - } - - variablesMap.ifPresent(m -> mapMultipartVariables(graphQLRequest, m, fileItems)); - GraphQLSingleInvocationInput invocationInput = - invocationInputFactory.create(graphQLRequest, request, response); - query(queryInvoker, graphQLObjectMapper, invocationInput, request, response); - return; - } - } - - response.setStatus(STATUS_BAD_REQUEST); - log.info("Bad POST multipart request: no part named " + Arrays.toString(MULTIPART_KEYS)); - } else { - // this is not a multipart request - InputStream inputStream = asMarkableInputStream(request.getInputStream()); - - if (isBatchedQuery(inputStream)) { - List requests = graphQLObjectMapper.readBatchedGraphQLRequest(inputStream); - GraphQLBatchedInvocationInput batchedInvocationInput = - invocationInputFactory.create(configuration.getContextSetting(), requests, request, response); - queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration); - } else { - query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readGraphQLRequest(inputStream), request, response), request, response); - } - } - } catch (Exception e) { - log.info("Bad POST request: parsing failed", e); - response.setStatus(STATUS_BAD_REQUEST); - } - }; - } + } - private InputStream asMarkableInputStream(InputStream inputStream) { - if (!inputStream.markSupported()) { - return new BufferedInputStream(inputStream); - } - return inputStream; - } - - private GraphQLRequest buildRequestFromQuery(InputStream inputStream, - GraphQLObjectMapper graphQLObjectMapper, - Map> fileItems) throws IOException { - GraphQLRequest graphQLRequest; - String query = new String(ByteStreams.toByteArray(inputStream)); - - Map variables = null; - final Optional variablesItem = getFileItem(fileItems, "variables"); - if (variablesItem.isPresent()) { - variables = graphQLObjectMapper.deserializeVariables(new String(ByteStreams.toByteArray(variablesItem.get().getInputStream()))); - } - - String operationName = null; - final Optional operationNameItem = getFileItem(fileItems, "operationName"); - if (operationNameItem.isPresent()) { - operationName = new String(ByteStreams.toByteArray(operationNameItem.get().getInputStream())).trim(); - } - - graphQLRequest = new GraphQLRequest(query, variables, operationName); - return graphQLRequest; - } - - private void mapMultipartVariables(GraphQLRequest request, - Map> variablesMap, - Map> fileItems) { - Map variables = request.getVariables(); - - variablesMap.forEach((partName, objectPaths) -> { - Part part = getFileItem(fileItems, partName) - .orElseThrow(() -> new RuntimeException("unable to find part name " + - partName + - " as referenced in the variables map")); - - objectPaths.forEach(objectPath -> VariableMapper.mapVariable(objectPath, variables, part)); - }); - } - - public void addListener(GraphQLServletListener servletListener) { - if (configuration != null) { - configuration.add(servletListener); + response.setStatus(STATUS_BAD_REQUEST); + log.info("Bad POST multipart request: no part named " + Arrays.toString(MULTIPART_KEYS)); } else { - listeners.add(servletListener); + // this is not a multipart request + InputStream inputStream = asMarkableInputStream(request.getInputStream()); + + if (isBatchedQuery(inputStream)) { + List requests = graphQLObjectMapper.readBatchedGraphQLRequest(inputStream); + GraphQLBatchedInvocationInput batchedInvocationInput = + invocationInputFactory.create(configuration.getContextSetting(), requests, request, response); + queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration); + } else { + query(queryInvoker, graphQLObjectMapper, + invocationInputFactory.create(graphQLObjectMapper.readGraphQLRequest(inputStream), request, response), + request, response); + } } + } catch (Exception e) { + log.info("Bad POST request: parsing failed", e); + response.setStatus(STATUS_BAD_REQUEST); + } + }; + } + + private InputStream asMarkableInputStream(InputStream inputStream) { + if (!inputStream.markSupported()) { + return new BufferedInputStream(inputStream); } - - public void removeListener(GraphQLServletListener servletListener) { - if (configuration != null) { - configuration.remove(servletListener); - } else { - listeners.remove(servletListener); - } + return inputStream; + } + + private GraphQLRequest buildRequestFromQuery(InputStream inputStream, + GraphQLObjectMapper graphQLObjectMapper, + Map> fileItems) throws IOException { + GraphQLRequest graphQLRequest; + String query = new String(ByteStreams.toByteArray(inputStream)); + + Map variables = null; + final Optional variablesItem = getFileItem(fileItems, "variables"); + if (variablesItem.isPresent()) { + variables = graphQLObjectMapper + .deserializeVariables(new String(ByteStreams.toByteArray(variablesItem.get().getInputStream()))); } - @Override - public String[] getQueries() { - return configuration.getInvocationInputFactory().getSchemaProvider().getSchema().getQueryType().getFieldDefinitions().stream().map(GraphQLFieldDefinition::getName).toArray(String[]::new); + String operationName = null; + final Optional operationNameItem = getFileItem(fileItems, "operationName"); + if (operationNameItem.isPresent()) { + operationName = new String(ByteStreams.toByteArray(operationNameItem.get().getInputStream())).trim(); } - @Override - public String[] getMutations() { - return configuration.getInvocationInputFactory().getSchemaProvider().getSchema().getMutationType().getFieldDefinitions().stream().map(GraphQLFieldDefinition::getName).toArray(String[]::new); + graphQLRequest = new GraphQLRequest(query, variables, operationName); + return graphQLRequest; + } + + private void mapMultipartVariables(GraphQLRequest request, + Map> variablesMap, + Map> fileItems) { + Map variables = request.getVariables(); + + variablesMap.forEach((partName, objectPaths) -> { + Part part = getFileItem(fileItems, partName) + .orElseThrow(() -> new RuntimeException("unable to find part name " + + partName + + " as referenced in the variables map")); + + objectPaths.forEach(objectPath -> VariableMapper.mapVariable(objectPath, variables, part)); + }); + } + + public void addListener(GraphQLServletListener servletListener) { + if (configuration != null) { + configuration.add(servletListener); + } else { + listeners.add(servletListener); } + } - @Override - public String executeQuery(String query) { - try { - return configuration.getObjectMapper().serializeResultAsJson(configuration.getQueryInvoker().query(configuration.getInvocationInputFactory().create(new GraphQLRequest(query, new HashMap<>(), null)))); - } catch (Exception e) { - return e.getMessage(); - } + public void removeListener(GraphQLServletListener servletListener) { + if (configuration != null) { + configuration.remove(servletListener); + } else { + listeners.remove(servletListener); } - - private void doRequestAsync(HttpServletRequest request, HttpServletResponse response, HttpRequestHandler handler) { - if (configuration.isAsyncServletModeEnabled()) { - AsyncContext asyncContext = request.startAsync(request, response); - HttpServletRequest asyncRequest = (HttpServletRequest) asyncContext.getRequest(); - HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse(); - configuration.getAsyncExecutor().execute(() -> doRequest(asyncRequest, asyncResponse, handler, asyncContext)); - } else { - doRequest(request, response, handler, null); - } + } + + @Override + public String[] getQueries() { + return configuration.getInvocationInputFactory().getSchemaProvider().getSchema().getQueryType() + .getFieldDefinitions().stream().map(GraphQLFieldDefinition::getName).toArray(String[]::new); + } + + @Override + public String[] getMutations() { + return configuration.getInvocationInputFactory().getSchemaProvider().getSchema().getMutationType() + .getFieldDefinitions().stream().map(GraphQLFieldDefinition::getName).toArray(String[]::new); + } + + @Override + public String executeQuery(String query) { + try { + return configuration.getObjectMapper().serializeResultAsJson(configuration.getQueryInvoker() + .query(configuration.getInvocationInputFactory().create(new GraphQLRequest(query, new HashMap<>(), null)))); + } catch (Exception e) { + return e.getMessage(); } - - private void doRequest(HttpServletRequest request, HttpServletResponse response, HttpRequestHandler handler, AsyncContext asyncContext) { - - List requestCallbacks = runListeners(l -> l.onRequest(request, response)); - - try { - handler.handle(request, response); - runCallbacks(requestCallbacks, c -> c.onSuccess(request, response)); - } catch (Throwable t) { - response.setStatus(500); - log.error("Error executing GraphQL request!", t); - runCallbacks(requestCallbacks, c -> c.onError(request, response, t)); - } finally { - runCallbacks(requestCallbacks, c -> c.onFinally(request, response)); - if (asyncContext != null) { - asyncContext.complete(); - } - } - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) { - init(); - doRequestAsync(req, resp, getHandler); + } + + private void doRequestAsync(HttpServletRequest request, HttpServletResponse response, HttpRequestHandler handler) { + if (configuration.isAsyncServletModeEnabled()) { + AsyncContext asyncContext = request.startAsync(request, response); + HttpServletRequest asyncRequest = (HttpServletRequest) asyncContext.getRequest(); + HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse(); + configuration.getAsyncExecutor().execute(() -> doRequest(asyncRequest, asyncResponse, handler, asyncContext)); + } else { + doRequest(request, response, handler, null); } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) { - init(); - doRequestAsync(req, resp, postHandler); - } - - private Optional getFileItem(Map> fileItems, String name) { - return Optional.ofNullable(fileItems.get(name)).filter(list -> !list.isEmpty()).map(list -> list.get(0)); + } + + private void doRequest(HttpServletRequest request, HttpServletResponse response, HttpRequestHandler handler, + AsyncContext asyncContext) { + + List requestCallbacks = runListeners(l -> l.onRequest(request, response)); + + try { + handler.handle(request, response); + runCallbacks(requestCallbacks, c -> c.onSuccess(request, response)); + } catch (Throwable t) { + response.setStatus(500); + log.error("Error executing GraphQL request!", t); + runCallbacks(requestCallbacks, c -> c.onError(request, response, t)); + } finally { + runCallbacks(requestCallbacks, c -> c.onFinally(request, response)); + if (asyncContext != null) { + asyncContext.complete(); + } } - - private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLSingleInvocationInput invocationInput, - HttpServletRequest req, HttpServletResponse resp) throws IOException { - ExecutionResult result = queryInvoker.query(invocationInput); - - boolean isDeferred = Objects.nonNull(result.getExtensions()) && result.getExtensions().containsKey(GraphQL.DEFERRED_RESULTS); - - if (!(result.getData() instanceof Publisher || isDeferred)) { - resp.setContentType(APPLICATION_JSON_UTF8); - resp.setStatus(STATUS_OK); - graphQLObjectMapper.serializeResultAsJson(resp.getWriter(), result); - } else { - if (req == null) { - throw new IllegalStateException("Http servlet request can not be null"); - } - resp.setContentType(APPLICATION_EVENT_STREAM_UTF8); - resp.setStatus(STATUS_OK); - - boolean isInAsyncThread = req.isAsyncStarted(); - AsyncContext asyncContext = isInAsyncThread ? req.getAsyncContext() : req.startAsync(req, resp); - asyncContext.setTimeout(configuration.getSubscriptionTimeout()); - AtomicReference subscriptionRef = new AtomicReference<>(); - asyncContext.addListener(new SubscriptionAsyncListener(subscriptionRef)); - ExecutionResultSubscriber subscriber = new ExecutionResultSubscriber(subscriptionRef, asyncContext, graphQLObjectMapper); - List> publishers = new ArrayList<>(); - if (result.getData() instanceof Publisher) { - publishers.add(result.getData()); - } else { - publishers.add(new StaticDataPublisher<>(result)); - final Publisher deferredResultsPublisher = (Publisher) result.getExtensions().get(GraphQL.DEFERRED_RESULTS); - publishers.add(deferredResultsPublisher); - } - publishers.forEach(it -> it.subscribe(subscriber)); - - if (isInAsyncThread) { - // We need to delay the completion of async context until after the subscription has terminated, otherwise the AsyncContext is prematurely closed. - try { - subscriber.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + init(); + doRequestAsync(req, resp, getHandler); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + init(); + doRequestAsync(req, resp, postHandler); + } + + private Optional getFileItem(Map> fileItems, String name) { + return Optional.ofNullable(fileItems.get(name)).filter(list -> !list.isEmpty()).map(list -> list.get(0)); + } + + private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, + GraphQLSingleInvocationInput invocationInput, + HttpServletRequest req, HttpServletResponse resp) throws IOException { + ExecutionResult result = queryInvoker.query(invocationInput); + + boolean isDeferred = + Objects.nonNull(result.getExtensions()) && result.getExtensions().containsKey(GraphQL.DEFERRED_RESULTS); + + if (!(result.getData() instanceof Publisher || isDeferred)) { + resp.setContentType(APPLICATION_JSON_UTF8); + resp.setStatus(STATUS_OK); + graphQLObjectMapper.serializeResultAsJson(resp.getWriter(), result); + } else { + if (req == null) { + throw new IllegalStateException("Http servlet request can not be null"); + } + resp.setContentType(APPLICATION_EVENT_STREAM_UTF8); + resp.setStatus(STATUS_OK); + + boolean isInAsyncThread = req.isAsyncStarted(); + AsyncContext asyncContext = isInAsyncThread ? req.getAsyncContext() : req.startAsync(req, resp); + asyncContext.setTimeout(configuration.getSubscriptionTimeout()); + AtomicReference subscriptionRef = new AtomicReference<>(); + asyncContext.addListener(new SubscriptionAsyncListener(subscriptionRef)); + ExecutionResultSubscriber subscriber = new ExecutionResultSubscriber(subscriptionRef, asyncContext, + graphQLObjectMapper); + List> publishers = new ArrayList<>(); + if (result.getData() instanceof Publisher) { + publishers.add(result.getData()); + } else { + publishers.add(new StaticDataPublisher<>(result)); + final Publisher deferredResultsPublisher = (Publisher) result.getExtensions() + .get(GraphQL.DEFERRED_RESULTS); + publishers.add(deferredResultsPublisher); + } + publishers.forEach(it -> it.subscribe(subscriber)); + + if (isInAsyncThread) { + // We need to delay the completion of async context until after the subscription has terminated, otherwise the AsyncContext is prematurely closed. + try { + subscriber.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } + } } - - private void queryBatched(GraphQLQueryInvoker queryInvoker, GraphQLBatchedInvocationInput batchedInvocationInput, HttpServletRequest request, - HttpServletResponse response, GraphQLConfiguration configuration) throws IOException { - BatchInputPreProcessor batchInputPreProcessor = configuration.getBatchInputPreProcessor(); - ContextSetting contextSetting = configuration.getContextSetting(); - BatchInputPreProcessResult batchInputPreProcessResult = batchInputPreProcessor.preProcessBatch(batchedInvocationInput, request, response); - if (batchInputPreProcessResult.isExecutable()) { - List results = queryInvoker.query(batchInputPreProcessResult.getBatchedInvocationInput().getExecutionInputs(), - contextSetting); - response.setContentType(AbstractGraphQLHttpServlet.APPLICATION_JSON_UTF8); - response.setStatus(AbstractGraphQLHttpServlet.STATUS_OK); - Writer writer = response.getWriter(); - Iterator executionInputIterator = results.iterator(); - writer.write("["); - GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper(); - while (executionInputIterator.hasNext()) { - String result = graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next()); - writer.write(result); - if (executionInputIterator.hasNext()) { - writer.write(","); - } - } - writer.write("]"); - } else { - response.sendError(batchInputPreProcessResult.getStatusCode(), batchInputPreProcessResult.getStatusMessage()); + } + + private void queryBatched(GraphQLQueryInvoker queryInvoker, GraphQLBatchedInvocationInput batchedInvocationInput, + HttpServletRequest request, + HttpServletResponse response, GraphQLConfiguration configuration) throws IOException { + BatchInputPreProcessor batchInputPreProcessor = configuration.getBatchInputPreProcessor(); + ContextSetting contextSetting = configuration.getContextSetting(); + BatchInputPreProcessResult batchInputPreProcessResult = batchInputPreProcessor + .preProcessBatch(batchedInvocationInput, request, response); + if (batchInputPreProcessResult.isExecutable()) { + List results = queryInvoker + .query(batchInputPreProcessResult.getBatchedInvocationInput().getExecutionInputs(), + contextSetting); + response.setContentType(AbstractGraphQLHttpServlet.APPLICATION_JSON_UTF8); + response.setStatus(AbstractGraphQLHttpServlet.STATUS_OK); + Writer writer = response.getWriter(); + Iterator executionInputIterator = results.iterator(); + writer.write("["); + GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper(); + while (executionInputIterator.hasNext()) { + String result = graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next()); + writer.write(result); + if (executionInputIterator.hasNext()) { + writer.write(","); } + } + writer.write("]"); + } else { + response.sendError(batchInputPreProcessResult.getStatusCode(), batchInputPreProcessResult.getStatusMessage()); } - - private List runListeners(Function action) { - return configuration.getListeners().stream() - .map(listener -> { - try { - return action.apply(listener); - } catch (Throwable t) { - log.error("Error running listener: {}", listener, t); - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + } + + private List runListeners(Function action) { + return configuration.getListeners().stream() + .map(listener -> { + try { + return action.apply(listener); + } catch (Throwable t) { + log.error("Error running listener: {}", listener, t); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private void runCallbacks(List callbacks, Consumer action) { + callbacks.forEach(callback -> { + try { + action.accept(callback); + } catch (Throwable t) { + log.error("Error running callback: {}", callback, t); + } + }); + } + + private boolean isBatchedQuery(InputStream inputStream) throws IOException { + if (inputStream == null) { + return false; } - private void runCallbacks(List callbacks, Consumer action) { - callbacks.forEach(callback -> { - try { - action.accept(callback); - } catch (Throwable t) { - log.error("Error running callback: {}", callback, t); - } - }); - } - - private boolean isBatchedQuery(InputStream inputStream) throws IOException { - if (inputStream == null) { - return false; - } - - final int BUFFER_SIZE = 128; - ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] buffer = new byte[BUFFER_SIZE]; - int length; - - inputStream.mark(BUFFER_SIZE); - while ((length = inputStream.read(buffer)) != -1) { - result.write(buffer, 0, length); - String chunk = result.toString(); - Boolean isArrayStart = isArrayStart(chunk); - if (isArrayStart != null) { - inputStream.reset(); - return isArrayStart; - } - } + final int BUFFER_SIZE = 128; + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[BUFFER_SIZE]; + int length; + inputStream.mark(BUFFER_SIZE); + while ((length = inputStream.read(buffer)) != -1) { + result.write(buffer, 0, length); + if (isArrayStart(result.toString())) { inputStream.reset(); - return false; - } - - private boolean isBatchedQuery(String query) { - if (query == null) { - return false; - } - - Boolean isArrayStart = isArrayStart(query); - return isArrayStart != null && isArrayStart; - } - - // return true if the first non whitespace character is the beginning of an array - private Boolean isArrayStart(String s) { - for (int i = 0; i < s.length(); i++) { - char ch = s.charAt(i); - if (!Character.isWhitespace(ch)) { - return ch == '['; - } - } - - return null; - } - - protected interface HttpRequestHandler extends BiConsumer { - @Override - default void accept(HttpServletRequest request, HttpServletResponse response) { - try { - handle(request, response); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - void handle(HttpServletRequest request, HttpServletResponse response) throws Exception; - } - - private static class SubscriptionAsyncListener implements AsyncListener { - private final AtomicReference subscriptionRef; - - public SubscriptionAsyncListener(AtomicReference subscriptionRef) { - this.subscriptionRef = subscriptionRef; - } - - @Override - public void onComplete(AsyncEvent event) { - subscriptionRef.get().cancel(); - } - - @Override - public void onTimeout(AsyncEvent event) { - subscriptionRef.get().cancel(); - } - - @Override - public void onError(AsyncEvent event) { - subscriptionRef.get().cancel(); - } - - @Override - public void onStartAsync(AsyncEvent event) { - } + return true; + } } - private static class ExecutionResultSubscriber implements Subscriber { + inputStream.reset(); + return false; + } - private final AtomicReference subscriptionRef; - private final AsyncContext asyncContext; - private final GraphQLObjectMapper graphQLObjectMapper; - private final CountDownLatch completedLatch = new CountDownLatch(1); + private boolean isBatchedQuery(String query) { + return isArrayStart(query); + } - public ExecutionResultSubscriber(AtomicReference subscriptionRef, AsyncContext asyncContext, GraphQLObjectMapper graphQLObjectMapper) { - this.subscriptionRef = subscriptionRef; - this.asyncContext = asyncContext; - this.graphQLObjectMapper = graphQLObjectMapper; - } - - @Override - public void onSubscribe(Subscription subscription) { - subscriptionRef.set(subscription); - subscriptionRef.get().request(1); - } - - @Override - public void onNext(ExecutionResult executionResult) { - try { - Writer writer = asyncContext.getResponse().getWriter(); - writer.write("data: "); - graphQLObjectMapper.serializeResultAsJson(writer, executionResult); - writer.write("\n\n"); - writer.flush(); - subscriptionRef.get().request(1); - } catch (IOException ignored) { - } - } - - @Override - public void onError(Throwable t) { - asyncContext.complete(); - completedLatch.countDown(); - } - - @Override - public void onComplete() { - asyncContext.complete(); - completedLatch.countDown(); - } - - public void await() throws InterruptedException { - completedLatch.await(); - } - } - - private static class StaticDataPublisher extends SingleSubscriberPublisher implements Publisher { - StaticDataPublisher(T data) { - super(); - super.offer(data); - super.noMoreData(); - } - } + private boolean isArrayStart(String s) { + return s != null && s.trim().startsWith("["); + } } diff --git a/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java b/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java new file mode 100644 index 00000000..dbc2b4ac --- /dev/null +++ b/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java @@ -0,0 +1,40 @@ +package graphql.servlet; + +import static graphql.servlet.HttpRequestHandler.APPLICATION_JSON_UTF8; +import static graphql.servlet.HttpRequestHandler.STATUS_OK; + +import graphql.ExecutionResult; +import graphql.servlet.core.GraphQLObjectMapper; +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class BatchedQueryResponseWriter implements QueryResponseWriter { + + private final List results; + private final GraphQLObjectMapper graphQLObjectMapper; + + @Override + public void write(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType(APPLICATION_JSON_UTF8); + response.setStatus(STATUS_OK); + + Writer writer = response.getWriter(); + Iterator executionInputIterator = results.iterator(); + writer.write("["); + while (executionInputIterator.hasNext()) { + String result = graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next()); + writer.write(result); + if (executionInputIterator.hasNext()) { + writer.write(","); + } + } + writer.write("]"); + } + +} diff --git a/src/main/java/graphql/servlet/DecoratedExecutionResult.java b/src/main/java/graphql/servlet/DecoratedExecutionResult.java new file mode 100644 index 00000000..0b500760 --- /dev/null +++ b/src/main/java/graphql/servlet/DecoratedExecutionResult.java @@ -0,0 +1,53 @@ +package graphql.servlet; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.GraphQLError; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.reactivestreams.Publisher; + +@RequiredArgsConstructor +class DecoratedExecutionResult implements ExecutionResult { + + private final ExecutionResult result; + + static DecoratedExecutionResult decorate(ExecutionResult result) { + return new DecoratedExecutionResult(result); + } + + boolean isAsynchronous() { + return result.getData() instanceof Publisher || isDeferred(); + } + + boolean isDeferred() { + return result.getExtensions() != null && result.getExtensions().containsKey(GraphQL.DEFERRED_RESULTS); + } + + @Override + public List getErrors() { + return result.getErrors(); + } + + @Override + public T getData() { + return result.getData(); + } + + @Override + public boolean isDataPresent() { + return result.isDataPresent(); + } + + @Override + public Map getExtensions() { + return result.getExtensions(); + } + + @Override + public Map toSpecification() { + return result.toSpecification(); + } + +} diff --git a/src/main/java/graphql/servlet/ErrorQueryResponseWriter.java b/src/main/java/graphql/servlet/ErrorQueryResponseWriter.java new file mode 100644 index 00000000..9cd91a5a --- /dev/null +++ b/src/main/java/graphql/servlet/ErrorQueryResponseWriter.java @@ -0,0 +1,19 @@ +package graphql.servlet; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class ErrorQueryResponseWriter implements QueryResponseWriter { + + private final int statusCode; + private final String message; + + @Override + public void write(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.sendError(statusCode, message); + } + +} diff --git a/src/main/java/graphql/servlet/ExecutionResultSubscriber.java b/src/main/java/graphql/servlet/ExecutionResultSubscriber.java new file mode 100644 index 00000000..ff271f63 --- /dev/null +++ b/src/main/java/graphql/servlet/ExecutionResultSubscriber.java @@ -0,0 +1,62 @@ +package graphql.servlet; + +import graphql.ExecutionResult; +import graphql.servlet.core.GraphQLObjectMapper; +import java.io.IOException; +import java.io.Writer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.AsyncContext; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +class ExecutionResultSubscriber implements Subscriber { + + private final AtomicReference subscriptionRef; + private final AsyncContext asyncContext; + private final GraphQLObjectMapper graphQLObjectMapper; + private final CountDownLatch completedLatch = new CountDownLatch(1); + + public ExecutionResultSubscriber(AtomicReference subscriptionRef, AsyncContext asyncContext, + GraphQLObjectMapper graphQLObjectMapper) { + this.subscriptionRef = subscriptionRef; + this.asyncContext = asyncContext; + this.graphQLObjectMapper = graphQLObjectMapper; + } + + @Override + public void onSubscribe(Subscription subscription) { + subscriptionRef.set(subscription); + subscriptionRef.get().request(1); + } + + @Override + public void onNext(ExecutionResult executionResult) { + try { + Writer writer = asyncContext.getResponse().getWriter(); + writer.write("data: "); + graphQLObjectMapper.serializeResultAsJson(writer, executionResult); + writer.write("\n\n"); + writer.flush(); + subscriptionRef.get().request(1); + } catch (IOException ignored) { + } + } + + @Override + public void onError(Throwable t) { + asyncContext.complete(); + completedLatch.countDown(); + } + + @Override + public void onComplete() { + asyncContext.complete(); + completedLatch.countDown(); + } + + public void await() throws InterruptedException { + completedLatch.await(); + } + +} diff --git a/src/main/java/graphql/servlet/GraphQLBatchedQueryResult.java b/src/main/java/graphql/servlet/GraphQLBatchedQueryResult.java new file mode 100644 index 00000000..2cc21252 --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLBatchedQueryResult.java @@ -0,0 +1,24 @@ +package graphql.servlet; + +import graphql.ExecutionResult; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class GraphQLBatchedQueryResult implements GraphQLQueryResult { + + @Getter + private final List results; + + @Override + public boolean isBatched() { + return true; + } + + @Override + public boolean isAsynchronous() { + return false; + } + +} diff --git a/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java b/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java new file mode 100644 index 00000000..80f3a15f --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java @@ -0,0 +1,29 @@ +package graphql.servlet; + +import graphql.ExecutionResult; +import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +class GraphQLErrorQueryResult implements GraphQLQueryResult { + + private final int statusCode; + private final String message; + + @Override + public boolean isBatched() { + return true; + } + + @Override + public boolean isAsynchronous() { + return false; + } + + @Override + public boolean isError() { + return true; + } +} diff --git a/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java new file mode 100644 index 00000000..35451618 --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java @@ -0,0 +1,63 @@ +package graphql.servlet; + +import graphql.servlet.context.ContextSetting; +import graphql.servlet.core.GraphQLObjectMapper; +import graphql.servlet.core.internal.GraphQLRequest; +import graphql.servlet.input.GraphQLInvocationInput; +import graphql.servlet.input.GraphQLInvocationInputFactory; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class GraphQLInvocationInputParser { + + private final GraphQLInvocationInputFactory invocationInputFactory; + private final GraphQLObjectMapper graphQLObjectMapper; + private final ContextSetting contextSetting; + + GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (isIntrospectionQuery(request)) { + GraphQLRequest graphqlRequest = GraphQLRequest.createIntrospectionRequest(); + return invocationInputFactory.create(graphqlRequest, request, response); + } + + String query = request.getParameter("query"); + if (!isBatchedQuery(query)) { + Map variables = getVariables(request); + String operationName = request.getParameter("operationName"); + GraphQLRequest graphqlRequest = new GraphQLRequest(query, variables, operationName); + return invocationInputFactory.create(graphqlRequest, request, response); + } + + List requests = graphQLObjectMapper.readBatchedGraphQLRequest(query); + return invocationInputFactory.createReadOnly(contextSetting, requests, request, response); + } + + private boolean isIntrospectionQuery(HttpServletRequest request) { + String path = Optional.ofNullable(request.getPathInfo()).orElseGet(request::getServletPath).toLowerCase(); + return path.contentEquals("/schema.json"); + } + + private Map getVariables(HttpServletRequest request) { + return Optional.ofNullable(request.getParameter("variables")) + .map(graphQLObjectMapper::deserializeVariables) + .map(HashMap::new) + .orElseGet(HashMap::new); + } + + private boolean isBatchedQuery(String query) { + return isArrayStart(query); + } + + private boolean isArrayStart(String s) { + return s != null && s.trim().startsWith("["); + } + +} diff --git a/src/main/java/graphql/servlet/GraphQLQueryProcessor.java b/src/main/java/graphql/servlet/GraphQLQueryProcessor.java new file mode 100644 index 00000000..9ac9b839 --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLQueryProcessor.java @@ -0,0 +1,9 @@ +package graphql.servlet; + +class GraphQLQueryProcessor { + + GraphQLQueryResult execute() { + return null; + } + +} diff --git a/src/main/java/graphql/servlet/GraphQLQueryResult.java b/src/main/java/graphql/servlet/GraphQLQueryResult.java new file mode 100644 index 00000000..535eda56 --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLQueryResult.java @@ -0,0 +1,31 @@ +package graphql.servlet; + +import static java.util.Collections.emptyList; + +import graphql.ExecutionResult; +import java.util.List; + +public interface GraphQLQueryResult { + + static GraphQLSingleQueryResult create(ExecutionResult result) { + return new GraphQLSingleQueryResult(new DecoratedExecutionResult(result)); + } + + static GraphQLBatchedQueryResult create(List results) { + return new GraphQLBatchedQueryResult(results); + } + + boolean isBatched(); + + boolean isAsynchronous(); + + default DecoratedExecutionResult getResult() { + return null; + } + + default List getResults() { + return emptyList(); + } + + default boolean isError() { return false; } +} diff --git a/src/main/java/graphql/servlet/GraphQLSingleQueryResult.java b/src/main/java/graphql/servlet/GraphQLSingleQueryResult.java new file mode 100644 index 00000000..3070974d --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLSingleQueryResult.java @@ -0,0 +1,22 @@ +package graphql.servlet; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class GraphQLSingleQueryResult implements GraphQLQueryResult { + + @Getter + private final DecoratedExecutionResult result; + + @Override + public boolean isBatched() { + return false; + } + + @Override + public boolean isAsynchronous() { + return result.isAsynchronous(); + } + +} diff --git a/src/main/java/graphql/servlet/HttpGetRequestHandler.java b/src/main/java/graphql/servlet/HttpGetRequestHandler.java new file mode 100644 index 00000000..a3bc1421 --- /dev/null +++ b/src/main/java/graphql/servlet/HttpGetRequestHandler.java @@ -0,0 +1,69 @@ +package graphql.servlet; + +import static graphql.servlet.QueryResponseWriter.createWriter; + +import graphql.servlet.config.GraphQLConfiguration; +import graphql.servlet.core.GraphQLQueryInvoker; +import graphql.servlet.input.BatchInputPreProcessResult; +import graphql.servlet.input.BatchInputPreProcessor; +import graphql.servlet.input.GraphQLBatchedInvocationInput; +import graphql.servlet.input.GraphQLInvocationInput; +import graphql.servlet.input.GraphQLSingleInvocationInput; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class HttpGetRequestHandler implements HttpRequestHandler { + + private final GraphQLConfiguration configuration; + private final GraphQLInvocationInputParser invocationInputParser; + private final GraphQLQueryInvoker queryInvoker; + + HttpGetRequestHandler(GraphQLConfiguration configuration) { + this.configuration = configuration; + invocationInputParser = new GraphQLInvocationInputParser( + configuration.getInvocationInputFactory(), + configuration.getObjectMapper(), + configuration.getContextSetting() + ); + queryInvoker = configuration.getQueryInvoker(); + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) { + try { + GraphQLInvocationInput invocationInput = invocationInputParser.getGraphQLInvocationInput(request, response); + + GraphQLQueryResult queryResult = invoke(invocationInput, request, response); + + QueryResponseWriter queryResponseWriter = createWriter(queryResult, configuration.getObjectMapper(), + configuration.getSubscriptionTimeout()); + queryResponseWriter.write(request, response); + } catch (Throwable t) { + response.setStatus(STATUS_BAD_REQUEST); + log.info("Bad GET request: path was not \"/schema.json\" or no query variable named \"query\" given"); + } + } + + private GraphQLQueryResult invoke(GraphQLInvocationInput invocationInput, HttpServletRequest request, + HttpServletResponse response) { + if (invocationInput instanceof GraphQLSingleInvocationInput) { + return queryInvoker.query(invocationInput); + } + return invokeBatched((GraphQLBatchedInvocationInput) invocationInput, request, response); + } + + private GraphQLQueryResult invokeBatched(GraphQLBatchedInvocationInput batchedInvocationInput, + HttpServletRequest request, + HttpServletResponse response) { + BatchInputPreProcessor preprocessor = configuration.getBatchInputPreProcessor(); + BatchInputPreProcessResult result = preprocessor.preProcessBatch(batchedInvocationInput, request, response); + if (result.isExecutable()) { + return queryInvoker.query(result.getBatchedInvocationInput()); + } + + return new GraphQLErrorQueryResult(result.getStatusCode(), result.getStatusMessage()); + } + +} diff --git a/src/main/java/graphql/servlet/HttpRequestHandler.java b/src/main/java/graphql/servlet/HttpRequestHandler.java new file mode 100644 index 00000000..9ed38213 --- /dev/null +++ b/src/main/java/graphql/servlet/HttpRequestHandler.java @@ -0,0 +1,26 @@ +package graphql.servlet; + +import java.util.function.BiConsumer; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +interface HttpRequestHandler extends BiConsumer { + + String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8"; + String APPLICATION_EVENT_STREAM_UTF8 = "text/event-stream;charset=UTF-8"; + String APPLICATION_GRAPHQL = "application/graphql"; + int STATUS_OK = 200; + int STATUS_BAD_REQUEST = 400; + + @Override + default void accept(HttpServletRequest request, HttpServletResponse response) { + try { + handle(request, response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + void handle(HttpServletRequest request, HttpServletResponse response) throws Exception; + +} diff --git a/src/main/java/graphql/servlet/QueryResponseWriter.java b/src/main/java/graphql/servlet/QueryResponseWriter.java new file mode 100644 index 00000000..01dd96eb --- /dev/null +++ b/src/main/java/graphql/servlet/QueryResponseWriter.java @@ -0,0 +1,31 @@ +package graphql.servlet; + +import graphql.servlet.core.GraphQLObjectMapper; +import java.io.IOException; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +interface QueryResponseWriter { + + static QueryResponseWriter createWriter( + GraphQLQueryResult result, + GraphQLObjectMapper graphQLObjectMapper, + long subscriptionTimeout + ) { + Objects.requireNonNull(result, "GraphQL query result cannot be null"); + + if (result.isBatched()) { + return new BatchedQueryResponseWriter(result.getResults(), graphQLObjectMapper); + } else if (result.isAsynchronous()) { + return new SingleAsynchronousQueryResponseWriter(result.getResult(), graphQLObjectMapper, subscriptionTimeout); + } else if (result.isError()) { + GraphQLErrorQueryResult errorResult = (GraphQLErrorQueryResult) result; + return new ErrorQueryResponseWriter(errorResult.getStatusCode(), errorResult.getMessage()); + } + return new SingleQueryResponseWriter(result.getResult(), graphQLObjectMapper); + } + + void write(HttpServletRequest request, HttpServletResponse response) throws IOException; + +} diff --git a/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java b/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java new file mode 100644 index 00000000..bdc6cb1e --- /dev/null +++ b/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java @@ -0,0 +1,65 @@ +package graphql.servlet; + +import static graphql.servlet.HttpRequestHandler.APPLICATION_EVENT_STREAM_UTF8; +import static graphql.servlet.HttpRequestHandler.APPLICATION_JSON_UTF8; +import static graphql.servlet.HttpRequestHandler.STATUS_OK; + +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.servlet.core.GraphQLObjectMapper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; + +@RequiredArgsConstructor +class SingleAsynchronousQueryResponseWriter implements QueryResponseWriter { + + @Getter + private final DecoratedExecutionResult result; + private final GraphQLObjectMapper graphQLObjectMapper; + private final long subscriptionTimeout; + + @Override + public void write(HttpServletRequest request, HttpServletResponse response) throws IOException { + Objects.requireNonNull(request, "Http servlet request cannot be null"); + response.setContentType(APPLICATION_EVENT_STREAM_UTF8); + response.setStatus(STATUS_OK); + + boolean isInAsyncThread = request.isAsyncStarted(); + AsyncContext asyncContext = isInAsyncThread ? request.getAsyncContext() : request.startAsync(request, response); + asyncContext.setTimeout(subscriptionTimeout); + AtomicReference subscriptionRef = new AtomicReference<>(); + asyncContext.addListener(new SubscriptionAsyncListener(subscriptionRef)); + ExecutionResultSubscriber subscriber = new ExecutionResultSubscriber(subscriptionRef, asyncContext, + graphQLObjectMapper); + List> publishers = new ArrayList<>(); + if (result.getData() instanceof Publisher) { + publishers.add(result.getData()); + } else { + publishers.add(new StaticDataPublisher<>(result)); + final Publisher deferredResultsPublisher = (Publisher) result.getExtensions() + .get(GraphQL.DEFERRED_RESULTS); + publishers.add(deferredResultsPublisher); + } + publishers.forEach(it -> it.subscribe(subscriber)); + + if (isInAsyncThread) { + // We need to delay the completion of async context until after the subscription has terminated, otherwise the AsyncContext is prematurely closed. + try { + subscriber.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + +} diff --git a/src/main/java/graphql/servlet/SingleQueryResponseWriter.java b/src/main/java/graphql/servlet/SingleQueryResponseWriter.java new file mode 100644 index 00000000..e0b7d029 --- /dev/null +++ b/src/main/java/graphql/servlet/SingleQueryResponseWriter.java @@ -0,0 +1,25 @@ +package graphql.servlet; + +import static graphql.servlet.HttpRequestHandler.APPLICATION_JSON_UTF8; +import static graphql.servlet.HttpRequestHandler.STATUS_OK; + +import graphql.servlet.core.GraphQLObjectMapper; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class SingleQueryResponseWriter implements QueryResponseWriter { + + private final DecoratedExecutionResult result; + private final GraphQLObjectMapper graphQLObjectMapper; + + @Override + public void write(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType(APPLICATION_JSON_UTF8); + response.setStatus(STATUS_OK); + graphQLObjectMapper.serializeResultAsJson(response.getWriter(), result); + } + +} diff --git a/src/main/java/graphql/servlet/StaticDataPublisher.java b/src/main/java/graphql/servlet/StaticDataPublisher.java new file mode 100644 index 00000000..bccfa7ba --- /dev/null +++ b/src/main/java/graphql/servlet/StaticDataPublisher.java @@ -0,0 +1,14 @@ +package graphql.servlet; + +import graphql.execution.reactive.SingleSubscriberPublisher; +import org.reactivestreams.Publisher; + +class StaticDataPublisher extends SingleSubscriberPublisher implements Publisher { + + StaticDataPublisher(T data) { + super(); + offer(data); + noMoreData(); + } + +} diff --git a/src/main/java/graphql/servlet/SubscriptionAsyncListener.java b/src/main/java/graphql/servlet/SubscriptionAsyncListener.java new file mode 100644 index 00000000..7be88fad --- /dev/null +++ b/src/main/java/graphql/servlet/SubscriptionAsyncListener.java @@ -0,0 +1,33 @@ +package graphql.servlet; + +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import lombok.RequiredArgsConstructor; +import org.reactivestreams.Subscription; + +@RequiredArgsConstructor +class SubscriptionAsyncListener implements AsyncListener { + + private final AtomicReference subscriptionRef; + + @Override + public void onComplete(AsyncEvent event) { + subscriptionRef.get().cancel(); + } + + @Override + public void onTimeout(AsyncEvent event) { + subscriptionRef.get().cancel(); + } + + @Override + public void onError(AsyncEvent event) { + subscriptionRef.get().cancel(); + } + + @Override + public void onStartAsync(AsyncEvent event) { + } + +} diff --git a/src/main/java/graphql/servlet/context/ContextSetting.java b/src/main/java/graphql/servlet/context/ContextSetting.java index b8648cfc..6f859969 100644 --- a/src/main/java/graphql/servlet/context/ContextSetting.java +++ b/src/main/java/graphql/servlet/context/ContextSetting.java @@ -50,11 +50,11 @@ public GraphQLBatchedInvocationInput getBatch(List requests, Gra case PER_QUERY_WITH_INSTRUMENTATION: //Intentional fallthrough case PER_QUERY_WITHOUT_INSTRUMENTATION: - return new PerQueryBatchedInvocationInput(requests, schema, contextSupplier, root); + return new PerQueryBatchedInvocationInput(requests, schema, contextSupplier, root, this); case PER_REQUEST_WITHOUT_INSTRUMENTATION: //Intentional fallthrough case PER_REQUEST_WITH_INSTRUMENTATION: - return new PerRequestBatchedInvocationInput(requests, schema, contextSupplier, root); + return new PerRequestBatchedInvocationInput(requests, schema, contextSupplier, root, this); default: throw new RuntimeException("Unconfigured context setting type"); } diff --git a/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java b/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java index 1ca26dcb..c40a8372 100644 --- a/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java +++ b/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java @@ -13,6 +13,7 @@ import graphql.servlet.core.internal.GraphQLRequest; import graphql.servlet.core.internal.VariablesDeserializer; +import java.util.stream.Stream; import javax.servlet.http.Part; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/graphql/servlet/core/GraphQLQueryInvoker.java b/src/main/java/graphql/servlet/core/GraphQLQueryInvoker.java index b30dc2e7..83d7b20f 100644 --- a/src/main/java/graphql/servlet/core/GraphQLQueryInvoker.java +++ b/src/main/java/graphql/servlet/core/GraphQLQueryInvoker.java @@ -11,163 +11,183 @@ import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; import graphql.execution.preparsed.PreparsedDocumentProvider; import graphql.schema.GraphQLSchema; +import graphql.servlet.GraphQLQueryResult; import graphql.servlet.config.DefaultExecutionStrategyProvider; import graphql.servlet.config.ExecutionStrategyProvider; import graphql.servlet.context.ContextSetting; +import graphql.servlet.input.GraphQLBatchedInvocationInput; +import graphql.servlet.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLSingleInvocationInput; - -import javax.security.auth.Subject; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; +import javax.security.auth.Subject; /** * @author Andrew Potter */ public class GraphQLQueryInvoker { - private final Supplier getExecutionStrategyProvider; - private final Supplier getInstrumentation; - private final Supplier getPreparsedDocumentProvider; - private final Supplier optionsSupplier; - - protected GraphQLQueryInvoker(Supplier getExecutionStrategyProvider, Supplier getInstrumentation, - Supplier getPreparsedDocumentProvider, - Supplier optionsSupplier) { - this.getExecutionStrategyProvider = getExecutionStrategyProvider; - this.getInstrumentation = getInstrumentation; - this.getPreparsedDocumentProvider = getPreparsedDocumentProvider; - this.optionsSupplier = optionsSupplier; + private final Supplier getExecutionStrategyProvider; + private final Supplier getInstrumentation; + private final Supplier getPreparsedDocumentProvider; + private final Supplier optionsSupplier; + + protected GraphQLQueryInvoker(Supplier getExecutionStrategyProvider, + Supplier getInstrumentation, + Supplier getPreparsedDocumentProvider, + Supplier optionsSupplier) { + this.getExecutionStrategyProvider = getExecutionStrategyProvider; + this.getInstrumentation = getInstrumentation; + this.getPreparsedDocumentProvider = getPreparsedDocumentProvider; + this.optionsSupplier = optionsSupplier; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) { + if (invocationInput instanceof GraphQLSingleInvocationInput) { + return GraphQLQueryResult.create(query((GraphQLSingleInvocationInput) invocationInput)); } - - public ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) { - return queryAsync(singleInvocationInput, getInstrumentation).join(); + GraphQLBatchedInvocationInput batchedInvocationInput = (GraphQLBatchedInvocationInput) invocationInput; + return GraphQLQueryResult.create(query(batchedInvocationInput.getExecutionInputs(), batchedInvocationInput.getContextSetting())); + } + + public ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) { + return queryAsync(singleInvocationInput, getInstrumentation).join(); + } + + private CompletableFuture queryAsync(GraphQLSingleInvocationInput singleInvocationInput, + Supplier configuredInstrumentation) { + return query(singleInvocationInput, configuredInstrumentation, singleInvocationInput.getExecutionInput()); + } + + public List query(List batchedInvocationInput, + ContextSetting contextSetting) { + List executionIds = batchedInvocationInput.stream() + .map(GraphQLSingleInvocationInput::getExecutionInput) + .collect(Collectors.toList()); + Supplier configuredInstrumentation = contextSetting + .configureInstrumentationForContext(getInstrumentation, executionIds, + optionsSupplier.get()); + return batchedInvocationInput.stream() + .map(input -> this.queryAsync(input, configuredInstrumentation)) + //We want eager eval + .collect(Collectors.toList()) + .stream() + .map(CompletableFuture::join) + .collect(Collectors.toList()); + } + + private GraphQL newGraphQL(GraphQLSchema schema, Supplier configuredInstrumentation) { + ExecutionStrategyProvider executionStrategyProvider = getExecutionStrategyProvider.get(); + GraphQL.Builder builder = GraphQL.newGraphQL(schema) + .queryExecutionStrategy(executionStrategyProvider.getQueryExecutionStrategy()) + .mutationExecutionStrategy(executionStrategyProvider.getMutationExecutionStrategy()) + .subscriptionExecutionStrategy(executionStrategyProvider.getSubscriptionExecutionStrategy()) + .preparsedDocumentProvider(getPreparsedDocumentProvider.get()); + Instrumentation instrumentation = configuredInstrumentation.get(); + builder.instrumentation(instrumentation); + if (containsDispatchInstrumentation(instrumentation)) { + builder.doNotAddDefaultInstrumentations(); } + return builder.build(); + } - private CompletableFuture queryAsync(GraphQLSingleInvocationInput singleInvocationInput, Supplier configuredInstrumentation) { - return query(singleInvocationInput, configuredInstrumentation, singleInvocationInput.getExecutionInput()); + private boolean containsDispatchInstrumentation(Instrumentation instrumentation) { + if (instrumentation instanceof ChainedInstrumentation) { + return ((ChainedInstrumentation) instrumentation).getInstrumentations().stream() + .anyMatch(this::containsDispatchInstrumentation); } - - public List query(List batchedInvocationInput, ContextSetting contextSetting) { - List executionIds = batchedInvocationInput.stream() - .map(GraphQLSingleInvocationInput::getExecutionInput) - .collect(Collectors.toList()); - Supplier configuredInstrumentation = contextSetting.configureInstrumentationForContext(getInstrumentation, executionIds, - optionsSupplier.get()); - return batchedInvocationInput.stream() - .map(input -> this.queryAsync(input, configuredInstrumentation)) - //We want eager eval - .collect(Collectors.toList()) - .stream() - .map(CompletableFuture::join) - .collect(Collectors.toList()); + return instrumentation instanceof DataLoaderDispatcherInstrumentation; + } + + private CompletableFuture query(GraphQLSingleInvocationInput invocationInput, + Supplier configuredInstrumentation, ExecutionInput executionInput) { + if (Subject.getSubject(AccessController.getContext()) == null && invocationInput.getSubject().isPresent()) { + return Subject + .doAs(invocationInput.getSubject().get(), (PrivilegedAction>) () -> { + try { + return query(invocationInput.getSchema(), executionInput, configuredInstrumentation); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } - private GraphQL newGraphQL(GraphQLSchema schema, Supplier configuredInstrumentation) { - ExecutionStrategyProvider executionStrategyProvider = getExecutionStrategyProvider.get(); - GraphQL.Builder builder = GraphQL.newGraphQL(schema) - .queryExecutionStrategy(executionStrategyProvider.getQueryExecutionStrategy()) - .mutationExecutionStrategy(executionStrategyProvider.getMutationExecutionStrategy()) - .subscriptionExecutionStrategy(executionStrategyProvider.getSubscriptionExecutionStrategy()) - .preparsedDocumentProvider(getPreparsedDocumentProvider.get()); - Instrumentation instrumentation = configuredInstrumentation.get(); - builder.instrumentation(instrumentation); - if (containsDispatchInstrumentation(instrumentation)) { - builder.doNotAddDefaultInstrumentations(); - } - return builder.build(); - } + return query(invocationInput.getSchema(), executionInput, configuredInstrumentation); + } - private boolean containsDispatchInstrumentation(Instrumentation instrumentation) { - if (instrumentation instanceof ChainedInstrumentation) { - return ((ChainedInstrumentation)instrumentation).getInstrumentations().stream().anyMatch(this::containsDispatchInstrumentation); - } - return instrumentation instanceof DataLoaderDispatcherInstrumentation; - } + private CompletableFuture query(GraphQLSchema schema, ExecutionInput executionInput, + Supplier configuredInstrumentation) { + return newGraphQL(schema, configuredInstrumentation).executeAsync(executionInput); + } + + public static class Builder { + + private Supplier getExecutionStrategyProvider = DefaultExecutionStrategyProvider::new; + private Supplier getInstrumentation = () -> SimpleInstrumentation.INSTANCE; + private Supplier getPreparsedDocumentProvider = () -> NoOpPreparsedDocumentProvider.INSTANCE; + private Supplier dataLoaderDispatcherInstrumentationOptionsSupplier = DataLoaderDispatcherInstrumentationOptions::newOptions; - private CompletableFuture query(GraphQLSingleInvocationInput invocationInput, Supplier configuredInstrumentation, ExecutionInput executionInput) { - if (Subject.getSubject(AccessController.getContext()) == null && invocationInput.getSubject().isPresent()) { - return Subject.doAs(invocationInput.getSubject().get(), (PrivilegedAction>) () -> { - try { - return query(invocationInput.getSchema(), executionInput, configuredInstrumentation); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - return query(invocationInput.getSchema(), executionInput, configuredInstrumentation); + + public Builder withExecutionStrategyProvider(ExecutionStrategyProvider provider) { + return withExecutionStrategyProvider(() -> provider); } - private CompletableFuture query(GraphQLSchema schema, ExecutionInput executionInput, Supplier configuredInstrumentation) { - return newGraphQL(schema, configuredInstrumentation).executeAsync(executionInput); + public Builder withExecutionStrategyProvider(Supplier supplier) { + this.getExecutionStrategyProvider = supplier; + return this; } - public static Builder newBuilder() { - return new Builder(); + public Builder withInstrumentation(Instrumentation instrumentation) { + return withInstrumentation(() -> instrumentation); } - public static class Builder { - private Supplier getExecutionStrategyProvider = DefaultExecutionStrategyProvider::new; - private Supplier getInstrumentation = () -> SimpleInstrumentation.INSTANCE; - private Supplier getPreparsedDocumentProvider = () -> NoOpPreparsedDocumentProvider.INSTANCE; - private Supplier dataLoaderDispatcherInstrumentationOptionsSupplier = DataLoaderDispatcherInstrumentationOptions::newOptions; + public Builder withInstrumentation(Supplier supplier) { + this.getInstrumentation = supplier; + return this; + } + public Builder with(List instrumentations) { + if (instrumentations.isEmpty()) { + return this; + } + if (instrumentations.size() == 1) { + withInstrumentation(instrumentations.get(0)); + } else { + withInstrumentation(new ChainedInstrumentation(instrumentations)); + } + return this; + } - public Builder withExecutionStrategyProvider(ExecutionStrategyProvider provider) { - return withExecutionStrategyProvider(() -> provider); - } + public Builder withPreparsedDocumentProvider(PreparsedDocumentProvider provider) { + return withPreparsedDocumentProvider(() -> provider); + } - public Builder withExecutionStrategyProvider(Supplier supplier) { - this.getExecutionStrategyProvider = supplier; - return this; - } + public Builder withPreparsedDocumentProvider(Supplier supplier) { + this.getPreparsedDocumentProvider = supplier; + return this; + } - public Builder withInstrumentation(Instrumentation instrumentation) { - return withInstrumentation(() -> instrumentation); - } + public Builder withDataLoaderDispatcherInstrumentationOptions(DataLoaderDispatcherInstrumentationOptions options) { + return withDataLoaderDispatcherInstrumentationOptions(() -> options); + } - public Builder withInstrumentation(Supplier supplier) { - this.getInstrumentation = supplier; - return this; - } + public Builder withDataLoaderDispatcherInstrumentationOptions( + Supplier supplier) { + this.dataLoaderDispatcherInstrumentationOptionsSupplier = supplier; + return this; + } - public Builder with(List instrumentations) { - if (instrumentations.isEmpty()) { - return this; - } - if (instrumentations.size() == 1) { - withInstrumentation(instrumentations.get(0)); - } else { - withInstrumentation(new ChainedInstrumentation(instrumentations)); - } - return this; - } - - public Builder withPreparsedDocumentProvider(PreparsedDocumentProvider provider) { - return withPreparsedDocumentProvider(() -> provider); - } - - public Builder withPreparsedDocumentProvider(Supplier supplier) { - this.getPreparsedDocumentProvider = supplier; - return this; - } - - public Builder withDataLoaderDispatcherInstrumentationOptions(DataLoaderDispatcherInstrumentationOptions options) { - return withDataLoaderDispatcherInstrumentationOptions(() -> options); - } - - public Builder withDataLoaderDispatcherInstrumentationOptions(Supplier supplier) { - this.dataLoaderDispatcherInstrumentationOptionsSupplier = supplier; - return this; - } - - public GraphQLQueryInvoker build() { - return new GraphQLQueryInvoker(getExecutionStrategyProvider, getInstrumentation, getPreparsedDocumentProvider, - dataLoaderDispatcherInstrumentationOptionsSupplier); - } + public GraphQLQueryInvoker build() { + return new GraphQLQueryInvoker(getExecutionStrategyProvider, getInstrumentation, getPreparsedDocumentProvider, + dataLoaderDispatcherInstrumentationOptionsSupplier); } + } } diff --git a/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java b/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java index 41a3958f..3f136a37 100644 --- a/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java +++ b/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import graphql.introspection.IntrospectionQuery; import java.util.HashMap; import java.util.Map; @@ -27,6 +28,10 @@ public GraphQLRequest(String query, Map variables, String operat } } + public static GraphQLRequest createIntrospectionRequest() { + return new GraphQLRequest(IntrospectionQuery.INTROSPECTION_QUERY, new HashMap<>(), null); + } + public String getQuery() { return query; } diff --git a/src/main/java/graphql/servlet/input/GraphQLBatchedInvocationInput.java b/src/main/java/graphql/servlet/input/GraphQLBatchedInvocationInput.java index cf41fe1f..d5f89bfb 100644 --- a/src/main/java/graphql/servlet/input/GraphQLBatchedInvocationInput.java +++ b/src/main/java/graphql/servlet/input/GraphQLBatchedInvocationInput.java @@ -1,14 +1,18 @@ package graphql.servlet.input; +import graphql.servlet.context.ContextSetting; import java.util.List; /** * Interface representing a batched input. */ -public interface GraphQLBatchedInvocationInput { +public interface GraphQLBatchedInvocationInput extends GraphQLInvocationInput { /** * @return each individual input in the batch, configured with a context. */ List getExecutionInputs(); + + ContextSetting getContextSetting(); + } diff --git a/src/main/java/graphql/servlet/input/GraphQLInvocationInput.java b/src/main/java/graphql/servlet/input/GraphQLInvocationInput.java new file mode 100644 index 00000000..acf88d50 --- /dev/null +++ b/src/main/java/graphql/servlet/input/GraphQLInvocationInput.java @@ -0,0 +1,5 @@ +package graphql.servlet.input; + +public interface GraphQLInvocationInput { + +} diff --git a/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java b/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java index a5088433..9dd39f19 100644 --- a/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java +++ b/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java @@ -36,6 +36,7 @@ public GraphQLSchemaProvider getSchemaProvider() { return schemaProviderSupplier.get(); } + public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, HttpServletRequest request, HttpServletResponse response) { return create(graphQLRequest, request, response, false); } diff --git a/src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java b/src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java index 302155d7..fb70a8b8 100644 --- a/src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java +++ b/src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java @@ -13,7 +13,7 @@ /** * Represents a single GraphQL execution. */ -public class GraphQLSingleInvocationInput { +public class GraphQLSingleInvocationInput implements GraphQLInvocationInput { private final GraphQLSchema schema; diff --git a/src/main/java/graphql/servlet/input/PerQueryBatchedInvocationInput.java b/src/main/java/graphql/servlet/input/PerQueryBatchedInvocationInput.java index 2f396e6d..4eff9b92 100644 --- a/src/main/java/graphql/servlet/input/PerQueryBatchedInvocationInput.java +++ b/src/main/java/graphql/servlet/input/PerQueryBatchedInvocationInput.java @@ -1,26 +1,27 @@ package graphql.servlet.input; import graphql.schema.GraphQLSchema; +import graphql.servlet.context.ContextSetting; import graphql.servlet.context.GraphQLContext; import graphql.servlet.core.internal.GraphQLRequest; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; +import lombok.Getter; /** * A Collection of GraphQLSingleInvocationInput that each have a unique context object. */ +@Getter public class PerQueryBatchedInvocationInput implements GraphQLBatchedInvocationInput { - private final List inputs; + private final List executionInputs; + private final ContextSetting contextSetting; - public PerQueryBatchedInvocationInput(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root) { - inputs = requests.stream() + public PerQueryBatchedInvocationInput(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root, ContextSetting contextSetting) { + executionInputs = requests.stream() .map(request -> new GraphQLSingleInvocationInput(request, schema, contextSupplier.get(), root)).collect(Collectors.toList()); + this.contextSetting = contextSetting; } - @Override - public List getExecutionInputs() { - return inputs; - } } diff --git a/src/main/java/graphql/servlet/input/PerRequestBatchedInvocationInput.java b/src/main/java/graphql/servlet/input/PerRequestBatchedInvocationInput.java index d68fd0bd..11714059 100644 --- a/src/main/java/graphql/servlet/input/PerRequestBatchedInvocationInput.java +++ b/src/main/java/graphql/servlet/input/PerRequestBatchedInvocationInput.java @@ -1,27 +1,28 @@ package graphql.servlet.input; import graphql.schema.GraphQLSchema; +import graphql.servlet.context.ContextSetting; import graphql.servlet.context.GraphQLContext; import graphql.servlet.core.internal.GraphQLRequest; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; +import lombok.Getter; /** * A collection of GraphQLSingleInvocationInputs that share a context object. */ +@Getter public class PerRequestBatchedInvocationInput implements GraphQLBatchedInvocationInput { - private final List inputs; + private final List executionInputs; + private final ContextSetting contextSetting; - public PerRequestBatchedInvocationInput(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root) { + public PerRequestBatchedInvocationInput(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root, ContextSetting contextSetting) { GraphQLContext context = contextSupplier.get(); - inputs = requests.stream().map(request -> new GraphQLSingleInvocationInput(request, schema, context, root)).collect(Collectors.toList()); + executionInputs = requests.stream().map(request -> new GraphQLSingleInvocationInput(request, schema, context, root)).collect(Collectors.toList()); + this.contextSetting = contextSetting; } - @Override - public List getExecutionInputs() { - return inputs; - } } From 088e5e52cd0849244f655113c9445ce76f99ae28 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Fri, 15 Nov 2019 16:26:27 +0100 Subject: [PATCH 02/26] Fix unit tests --- .../servlet/AbstractGraphQLHttpServlet.java | 288 +----------------- .../AbstractGraphQLInvocationInputParser.java | 23 ++ .../GraphQLGetInvocationInputParser.java | 61 ++++ .../servlet/GraphQLInvocationInputParser.java | 66 ++-- ...GraphQLMultipartInvocationInputParser.java | 134 ++++++++ .../GraphQLPostInvocationInputParser.java | 41 +++ ...ndler.java => HttpRequestHandlerImpl.java} | 16 +- .../servlet/core/internal/GraphQLRequest.java | 4 + .../AbstractGraphQLHttpServletSpec.groovy | 15 + .../servlet/DataLoaderDispatchingSpec.groovy | 21 +- 10 files changed, 323 insertions(+), 346 deletions(-) create mode 100644 src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java create mode 100644 src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java create mode 100644 src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java create mode 100644 src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java rename src/main/java/graphql/servlet/{HttpGetRequestHandler.java => HttpRequestHandlerImpl.java} (86%) diff --git a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java index 448181c3..6780da66 100644 --- a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java @@ -1,59 +1,26 @@ package graphql.servlet; -import static graphql.servlet.HttpRequestHandler.APPLICATION_GRAPHQL; -import static graphql.servlet.HttpRequestHandler.STATUS_BAD_REQUEST; - -import com.google.common.io.ByteStreams; -import com.google.common.io.CharStreams; -import graphql.ExecutionResult; -import graphql.GraphQL; -import graphql.execution.reactive.SingleSubscriberPublisher; -import graphql.introspection.IntrospectionQuery; import graphql.schema.GraphQLFieldDefinition; import graphql.servlet.config.GraphQLConfiguration; -import graphql.servlet.context.ContextSetting; import graphql.servlet.core.GraphQLMBean; import graphql.servlet.core.GraphQLObjectMapper; import graphql.servlet.core.GraphQLQueryInvoker; import graphql.servlet.core.GraphQLServletListener; import graphql.servlet.core.internal.GraphQLRequest; -import graphql.servlet.core.internal.VariableMapper; -import graphql.servlet.input.BatchInputPreProcessResult; -import graphql.servlet.input.BatchInputPreProcessor; -import graphql.servlet.input.GraphQLBatchedInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; -import graphql.servlet.input.GraphQLSingleInvocationInput; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import javax.servlet.AsyncContext; -import javax.servlet.AsyncEvent; -import javax.servlet.AsyncListener; import javax.servlet.Servlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.Part; import lombok.extern.slf4j.Slf4j; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; /** * @author Andrew Potter @@ -61,15 +28,13 @@ @Slf4j public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements Servlet, GraphQLMBean { - private static final String[] MULTIPART_KEYS = new String[]{"operations", "graphql", "query"}; /** * @deprecated use {@link #getConfiguration()} instead */ @Deprecated private final List listeners; private GraphQLConfiguration configuration; - private HttpRequestHandler getHandler; - private HttpRequestHandler postHandler; + private HttpRequestHandler requestHandler; public AbstractGraphQLHttpServlet() { this(null); @@ -114,138 +79,10 @@ protected GraphQLConfiguration getConfiguration() { @Override public void init() { - if (configuration != null) { - return; + if (configuration == null) { + this.configuration = getConfiguration(); + this.requestHandler = new HttpRequestHandlerImpl(configuration); } - this.configuration = getConfiguration(); - this.getHandler = new HttpGetRequestHandler(configuration); - - this.postHandler = (request, response) -> { - GraphQLInvocationInputFactory invocationInputFactory = configuration.getInvocationInputFactory(); - GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper(); - GraphQLQueryInvoker queryInvoker = configuration.getQueryInvoker(); - - try { - if (APPLICATION_GRAPHQL.equals(request.getContentType())) { - String query = CharStreams.toString(request.getReader()); - query(queryInvoker, graphQLObjectMapper, - invocationInputFactory.create(new GraphQLRequest(query, null, null), request, response), - request, response); - } else if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data") - && !request.getParts().isEmpty()) { - final Map> fileItems = request.getParts() - .stream() - .collect(Collectors.groupingBy(Part::getName)); - - for (String key : MULTIPART_KEYS) { - // Check to see if there is a part under the key we seek - if (!fileItems.containsKey(key)) { - continue; - } - - final Optional queryItem = getFileItem(fileItems, key); - if (!queryItem.isPresent()) { - // If there is a part, but we don't see an item, then break and return BAD_REQUEST - break; - } - - InputStream inputStream = asMarkableInputStream(queryItem.get().getInputStream()); - - final Optional>> variablesMap = - getFileItem(fileItems, "map").map(graphQLObjectMapper::deserializeMultipartMap); - - if (isBatchedQuery(inputStream)) { - List graphQLRequests = - graphQLObjectMapper.readBatchedGraphQLRequest(inputStream); - variablesMap.ifPresent(map -> graphQLRequests.forEach(r -> mapMultipartVariables(r, map, fileItems))); - GraphQLBatchedInvocationInput batchedInvocationInput = invocationInputFactory - .create(configuration.getContextSetting(), - graphQLRequests, request, response); - queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration); - return; - } else { - GraphQLRequest graphQLRequest; - if ("query".equals(key)) { - graphQLRequest = buildRequestFromQuery(inputStream, graphQLObjectMapper, fileItems); - } else { - graphQLRequest = graphQLObjectMapper.readGraphQLRequest(inputStream); - } - - variablesMap.ifPresent(m -> mapMultipartVariables(graphQLRequest, m, fileItems)); - GraphQLSingleInvocationInput invocationInput = - invocationInputFactory.create(graphQLRequest, request, response); - query(queryInvoker, graphQLObjectMapper, invocationInput, request, response); - return; - } - } - - response.setStatus(STATUS_BAD_REQUEST); - log.info("Bad POST multipart request: no part named " + Arrays.toString(MULTIPART_KEYS)); - } else { - // this is not a multipart request - InputStream inputStream = asMarkableInputStream(request.getInputStream()); - - if (isBatchedQuery(inputStream)) { - List requests = graphQLObjectMapper.readBatchedGraphQLRequest(inputStream); - GraphQLBatchedInvocationInput batchedInvocationInput = - invocationInputFactory.create(configuration.getContextSetting(), requests, request, response); - queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration); - } else { - query(queryInvoker, graphQLObjectMapper, - invocationInputFactory.create(graphQLObjectMapper.readGraphQLRequest(inputStream), request, response), - request, response); - } - } - } catch (Exception e) { - log.info("Bad POST request: parsing failed", e); - response.setStatus(STATUS_BAD_REQUEST); - } - }; - } - - private InputStream asMarkableInputStream(InputStream inputStream) { - if (!inputStream.markSupported()) { - return new BufferedInputStream(inputStream); - } - return inputStream; - } - - private GraphQLRequest buildRequestFromQuery(InputStream inputStream, - GraphQLObjectMapper graphQLObjectMapper, - Map> fileItems) throws IOException { - GraphQLRequest graphQLRequest; - String query = new String(ByteStreams.toByteArray(inputStream)); - - Map variables = null; - final Optional variablesItem = getFileItem(fileItems, "variables"); - if (variablesItem.isPresent()) { - variables = graphQLObjectMapper - .deserializeVariables(new String(ByteStreams.toByteArray(variablesItem.get().getInputStream()))); - } - - String operationName = null; - final Optional operationNameItem = getFileItem(fileItems, "operationName"); - if (operationNameItem.isPresent()) { - operationName = new String(ByteStreams.toByteArray(operationNameItem.get().getInputStream())).trim(); - } - - graphQLRequest = new GraphQLRequest(query, variables, operationName); - return graphQLRequest; - } - - private void mapMultipartVariables(GraphQLRequest request, - Map> variablesMap, - Map> fileItems) { - Map variables = request.getVariables(); - - variablesMap.forEach((partName, objectPaths) -> { - Part part = getFileItem(fileItems, partName) - .orElseThrow(() -> new RuntimeException("unable to find part name " + - partName + - " as referenced in the variables map")); - - objectPaths.forEach(objectPath -> VariableMapper.mapVariable(objectPath, variables, part)); - }); } public void addListener(GraphQLServletListener servletListener) { @@ -320,95 +157,13 @@ private void doRequest(HttpServletRequest request, HttpServletResponse response, @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { init(); - doRequestAsync(req, resp, getHandler); + doRequestAsync(req, resp, requestHandler); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { init(); - doRequestAsync(req, resp, postHandler); - } - - private Optional getFileItem(Map> fileItems, String name) { - return Optional.ofNullable(fileItems.get(name)).filter(list -> !list.isEmpty()).map(list -> list.get(0)); - } - - private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, - GraphQLSingleInvocationInput invocationInput, - HttpServletRequest req, HttpServletResponse resp) throws IOException { - ExecutionResult result = queryInvoker.query(invocationInput); - - boolean isDeferred = - Objects.nonNull(result.getExtensions()) && result.getExtensions().containsKey(GraphQL.DEFERRED_RESULTS); - - if (!(result.getData() instanceof Publisher || isDeferred)) { - resp.setContentType(APPLICATION_JSON_UTF8); - resp.setStatus(STATUS_OK); - graphQLObjectMapper.serializeResultAsJson(resp.getWriter(), result); - } else { - if (req == null) { - throw new IllegalStateException("Http servlet request can not be null"); - } - resp.setContentType(APPLICATION_EVENT_STREAM_UTF8); - resp.setStatus(STATUS_OK); - - boolean isInAsyncThread = req.isAsyncStarted(); - AsyncContext asyncContext = isInAsyncThread ? req.getAsyncContext() : req.startAsync(req, resp); - asyncContext.setTimeout(configuration.getSubscriptionTimeout()); - AtomicReference subscriptionRef = new AtomicReference<>(); - asyncContext.addListener(new SubscriptionAsyncListener(subscriptionRef)); - ExecutionResultSubscriber subscriber = new ExecutionResultSubscriber(subscriptionRef, asyncContext, - graphQLObjectMapper); - List> publishers = new ArrayList<>(); - if (result.getData() instanceof Publisher) { - publishers.add(result.getData()); - } else { - publishers.add(new StaticDataPublisher<>(result)); - final Publisher deferredResultsPublisher = (Publisher) result.getExtensions() - .get(GraphQL.DEFERRED_RESULTS); - publishers.add(deferredResultsPublisher); - } - publishers.forEach(it -> it.subscribe(subscriber)); - - if (isInAsyncThread) { - // We need to delay the completion of async context until after the subscription has terminated, otherwise the AsyncContext is prematurely closed. - try { - subscriber.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - } - - private void queryBatched(GraphQLQueryInvoker queryInvoker, GraphQLBatchedInvocationInput batchedInvocationInput, - HttpServletRequest request, - HttpServletResponse response, GraphQLConfiguration configuration) throws IOException { - BatchInputPreProcessor batchInputPreProcessor = configuration.getBatchInputPreProcessor(); - ContextSetting contextSetting = configuration.getContextSetting(); - BatchInputPreProcessResult batchInputPreProcessResult = batchInputPreProcessor - .preProcessBatch(batchedInvocationInput, request, response); - if (batchInputPreProcessResult.isExecutable()) { - List results = queryInvoker - .query(batchInputPreProcessResult.getBatchedInvocationInput().getExecutionInputs(), - contextSetting); - response.setContentType(AbstractGraphQLHttpServlet.APPLICATION_JSON_UTF8); - response.setStatus(AbstractGraphQLHttpServlet.STATUS_OK); - Writer writer = response.getWriter(); - Iterator executionInputIterator = results.iterator(); - writer.write("["); - GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper(); - while (executionInputIterator.hasNext()) { - String result = graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next()); - writer.write(result); - if (executionInputIterator.hasNext()) { - writer.write(","); - } - } - writer.write("]"); - } else { - response.sendError(batchInputPreProcessResult.getStatusCode(), batchInputPreProcessResult.getStatusMessage()); - } + doRequestAsync(req, resp, requestHandler); } private List runListeners(Function action) { @@ -435,35 +190,4 @@ private void runCallbacks(List callbacks, Consumer action) { }); } - private boolean isBatchedQuery(InputStream inputStream) throws IOException { - if (inputStream == null) { - return false; - } - - final int BUFFER_SIZE = 128; - ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] buffer = new byte[BUFFER_SIZE]; - int length; - - inputStream.mark(BUFFER_SIZE); - while ((length = inputStream.read(buffer)) != -1) { - result.write(buffer, 0, length); - if (isArrayStart(result.toString())) { - inputStream.reset(); - return true; - } - } - - inputStream.reset(); - return false; - } - - private boolean isBatchedQuery(String query) { - return isArrayStart(query); - } - - private boolean isArrayStart(String s) { - return s != null && s.trim().startsWith("["); - } - } diff --git a/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java b/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java new file mode 100644 index 00000000..f0cead14 --- /dev/null +++ b/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java @@ -0,0 +1,23 @@ +package graphql.servlet; + +import graphql.servlet.context.ContextSetting; +import graphql.servlet.core.GraphQLObjectMapper; +import graphql.servlet.input.GraphQLInvocationInputFactory; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +abstract class AbstractGraphQLInvocationInputParser implements GraphQLInvocationInputParser { + + final GraphQLInvocationInputFactory invocationInputFactory; + final GraphQLObjectMapper graphQLObjectMapper; + final ContextSetting contextSetting; + + boolean isSingleQuery(String query) { + return !isArrayStart(query); + } + + private boolean isArrayStart(String s) { + return s != null && s.trim().startsWith("["); + } + +} diff --git a/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java new file mode 100644 index 00000000..8c524a8c --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java @@ -0,0 +1,61 @@ +package graphql.servlet; + +import graphql.GraphQLException; +import graphql.servlet.context.ContextSetting; +import graphql.servlet.core.GraphQLObjectMapper; +import graphql.servlet.core.internal.GraphQLRequest; +import graphql.servlet.input.GraphQLInvocationInput; +import graphql.servlet.input.GraphQLInvocationInputFactory; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class GraphQLGetInvocationInputParser extends AbstractGraphQLInvocationInputParser { + + GraphQLGetInvocationInputParser(GraphQLInvocationInputFactory invocationInputFactory, + GraphQLObjectMapper graphQLObjectMapper, ContextSetting contextSetting) { + super(invocationInputFactory, graphQLObjectMapper, contextSetting); + } + + public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (isIntrospectionQuery(request)) { + GraphQLRequest graphqlRequest = GraphQLRequest.createIntrospectionRequest(); + return invocationInputFactory.create(graphqlRequest, request, response); + } + + String query = request.getParameter("query"); + if (query == null) { + throw new GraphQLException("Query not found in request"); + } + + if (isSingleQuery(query)) { + Map variables = getVariables(request); + String operationName = request.getParameter("operationName"); + GraphQLRequest graphqlRequest = new GraphQLRequest(query, variables, operationName); + return invocationInputFactory.createReadOnly(graphqlRequest, request, response); + } + + List requests = graphQLObjectMapper.readBatchedGraphQLRequest(query); + return invocationInputFactory.createReadOnly(contextSetting, requests, request, response); + } + + private boolean isIntrospectionQuery(HttpServletRequest request) { + String path = Optional.ofNullable(request.getPathInfo()).orElseGet(request::getServletPath).toLowerCase(); + return path.contentEquals("/schema.json"); + } + + private Map getVariables(HttpServletRequest request) { + return Optional.ofNullable(request.getParameter("variables")) + .map(graphQLObjectMapper::deserializeVariables) + .map(HashMap::new) + .orElseGet(HashMap::new); + } + +} diff --git a/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java index 35451618..a33ef31e 100644 --- a/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java @@ -2,62 +2,38 @@ import graphql.servlet.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.internal.GraphQLRequest; import graphql.servlet.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor -class GraphQLInvocationInputParser { +interface GraphQLInvocationInputParser { - private final GraphQLInvocationInputFactory invocationInputFactory; - private final GraphQLObjectMapper graphQLObjectMapper; - private final ContextSetting contextSetting; - - GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest request, HttpServletResponse response) - throws IOException { - if (isIntrospectionQuery(request)) { - GraphQLRequest graphqlRequest = GraphQLRequest.createIntrospectionRequest(); - return invocationInputFactory.create(graphqlRequest, request, response); + static GraphQLInvocationInputParser create(HttpServletRequest request, + GraphQLInvocationInputFactory invocationInputFactory, + GraphQLObjectMapper graphQLObjectMapper, + ContextSetting contextSetting + ) throws IOException { + if ("GET".equalsIgnoreCase(request.getMethod())) { + return new GraphQLGetInvocationInputParser(invocationInputFactory, graphQLObjectMapper, contextSetting); } - String query = request.getParameter("query"); - if (!isBatchedQuery(query)) { - Map variables = getVariables(request); - String operationName = request.getParameter("operationName"); - GraphQLRequest graphqlRequest = new GraphQLRequest(query, variables, operationName); - return invocationInputFactory.create(graphqlRequest, request, response); + try { + boolean notMultipartRequest =request.getContentType() == null || + !request.getContentType().startsWith("multipart/form-data") || + request.getParts().isEmpty(); + if (notMultipartRequest) { + return new GraphQLPostInvocationInputParser(invocationInputFactory, graphQLObjectMapper, contextSetting); + } + return new GraphQLMultipartInvocationInputParser(invocationInputFactory, graphQLObjectMapper, contextSetting); + } catch (ServletException e) { + throw new IOException("Cannot get parts of request", e); } - - List requests = graphQLObjectMapper.readBatchedGraphQLRequest(query); - return invocationInputFactory.createReadOnly(contextSetting, requests, request, response); } - private boolean isIntrospectionQuery(HttpServletRequest request) { - String path = Optional.ofNullable(request.getPathInfo()).orElseGet(request::getServletPath).toLowerCase(); - return path.contentEquals("/schema.json"); - } - - private Map getVariables(HttpServletRequest request) { - return Optional.ofNullable(request.getParameter("variables")) - .map(graphQLObjectMapper::deserializeVariables) - .map(HashMap::new) - .orElseGet(HashMap::new); - } - - private boolean isBatchedQuery(String query) { - return isArrayStart(query); - } - - private boolean isArrayStart(String s) { - return s != null && s.trim().startsWith("["); - } + GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest request, HttpServletResponse response) + throws IOException; } diff --git a/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java new file mode 100644 index 00000000..57f548ac --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java @@ -0,0 +1,134 @@ +package graphql.servlet; + +import static java.util.stream.Collectors.joining; + +import graphql.GraphQLException; +import graphql.servlet.context.ContextSetting; +import graphql.servlet.core.GraphQLObjectMapper; +import graphql.servlet.core.internal.GraphQLRequest; +import graphql.servlet.core.internal.VariableMapper; +import graphql.servlet.input.GraphQLInvocationInput; +import graphql.servlet.input.GraphQLInvocationInputFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class GraphQLMultipartInvocationInputParser extends AbstractGraphQLInvocationInputParser { + + private static final String[] MULTIPART_KEYS = new String[]{"operations", "graphql", "query"}; + + GraphQLMultipartInvocationInputParser(GraphQLInvocationInputFactory invocationInputFactory, + GraphQLObjectMapper graphQLObjectMapper, ContextSetting contextSetting) { + super(invocationInputFactory, graphQLObjectMapper, contextSetting); + } + + @Override + public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest request, HttpServletResponse response) + throws IOException { + try { + final Map> parts = request.getParts() + .stream() + .collect(Collectors.groupingBy(Part::getName)); + + for (String key : MULTIPART_KEYS) { + // Check to see if there is a part under the key we seek + if (!parts.containsKey(key)) { + continue; + } + + final Optional queryItem = getPart(parts, key); + if (!queryItem.isPresent()) { + // If there is a part, but we don't see an item, then break and return BAD_REQUEST + break; + } + + InputStream inputStream = queryItem.get().getInputStream(); + + final Optional>> variablesMap = + getPart(parts, "map").map(graphQLObjectMapper::deserializeMultipartMap); + + if ("query".equals(key)) { + GraphQLRequest graphqlRequest = buildRequestFromQuery(inputStream, graphQLObjectMapper, parts); + variablesMap.ifPresent(m -> mapMultipartVariables(graphqlRequest, m, parts)); + return invocationInputFactory.create(graphqlRequest, request, response); + } else { + String body = read(inputStream); + if (isSingleQuery(body)) { + GraphQLRequest graphqlRequest = graphQLObjectMapper.readGraphQLRequest(body); + variablesMap.ifPresent(m -> mapMultipartVariables(graphqlRequest, m, parts)); + return invocationInputFactory.create(graphqlRequest, request, response); + } else { + List graphqlRequests = graphQLObjectMapper.readBatchedGraphQLRequest(body); + variablesMap.ifPresent(map -> graphqlRequests.forEach(r -> mapMultipartVariables(r, map, parts))); + return invocationInputFactory.create(contextSetting, graphqlRequests, request, response); + } + } + } + + log.info("Bad POST multipart request: no part named {}", Arrays.toString(MULTIPART_KEYS)); + throw new GraphQLException("Bad POST multipart request: no part named " + Arrays.toString(MULTIPART_KEYS)); + } catch (ServletException e) { + throw new IOException("Cannot get parts from request", e); + } + } + + private Optional getPart(Map> parts, String name) { + return Optional.ofNullable(parts.get(name)).filter(list -> !list.isEmpty()).map(list -> list.get(0)); + } + + private void mapMultipartVariables(GraphQLRequest request, + Map> variablesMap, + Map> fileItems) { + Map variables = request.getVariables(); + + variablesMap.forEach((partName, objectPaths) -> { + Part part = getPart(fileItems, partName) + .orElseThrow(() -> new RuntimeException("unable to find part name " + + partName + + " as referenced in the variables map")); + + objectPaths.forEach(objectPath -> VariableMapper.mapVariable(objectPath, variables, part)); + }); + } + + private GraphQLRequest buildRequestFromQuery(InputStream inputStream, + GraphQLObjectMapper graphQLObjectMapper, + Map> parts) throws IOException { + String query = read(inputStream); + + Map variables = null; + final Optional variablesItem = getPart(parts, "variables"); + if (variablesItem.isPresent()) { + variables = graphQLObjectMapper + .deserializeVariables(read(variablesItem.get().getInputStream())); + } + + String operationName = null; + final Optional operationNameItem = getPart(parts, "operationName"); + if (operationNameItem.isPresent()) { + operationName = read(operationNameItem.get().getInputStream()).trim(); + } + + return new GraphQLRequest(query, variables, operationName); + } + + private String read(InputStream inputStream) throws IOException { + try (InputStreamReader streamReader = new InputStreamReader(inputStream); + BufferedReader reader = new BufferedReader(streamReader)) { + return reader.lines().collect(joining()); + } + } + +} diff --git a/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java new file mode 100644 index 00000000..19b3daf1 --- /dev/null +++ b/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java @@ -0,0 +1,41 @@ +package graphql.servlet; + +import static graphql.servlet.HttpRequestHandler.APPLICATION_GRAPHQL; +import static java.util.stream.Collectors.joining; + +import graphql.servlet.context.ContextSetting; +import graphql.servlet.core.GraphQLObjectMapper; +import graphql.servlet.core.internal.GraphQLRequest; +import graphql.servlet.input.GraphQLInvocationInput; +import graphql.servlet.input.GraphQLInvocationInputFactory; +import java.io.IOException; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +class GraphQLPostInvocationInputParser extends AbstractGraphQLInvocationInputParser { + + GraphQLPostInvocationInputParser(GraphQLInvocationInputFactory invocationInputFactory, + GraphQLObjectMapper graphQLObjectMapper, ContextSetting contextSetting) { + super(invocationInputFactory, graphQLObjectMapper, contextSetting); + } + + public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (APPLICATION_GRAPHQL.equals(request.getContentType())) { + String query = request.getReader().lines().collect(joining()); + GraphQLRequest graphqlRequest = GraphQLRequest.createQueryOnlyRequest(query); + return invocationInputFactory.create(graphqlRequest, request, response); + } + + String body = request.getReader().lines().collect(joining()); + if (isSingleQuery(body)) { + GraphQLRequest graphqlRequest = graphQLObjectMapper.readGraphQLRequest(body); + return invocationInputFactory.create(graphqlRequest, request, response); + } + + List requests = graphQLObjectMapper.readBatchedGraphQLRequest(body); + return invocationInputFactory.create(contextSetting, requests, request, response); + } + +} diff --git a/src/main/java/graphql/servlet/HttpGetRequestHandler.java b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java similarity index 86% rename from src/main/java/graphql/servlet/HttpGetRequestHandler.java rename to src/main/java/graphql/servlet/HttpRequestHandlerImpl.java index a3bc1421..3188aa4f 100644 --- a/src/main/java/graphql/servlet/HttpGetRequestHandler.java +++ b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java @@ -14,25 +14,25 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -class HttpGetRequestHandler implements HttpRequestHandler { +class HttpRequestHandlerImpl implements HttpRequestHandler { private final GraphQLConfiguration configuration; - private final GraphQLInvocationInputParser invocationInputParser; private final GraphQLQueryInvoker queryInvoker; - HttpGetRequestHandler(GraphQLConfiguration configuration) { + HttpRequestHandlerImpl(GraphQLConfiguration configuration) { this.configuration = configuration; - invocationInputParser = new GraphQLInvocationInputParser( - configuration.getInvocationInputFactory(), - configuration.getObjectMapper(), - configuration.getContextSetting() - ); queryInvoker = configuration.getQueryInvoker(); } @Override public void handle(HttpServletRequest request, HttpServletResponse response) { try { + GraphQLInvocationInputParser invocationInputParser = GraphQLInvocationInputParser.create( + request, + configuration.getInvocationInputFactory(), + configuration.getObjectMapper(), + configuration.getContextSetting() + ); GraphQLInvocationInput invocationInput = invocationInputParser.getGraphQLInvocationInput(request, response); GraphQLQueryResult queryResult = invoke(invocationInput, request, response); diff --git a/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java b/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java index 3f136a37..8a2ad61d 100644 --- a/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java +++ b/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java @@ -32,6 +32,10 @@ public static GraphQLRequest createIntrospectionRequest() { return new GraphQLRequest(IntrospectionQuery.INTROSPECTION_QUERY, new HashMap<>(), null); } + public static GraphQLRequest createQueryOnlyRequest(String query) { + return new GraphQLRequest(query, new HashMap<>(), null); + } + public String getQuery() { return query; } diff --git a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy index 89e4c3a4..879cfba7 100644 --- a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy +++ b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy @@ -54,6 +54,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { request = new MockHttpServletRequest() request.setAsyncSupported(true) request.asyncSupported = true + request.setMethod("GET") response = new MockHttpServletResponse() } @@ -402,6 +403,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { request.setContent(mapper.writeValueAsBytes([ query: 'query { echo(arg:"test") }' ])) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -437,6 +439,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { setup: request.addHeader("Content-Type", "application/graphql") request.setContent('query { echo(arg:"test") }'.getBytes("UTF-8")) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -453,6 +456,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { query : 'query Echo($arg: String) { echo(arg:$arg) }', variables: '{"arg": "test"}' ])) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -469,6 +473,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { query : 'query one{ echoOne: echo(arg:"test-one") } query two{ echoTwo: echo(arg:"test-two") }', operationName: 'two' ])) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -486,6 +491,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { query : 'query echo{ echo: echo(arg:"test") }', operationName: '' ])) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -502,6 +508,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { query: 'query { echo(arg:"test") }', test : 'test' ])) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -723,6 +730,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "batched query over HTTP POST body returns data"() { setup: request.setContent('[{ "query": "query { echo(arg:\\"test\\") }" }, { "query": "query { echo(arg:\\"test\\") }" }]'.bytes) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -737,6 +745,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "batched query over HTTP POST body with variables returns data"() { setup: request.setContent('[{ "query": "query { echo(arg:\\"test\\") }", "variables": { "arg": "test" } }, { "query": "query { echo(arg:\\"test\\") }", "variables": { "arg": "test" } }]'.bytes) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -751,6 +760,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "batched query over HTTP POST body with operationName returns data"() { setup: request.setContent('[{ "query": "query one{ echoOne: echo(arg:\\"test-one\\") } query two{ echoTwo: echo(arg:\\"test-two\\") }", "operationName": "one" }, { "query": "query one{ echoOne: echo(arg:\\"test-one\\") } query two{ echoTwo: echo(arg:\\"test-two\\") }", "operationName": "two" }]'.bytes) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -767,6 +777,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "batched query over HTTP POST body with empty non-null operationName returns data"() { setup: request.setContent('[{ "query": "query echo{ echo: echo(arg:\\"test\\") }", "operationName": "" }, { "query": "query echo{ echo: echo(arg:\\"test\\") }", "operationName": "" }]'.bytes) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -781,6 +792,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "batched query over HTTP POST body with unknown property 'test' returns data"() { setup: request.setContent('[{ "query": "query { echo(arg:\\"test\\") }", "test": "test" }, { "query": "query { echo(arg:\\"test\\") }", "test": "test" }]'.bytes) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -997,6 +1009,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { request.setContent(mapper.writeValueAsBytes([ query: 'mutation { echo(arg:"test") }' ])) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -1010,6 +1023,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "batched mutation over HTTP POST body returns data"() { setup: request.setContent('[{ "query": "mutation { echo(arg:\\"test\\") }" }, { "query": "mutation { echo(arg:\\"test\\") }" }]'.bytes) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -1024,6 +1038,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "batched mutation over HTTP POST body with unknown property 'test' returns data"() { setup: request.setContent('[{ "query": "mutation { echo(arg:\\"test\\") }", "test": "test" }, { "query": "mutation { echo(arg:\\"test\\") }", "test": "test" }]'.bytes) + request.setMethod("POST") when: servlet.doPost(request, response) diff --git a/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy b/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy index 423f3198..6e5f7dfa 100644 --- a/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy +++ b/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy @@ -8,10 +8,10 @@ import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment +import graphql.servlet.context.ContextSetting import graphql.servlet.context.DefaultGraphQLContext import graphql.servlet.context.GraphQLContext import graphql.servlet.context.GraphQLContextBuilder -import graphql.servlet.context.ContextSetting import graphql.servlet.instrumentation.ConfigurableDispatchInstrumentation import org.dataloader.BatchLoader import org.dataloader.DataLoader @@ -23,7 +23,6 @@ import spock.lang.Specification import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse -import javax.servlet.http.Part import javax.websocket.Session import javax.websocket.server.HandshakeRequest import java.util.concurrent.CompletableFuture @@ -84,7 +83,7 @@ class DataLoaderDispatchingSpec extends Specification { } } - def contextBuilder () { + def contextBuilder() { return new GraphQLContextBuilder() { @Override GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { @@ -123,8 +122,8 @@ class DataLoaderDispatchingSpec extends Specification { Instrumentation simpleInstrumentation = new SimpleInstrumentation() ChainedInstrumentation chainedInstrumentation = new ChainedInstrumentation(Collections.singletonList(simpleInstrumentation)) - def simpleSupplier = {simpleInstrumentation} - def chainedSupplier = {chainedInstrumentation} + def simpleSupplier = { simpleInstrumentation } + def chainedSupplier = { chainedInstrumentation } def "batched query with per query context does not batch loads together"() { setup: @@ -181,7 +180,7 @@ class DataLoaderDispatchingSpec extends Specification { return Collections.singletonList(instrumentation) } else { List instrumentations = new ArrayList<>() - for (Instrumentation current : ((ChainedInstrumentation)instrumentation).getInstrumentations()) { + for (Instrumentation current : ((ChainedInstrumentation) instrumentation).getInstrumentations()) { if (current instanceof ChainedInstrumentation) { instrumentations.addAll(unwrapChainedInstrumentations(current)) } else { @@ -225,10 +224,10 @@ class DataLoaderDispatchingSpec extends Specification { then: fromSimple.size() == 2 fromSimple.contains(simpleInstrumentation) - fromSimple.stream().anyMatch({inst -> inst instanceof ConfigurableDispatchInstrumentation}) + fromSimple.stream().anyMatch({ inst -> inst instanceof ConfigurableDispatchInstrumentation }) fromChained.size() == 2 fromChained.contains(simpleInstrumentation) - fromChained.stream().anyMatch({inst -> inst instanceof ConfigurableDispatchInstrumentation}) + fromChained.stream().anyMatch({ inst -> inst instanceof ConfigurableDispatchInstrumentation }) } def "PER_REQUEST_WITH_INSTRUMENTATION adds instrumentation"() { @@ -244,9 +243,9 @@ class DataLoaderDispatchingSpec extends Specification { then: fromSimple.size() == 2 fromSimple.contains(simpleInstrumentation) - fromSimple.stream().anyMatch({inst -> inst instanceof ConfigurableDispatchInstrumentation}) + fromSimple.stream().anyMatch({ inst -> inst instanceof ConfigurableDispatchInstrumentation }) fromChained.size() == 2 fromChained.contains(simpleInstrumentation) - fromChained.stream().anyMatch({inst -> inst instanceof ConfigurableDispatchInstrumentation}) + fromChained.stream().anyMatch({ inst -> inst instanceof ConfigurableDispatchInstrumentation }) } -} \ No newline at end of file +} From 26cdad42b8d0cb2d12609a3c718751091c046e7d Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Fri, 15 Nov 2019 20:46:49 +0100 Subject: [PATCH 03/26] Fix unit tests --- .../AbstractGraphQLInvocationInputParser.java | 6 +++--- .../graphql/servlet/GraphQLErrorQueryResult.java | 4 +--- .../servlet/GraphQLGetInvocationInputParser.java | 6 +++--- .../GraphQLMultipartInvocationInputParser.java | 16 +++++++--------- .../GraphQLPostInvocationInputParser.java | 9 +++++++-- .../graphql/servlet/HttpRequestHandlerImpl.java | 14 ++++++++++++++ .../AbstractGraphQLHttpServletSpec.groovy | 1 + .../servlet/DataLoaderDispatchingSpec.groovy | 2 ++ 8 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java b/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java index f0cead14..4b9a2d30 100644 --- a/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java +++ b/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java @@ -13,11 +13,11 @@ abstract class AbstractGraphQLInvocationInputParser implements GraphQLInvocation final ContextSetting contextSetting; boolean isSingleQuery(String query) { - return !isArrayStart(query); + return query != null && !query.trim().isEmpty() && !query.trim().startsWith("["); } - private boolean isArrayStart(String s) { - return s != null && s.trim().startsWith("["); + boolean isBatchedQuery(String query) { + return query != null && !query.trim().isEmpty() && query.trim().startsWith("["); } } diff --git a/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java b/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java index 80f3a15f..284b1c30 100644 --- a/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java +++ b/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java @@ -1,7 +1,5 @@ package graphql.servlet; -import graphql.ExecutionResult; -import java.util.List; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -14,7 +12,7 @@ class GraphQLErrorQueryResult implements GraphQLQueryResult { @Override public boolean isBatched() { - return true; + return false; } @Override diff --git a/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java index 8c524a8c..b0e37ab9 100644 --- a/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java @@ -32,7 +32,7 @@ public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest reque String query = request.getParameter("query"); if (query == null) { - throw new GraphQLException("Query not found in request"); + throw new GraphQLException("Query parameter not found in GET request"); } if (isSingleQuery(query)) { @@ -42,8 +42,8 @@ public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest reque return invocationInputFactory.createReadOnly(graphqlRequest, request, response); } - List requests = graphQLObjectMapper.readBatchedGraphQLRequest(query); - return invocationInputFactory.createReadOnly(contextSetting, requests, request, response); + List graphqlRequests = graphQLObjectMapper.readBatchedGraphQLRequest(query); + return invocationInputFactory.createReadOnly(contextSetting, graphqlRequests, request, response); } private boolean isIntrospectionQuery(HttpServletRequest request) { diff --git a/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java index 57f548ac..06094bd9 100644 --- a/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java @@ -59,18 +59,18 @@ public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest reque final Optional>> variablesMap = getPart(parts, "map").map(graphQLObjectMapper::deserializeMultipartMap); - if ("query".equals(key)) { - GraphQLRequest graphqlRequest = buildRequestFromQuery(inputStream, graphQLObjectMapper, parts); + String query = read(inputStream); + if ("query".equals(key) && isSingleQuery(query)) { + GraphQLRequest graphqlRequest = buildRequestFromQuery(query, graphQLObjectMapper, parts); variablesMap.ifPresent(m -> mapMultipartVariables(graphqlRequest, m, parts)); return invocationInputFactory.create(graphqlRequest, request, response); } else { - String body = read(inputStream); - if (isSingleQuery(body)) { - GraphQLRequest graphqlRequest = graphQLObjectMapper.readGraphQLRequest(body); + if (isSingleQuery(query)) { + GraphQLRequest graphqlRequest = graphQLObjectMapper.readGraphQLRequest(query); variablesMap.ifPresent(m -> mapMultipartVariables(graphqlRequest, m, parts)); return invocationInputFactory.create(graphqlRequest, request, response); } else { - List graphqlRequests = graphQLObjectMapper.readBatchedGraphQLRequest(body); + List graphqlRequests = graphQLObjectMapper.readBatchedGraphQLRequest(query); variablesMap.ifPresent(map -> graphqlRequests.forEach(r -> mapMultipartVariables(r, map, parts))); return invocationInputFactory.create(contextSetting, graphqlRequests, request, response); } @@ -103,11 +103,9 @@ private void mapMultipartVariables(GraphQLRequest request, }); } - private GraphQLRequest buildRequestFromQuery(InputStream inputStream, + private GraphQLRequest buildRequestFromQuery(String query, GraphQLObjectMapper graphQLObjectMapper, Map> parts) throws IOException { - String query = read(inputStream); - Map variables = null; final Optional variablesItem = getPart(parts, "variables"); if (variablesItem.isPresent()) { diff --git a/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java index 19b3daf1..7581a3cb 100644 --- a/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java @@ -3,6 +3,7 @@ import static graphql.servlet.HttpRequestHandler.APPLICATION_GRAPHQL; import static java.util.stream.Collectors.joining; +import graphql.GraphQLException; import graphql.servlet.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; import graphql.servlet.core.internal.GraphQLRequest; @@ -34,8 +35,12 @@ public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest reque return invocationInputFactory.create(graphqlRequest, request, response); } - List requests = graphQLObjectMapper.readBatchedGraphQLRequest(body); - return invocationInputFactory.create(contextSetting, requests, request, response); + if (isBatchedQuery(body)) { + List requests = graphQLObjectMapper.readBatchedGraphQLRequest(body); + return invocationInputFactory.create(contextSetting, requests, request, response); + } + + throw new GraphQLException("No valid query found in request"); } } diff --git a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java index 3188aa4f..6e7e0396 100644 --- a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java +++ b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java @@ -2,6 +2,7 @@ import static graphql.servlet.QueryResponseWriter.createWriter; +import graphql.GraphQLException; import graphql.servlet.config.GraphQLConfiguration; import graphql.servlet.core.GraphQLQueryInvoker; import graphql.servlet.input.BatchInputPreProcessResult; @@ -9,6 +10,7 @@ import graphql.servlet.input.GraphQLBatchedInvocationInput; import graphql.servlet.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLSingleInvocationInput; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -34,7 +36,19 @@ public void handle(HttpServletRequest request, HttpServletResponse response) { configuration.getContextSetting() ); GraphQLInvocationInput invocationInput = invocationInputParser.getGraphQLInvocationInput(request, response); + execute(invocationInput, request, response); + } catch (GraphQLException e) { + response.setStatus(STATUS_BAD_REQUEST); + log.info("Bad request: cannot create invocation input parser", e); + } catch (Throwable t) { + response.setStatus(500); + log.info("Bad request: cannot create invocation input parser", t); + } + } + private void execute(GraphQLInvocationInput invocationInput, HttpServletRequest request, + HttpServletResponse response) { + try { GraphQLQueryResult queryResult = invoke(invocationInput, request, response); QueryResponseWriter queryResponseWriter = createWriter(queryResult, configuration.getObjectMapper(), diff --git a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy index 879cfba7..7213da5e 100644 --- a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy +++ b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy @@ -392,6 +392,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "query over HTTP POST without part or body returns bad request"() { when: + request.setMethod("POST") servlet.doPost(request, response) then: diff --git a/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy b/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy index 6e5f7dfa..6c9da55b 100644 --- a/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy +++ b/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy @@ -131,6 +131,7 @@ class DataLoaderDispatchingSpec extends Specification { request.addParameter('query', '[{ "query": "query { query(arg:\\"test\\") { echo(arg:\\"test\\") { echo(arg:\\"test\\") } }}" }, { "query": "query{query(arg:\\"test\\") { echo (arg:\\"test\\") { echo(arg:\\"test\\")} }}" },' + ' { "query": "query{queryTwo(arg:\\"test\\") { echo (arg:\\"test\\")}}" }, { "query": "query{queryTwo(arg:\\"test\\") { echo (arg:\\"test\\")}}" }]') resetCounters() + request.setMethod("GET") when: servlet.doGet(request, response) @@ -156,6 +157,7 @@ class DataLoaderDispatchingSpec extends Specification { request.addParameter('query', '[{ "query": "query { query(arg:\\"test\\") { echo(arg:\\"test\\") { echo(arg:\\"test\\") } }}" }, { "query": "query{query(arg:\\"test\\") { echo (arg:\\"test\\") { echo(arg:\\"test\\")} }}" },' + ' { "query": "query{queryTwo(arg:\\"test\\") { echo (arg:\\"test\\")}}" }, { "query": "query{queryTwo(arg:\\"test\\") { echo (arg:\\"test\\")}}" }]') resetCounters() + request.setMethod("GET") when: servlet.doGet(request, response) From 039176cca6d4c9cc81ce3fe0e1a2218671cc975f Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Fri, 15 Nov 2019 21:24:53 +0100 Subject: [PATCH 04/26] Fix unit tests --- .../servlet/ExecutionResultSubscriber.java | 6 +-- ...SingleAsynchronousQueryResponseWriter.java | 2 +- .../AbstractGraphQLHttpServletSpec.groovy | 41 +++++-------------- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/src/main/java/graphql/servlet/ExecutionResultSubscriber.java b/src/main/java/graphql/servlet/ExecutionResultSubscriber.java index ff271f63..b490eb00 100644 --- a/src/main/java/graphql/servlet/ExecutionResultSubscriber.java +++ b/src/main/java/graphql/servlet/ExecutionResultSubscriber.java @@ -17,7 +17,7 @@ class ExecutionResultSubscriber implements Subscriber { private final GraphQLObjectMapper graphQLObjectMapper; private final CountDownLatch completedLatch = new CountDownLatch(1); - public ExecutionResultSubscriber(AtomicReference subscriptionRef, AsyncContext asyncContext, + ExecutionResultSubscriber(AtomicReference subscriptionRef, AsyncContext asyncContext, GraphQLObjectMapper graphQLObjectMapper) { this.subscriptionRef = subscriptionRef; this.asyncContext = asyncContext; @@ -35,7 +35,7 @@ public void onNext(ExecutionResult executionResult) { try { Writer writer = asyncContext.getResponse().getWriter(); writer.write("data: "); - graphQLObjectMapper.serializeResultAsJson(writer, executionResult); + writer.write(graphQLObjectMapper.serializeResultAsJson(executionResult)); writer.write("\n\n"); writer.flush(); subscriptionRef.get().request(1); @@ -55,7 +55,7 @@ public void onComplete() { completedLatch.countDown(); } - public void await() throws InterruptedException { + void await() throws InterruptedException { completedLatch.await(); } diff --git a/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java b/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java index bdc6cb1e..2e62f54f 100644 --- a/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java +++ b/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java @@ -29,7 +29,7 @@ class SingleAsynchronousQueryResponseWriter implements QueryResponseWriter { private final long subscriptionTimeout; @Override - public void write(HttpServletRequest request, HttpServletResponse response) throws IOException { + public void write(HttpServletRequest request, HttpServletResponse response) { Objects.requireNonNull(request, "Http servlet request cannot be null"); response.setContentType(APPLICATION_EVENT_STREAM_UTF8); response.setStatus(STATUS_OK); diff --git a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy index 7213da5e..0f040b55 100644 --- a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy +++ b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy @@ -3,7 +3,9 @@ package graphql.servlet import com.fasterxml.jackson.databind.ObjectMapper import graphql.Scalars import graphql.execution.ExecutionStepInfo +import graphql.execution.MergedField import graphql.execution.reactive.SingleSubscriberPublisher +import graphql.language.Field import graphql.schema.GraphQLNonNull import graphql.servlet.input.GraphQLInvocationInputFactory import org.springframework.mock.web.MockHttpServletRequest @@ -112,7 +114,6 @@ class AbstractGraphQLHttpServletSpec extends Specification { getResponseContent().data.echo == "test" } - @Ignore def "async query over HTTP GET starts async request"() { setup: servlet = TestUtils.createDefaultServlet({ env -> env.arguments.arg },{ env -> env.arguments.arg }, { env -> @@ -284,7 +285,6 @@ class AbstractGraphQLHttpServletSpec extends Specification { getBatchedResponseContent()[1].data.echo == "test" } - @Ignore def "deferred query over HTTP GET"() { setup: request.addParameter('query', 'query { echo(arg:"test") @defer }') @@ -369,7 +369,6 @@ class AbstractGraphQLHttpServletSpec extends Specification { getBatchedResponseContent()[1].errors.size() == 1 } - @Ignore def "subscription query over HTTP GET with variables as string returns data"() { setup: request.addParameter('query', 'subscription Subscription($arg: String!) { echo(arg: $arg) }') @@ -1051,11 +1050,11 @@ class AbstractGraphQLHttpServletSpec extends Specification { getBatchedResponseContent()[1].data.echo == "test" } - @Ignore def "subscription query over HTTP POST with variables as string returns data"() { setup: request.setContent('{"query": "subscription Subscription($arg: String!) { echo(arg: $arg) }", "operationName": "Subscription", "variables": {"arg": "test"}}'.bytes) request.setAsyncSupported(true) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -1070,11 +1069,11 @@ class AbstractGraphQLHttpServletSpec extends Specification { getSubscriptionResponseContent()[1].data.echo == "Second\n\ntest" } - @Ignore def "defer query over HTTP POST"() { setup: request.setContent('{"query": "subscription Subscription($arg: String!) { echo(arg: $arg) }", "operationName": "Subscription", "variables": {"arg": "test"}}'.bytes) request.setAsyncSupported(true) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -1089,7 +1088,6 @@ class AbstractGraphQLHttpServletSpec extends Specification { getSubscriptionResponseContent()[1].data.echo == "Second\n\ntest" } - @Ignore def "deferred query that takes longer than initial results, should still be sent second"() { setup: servlet = TestUtils.createDefaultServlet({ env -> @@ -1109,6 +1107,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { ''' ])) request.setAsyncSupported(true) + request.setMethod("POST") when: servlet.doPost(request, response) @@ -1230,33 +1229,13 @@ class AbstractGraphQLHttpServletSpec extends Specification { resp[1].errors != null } - @Ignore def "typeInfo is serialized correctly"() { - expect: - servlet.getConfiguration().getObjectMapper().getJacksonMapper().writeValueAsString(ExecutionStepInfo.newExecutionStepInfo().type(new GraphQLNonNull(Scalars.GraphQLString)).build()) != "{}" - } - - @Ignore - def "isBatchedQuery check uses buffer length as read limit"() { setup: - HttpServletRequest mockRequest = Mock() - ServletInputStream mockInputStream = Mock() - - mockInputStream.markSupported() >> true - mockRequest.getInputStream() >> mockInputStream - mockRequest.getMethod() >> "POST" - mockRequest.getParts() >> Collections.emptyList() - - when: - servlet.doPost(mockRequest, response) - - then: - 1 * mockInputStream.mark(128) - - then: - 1 * mockInputStream.read({ it.length == 128 }) >> -1 + MergedField field = MergedField.newMergedField().addField(new Field("test")).build() + ExecutionStepInfo stepInfo = ExecutionStepInfo.newExecutionStepInfo().field(field).type(new GraphQLNonNull(Scalars.GraphQLString)).build() - then: - 1 * mockInputStream.reset() + expect: + servlet.getConfiguration().getObjectMapper().getJacksonMapper().writeValueAsString(stepInfo) != "{}" } + } From 5b1047f63b076dc5b56bc165f1454fe3aa9e485b Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Fri, 15 Nov 2019 21:26:46 +0100 Subject: [PATCH 05/26] refactor servlert fix #182 --- .../java/graphql/servlet/HttpRequestHandler.java | 12 +----------- .../java/graphql/servlet/HttpRequestHandlerImpl.java | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/main/java/graphql/servlet/HttpRequestHandler.java b/src/main/java/graphql/servlet/HttpRequestHandler.java index 9ed38213..81e05805 100644 --- a/src/main/java/graphql/servlet/HttpRequestHandler.java +++ b/src/main/java/graphql/servlet/HttpRequestHandler.java @@ -1,10 +1,9 @@ package graphql.servlet; -import java.util.function.BiConsumer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -interface HttpRequestHandler extends BiConsumer { +interface HttpRequestHandler { String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8"; String APPLICATION_EVENT_STREAM_UTF8 = "text/event-stream;charset=UTF-8"; @@ -12,15 +11,6 @@ interface HttpRequestHandler extends BiConsumer Date: Fri, 15 Nov 2019 21:35:10 +0100 Subject: [PATCH 06/26] Add content length when writing response --- .../servlet/BatchedQueryResponseWriter.java | 16 +++++++++------- .../servlet/SingleQueryResponseWriter.java | 4 +++- .../AbstractGraphQLHttpServletSpec.groovy | 17 ++++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java b/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java index dbc2b4ac..763cb5c0 100644 --- a/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java +++ b/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java @@ -6,7 +6,6 @@ import graphql.ExecutionResult; import graphql.servlet.core.GraphQLObjectMapper; import java.io.IOException; -import java.io.Writer; import java.util.Iterator; import java.util.List; import javax.servlet.http.HttpServletRequest; @@ -24,17 +23,20 @@ public void write(HttpServletRequest request, HttpServletResponse response) thro response.setContentType(APPLICATION_JSON_UTF8); response.setStatus(STATUS_OK); - Writer writer = response.getWriter(); Iterator executionInputIterator = results.iterator(); - writer.write("["); + StringBuilder responseBuilder = new StringBuilder(); + responseBuilder.append('['); while (executionInputIterator.hasNext()) { - String result = graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next()); - writer.write(result); + responseBuilder.append(graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next())); if (executionInputIterator.hasNext()) { - writer.write(","); + responseBuilder.append(','); } } - writer.write("]"); + responseBuilder.append(']'); + + String responseContent = responseBuilder.toString(); + response.setContentLength(responseContent.length()); + response.getWriter().write(responseContent); } } diff --git a/src/main/java/graphql/servlet/SingleQueryResponseWriter.java b/src/main/java/graphql/servlet/SingleQueryResponseWriter.java index e0b7d029..a0271c61 100644 --- a/src/main/java/graphql/servlet/SingleQueryResponseWriter.java +++ b/src/main/java/graphql/servlet/SingleQueryResponseWriter.java @@ -19,7 +19,9 @@ class SingleQueryResponseWriter implements QueryResponseWriter { public void write(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType(APPLICATION_JSON_UTF8); response.setStatus(STATUS_OK); - graphQLObjectMapper.serializeResultAsJson(response.getWriter(), result); + String responseContent = graphQLObjectMapper.serializeResultAsJson(result); + response.setContentLength(responseContent.length()); + response.getWriter().write(responseContent); } } diff --git a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy index 0f040b55..72e43aa3 100644 --- a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy +++ b/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy @@ -10,12 +10,9 @@ import graphql.schema.GraphQLNonNull import graphql.servlet.input.GraphQLInvocationInputFactory import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse -import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification -import javax.servlet.ServletInputStream -import javax.servlet.http.HttpServletRequest import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReference @@ -111,12 +108,13 @@ class AbstractGraphQLHttpServletSpec extends Specification { then: response.getStatus() == STATUS_OK response.getContentType() == CONTENT_TYPE_JSON_UTF8 + response.getContentLength() == mapper.writeValueAsString(["data": ["echo": "test"]]).length() getResponseContent().data.echo == "test" } def "async query over HTTP GET starts async request"() { setup: - servlet = TestUtils.createDefaultServlet({ env -> env.arguments.arg },{ env -> env.arguments.arg }, { env -> + servlet = TestUtils.createDefaultServlet({ env -> env.arguments.arg }, { env -> env.arguments.arg }, { env -> AtomicReference> publisherRef = new AtomicReference<>(); publisherRef.set(new SingleSubscriberPublisher<>({ subscription -> publisherRef.get().offer(env.arguments.arg) @@ -373,7 +371,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { setup: request.addParameter('query', 'subscription Subscription($arg: String!) { echo(arg: $arg) }') request.addParameter('operationName', 'Subscription') - request.addParameter( 'variables', '{"arg": "test"}') + request.addParameter('variables', '{"arg": "test"}') request.setAsyncSupported(true) when: @@ -416,7 +414,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { def "async query over HTTP POST starts async request"() { setup: - servlet = TestUtils.createDefaultServlet({ env -> env.arguments.arg },{ env -> env.arguments.arg }, { env -> + servlet = TestUtils.createDefaultServlet({ env -> env.arguments.arg }, { env -> env.arguments.arg }, { env -> AtomicReference> publisherRef = new AtomicReference<>(); publisherRef.set(new SingleSubscriberPublisher<>({ subscription -> publisherRef.get().offer(env.arguments.arg) @@ -424,9 +422,9 @@ class AbstractGraphQLHttpServletSpec extends Specification { })) return publisherRef.get() }, true) - request.setContent(mapper.writeValueAsBytes([ - query: 'query { echo(arg:"test") }' - ])) + request.setContent(mapper.writeValueAsBytes([ + query: 'query { echo(arg:"test") }' + ])) when: servlet.doPost(request, response) @@ -738,6 +736,7 @@ class AbstractGraphQLHttpServletSpec extends Specification { then: response.getStatus() == STATUS_OK response.getContentType() == CONTENT_TYPE_JSON_UTF8 + response.getContentLength() == mapper.writeValueAsString([["data": ["echo": "test"]], ["data": ["echo": "test"]]]).length() getBatchedResponseContent()[0].data.echo == "test" getBatchedResponseContent()[1].data.echo == "test" } From c0b22ac2ad2f341399cdb0ccfe5a4eb324f3d8c5 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sat, 16 Nov 2019 08:37:16 +0100 Subject: [PATCH 07/26] Rethrow caught exceptions to allow listeners to adjust response fix #201, fix #129 --- .../java/graphql/servlet/AbstractGraphQLHttpServlet.java | 1 - src/main/java/graphql/servlet/HttpRequestHandlerImpl.java | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java index 6780da66..55bffd2b 100644 --- a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java @@ -143,7 +143,6 @@ private void doRequest(HttpServletRequest request, HttpServletResponse response, handler.handle(request, response); runCallbacks(requestCallbacks, c -> c.onSuccess(request, response)); } catch (Throwable t) { - response.setStatus(500); log.error("Error executing GraphQL request!", t); runCallbacks(requestCallbacks, c -> c.onError(request, response, t)); } finally { diff --git a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java index 1b6cbd69..68f4a1f7 100644 --- a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java +++ b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java @@ -10,6 +10,7 @@ import graphql.servlet.input.GraphQLBatchedInvocationInput; import graphql.servlet.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLSingleInvocationInput; +import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -26,7 +27,7 @@ class HttpRequestHandlerImpl implements HttpRequestHandler { } @Override - public void handle(HttpServletRequest request, HttpServletResponse response) { + public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { try { GraphQLInvocationInputParser invocationInputParser = GraphQLInvocationInputParser.create( request, @@ -39,9 +40,11 @@ public void handle(HttpServletRequest request, HttpServletResponse response) { } catch (GraphQLException e) { response.setStatus(STATUS_BAD_REQUEST); log.info("Bad request: cannot create invocation input parser", e); + throw e; } catch (Throwable t) { response.setStatus(500); log.info("Bad request: cannot create invocation input parser", t); + throw t; } } From 7dfbd1566304d24f23e4264a56fceeb4d07bc5d6 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 17 Nov 2019 11:17:21 +0100 Subject: [PATCH 08/26] Split based on servlet and websocket dependencies --- .../execution}/DecoratedExecutionResult.java | 8 +- .../execution}/GraphQLBatchedQueryResult.java | 2 +- .../execution}/GraphQLErrorQueryResult.java | 2 +- .../execution}/GraphQLQueryInvoker.java | 15 +- .../execution}/GraphQLQueryResult.java | 14 +- .../execution}/GraphQLSingleQueryResult.java | 2 +- .../ConfiguringObjectMapperProvider.java | 6 +- .../DefaultExecutionStrategyProvider.java | 51 ++ .../config/ExecutionStrategyProvider.java | 2 +- .../config/GraphQLCodeRegistryProvider.java | 3 +- .../config/GraphQLConfiguration.java | 10 +- .../config/InstrumentationProvider.java | 2 +- .../config/ObjectMapperConfigurer.java | 2 +- .../config/ObjectMapperProvider.java | 2 +- .../execution}/context/ContextSetting.java | 14 +- .../context/DefaultGraphQLContext.java | 2 +- .../execution}/context/GraphQLContext.java | 2 +- .../error/DefaultGraphQLErrorHandler.java | 63 ++ .../error}/DefaultObjectMapperConfigurer.java | 4 +- .../execution/error}/GenericGraphQLError.java | 2 +- .../execution/error}/GraphQLErrorHandler.java | 2 +- ...enderableNonNullableFieldWasNullError.java | 2 +- .../input/GraphQLBatchedInvocationInput.java | 6 +- .../input/GraphQLInvocationInput.java | 2 +- .../input/GraphQLSingleInvocationInput.java | 59 ++ .../input/NoOpBatchInputPreProcessor.java | 4 +- .../input/PerQueryBatchedInvocationInput.java | 6 +- .../PerRequestBatchedInvocationInput.java | 6 +- .../AbstractTrackingApproach.java | 220 ++++++ .../ConfigurableDispatchInstrumentation.java | 2 +- ...aLoaderDispatcherInstrumentationState.java | 2 +- .../FieldLevelTrackingApproach.java | 2 +- .../NoOpInstrumentationProvider.java | 4 +- .../RequestLevelTrackingApproach.java | 4 +- .../instrumentation/RequestStack.java | 4 +- .../instrumentation/TrackingApproach.java | 2 +- .../SubscriptionConnectionListener.java | 2 +- .../subscription}/SubscriptionException.java | 2 +- .../servlet/AbstractGraphQLHttpServlet.java | 4 +- .../AbstractGraphQLInvocationInputParser.java | 2 +- .../servlet/ConfiguredGraphQLHttpServlet.java | 2 +- .../servlet/DefaultGraphQLServlet.java | 2 +- .../GraphQLGetInvocationInputParser.java | 4 +- .../graphql/servlet/GraphQLHttpServlet.java | 4 +- .../servlet/GraphQLInvocationInputParser.java | 4 +- ...GraphQLMultipartInvocationInputParser.java | 4 +- .../GraphQLPostInvocationInputParser.java | 4 +- .../servlet/GraphQLQueryProcessor.java | 9 - .../servlet/GraphQLWebsocketServlet.java | 4 +- .../servlet/HttpRequestHandlerImpl.java | 13 +- .../servlet/OsgiGraphQLHttpServlet.java | 639 +++++++++--------- .../graphql/servlet/QueryResponseWriter.java | 4 +- .../servlet/SimpleGraphQLHttpServlet.java | 4 +- ...SingleAsynchronousQueryResponseWriter.java | 4 +- .../servlet/SingleQueryResponseWriter.java | 3 +- .../graphql/servlet/apollo/ApolloScalars.java | 44 ++ .../ApolloSubscriptionConnectionListener.java | 4 +- .../DefaultExecutionStrategyProvider.java | 50 -- .../config/DefaultGraphQLSchemaProvider.java | 52 +- .../servlet/config/GraphQLQueryProvider.java | 18 - .../context/DefaultGraphQLContextBuilder.java | 5 +- .../context/DefaultGraphQLServletContext.java | 1 + .../DefaultGraphQLWebSocketContext.java | 3 +- .../context/GraphQLContextBuilder.java | 1 + .../context/GraphQLServletContext.java | 4 +- .../context/GraphQLWebSocketContext.java | 1 + .../graphql/servlet/core/ApolloScalars.java | 41 -- .../core/DefaultGraphQLErrorHandler.java | 66 -- .../servlet/core/GraphQLObjectMapper.java | 308 +++++---- .../ApolloSubscriptionProtocolFactory.java | 2 +- .../ApolloSubscriptionProtocolHandler.java | 6 +- .../FallbackSubscriptionProtocolHandler.java | 2 +- .../internal/SubscriptionHandlerInput.java | 4 +- .../input/BatchInputPreProcessResult.java | 4 +- .../servlet/input/BatchInputPreProcessor.java | 1 + .../input/GraphQLInvocationInputFactory.java | 244 +++---- .../input/GraphQLSingleInvocationInput.java | 59 -- .../AbstractTrackingApproach.java | 225 ------ .../GraphQLMutationProvider.java | 8 +- .../{config => osgi}/GraphQLProvider.java | 2 +- .../servlet/osgi/GraphQLQueryProvider.java | 16 + .../GraphQLSubscriptionProvider.java | 4 +- .../GraphQLTypesProvider.java | 7 +- .../servlet/DataLoaderDispatchingSpec.groovy | 8 +- .../servlet/OsgiGraphQLHttpServletSpec.groovy | 8 +- .../servlet/TestBatchInputPreProcessor.java | 2 +- .../groovy/graphql/servlet/TestUtils.groovy | 7 +- 87 files changed, 1246 insertions(+), 1210 deletions(-) rename src/main/java/graphql/{servlet => kickstart/execution}/DecoratedExecutionResult.java (85%) rename src/main/java/graphql/{servlet => kickstart/execution}/GraphQLBatchedQueryResult.java (91%) rename src/main/java/graphql/{servlet => kickstart/execution}/GraphQLErrorQueryResult.java (91%) rename src/main/java/graphql/{servlet/core => kickstart/execution}/GraphQLQueryInvoker.java (94%) rename src/main/java/graphql/{servlet => kickstart/execution}/GraphQLQueryResult.java (69%) rename src/main/java/graphql/{servlet => kickstart/execution}/GraphQLSingleQueryResult.java (90%) rename src/main/java/graphql/{servlet => kickstart/execution}/config/ConfiguringObjectMapperProvider.java (85%) create mode 100644 src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java rename src/main/java/graphql/{servlet => kickstart/execution}/config/ExecutionStrategyProvider.java (85%) rename src/main/java/graphql/{servlet => kickstart/execution}/config/GraphQLCodeRegistryProvider.java (64%) rename src/main/java/graphql/{servlet => kickstart/execution}/config/GraphQLConfiguration.java (95%) rename src/main/java/graphql/{servlet => kickstart/execution}/config/InstrumentationProvider.java (76%) rename src/main/java/graphql/{servlet => kickstart/execution}/config/ObjectMapperConfigurer.java (79%) rename src/main/java/graphql/{servlet => kickstart/execution}/config/ObjectMapperProvider.java (73%) rename src/main/java/graphql/{servlet => kickstart/execution}/context/ContextSetting.java (89%) rename src/main/java/graphql/{servlet => kickstart/execution}/context/DefaultGraphQLContext.java (95%) rename src/main/java/graphql/{servlet => kickstart/execution}/context/GraphQLContext.java (91%) create mode 100644 src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java rename src/main/java/graphql/{servlet/core => kickstart/execution/error}/DefaultObjectMapperConfigurer.java (85%) rename src/main/java/graphql/{servlet/core => kickstart/execution/error}/GenericGraphQLError.java (93%) rename src/main/java/graphql/{servlet/core => kickstart/execution/error}/GraphQLErrorHandler.java (88%) rename src/main/java/graphql/{servlet/core => kickstart/execution/error}/RenderableNonNullableFieldWasNullError.java (96%) rename src/main/java/graphql/{servlet => kickstart/execution}/input/GraphQLBatchedInvocationInput.java (59%) rename src/main/java/graphql/{servlet => kickstart/execution}/input/GraphQLInvocationInput.java (51%) create mode 100644 src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java rename src/main/java/graphql/{servlet => kickstart/execution}/input/NoOpBatchInputPreProcessor.java (78%) rename src/main/java/graphql/{servlet => kickstart/execution}/input/PerQueryBatchedInvocationInput.java (85%) rename src/main/java/graphql/{servlet => kickstart/execution}/input/PerRequestBatchedInvocationInput.java (85%) create mode 100644 src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java rename src/main/java/graphql/{servlet => kickstart/execution}/instrumentation/ConfigurableDispatchInstrumentation.java (99%) rename src/main/java/graphql/{servlet => kickstart/execution}/instrumentation/DataLoaderDispatcherInstrumentationState.java (96%) rename src/main/java/graphql/{servlet => kickstart/execution}/instrumentation/FieldLevelTrackingApproach.java (94%) rename src/main/java/graphql/{servlet => kickstart/execution}/instrumentation/NoOpInstrumentationProvider.java (72%) rename src/main/java/graphql/{servlet => kickstart/execution}/instrumentation/RequestLevelTrackingApproach.java (94%) rename src/main/java/graphql/{servlet => kickstart/execution}/instrumentation/RequestStack.java (99%) rename src/main/java/graphql/{servlet => kickstart/execution}/instrumentation/TrackingApproach.java (97%) rename src/main/java/graphql/{servlet/core => kickstart/execution/subscription}/SubscriptionConnectionListener.java (61%) rename src/main/java/graphql/{servlet/core => kickstart/execution/subscription}/SubscriptionException.java (85%) delete mode 100644 src/main/java/graphql/servlet/GraphQLQueryProcessor.java create mode 100644 src/main/java/graphql/servlet/apollo/ApolloScalars.java rename src/main/java/graphql/servlet/{core => apollo}/ApolloSubscriptionConnectionListener.java (86%) delete mode 100644 src/main/java/graphql/servlet/config/DefaultExecutionStrategyProvider.java delete mode 100644 src/main/java/graphql/servlet/config/GraphQLQueryProvider.java delete mode 100644 src/main/java/graphql/servlet/core/ApolloScalars.java delete mode 100644 src/main/java/graphql/servlet/core/DefaultGraphQLErrorHandler.java delete mode 100644 src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java delete mode 100644 src/main/java/graphql/servlet/instrumentation/AbstractTrackingApproach.java rename src/main/java/graphql/servlet/{config => osgi}/GraphQLMutationProvider.java (52%) rename src/main/java/graphql/servlet/{config => osgi}/GraphQLProvider.java (54%) create mode 100644 src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java rename src/main/java/graphql/servlet/{config => osgi}/GraphQLSubscriptionProvider.java (72%) rename src/main/java/graphql/servlet/{config => osgi}/GraphQLTypesProvider.java (52%) diff --git a/src/main/java/graphql/servlet/DecoratedExecutionResult.java b/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java similarity index 85% rename from src/main/java/graphql/servlet/DecoratedExecutionResult.java rename to src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java index 0b500760..149f3657 100644 --- a/src/main/java/graphql/servlet/DecoratedExecutionResult.java +++ b/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java @@ -1,4 +1,4 @@ -package graphql.servlet; +package graphql.kickstart.execution; import graphql.ExecutionResult; import graphql.GraphQL; @@ -13,15 +13,11 @@ class DecoratedExecutionResult implements ExecutionResult { private final ExecutionResult result; - static DecoratedExecutionResult decorate(ExecutionResult result) { - return new DecoratedExecutionResult(result); - } - boolean isAsynchronous() { return result.getData() instanceof Publisher || isDeferred(); } - boolean isDeferred() { + private boolean isDeferred() { return result.getExtensions() != null && result.getExtensions().containsKey(GraphQL.DEFERRED_RESULTS); } diff --git a/src/main/java/graphql/servlet/GraphQLBatchedQueryResult.java b/src/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java similarity index 91% rename from src/main/java/graphql/servlet/GraphQLBatchedQueryResult.java rename to src/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java index 2cc21252..20b97800 100644 --- a/src/main/java/graphql/servlet/GraphQLBatchedQueryResult.java +++ b/src/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java @@ -1,4 +1,4 @@ -package graphql.servlet; +package graphql.kickstart.execution; import graphql.ExecutionResult; import java.util.List; diff --git a/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java b/src/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java similarity index 91% rename from src/main/java/graphql/servlet/GraphQLErrorQueryResult.java rename to src/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java index 284b1c30..bf1fc9d8 100644 --- a/src/main/java/graphql/servlet/GraphQLErrorQueryResult.java +++ b/src/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java @@ -1,4 +1,4 @@ -package graphql.servlet; +package graphql.kickstart.execution; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/graphql/servlet/core/GraphQLQueryInvoker.java b/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java similarity index 94% rename from src/main/java/graphql/servlet/core/GraphQLQueryInvoker.java rename to src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java index 83d7b20f..64d2afcf 100644 --- a/src/main/java/graphql/servlet/core/GraphQLQueryInvoker.java +++ b/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java @@ -1,4 +1,4 @@ -package graphql.servlet.core; +package graphql.kickstart.execution; import graphql.ExecutionInput; import graphql.ExecutionResult; @@ -11,13 +11,12 @@ import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; import graphql.execution.preparsed.PreparsedDocumentProvider; import graphql.schema.GraphQLSchema; -import graphql.servlet.GraphQLQueryResult; -import graphql.servlet.config.DefaultExecutionStrategyProvider; -import graphql.servlet.config.ExecutionStrategyProvider; -import graphql.servlet.context.ContextSetting; -import graphql.servlet.input.GraphQLBatchedInvocationInput; -import graphql.servlet.input.GraphQLInvocationInput; -import graphql.servlet.input.GraphQLSingleInvocationInput; +import graphql.kickstart.execution.config.DefaultExecutionStrategyProvider; +import graphql.kickstart.execution.config.ExecutionStrategyProvider; +import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.List; diff --git a/src/main/java/graphql/servlet/GraphQLQueryResult.java b/src/main/java/graphql/kickstart/execution/GraphQLQueryResult.java similarity index 69% rename from src/main/java/graphql/servlet/GraphQLQueryResult.java rename to src/main/java/graphql/kickstart/execution/GraphQLQueryResult.java index 535eda56..29139aad 100644 --- a/src/main/java/graphql/servlet/GraphQLQueryResult.java +++ b/src/main/java/graphql/kickstart/execution/GraphQLQueryResult.java @@ -1,4 +1,4 @@ -package graphql.servlet; +package graphql.kickstart.execution; import static java.util.Collections.emptyList; @@ -15,6 +15,10 @@ static GraphQLBatchedQueryResult create(List results) { return new GraphQLBatchedQueryResult(results); } + static GraphQLErrorQueryResult createError(int statusCode, String message) { + return new GraphQLErrorQueryResult(statusCode, message); + } + boolean isBatched(); boolean isAsynchronous(); @@ -28,4 +32,12 @@ default List getResults() { } default boolean isError() { return false; } + + default int getStatusCode() { + return 200; + } + + default String getMessage() { + return null; + } } diff --git a/src/main/java/graphql/servlet/GraphQLSingleQueryResult.java b/src/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java similarity index 90% rename from src/main/java/graphql/servlet/GraphQLSingleQueryResult.java rename to src/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java index 3070974d..20f1083a 100644 --- a/src/main/java/graphql/servlet/GraphQLSingleQueryResult.java +++ b/src/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java @@ -1,4 +1,4 @@ -package graphql.servlet; +package graphql.kickstart.execution; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/graphql/servlet/config/ConfiguringObjectMapperProvider.java b/src/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java similarity index 85% rename from src/main/java/graphql/servlet/config/ConfiguringObjectMapperProvider.java rename to src/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java index 521465e5..491bd5f1 100644 --- a/src/main/java/graphql/servlet/config/ConfiguringObjectMapperProvider.java +++ b/src/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java @@ -1,9 +1,7 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import com.fasterxml.jackson.databind.ObjectMapper; -import graphql.servlet.config.ObjectMapperConfigurer; -import graphql.servlet.config.ObjectMapperProvider; -import graphql.servlet.core.DefaultObjectMapperConfigurer; +import graphql.kickstart.execution.error.DefaultObjectMapperConfigurer; public class ConfiguringObjectMapperProvider implements ObjectMapperProvider { diff --git a/src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java b/src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java new file mode 100644 index 00000000..47aa6211 --- /dev/null +++ b/src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java @@ -0,0 +1,51 @@ +package graphql.kickstart.execution.config; + +import graphql.execution.AsyncExecutionStrategy; +import graphql.execution.ExecutionStrategy; +import graphql.execution.SubscriptionExecutionStrategy; + +/** + * @author Andrew Potter + */ +public class DefaultExecutionStrategyProvider implements ExecutionStrategyProvider { + + private final ExecutionStrategy queryExecutionStrategy; + private final ExecutionStrategy mutationExecutionStrategy; + private final ExecutionStrategy subscriptionExecutionStrategy; + + public DefaultExecutionStrategyProvider() { + this(null); + } + + public DefaultExecutionStrategyProvider(ExecutionStrategy executionStrategy) { + this(executionStrategy, null, null); + } + + public DefaultExecutionStrategyProvider(ExecutionStrategy queryExecutionStrategy, + ExecutionStrategy mutationExecutionStrategy, ExecutionStrategy subscriptionExecutionStrategy) { + this.queryExecutionStrategy = defaultIfNull(queryExecutionStrategy, new AsyncExecutionStrategy()); + this.mutationExecutionStrategy = defaultIfNull(mutationExecutionStrategy, this.queryExecutionStrategy); + this.subscriptionExecutionStrategy = defaultIfNull(subscriptionExecutionStrategy, + new SubscriptionExecutionStrategy()); + } + + private ExecutionStrategy defaultIfNull(ExecutionStrategy executionStrategy, ExecutionStrategy defaultStrategy) { + return executionStrategy != null ? executionStrategy : defaultStrategy; + } + + @Override + public ExecutionStrategy getQueryExecutionStrategy() { + return queryExecutionStrategy; + } + + @Override + public ExecutionStrategy getMutationExecutionStrategy() { + return mutationExecutionStrategy; + } + + @Override + public ExecutionStrategy getSubscriptionExecutionStrategy() { + return subscriptionExecutionStrategy; + } + +} diff --git a/src/main/java/graphql/servlet/config/ExecutionStrategyProvider.java b/src/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java similarity index 85% rename from src/main/java/graphql/servlet/config/ExecutionStrategyProvider.java rename to src/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java index d7ab70b8..dc2977a4 100644 --- a/src/main/java/graphql/servlet/config/ExecutionStrategyProvider.java +++ b/src/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java @@ -1,4 +1,4 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import graphql.execution.ExecutionStrategy; diff --git a/src/main/java/graphql/servlet/config/GraphQLCodeRegistryProvider.java b/src/main/java/graphql/kickstart/execution/config/GraphQLCodeRegistryProvider.java similarity index 64% rename from src/main/java/graphql/servlet/config/GraphQLCodeRegistryProvider.java rename to src/main/java/graphql/kickstart/execution/config/GraphQLCodeRegistryProvider.java index 51e46528..af2e16b5 100644 --- a/src/main/java/graphql/servlet/config/GraphQLCodeRegistryProvider.java +++ b/src/main/java/graphql/kickstart/execution/config/GraphQLCodeRegistryProvider.java @@ -1,6 +1,7 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import graphql.schema.GraphQLCodeRegistry; +import graphql.servlet.osgi.GraphQLProvider; public interface GraphQLCodeRegistryProvider extends GraphQLProvider { GraphQLCodeRegistry getCodeRegistry(); diff --git a/src/main/java/graphql/servlet/config/GraphQLConfiguration.java b/src/main/java/graphql/kickstart/execution/config/GraphQLConfiguration.java similarity index 95% rename from src/main/java/graphql/servlet/config/GraphQLConfiguration.java rename to src/main/java/graphql/kickstart/execution/config/GraphQLConfiguration.java index 938230fc..b1b6cfe1 100644 --- a/src/main/java/graphql/servlet/config/GraphQLConfiguration.java +++ b/src/main/java/graphql/kickstart/execution/config/GraphQLConfiguration.java @@ -1,16 +1,18 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import graphql.schema.GraphQLSchema; +import graphql.servlet.config.DefaultGraphQLSchemaProvider; +import graphql.servlet.config.GraphQLSchemaProvider; import graphql.servlet.context.GraphQLContextBuilder; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.GraphQLQueryInvoker; +import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.core.GraphQLRootObjectBuilder; import graphql.servlet.core.GraphQLServletListener; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.input.BatchInputPreProcessor; import graphql.servlet.input.GraphQLInvocationInputFactory; import graphql.servlet.core.internal.GraphQLThreadFactory; -import graphql.servlet.input.NoOpBatchInputPreProcessor; +import graphql.kickstart.execution.input.NoOpBatchInputPreProcessor; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/graphql/servlet/config/InstrumentationProvider.java b/src/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java similarity index 76% rename from src/main/java/graphql/servlet/config/InstrumentationProvider.java rename to src/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java index 5d6990fd..a48e1fac 100644 --- a/src/main/java/graphql/servlet/config/InstrumentationProvider.java +++ b/src/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java @@ -1,4 +1,4 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import graphql.execution.instrumentation.Instrumentation; diff --git a/src/main/java/graphql/servlet/config/ObjectMapperConfigurer.java b/src/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java similarity index 79% rename from src/main/java/graphql/servlet/config/ObjectMapperConfigurer.java rename to src/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java index 1e13637b..fd514119 100644 --- a/src/main/java/graphql/servlet/config/ObjectMapperConfigurer.java +++ b/src/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java @@ -1,4 +1,4 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/graphql/servlet/config/ObjectMapperProvider.java b/src/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java similarity index 73% rename from src/main/java/graphql/servlet/config/ObjectMapperProvider.java rename to src/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java index bb052045..097d10c0 100644 --- a/src/main/java/graphql/servlet/config/ObjectMapperProvider.java +++ b/src/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java @@ -1,4 +1,4 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/graphql/servlet/context/ContextSetting.java b/src/main/java/graphql/kickstart/execution/context/ContextSetting.java similarity index 89% rename from src/main/java/graphql/servlet/context/ContextSetting.java rename to src/main/java/graphql/kickstart/execution/context/ContextSetting.java index 6f859969..45f62297 100644 --- a/src/main/java/graphql/servlet/context/ContextSetting.java +++ b/src/main/java/graphql/kickstart/execution/context/ContextSetting.java @@ -1,4 +1,4 @@ -package graphql.servlet.context; +package graphql.kickstart.execution.context; import graphql.ExecutionInput; import graphql.execution.ExecutionId; @@ -6,12 +6,12 @@ import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions; import graphql.schema.GraphQLSchema; -import graphql.servlet.input.GraphQLBatchedInvocationInput; -import graphql.servlet.input.PerQueryBatchedInvocationInput; -import graphql.servlet.input.PerRequestBatchedInvocationInput; -import graphql.servlet.instrumentation.ConfigurableDispatchInstrumentation; -import graphql.servlet.instrumentation.FieldLevelTrackingApproach; -import graphql.servlet.instrumentation.RequestLevelTrackingApproach; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.PerQueryBatchedInvocationInput; +import graphql.kickstart.execution.input.PerRequestBatchedInvocationInput; +import graphql.kickstart.execution.instrumentation.ConfigurableDispatchInstrumentation; +import graphql.kickstart.execution.instrumentation.FieldLevelTrackingApproach; +import graphql.kickstart.execution.instrumentation.RequestLevelTrackingApproach; import graphql.servlet.core.internal.GraphQLRequest; import org.dataloader.DataLoaderRegistry; diff --git a/src/main/java/graphql/servlet/context/DefaultGraphQLContext.java b/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java similarity index 95% rename from src/main/java/graphql/servlet/context/DefaultGraphQLContext.java rename to src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java index 98bc11f6..cf9ef17d 100644 --- a/src/main/java/graphql/servlet/context/DefaultGraphQLContext.java +++ b/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java @@ -1,4 +1,4 @@ -package graphql.servlet.context; +package graphql.kickstart.execution.context; import org.dataloader.DataLoaderRegistry; diff --git a/src/main/java/graphql/servlet/context/GraphQLContext.java b/src/main/java/graphql/kickstart/execution/context/GraphQLContext.java similarity index 91% rename from src/main/java/graphql/servlet/context/GraphQLContext.java rename to src/main/java/graphql/kickstart/execution/context/GraphQLContext.java index 25d16ee9..5fc9546a 100644 --- a/src/main/java/graphql/servlet/context/GraphQLContext.java +++ b/src/main/java/graphql/kickstart/execution/context/GraphQLContext.java @@ -1,4 +1,4 @@ -package graphql.servlet.context; +package graphql.kickstart.execution.context; import org.dataloader.DataLoaderRegistry; diff --git a/src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java b/src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java new file mode 100644 index 00000000..9769ae2c --- /dev/null +++ b/src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java @@ -0,0 +1,63 @@ +package graphql.kickstart.execution.error; + +import graphql.ExceptionWhileDataFetching; +import graphql.GraphQLError; +import graphql.execution.NonNullableFieldWasNullError; +import java.util.List; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + +/** + * @author Andrew Potter + */ +@Slf4j +public class DefaultGraphQLErrorHandler implements GraphQLErrorHandler { + + @Override + public List processErrors(List errors) { + final List clientErrors = filterGraphQLErrors(errors); + if (clientErrors.size() < errors.size()) { + + // Some errors were filtered out to hide implementation - put a generic error in place. + clientErrors.add(new GenericGraphQLError("Internal Server Error(s) while executing query")); + + errors.stream() + .filter(error -> !isClientError(error)) + .forEach(this::logError); + } + + return clientErrors; + } + + protected void logError(GraphQLError error) { + if (error instanceof Throwable) { + log.error("Error executing query!", (Throwable) error); + } else if (error instanceof ExceptionWhileDataFetching) { + log.error("Error executing query {}", error.getMessage(), ((ExceptionWhileDataFetching) error).getException()); + } else { + log.error("Error executing query ({}): {}", error.getClass().getSimpleName(), error.getMessage()); + } + } + + protected List filterGraphQLErrors(List errors) { + return errors.stream() + .filter(this::isClientError) + .map(this::replaceNonNullableFieldWasNullError) + .collect(Collectors.toList()); + } + + protected boolean isClientError(GraphQLError error) { + if (error instanceof ExceptionWhileDataFetching) { + return ((ExceptionWhileDataFetching) error).getException() instanceof GraphQLError; + } + return true; + } + + private GraphQLError replaceNonNullableFieldWasNullError(GraphQLError error) { + if (error instanceof NonNullableFieldWasNullError) { + return new RenderableNonNullableFieldWasNullError((NonNullableFieldWasNullError) error); + } else { + return error; + } + } +} diff --git a/src/main/java/graphql/servlet/core/DefaultObjectMapperConfigurer.java b/src/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java similarity index 85% rename from src/main/java/graphql/servlet/core/DefaultObjectMapperConfigurer.java rename to src/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java index 57b5fca8..d7c5b029 100644 --- a/src/main/java/graphql/servlet/core/DefaultObjectMapperConfigurer.java +++ b/src/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java @@ -1,10 +1,10 @@ -package graphql.servlet.core; +package graphql.kickstart.execution.error; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import graphql.servlet.config.ObjectMapperConfigurer; +import graphql.kickstart.execution.config.ObjectMapperConfigurer; /** * @author Andrew Potter diff --git a/src/main/java/graphql/servlet/core/GenericGraphQLError.java b/src/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java similarity index 93% rename from src/main/java/graphql/servlet/core/GenericGraphQLError.java rename to src/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java index c50dc6e8..569a9533 100644 --- a/src/main/java/graphql/servlet/core/GenericGraphQLError.java +++ b/src/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java @@ -1,4 +1,4 @@ -package graphql.servlet.core; +package graphql.kickstart.execution.error; import com.fasterxml.jackson.annotation.JsonIgnore; import graphql.ErrorType; diff --git a/src/main/java/graphql/servlet/core/GraphQLErrorHandler.java b/src/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java similarity index 88% rename from src/main/java/graphql/servlet/core/GraphQLErrorHandler.java rename to src/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java index 666f12a9..3cad4b51 100644 --- a/src/main/java/graphql/servlet/core/GraphQLErrorHandler.java +++ b/src/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java @@ -1,4 +1,4 @@ -package graphql.servlet.core; +package graphql.kickstart.execution.error; import graphql.GraphQLError; diff --git a/src/main/java/graphql/servlet/core/RenderableNonNullableFieldWasNullError.java b/src/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java similarity index 96% rename from src/main/java/graphql/servlet/core/RenderableNonNullableFieldWasNullError.java rename to src/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java index 6522a3d4..f63a717e 100644 --- a/src/main/java/graphql/servlet/core/RenderableNonNullableFieldWasNullError.java +++ b/src/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java @@ -1,4 +1,4 @@ -package graphql.servlet.core; +package graphql.kickstart.execution.error; import com.fasterxml.jackson.annotation.JsonInclude; import graphql.ErrorType; diff --git a/src/main/java/graphql/servlet/input/GraphQLBatchedInvocationInput.java b/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java similarity index 59% rename from src/main/java/graphql/servlet/input/GraphQLBatchedInvocationInput.java rename to src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java index d5f89bfb..95182256 100644 --- a/src/main/java/graphql/servlet/input/GraphQLBatchedInvocationInput.java +++ b/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java @@ -1,6 +1,8 @@ -package graphql.servlet.input; +package graphql.kickstart.execution.input; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import java.util.List; /** diff --git a/src/main/java/graphql/servlet/input/GraphQLInvocationInput.java b/src/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java similarity index 51% rename from src/main/java/graphql/servlet/input/GraphQLInvocationInput.java rename to src/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java index acf88d50..2f90eab0 100644 --- a/src/main/java/graphql/servlet/input/GraphQLInvocationInput.java +++ b/src/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java @@ -1,4 +1,4 @@ -package graphql.servlet.input; +package graphql.kickstart.execution.input; public interface GraphQLInvocationInput { diff --git a/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java b/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java new file mode 100644 index 00000000..93a4a847 --- /dev/null +++ b/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java @@ -0,0 +1,59 @@ +package graphql.kickstart.execution.input; + +import graphql.ExecutionInput; +import graphql.execution.ExecutionId; +import graphql.schema.GraphQLSchema; +import graphql.kickstart.execution.context.GraphQLContext; +import graphql.servlet.core.internal.GraphQLRequest; +import java.util.Optional; +import javax.security.auth.Subject; +import org.dataloader.DataLoaderRegistry; + +/** + * Represents a single GraphQL execution. + */ +public class GraphQLSingleInvocationInput implements GraphQLInvocationInput { + + private final GraphQLSchema schema; + + private final ExecutionInput executionInput; + + private final Subject subject; + + public GraphQLSingleInvocationInput(GraphQLRequest request, GraphQLSchema schema, GraphQLContext context, + Object root) { + this.schema = schema; + this.executionInput = createExecutionInput(request, context, root); + subject = context.getSubject().orElse(null); + } + + /** + * @return the schema to use to execute this query. + */ + public GraphQLSchema getSchema() { + return schema; + } + + /** + * @return a subject to execute the query as. + */ + public Optional getSubject() { + return Optional.ofNullable(subject); + } + + private ExecutionInput createExecutionInput(GraphQLRequest graphQLRequest, GraphQLContext context, Object root) { + return ExecutionInput.newExecutionInput() + .query(graphQLRequest.getQuery()) + .operationName(graphQLRequest.getOperationName()) + .context(context) + .root(root) + .variables(graphQLRequest.getVariables()) + .dataLoaderRegistry(context.getDataLoaderRegistry().orElse(new DataLoaderRegistry())) + .executionId(ExecutionId.generate()) + .build(); + } + + public ExecutionInput getExecutionInput() { + return executionInput; + } +} diff --git a/src/main/java/graphql/servlet/input/NoOpBatchInputPreProcessor.java b/src/main/java/graphql/kickstart/execution/input/NoOpBatchInputPreProcessor.java similarity index 78% rename from src/main/java/graphql/servlet/input/NoOpBatchInputPreProcessor.java rename to src/main/java/graphql/kickstart/execution/input/NoOpBatchInputPreProcessor.java index 6b19d93b..4e008f36 100644 --- a/src/main/java/graphql/servlet/input/NoOpBatchInputPreProcessor.java +++ b/src/main/java/graphql/kickstart/execution/input/NoOpBatchInputPreProcessor.java @@ -1,5 +1,7 @@ -package graphql.servlet.input; +package graphql.kickstart.execution.input; +import graphql.servlet.input.BatchInputPreProcessResult; +import graphql.servlet.input.BatchInputPreProcessor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/src/main/java/graphql/servlet/input/PerQueryBatchedInvocationInput.java b/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java similarity index 85% rename from src/main/java/graphql/servlet/input/PerQueryBatchedInvocationInput.java rename to src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java index 4eff9b92..87948528 100644 --- a/src/main/java/graphql/servlet/input/PerQueryBatchedInvocationInput.java +++ b/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java @@ -1,8 +1,8 @@ -package graphql.servlet.input; +package graphql.kickstart.execution.input; import graphql.schema.GraphQLSchema; -import graphql.servlet.context.ContextSetting; -import graphql.servlet.context.GraphQLContext; +import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.execution.context.GraphQLContext; import graphql.servlet.core.internal.GraphQLRequest; import java.util.List; diff --git a/src/main/java/graphql/servlet/input/PerRequestBatchedInvocationInput.java b/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java similarity index 85% rename from src/main/java/graphql/servlet/input/PerRequestBatchedInvocationInput.java rename to src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java index 11714059..f03f25fc 100644 --- a/src/main/java/graphql/servlet/input/PerRequestBatchedInvocationInput.java +++ b/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java @@ -1,8 +1,8 @@ -package graphql.servlet.input; +package graphql.kickstart.execution.input; import graphql.schema.GraphQLSchema; -import graphql.servlet.context.ContextSetting; -import graphql.servlet.context.GraphQLContext; +import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.execution.context.GraphQLContext; import graphql.servlet.core.internal.GraphQLRequest; import java.util.List; diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java b/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java new file mode 100644 index 00000000..71478b3c --- /dev/null +++ b/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java @@ -0,0 +1,220 @@ +package graphql.kickstart.execution.instrumentation; + +import graphql.ExecutionResult; +import graphql.execution.ExecutionId; +import graphql.execution.ExecutionPath; +import graphql.execution.FieldValueInfo; +import graphql.execution.MergedField; +import graphql.execution.instrumentation.DeferredFieldInstrumentationContext; +import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; +import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.parameters.InstrumentationDeferredFieldParameters; +import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import lombok.extern.slf4j.Slf4j; +import org.dataloader.DataLoaderRegistry; + +/** + * Handles logic common to tracking approaches. + */ +@Slf4j +public abstract class AbstractTrackingApproach implements TrackingApproach { + + private final DataLoaderRegistry dataLoaderRegistry; + + private final RequestStack stack = new RequestStack(); + + public AbstractTrackingApproach(DataLoaderRegistry dataLoaderRegistry) { + this.dataLoaderRegistry = dataLoaderRegistry; + } + + /** + * @return allows extending classes to modify the stack. + */ + protected RequestStack getStack() { + return stack; + } + + @Override + public ExecutionStrategyInstrumentationContext beginExecutionStrategy( + InstrumentationExecutionStrategyParameters parameters) { + ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); + ExecutionPath path = parameters.getExecutionStrategyParameters().getPath(); + int parentLevel = path.getLevel(); + int curLevel = parentLevel + 1; + int fieldCount = parameters.getExecutionStrategyParameters().getFields().size(); + synchronized (stack) { + stack.increaseExpectedFetchCount(executionId, curLevel, fieldCount); + stack.increaseHappenedStrategyCalls(executionId, curLevel); + } + + return new ExecutionStrategyInstrumentationContext() { + @Override + public void onDispatched(CompletableFuture result) { + + } + + @Override + public void onCompleted(ExecutionResult result, Throwable t) { + + } + + @Override + public void onFieldValuesInfo(List fieldValueInfoList) { + synchronized (stack) { + stack.setStatus(executionId, handleOnFieldValuesInfo(fieldValueInfoList, stack, executionId, curLevel)); + if (stack.allReady()) { + dispatchWithoutLocking(); + } + } + } + + @Override + public void onDeferredField(MergedField field) { + // fake fetch count for this field + synchronized (stack) { + stack.increaseFetchCount(executionId, curLevel); + stack.setStatus(executionId, dispatchIfNeeded(stack, executionId, curLevel)); + if (stack.allReady()) { + dispatchWithoutLocking(); + } + } + } + }; + } + + // + // thread safety : called with synchronised(stack) + // + private boolean handleOnFieldValuesInfo(List fieldValueInfoList, RequestStack stack, + ExecutionId executionId, int curLevel) { + stack.increaseHappenedOnFieldValueCalls(executionId, curLevel); + int expectedStrategyCalls = 0; + for (FieldValueInfo fieldValueInfo : fieldValueInfoList) { + if (fieldValueInfo.getCompleteValueType() == FieldValueInfo.CompleteValueType.OBJECT) { + expectedStrategyCalls++; + } else if (fieldValueInfo.getCompleteValueType() == FieldValueInfo.CompleteValueType.LIST) { + expectedStrategyCalls += getCountForList(fieldValueInfo); + } + } + stack.increaseExpectedStrategyCalls(executionId, curLevel + 1, expectedStrategyCalls); + return dispatchIfNeeded(stack, executionId, curLevel + 1); + } + + private int getCountForList(FieldValueInfo fieldValueInfo) { + int result = 0; + for (FieldValueInfo cvi : fieldValueInfo.getFieldValueInfos()) { + if (cvi.getCompleteValueType() == FieldValueInfo.CompleteValueType.OBJECT) { + result++; + } else if (cvi.getCompleteValueType() == FieldValueInfo.CompleteValueType.LIST) { + result += getCountForList(cvi); + } + } + return result; + } + + @Override + public DeferredFieldInstrumentationContext beginDeferredField(InstrumentationDeferredFieldParameters parameters) { + ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); + int level = parameters.getExecutionStrategyParameters().getPath().getLevel(); + synchronized (stack) { + stack.clearAndMarkCurrentLevelAsReady(executionId, level); + } + + return new DeferredFieldInstrumentationContext() { + @Override + public void onDispatched(CompletableFuture result) { + + } + + @Override + public void onCompleted(ExecutionResult result, Throwable t) { + } + + @Override + public void onFieldValueInfo(FieldValueInfo fieldValueInfo) { + synchronized (stack) { + stack.setStatus(executionId, + handleOnFieldValuesInfo(Collections.singletonList(fieldValueInfo), stack, executionId, level)); + if (stack.allReady()) { + dispatchWithoutLocking(); + } + } + } + }; + } + + @Override + public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { + ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); + ExecutionPath path = parameters.getEnvironment().getExecutionStepInfo().getPath(); + int level = path.getLevel(); + return new InstrumentationContext() { + + @Override + public void onDispatched(CompletableFuture result) { + synchronized (stack) { + stack.increaseFetchCount(executionId, level); + stack.setStatus(executionId, dispatchIfNeeded(stack, executionId, level)); + + if (stack.allReady()) { + dispatchWithoutLocking(); + } + } + } + + @Override + public void onCompleted(Object result, Throwable t) { + } + }; + } + + @Override + public void removeTracking(ExecutionId executionId) { + synchronized (stack) { + stack.removeExecution(executionId); + if (stack.allReady()) { + dispatchWithoutLocking(); + } + } + } + + + // + // thread safety : called with synchronised(stack) + // + private boolean dispatchIfNeeded(RequestStack stack, ExecutionId executionId, int level) { + if (levelReady(stack, executionId, level)) { + return stack.dispatchIfNotDispatchedBefore(executionId, level); + } + return false; + } + + // + // thread safety : called with synchronised(stack) + // + private boolean levelReady(RequestStack stack, ExecutionId executionId, int level) { + if (level == 1) { + // level 1 is special: there is only one strategy call and that's it + return stack.allFetchesHappened(executionId, 1); + } + return (levelReady(stack, executionId, level - 1) && stack.allOnFieldCallsHappened(executionId, level - 1) + && stack.allStrategyCallsHappened(executionId, level) && stack.allFetchesHappened(executionId, level)); + } + + @Override + public void dispatch() { + synchronized (stack) { + dispatchWithoutLocking(); + } + } + + private void dispatchWithoutLocking() { + log.debug("Dispatching data loaders ({})", dataLoaderRegistry.getKeys()); + dataLoaderRegistry.dispatchAll(); + stack.allReset(); + } +} diff --git a/src/main/java/graphql/servlet/instrumentation/ConfigurableDispatchInstrumentation.java b/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java similarity index 99% rename from src/main/java/graphql/servlet/instrumentation/ConfigurableDispatchInstrumentation.java rename to src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java index d2c05164..0f22c0cb 100644 --- a/src/main/java/graphql/servlet/instrumentation/ConfigurableDispatchInstrumentation.java +++ b/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java @@ -1,4 +1,4 @@ -package graphql.servlet.instrumentation; +package graphql.kickstart.execution.instrumentation; import graphql.ExecutionResult; import graphql.ExecutionResultImpl; diff --git a/src/main/java/graphql/servlet/instrumentation/DataLoaderDispatcherInstrumentationState.java b/src/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java similarity index 96% rename from src/main/java/graphql/servlet/instrumentation/DataLoaderDispatcherInstrumentationState.java rename to src/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java index c50c5d86..efc56687 100644 --- a/src/main/java/graphql/servlet/instrumentation/DataLoaderDispatcherInstrumentationState.java +++ b/src/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java @@ -1,4 +1,4 @@ -package graphql.servlet.instrumentation; +package graphql.kickstart.execution.instrumentation; import graphql.execution.ExecutionId; import graphql.execution.instrumentation.InstrumentationState; diff --git a/src/main/java/graphql/servlet/instrumentation/FieldLevelTrackingApproach.java b/src/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java similarity index 94% rename from src/main/java/graphql/servlet/instrumentation/FieldLevelTrackingApproach.java rename to src/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java index c2a6101a..7b9e644e 100644 --- a/src/main/java/graphql/servlet/instrumentation/FieldLevelTrackingApproach.java +++ b/src/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java @@ -1,4 +1,4 @@ -package graphql.servlet.instrumentation; +package graphql.kickstart.execution.instrumentation; import graphql.Internal; import graphql.execution.ExecutionId; diff --git a/src/main/java/graphql/servlet/instrumentation/NoOpInstrumentationProvider.java b/src/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java similarity index 72% rename from src/main/java/graphql/servlet/instrumentation/NoOpInstrumentationProvider.java rename to src/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java index 7f09ff7c..17b635ff 100644 --- a/src/main/java/graphql/servlet/instrumentation/NoOpInstrumentationProvider.java +++ b/src/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java @@ -1,8 +1,8 @@ -package graphql.servlet.instrumentation; +package graphql.kickstart.execution.instrumentation; import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.SimpleInstrumentation; -import graphql.servlet.config.InstrumentationProvider; +import graphql.kickstart.execution.config.InstrumentationProvider; public class NoOpInstrumentationProvider implements InstrumentationProvider { diff --git a/src/main/java/graphql/servlet/instrumentation/RequestLevelTrackingApproach.java b/src/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java similarity index 94% rename from src/main/java/graphql/servlet/instrumentation/RequestLevelTrackingApproach.java rename to src/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java index e6ae0fd2..53893d05 100644 --- a/src/main/java/graphql/servlet/instrumentation/RequestLevelTrackingApproach.java +++ b/src/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java @@ -1,4 +1,4 @@ -package graphql.servlet.instrumentation; +package graphql.kickstart.execution.instrumentation; import graphql.execution.ExecutionId; import graphql.execution.instrumentation.InstrumentationState; @@ -25,4 +25,4 @@ public InstrumentationState createState(ExecutionId executionId) { return null; } -} \ No newline at end of file +} diff --git a/src/main/java/graphql/servlet/instrumentation/RequestStack.java b/src/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java similarity index 99% rename from src/main/java/graphql/servlet/instrumentation/RequestStack.java rename to src/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java index 0b459dd1..800d13fd 100644 --- a/src/main/java/graphql/servlet/instrumentation/RequestStack.java +++ b/src/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java @@ -1,4 +1,4 @@ -package graphql.servlet.instrumentation; +package graphql.kickstart.execution.instrumentation; import graphql.Assert; import graphql.execution.ExecutionId; @@ -302,4 +302,4 @@ public void clearAndMarkCurrentLevelAsReady(ExecutionId executionId, int level) } activeRequests.get(executionId).clearAndMarkCurrentLevelAsReady(level); } -} \ No newline at end of file +} diff --git a/src/main/java/graphql/servlet/instrumentation/TrackingApproach.java b/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java similarity index 97% rename from src/main/java/graphql/servlet/instrumentation/TrackingApproach.java rename to src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java index 5d359caf..d91e4c32 100644 --- a/src/main/java/graphql/servlet/instrumentation/TrackingApproach.java +++ b/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java @@ -1,4 +1,4 @@ -package graphql.servlet.instrumentation; +package graphql.kickstart.execution.instrumentation; import graphql.execution.ExecutionId; import graphql.execution.instrumentation.DeferredFieldInstrumentationContext; diff --git a/src/main/java/graphql/servlet/core/SubscriptionConnectionListener.java b/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java similarity index 61% rename from src/main/java/graphql/servlet/core/SubscriptionConnectionListener.java rename to src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java index 8325d1de..33f6294a 100644 --- a/src/main/java/graphql/servlet/core/SubscriptionConnectionListener.java +++ b/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java @@ -1,4 +1,4 @@ -package graphql.servlet.core; +package graphql.kickstart.execution.subscription; /** * Marker interface diff --git a/src/main/java/graphql/servlet/core/SubscriptionException.java b/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java similarity index 85% rename from src/main/java/graphql/servlet/core/SubscriptionException.java rename to src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java index c1ee9d29..7bc19906 100644 --- a/src/main/java/graphql/servlet/core/SubscriptionException.java +++ b/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java @@ -1,4 +1,4 @@ -package graphql.servlet.core; +package graphql.kickstart.execution.subscription; public class SubscriptionException extends Exception { diff --git a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java index 55bffd2b..e94fe446 100644 --- a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java @@ -1,10 +1,10 @@ package graphql.servlet; import graphql.schema.GraphQLFieldDefinition; -import graphql.servlet.config.GraphQLConfiguration; +import graphql.kickstart.execution.config.GraphQLConfiguration; import graphql.servlet.core.GraphQLMBean; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.GraphQLQueryInvoker; +import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.core.GraphQLServletListener; import graphql.servlet.core.internal.GraphQLRequest; import graphql.servlet.input.GraphQLInvocationInputFactory; diff --git a/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java b/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java index 4b9a2d30..1664cfc3 100644 --- a/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java +++ b/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java @@ -1,6 +1,6 @@ package graphql.servlet; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; import graphql.servlet.input.GraphQLInvocationInputFactory; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java b/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java index 9e46f152..24a671c3 100644 --- a/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java @@ -1,6 +1,6 @@ package graphql.servlet; -import graphql.servlet.config.GraphQLConfiguration; +import graphql.kickstart.execution.config.GraphQLConfiguration; import java.util.Objects; diff --git a/src/main/java/graphql/servlet/DefaultGraphQLServlet.java b/src/main/java/graphql/servlet/DefaultGraphQLServlet.java index 3be92e86..f1d22119 100644 --- a/src/main/java/graphql/servlet/DefaultGraphQLServlet.java +++ b/src/main/java/graphql/servlet/DefaultGraphQLServlet.java @@ -1,7 +1,7 @@ package graphql.servlet; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.GraphQLQueryInvoker; +import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.input.GraphQLInvocationInputFactory; public class DefaultGraphQLServlet extends AbstractGraphQLHttpServlet { diff --git a/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java index b0e37ab9..0e47795b 100644 --- a/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java @@ -1,10 +1,10 @@ package graphql.servlet; import graphql.GraphQLException; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; import graphql.servlet.core.internal.GraphQLRequest; -import graphql.servlet.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; import java.util.HashMap; diff --git a/src/main/java/graphql/servlet/GraphQLHttpServlet.java b/src/main/java/graphql/servlet/GraphQLHttpServlet.java index b16c072b..2709d046 100644 --- a/src/main/java/graphql/servlet/GraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/GraphQLHttpServlet.java @@ -2,8 +2,8 @@ import graphql.schema.GraphQLSchema; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.GraphQLQueryInvoker; -import graphql.servlet.config.GraphQLConfiguration; +import graphql.kickstart.execution.GraphQLQueryInvoker; +import graphql.kickstart.execution.config.GraphQLConfiguration; import graphql.servlet.input.GraphQLInvocationInputFactory; /** diff --git a/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java index a33ef31e..1232504a 100644 --- a/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java @@ -1,8 +1,8 @@ package graphql.servlet; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; import javax.servlet.ServletException; diff --git a/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java index 06094bd9..1fc5db23 100644 --- a/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java @@ -3,11 +3,11 @@ import static java.util.stream.Collectors.joining; import graphql.GraphQLException; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; import graphql.servlet.core.internal.GraphQLRequest; import graphql.servlet.core.internal.VariableMapper; -import graphql.servlet.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java b/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java index 7581a3cb..7b73d6d2 100644 --- a/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java +++ b/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java @@ -4,10 +4,10 @@ import static java.util.stream.Collectors.joining; import graphql.GraphQLException; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; import graphql.servlet.core.internal.GraphQLRequest; -import graphql.servlet.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; import java.util.List; diff --git a/src/main/java/graphql/servlet/GraphQLQueryProcessor.java b/src/main/java/graphql/servlet/GraphQLQueryProcessor.java deleted file mode 100644 index 9ac9b839..00000000 --- a/src/main/java/graphql/servlet/GraphQLQueryProcessor.java +++ /dev/null @@ -1,9 +0,0 @@ -package graphql.servlet; - -class GraphQLQueryProcessor { - - GraphQLQueryResult execute() { - return null; - } - -} diff --git a/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java b/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java index ba358b31..4c4c87da 100644 --- a/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java +++ b/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java @@ -1,8 +1,8 @@ package graphql.servlet; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.GraphQLQueryInvoker; -import graphql.servlet.core.SubscriptionConnectionListener; +import graphql.kickstart.execution.GraphQLQueryInvoker; +import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; import graphql.servlet.input.GraphQLInvocationInputFactory; import graphql.servlet.core.internal.*; import org.slf4j.Logger; diff --git a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java index 68f4a1f7..6c1b2fc4 100644 --- a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java +++ b/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java @@ -3,13 +3,14 @@ import static graphql.servlet.QueryResponseWriter.createWriter; import graphql.GraphQLException; -import graphql.servlet.config.GraphQLConfiguration; -import graphql.servlet.core.GraphQLQueryInvoker; +import graphql.kickstart.execution.GraphQLQueryResult; +import graphql.kickstart.execution.config.GraphQLConfiguration; +import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.input.BatchInputPreProcessResult; import graphql.servlet.input.BatchInputPreProcessor; -import graphql.servlet.input.GraphQLBatchedInvocationInput; -import graphql.servlet.input.GraphQLInvocationInput; -import graphql.servlet.input.GraphQLSingleInvocationInput; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -79,7 +80,7 @@ private GraphQLQueryResult invokeBatched(GraphQLBatchedInvocationInput batchedIn return queryInvoker.query(result.getBatchedInvocationInput()); } - return new GraphQLErrorQueryResult(result.getStatusCode(), result.getStatusMessage()); + return GraphQLQueryResult.createError(result.getStatusCode(), result.getStatusMessage()); } } diff --git a/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java b/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java index 80d37490..6a743658 100644 --- a/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java @@ -3,6 +3,33 @@ import static graphql.schema.GraphQLObjectType.newObject; import static graphql.schema.GraphQLSchema.newSchema; +import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; +import graphql.execution.preparsed.PreparsedDocumentProvider; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLType; +import graphql.kickstart.execution.config.DefaultExecutionStrategyProvider; +import graphql.servlet.config.DefaultGraphQLSchemaProvider; +import graphql.kickstart.execution.config.ExecutionStrategyProvider; +import graphql.kickstart.execution.config.GraphQLCodeRegistryProvider; +import graphql.servlet.osgi.GraphQLMutationProvider; +import graphql.servlet.osgi.GraphQLProvider; +import graphql.servlet.osgi.GraphQLQueryProvider; +import graphql.servlet.config.GraphQLSchemaProvider; +import graphql.servlet.osgi.GraphQLSubscriptionProvider; +import graphql.servlet.osgi.GraphQLTypesProvider; +import graphql.kickstart.execution.config.InstrumentationProvider; +import graphql.servlet.context.DefaultGraphQLContextBuilder; +import graphql.servlet.context.GraphQLContextBuilder; +import graphql.kickstart.execution.error.DefaultGraphQLErrorHandler; +import graphql.servlet.core.DefaultGraphQLRootObjectBuilder; +import graphql.kickstart.execution.error.GraphQLErrorHandler; +import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLQueryInvoker; +import graphql.servlet.core.GraphQLRootObjectBuilder; +import graphql.servlet.core.GraphQLServletListener; +import graphql.servlet.input.GraphQLInvocationInputFactory; +import graphql.kickstart.execution.instrumentation.NoOpInstrumentationProvider; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -11,29 +38,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; - -import graphql.servlet.config.DefaultExecutionStrategyProvider; -import graphql.servlet.context.DefaultGraphQLContextBuilder; -import graphql.servlet.core.DefaultGraphQLErrorHandler; -import graphql.servlet.core.DefaultGraphQLRootObjectBuilder; -import graphql.servlet.config.DefaultGraphQLSchemaProvider; -import graphql.servlet.config.ExecutionStrategyProvider; -import graphql.servlet.config.GraphQLCodeRegistryProvider; -import graphql.servlet.context.GraphQLContextBuilder; -import graphql.servlet.core.GraphQLErrorHandler; -import graphql.servlet.config.GraphQLMutationProvider; -import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.config.GraphQLProvider; -import graphql.servlet.core.GraphQLQueryInvoker; -import graphql.servlet.config.GraphQLQueryProvider; -import graphql.servlet.core.GraphQLRootObjectBuilder; -import graphql.servlet.config.GraphQLSchemaProvider; -import graphql.servlet.core.GraphQLServletListener; -import graphql.servlet.config.GraphQLSubscriptionProvider; -import graphql.servlet.config.GraphQLTypesProvider; -import graphql.servlet.config.InstrumentationProvider; -import graphql.servlet.instrumentation.NoOpInstrumentationProvider; -import graphql.servlet.input.GraphQLInvocationInputFactory; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; @@ -42,331 +46,344 @@ import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; -import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; -import graphql.execution.preparsed.PreparsedDocumentProvider; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLType; -import graphql.schema.GraphQLCodeRegistry; - @Component( - service={javax.servlet.http.HttpServlet.class,javax.servlet.Servlet.class}, - property = {"alias=/graphql", "jmx.objectname=graphql.servlet:type=graphql"} + service = {javax.servlet.http.HttpServlet.class, javax.servlet.Servlet.class}, + property = {"alias=/graphql", "jmx.objectname=graphql.servlet:type=graphql"} ) public class OsgiGraphQLHttpServlet extends AbstractGraphQLHttpServlet { - private final List queryProviders = new ArrayList<>(); - private final List mutationProviders = new ArrayList<>(); - private final List subscriptionProviders = new ArrayList<>(); - private final List typesProviders = new ArrayList<>(); - - private final GraphQLQueryInvoker queryInvoker; - private final GraphQLInvocationInputFactory invocationInputFactory; - private final GraphQLObjectMapper graphQLObjectMapper; - - private GraphQLContextBuilder contextBuilder = new DefaultGraphQLContextBuilder(); - private GraphQLRootObjectBuilder rootObjectBuilder = new DefaultGraphQLRootObjectBuilder(); - private ExecutionStrategyProvider executionStrategyProvider = new DefaultExecutionStrategyProvider(); - private InstrumentationProvider instrumentationProvider = new NoOpInstrumentationProvider(); - private GraphQLErrorHandler errorHandler = new DefaultGraphQLErrorHandler(); - private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE; - private GraphQLCodeRegistryProvider codeRegistryProvider = () -> GraphQLCodeRegistry.newCodeRegistry().build(); - - private GraphQLSchemaProvider schemaProvider; - - private ScheduledExecutorService executor; - private ScheduledFuture updateFuture; - private int schemaUpdateDelay; - - @interface Config { - int schema_update_delay() default 0; - } - - @Activate - public void activate(Config config) { - this.schemaUpdateDelay = config.schema_update_delay(); - if (schemaUpdateDelay!=0) - executor = Executors.newSingleThreadScheduledExecutor(); - } - - @Deactivate - public void deactivate() { - if (executor!=null) executor.shutdown(); - } - - @Override - protected GraphQLQueryInvoker getQueryInvoker() { - return queryInvoker; - } - - @Override - protected GraphQLInvocationInputFactory getInvocationInputFactory() { - return invocationInputFactory; - } - - @Override - protected GraphQLObjectMapper getGraphQLObjectMapper() { - return graphQLObjectMapper; - } + private final List queryProviders = new ArrayList<>(); + private final List mutationProviders = new ArrayList<>(); + private final List subscriptionProviders = new ArrayList<>(); + private final List typesProviders = new ArrayList<>(); + + private final GraphQLQueryInvoker queryInvoker; + private final GraphQLInvocationInputFactory invocationInputFactory; + private final GraphQLObjectMapper graphQLObjectMapper; + + private GraphQLContextBuilder contextBuilder = new DefaultGraphQLContextBuilder(); + private GraphQLRootObjectBuilder rootObjectBuilder = new DefaultGraphQLRootObjectBuilder(); + private ExecutionStrategyProvider executionStrategyProvider = new DefaultExecutionStrategyProvider(); + private InstrumentationProvider instrumentationProvider = new NoOpInstrumentationProvider(); + private GraphQLErrorHandler errorHandler = new DefaultGraphQLErrorHandler(); + private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE; + private GraphQLCodeRegistryProvider codeRegistryProvider = () -> GraphQLCodeRegistry.newCodeRegistry().build(); + + private GraphQLSchemaProvider schemaProvider; + + private ScheduledExecutorService executor; + private ScheduledFuture updateFuture; + private int schemaUpdateDelay; + + public OsgiGraphQLHttpServlet() { + updateSchema(); + + this.queryInvoker = GraphQLQueryInvoker.newBuilder() + .withPreparsedDocumentProvider(this::getPreparsedDocumentProvider) + .withInstrumentation(() -> this.getInstrumentationProvider().getInstrumentation()) + .withExecutionStrategyProvider(this::getExecutionStrategyProvider).build(); + + this.invocationInputFactory = GraphQLInvocationInputFactory.newBuilder(this::getSchemaProvider) + .withGraphQLContextBuilder(this::getContextBuilder) + .withGraphQLRootObjectBuilder(this::getRootObjectBuilder) + .build(); + + this.graphQLObjectMapper = GraphQLObjectMapper.newBuilder() + .withGraphQLErrorHandler(this::getErrorHandler) + .build(); + } + + @Activate + public void activate(Config config) { + this.schemaUpdateDelay = config.schema_update_delay(); + if (schemaUpdateDelay != 0) { + executor = Executors.newSingleThreadScheduledExecutor(); + } + } + + @Deactivate + public void deactivate() { + if (executor != null) { + executor.shutdown(); + } + } + + @Override + protected GraphQLQueryInvoker getQueryInvoker() { + return queryInvoker; + } + + @Override + protected GraphQLInvocationInputFactory getInvocationInputFactory() { + return invocationInputFactory; + } + + @Override + protected GraphQLObjectMapper getGraphQLObjectMapper() { + return graphQLObjectMapper; + } + + @Override + protected boolean isAsyncServletMode() { + return false; + } + + protected void updateSchema() { + if (schemaUpdateDelay == 0) { + doUpdateSchema(); + } else { + if (updateFuture != null) { + updateFuture.cancel(true); + } - @Override - protected boolean isAsyncServletMode() { - return false; + updateFuture = executor.schedule(new Runnable() { + @Override + public void run() { + doUpdateSchema(); + } + }, schemaUpdateDelay, TimeUnit.MILLISECONDS); } + } - public OsgiGraphQLHttpServlet() { - updateSchema(); - - this.queryInvoker = GraphQLQueryInvoker.newBuilder() - .withPreparsedDocumentProvider(this::getPreparsedDocumentProvider) - .withInstrumentation(() -> this.getInstrumentationProvider().getInstrumentation()) - .withExecutionStrategyProvider(this::getExecutionStrategyProvider).build(); + private void doUpdateSchema() { + final GraphQLObjectType.Builder queryTypeBuilder = newObject().name("Query").description("Root query type"); - this.invocationInputFactory = GraphQLInvocationInputFactory.newBuilder(this::getSchemaProvider) - .withGraphQLContextBuilder(this::getContextBuilder) - .withGraphQLRootObjectBuilder(this::getRootObjectBuilder) - .build(); - - this.graphQLObjectMapper = GraphQLObjectMapper.newBuilder() - .withGraphQLErrorHandler(this::getErrorHandler) - .build(); + for (GraphQLQueryProvider provider : queryProviders) { + if (provider.getQueries() != null && !provider.getQueries().isEmpty()) { + provider.getQueries().forEach(queryTypeBuilder::field); + } } - protected void updateSchema() { - if (schemaUpdateDelay==0) { - doUpdateSchema(); - } - else { - if (updateFuture!=null) - updateFuture.cancel(true); - - updateFuture = executor.schedule(new Runnable() { - @Override - public void run() { - doUpdateSchema(); - } - }, schemaUpdateDelay, TimeUnit.MILLISECONDS); - } + final Set types = new HashSet<>(); + for (GraphQLTypesProvider typesProvider : typesProviders) { + types.addAll(typesProvider.getTypes()); } - private void doUpdateSchema() { - final GraphQLObjectType.Builder queryTypeBuilder = newObject().name("Query").description("Root query type"); + GraphQLObjectType mutationType = null; - for (GraphQLQueryProvider provider : queryProviders) { - if (provider.getQueries() != null && !provider.getQueries().isEmpty()) { - provider.getQueries().forEach(queryTypeBuilder::field); - } - } + if (!mutationProviders.isEmpty()) { + final GraphQLObjectType.Builder mutationTypeBuilder = newObject().name("Mutation") + .description("Root mutation type"); - final Set types = new HashSet<>(); - for (GraphQLTypesProvider typesProvider : typesProviders) { - types.addAll(typesProvider.getTypes()); - } - - GraphQLObjectType mutationType = null; - - if (!mutationProviders.isEmpty()) { - final GraphQLObjectType.Builder mutationTypeBuilder = newObject().name("Mutation").description("Root mutation type"); - - for (GraphQLMutationProvider provider : mutationProviders) { - provider.getMutations().forEach(mutationTypeBuilder::field); - } - - if (!mutationTypeBuilder.build().getFieldDefinitions().isEmpty()) { - mutationType = mutationTypeBuilder.build(); - } - } + for (GraphQLMutationProvider provider : mutationProviders) { + provider.getMutations().forEach(mutationTypeBuilder::field); + } - GraphQLObjectType subscriptionType = null; - - if (!subscriptionProviders.isEmpty()) { - final GraphQLObjectType.Builder subscriptionTypeBuilder = newObject().name("Subscription").description("Root subscription type"); - - for (GraphQLSubscriptionProvider provider : subscriptionProviders) { - provider.getSubscriptions().forEach(subscriptionTypeBuilder::field); - } - - if (!subscriptionTypeBuilder.build().getFieldDefinitions().isEmpty()) { - subscriptionType = subscriptionTypeBuilder.build(); - } - } - - this.schemaProvider = new DefaultGraphQLSchemaProvider(newSchema().query(queryTypeBuilder.build()) - .mutation(mutationType) - .subscription(subscriptionType) - .additionalTypes(types) - .codeRegistry(codeRegistryProvider.getCodeRegistry()) - .build()); + if (!mutationTypeBuilder.build().getFieldDefinitions().isEmpty()) { + mutationType = mutationTypeBuilder.build(); + } } - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void bindProvider(GraphQLProvider provider) { - if (provider instanceof GraphQLQueryProvider) { - queryProviders.add((GraphQLQueryProvider) provider); - } - if (provider instanceof GraphQLMutationProvider) { - mutationProviders.add((GraphQLMutationProvider) provider); - } - if (provider instanceof GraphQLSubscriptionProvider) { - subscriptionProviders.add((GraphQLSubscriptionProvider) provider); - } - if (provider instanceof GraphQLTypesProvider) { - typesProviders.add((GraphQLTypesProvider) provider); - } - if (provider instanceof GraphQLCodeRegistryProvider) { - codeRegistryProvider = (GraphQLCodeRegistryProvider) provider; - } - updateSchema(); - } - public void unbindProvider(GraphQLProvider provider) { - if (provider instanceof GraphQLQueryProvider) { - queryProviders.remove(provider); - } - if (provider instanceof GraphQLMutationProvider) { - mutationProviders.remove(provider); - } - if (provider instanceof GraphQLSubscriptionProvider) { - subscriptionProviders.remove(provider); - } - if (provider instanceof GraphQLTypesProvider) { - typesProviders.remove(provider); - } - if (provider instanceof GraphQLCodeRegistryProvider) { - codeRegistryProvider = () -> GraphQLCodeRegistry.newCodeRegistry().build(); - } - updateSchema(); - } + GraphQLObjectType subscriptionType = null; - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void bindQueryProvider(GraphQLQueryProvider queryProvider) { - queryProviders.add(queryProvider); - updateSchema(); - } - public void unbindQueryProvider(GraphQLQueryProvider queryProvider) { - queryProviders.remove(queryProvider); - updateSchema(); - } + if (!subscriptionProviders.isEmpty()) { + final GraphQLObjectType.Builder subscriptionTypeBuilder = newObject().name("Subscription") + .description("Root subscription type"); - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void bindMutationProvider(GraphQLMutationProvider mutationProvider) { - mutationProviders.add(mutationProvider); - updateSchema(); - } - public void unbindMutationProvider(GraphQLMutationProvider mutationProvider) { - mutationProviders.remove(mutationProvider); - updateSchema(); - } + for (GraphQLSubscriptionProvider provider : subscriptionProviders) { + provider.getSubscriptions().forEach(subscriptionTypeBuilder::field); + } - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void bindSubscriptionProvider(GraphQLSubscriptionProvider subscriptionProvider) { - subscriptionProviders.add(subscriptionProvider); - updateSchema(); - } - public void unbindSubscriptionProvider(GraphQLSubscriptionProvider subscriptionProvider) { - subscriptionProviders.remove(subscriptionProvider); - updateSchema(); + if (!subscriptionTypeBuilder.build().getFieldDefinitions().isEmpty()) { + subscriptionType = subscriptionTypeBuilder.build(); + } } - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void bindTypesProvider(GraphQLTypesProvider typesProvider) { - typesProviders.add(typesProvider); - updateSchema(); - } - public void unbindTypesProvider(GraphQLTypesProvider typesProvider) { - typesProviders.remove(typesProvider); - updateSchema(); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void bindServletListener(GraphQLServletListener listener) { - this.addListener(listener); - } - public void unbindServletListener(GraphQLServletListener listener) { - this.removeListener(listener); - } + this.schemaProvider = new DefaultGraphQLSchemaProvider(newSchema().query(queryTypeBuilder.build()) + .mutation(mutationType) + .subscription(subscriptionType) + .additionalTypes(types) + .codeRegistry(codeRegistryProvider.getCodeRegistry()) + .build()); + } - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) - public void setContextProvider(GraphQLContextBuilder contextBuilder) { - this.contextBuilder = contextBuilder; - } - public void unsetContextProvider(GraphQLContextBuilder contextBuilder) { - this.contextBuilder = new DefaultGraphQLContextBuilder(); - } - - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) - public void setRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { - this.rootObjectBuilder = rootObjectBuilder; - } - public void unsetRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { - this.rootObjectBuilder = new DefaultGraphQLRootObjectBuilder(); - } - - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy= ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) - public void setExecutionStrategyProvider(ExecutionStrategyProvider provider) { - executionStrategyProvider = provider; - } - public void unsetExecutionStrategyProvider(ExecutionStrategyProvider provider) { - executionStrategyProvider = new DefaultExecutionStrategyProvider(); + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void bindProvider(GraphQLProvider provider) { + if (provider instanceof GraphQLQueryProvider) { + queryProviders.add((GraphQLQueryProvider) provider); } - - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy= ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) - public void setInstrumentationProvider(InstrumentationProvider provider) { - instrumentationProvider = provider; + if (provider instanceof GraphQLMutationProvider) { + mutationProviders.add((GraphQLMutationProvider) provider); } - public void unsetInstrumentationProvider(InstrumentationProvider provider) { - instrumentationProvider = new NoOpInstrumentationProvider(); + if (provider instanceof GraphQLSubscriptionProvider) { + subscriptionProviders.add((GraphQLSubscriptionProvider) provider); } - - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy= ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) - public void setErrorHandler(GraphQLErrorHandler errorHandler) { - this.errorHandler = errorHandler; + if (provider instanceof GraphQLTypesProvider) { + typesProviders.add((GraphQLTypesProvider) provider); } - public void unsetErrorHandler(GraphQLErrorHandler errorHandler) { - this.errorHandler = new DefaultGraphQLErrorHandler(); + if (provider instanceof GraphQLCodeRegistryProvider) { + codeRegistryProvider = (GraphQLCodeRegistryProvider) provider; } + updateSchema(); + } - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy= ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) - public void setPreparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) { - this.preparsedDocumentProvider = preparsedDocumentProvider; + public void unbindProvider(GraphQLProvider provider) { + if (provider instanceof GraphQLQueryProvider) { + queryProviders.remove(provider); } - public void unsetPreparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) { - this.preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE; + if (provider instanceof GraphQLMutationProvider) { + mutationProviders.remove(provider); } - - @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy= ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) - public void bindCodeRegistryProvider(GraphQLCodeRegistryProvider graphQLCodeRegistryProvider) { - this.codeRegistryProvider = graphQLCodeRegistryProvider; - updateSchema(); + if (provider instanceof GraphQLSubscriptionProvider) { + subscriptionProviders.remove(provider); } - public void unbindCodeRegistryProvider(GraphQLCodeRegistryProvider graphQLCodeRegistryProvider) { - this.codeRegistryProvider = () -> GraphQLCodeRegistry.newCodeRegistry().build(); - updateSchema(); + if (provider instanceof GraphQLTypesProvider) { + typesProviders.remove(provider); } - - public GraphQLContextBuilder getContextBuilder() { - return contextBuilder; + if (provider instanceof GraphQLCodeRegistryProvider) { + codeRegistryProvider = () -> GraphQLCodeRegistry.newCodeRegistry().build(); } + updateSchema(); + } - public GraphQLRootObjectBuilder getRootObjectBuilder() { - return rootObjectBuilder; - } + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void bindQueryProvider(GraphQLQueryProvider queryProvider) { + queryProviders.add(queryProvider); + updateSchema(); + } - public ExecutionStrategyProvider getExecutionStrategyProvider() { - return executionStrategyProvider; - } + public void unbindQueryProvider(GraphQLQueryProvider queryProvider) { + queryProviders.remove(queryProvider); + updateSchema(); + } - public InstrumentationProvider getInstrumentationProvider() { - return instrumentationProvider; - } + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void bindMutationProvider(GraphQLMutationProvider mutationProvider) { + mutationProviders.add(mutationProvider); + updateSchema(); + } - public GraphQLErrorHandler getErrorHandler() { - return errorHandler; - } + public void unbindMutationProvider(GraphQLMutationProvider mutationProvider) { + mutationProviders.remove(mutationProvider); + updateSchema(); + } - public PreparsedDocumentProvider getPreparsedDocumentProvider() { - return preparsedDocumentProvider; - } - - public GraphQLSchemaProvider getSchemaProvider() { - return schemaProvider; - } + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void bindSubscriptionProvider(GraphQLSubscriptionProvider subscriptionProvider) { + subscriptionProviders.add(subscriptionProvider); + updateSchema(); + } + + public void unbindSubscriptionProvider(GraphQLSubscriptionProvider subscriptionProvider) { + subscriptionProviders.remove(subscriptionProvider); + updateSchema(); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void bindTypesProvider(GraphQLTypesProvider typesProvider) { + typesProviders.add(typesProvider); + updateSchema(); + } + + public void unbindTypesProvider(GraphQLTypesProvider typesProvider) { + typesProviders.remove(typesProvider); + updateSchema(); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void bindServletListener(GraphQLServletListener listener) { + this.addListener(listener); + } + + public void unbindServletListener(GraphQLServletListener listener) { + this.removeListener(listener); + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) + public void setContextProvider(GraphQLContextBuilder contextBuilder) { + this.contextBuilder = contextBuilder; + } + + public void unsetContextProvider(GraphQLContextBuilder contextBuilder) { + this.contextBuilder = new DefaultGraphQLContextBuilder(); + } + + public void unsetRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { + this.rootObjectBuilder = new DefaultGraphQLRootObjectBuilder(); + } + + public void unsetExecutionStrategyProvider(ExecutionStrategyProvider provider) { + executionStrategyProvider = new DefaultExecutionStrategyProvider(); + } + + public void unsetInstrumentationProvider(InstrumentationProvider provider) { + instrumentationProvider = new NoOpInstrumentationProvider(); + } + + public void unsetErrorHandler(GraphQLErrorHandler errorHandler) { + this.errorHandler = new DefaultGraphQLErrorHandler(); + } + + public void unsetPreparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) { + this.preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE; + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + public void bindCodeRegistryProvider(GraphQLCodeRegistryProvider graphQLCodeRegistryProvider) { + this.codeRegistryProvider = graphQLCodeRegistryProvider; + updateSchema(); + } + + public void unbindCodeRegistryProvider(GraphQLCodeRegistryProvider graphQLCodeRegistryProvider) { + this.codeRegistryProvider = () -> GraphQLCodeRegistry.newCodeRegistry().build(); + updateSchema(); + } + + public GraphQLContextBuilder getContextBuilder() { + return contextBuilder; + } + + public GraphQLRootObjectBuilder getRootObjectBuilder() { + return rootObjectBuilder; + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) + public void setRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { + this.rootObjectBuilder = rootObjectBuilder; + } + + public ExecutionStrategyProvider getExecutionStrategyProvider() { + return executionStrategyProvider; + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + public void setExecutionStrategyProvider(ExecutionStrategyProvider provider) { + executionStrategyProvider = provider; + } + + public InstrumentationProvider getInstrumentationProvider() { + return instrumentationProvider; + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + public void setInstrumentationProvider(InstrumentationProvider provider) { + instrumentationProvider = provider; + } + + public GraphQLErrorHandler getErrorHandler() { + return errorHandler; + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + public void setErrorHandler(GraphQLErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + public PreparsedDocumentProvider getPreparsedDocumentProvider() { + return preparsedDocumentProvider; + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + public void setPreparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) { + this.preparsedDocumentProvider = preparsedDocumentProvider; + } + + public GraphQLSchemaProvider getSchemaProvider() { + return schemaProvider; + } + + @interface Config { + + int schema_update_delay() default 0; + } } diff --git a/src/main/java/graphql/servlet/QueryResponseWriter.java b/src/main/java/graphql/servlet/QueryResponseWriter.java index 01dd96eb..d35420da 100644 --- a/src/main/java/graphql/servlet/QueryResponseWriter.java +++ b/src/main/java/graphql/servlet/QueryResponseWriter.java @@ -1,5 +1,6 @@ package graphql.servlet; +import graphql.kickstart.execution.GraphQLQueryResult; import graphql.servlet.core.GraphQLObjectMapper; import java.io.IOException; import java.util.Objects; @@ -20,8 +21,7 @@ static QueryResponseWriter createWriter( } else if (result.isAsynchronous()) { return new SingleAsynchronousQueryResponseWriter(result.getResult(), graphQLObjectMapper, subscriptionTimeout); } else if (result.isError()) { - GraphQLErrorQueryResult errorResult = (GraphQLErrorQueryResult) result; - return new ErrorQueryResponseWriter(errorResult.getStatusCode(), errorResult.getMessage()); + return new ErrorQueryResponseWriter(result.getStatusCode(), result.getMessage()); } return new SingleQueryResponseWriter(result.getResult(), graphQLObjectMapper); } diff --git a/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java b/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java index bfa7d5f7..15db66ec 100644 --- a/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java +++ b/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java @@ -2,10 +2,10 @@ import graphql.schema.GraphQLSchema; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.GraphQLQueryInvoker; +import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.config.GraphQLSchemaProvider; import graphql.servlet.core.GraphQLServletListener; -import graphql.servlet.config.GraphQLConfiguration; +import graphql.kickstart.execution.config.GraphQLConfiguration; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.util.ArrayList; diff --git a/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java b/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java index 2e62f54f..cf85cf73 100644 --- a/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java +++ b/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java @@ -1,13 +1,11 @@ package graphql.servlet; import static graphql.servlet.HttpRequestHandler.APPLICATION_EVENT_STREAM_UTF8; -import static graphql.servlet.HttpRequestHandler.APPLICATION_JSON_UTF8; import static graphql.servlet.HttpRequestHandler.STATUS_OK; import graphql.ExecutionResult; import graphql.GraphQL; import graphql.servlet.core.GraphQLObjectMapper; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -24,7 +22,7 @@ class SingleAsynchronousQueryResponseWriter implements QueryResponseWriter { @Getter - private final DecoratedExecutionResult result; + private final ExecutionResult result; private final GraphQLObjectMapper graphQLObjectMapper; private final long subscriptionTimeout; diff --git a/src/main/java/graphql/servlet/SingleQueryResponseWriter.java b/src/main/java/graphql/servlet/SingleQueryResponseWriter.java index a0271c61..1b62661b 100644 --- a/src/main/java/graphql/servlet/SingleQueryResponseWriter.java +++ b/src/main/java/graphql/servlet/SingleQueryResponseWriter.java @@ -3,6 +3,7 @@ import static graphql.servlet.HttpRequestHandler.APPLICATION_JSON_UTF8; import static graphql.servlet.HttpRequestHandler.STATUS_OK; +import graphql.ExecutionResult; import graphql.servlet.core.GraphQLObjectMapper; import java.io.IOException; import javax.servlet.http.HttpServletRequest; @@ -12,7 +13,7 @@ @RequiredArgsConstructor class SingleQueryResponseWriter implements QueryResponseWriter { - private final DecoratedExecutionResult result; + private final ExecutionResult result; private final GraphQLObjectMapper graphQLObjectMapper; @Override diff --git a/src/main/java/graphql/servlet/apollo/ApolloScalars.java b/src/main/java/graphql/servlet/apollo/ApolloScalars.java new file mode 100644 index 00000000..4cc34ce1 --- /dev/null +++ b/src/main/java/graphql/servlet/apollo/ApolloScalars.java @@ -0,0 +1,44 @@ +package graphql.servlet.apollo; + +import graphql.schema.Coercing; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; +import graphql.schema.GraphQLScalarType; +import javax.servlet.http.Part; + +public class ApolloScalars { + + public static final GraphQLScalarType Upload = + GraphQLScalarType.newScalar() + .name("Upload") + .description("A file part in a multipart request") + .coercing(new Coercing() { + @Override + public Void serialize(Object dataFetcherResult) { + throw new CoercingSerializeException("Upload is an input-only type"); + } + + @Override + public Part parseValue(Object input) { + if (input instanceof Part) { + return (Part) input; + } else if (null == input) { + return null; + } else { + throw new CoercingParseValueException("Expected type " + + Part.class.getName() + + " but was " + + input.getClass().getName()); + } + } + + @Override + public Part parseLiteral(Object input) { + throw new CoercingParseLiteralException( + "Must use variables to specify Upload values"); + } + }) + .build(); + +} diff --git a/src/main/java/graphql/servlet/core/ApolloSubscriptionConnectionListener.java b/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java similarity index 86% rename from src/main/java/graphql/servlet/core/ApolloSubscriptionConnectionListener.java rename to src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java index ba0eb01d..25f01dd5 100644 --- a/src/main/java/graphql/servlet/core/ApolloSubscriptionConnectionListener.java +++ b/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java @@ -1,5 +1,7 @@ -package graphql.servlet.core; +package graphql.servlet.apollo; +import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; +import graphql.kickstart.execution.subscription.SubscriptionException; import java.time.Duration; import java.util.Optional; diff --git a/src/main/java/graphql/servlet/config/DefaultExecutionStrategyProvider.java b/src/main/java/graphql/servlet/config/DefaultExecutionStrategyProvider.java deleted file mode 100644 index d70a8d97..00000000 --- a/src/main/java/graphql/servlet/config/DefaultExecutionStrategyProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -package graphql.servlet.config; - -import graphql.execution.AsyncExecutionStrategy; -import graphql.execution.ExecutionStrategy; -import graphql.execution.SubscriptionExecutionStrategy; -import graphql.servlet.config.ExecutionStrategyProvider; - -/** - * @author Andrew Potter - */ -public class DefaultExecutionStrategyProvider implements ExecutionStrategyProvider { - - private final ExecutionStrategy queryExecutionStrategy; - private final ExecutionStrategy mutationExecutionStrategy; - private final ExecutionStrategy subscriptionExecutionStrategy; - - public DefaultExecutionStrategyProvider() { - this(null); - } - - public DefaultExecutionStrategyProvider(ExecutionStrategy executionStrategy) { - this(executionStrategy, null, null); - } - - public DefaultExecutionStrategyProvider(ExecutionStrategy queryExecutionStrategy, ExecutionStrategy mutationExecutionStrategy, ExecutionStrategy subscriptionExecutionStrategy) { - this.queryExecutionStrategy = defaultIfNull(queryExecutionStrategy, new AsyncExecutionStrategy()); - this.mutationExecutionStrategy = defaultIfNull(mutationExecutionStrategy, this.queryExecutionStrategy); - this.subscriptionExecutionStrategy = defaultIfNull(subscriptionExecutionStrategy, new SubscriptionExecutionStrategy()); - } - - private ExecutionStrategy defaultIfNull(ExecutionStrategy executionStrategy, ExecutionStrategy defaultStrategy) { - return executionStrategy != null ? executionStrategy : defaultStrategy; - } - - @Override - public ExecutionStrategy getQueryExecutionStrategy() { - return queryExecutionStrategy; - } - - @Override - public ExecutionStrategy getMutationExecutionStrategy() { - return mutationExecutionStrategy; - } - - @Override - public ExecutionStrategy getSubscriptionExecutionStrategy() { - return subscriptionExecutionStrategy; - } - -} diff --git a/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java b/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java index bcdb7c4c..d9d8dc49 100644 --- a/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java +++ b/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java @@ -1,8 +1,6 @@ package graphql.servlet.config; import graphql.schema.GraphQLSchema; -import graphql.servlet.config.GraphQLSchemaProvider; - import javax.servlet.http.HttpServletRequest; import javax.websocket.server.HandshakeRequest; @@ -11,36 +9,36 @@ */ public class DefaultGraphQLSchemaProvider implements GraphQLSchemaProvider { - private final GraphQLSchema schema; - private final GraphQLSchema readOnlySchema; + private final GraphQLSchema schema; + private final GraphQLSchema readOnlySchema; - public DefaultGraphQLSchemaProvider(GraphQLSchema schema) { - this(schema, GraphQLSchemaProvider.copyReadOnly(schema)); - } + public DefaultGraphQLSchemaProvider(GraphQLSchema schema) { + this(schema, GraphQLSchemaProvider.copyReadOnly(schema)); + } - public DefaultGraphQLSchemaProvider(GraphQLSchema schema, GraphQLSchema readOnlySchema) { - this.schema = schema; - this.readOnlySchema = readOnlySchema; - } + public DefaultGraphQLSchemaProvider(GraphQLSchema schema, GraphQLSchema readOnlySchema) { + this.schema = schema; + this.readOnlySchema = readOnlySchema; + } - @Override - public GraphQLSchema getSchema(HttpServletRequest request) { - return getSchema(); - } + @Override + public GraphQLSchema getSchema(HttpServletRequest request) { + return getSchema(); + } - @Override - public GraphQLSchema getSchema(HandshakeRequest request) { - return getSchema(); - } + @Override + public GraphQLSchema getSchema(HandshakeRequest request) { + return getSchema(); + } - @Override - public GraphQLSchema getSchema() { - return schema; - } + @Override + public GraphQLSchema getSchema() { + return schema; + } - @Override - public GraphQLSchema getReadOnlySchema(HttpServletRequest request) { - return readOnlySchema; - } + @Override + public GraphQLSchema getReadOnlySchema(HttpServletRequest request) { + return readOnlySchema; + } } diff --git a/src/main/java/graphql/servlet/config/GraphQLQueryProvider.java b/src/main/java/graphql/servlet/config/GraphQLQueryProvider.java deleted file mode 100644 index c46952eb..00000000 --- a/src/main/java/graphql/servlet/config/GraphQLQueryProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package graphql.servlet.config; - -import graphql.schema.GraphQLFieldDefinition; -import graphql.servlet.config.GraphQLProvider; - -import java.util.Collection; - -/** - * This interface is used by OSGi bundles to plugin new field into the root query type - */ -public interface GraphQLQueryProvider extends GraphQLProvider { - - /** - * @return a collection of field definitions that will be added to the root query type. - */ - Collection getQueries(); - -} diff --git a/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java b/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java index 48950ca6..85491170 100644 --- a/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java +++ b/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java @@ -1,12 +1,11 @@ package graphql.servlet.context; +import graphql.kickstart.execution.context.DefaultGraphQLContext; +import graphql.kickstart.execution.context.GraphQLContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.Part; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; -import java.util.List; -import java.util.Map; /** * Returns an empty context. diff --git a/src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java b/src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java index 03ef9463..f396f71e 100644 --- a/src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java +++ b/src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java @@ -1,5 +1,6 @@ package graphql.servlet.context; +import graphql.kickstart.execution.context.DefaultGraphQLContext; import org.dataloader.DataLoaderRegistry; import javax.security.auth.Subject; diff --git a/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java b/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java index fe1d4d60..983d288c 100644 --- a/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java +++ b/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java @@ -1,6 +1,7 @@ package graphql.servlet.context; -import graphql.servlet.core.ApolloSubscriptionConnectionListener; +import graphql.kickstart.execution.context.DefaultGraphQLContext; +import graphql.servlet.apollo.ApolloSubscriptionConnectionListener; import org.dataloader.DataLoaderRegistry; import javax.security.auth.Subject; diff --git a/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java b/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java index 4ca7916a..b0960575 100644 --- a/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java +++ b/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java @@ -1,5 +1,6 @@ package graphql.servlet.context; +import graphql.kickstart.execution.context.GraphQLContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.Session; diff --git a/src/main/java/graphql/servlet/context/GraphQLServletContext.java b/src/main/java/graphql/servlet/context/GraphQLServletContext.java index 58af94da..73ff70fd 100644 --- a/src/main/java/graphql/servlet/context/GraphQLServletContext.java +++ b/src/main/java/graphql/servlet/context/GraphQLServletContext.java @@ -1,13 +1,11 @@ package graphql.servlet.context; +import graphql.kickstart.execution.context.GraphQLContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; -import javax.websocket.Session; -import javax.websocket.server.HandshakeRequest; import java.util.List; import java.util.Map; -import java.util.Optional; public interface GraphQLServletContext extends GraphQLContext { diff --git a/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java b/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java index fbe29dfb..0b7193a7 100644 --- a/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java +++ b/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java @@ -1,5 +1,6 @@ package graphql.servlet.context; +import graphql.kickstart.execution.context.GraphQLContext; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; import java.util.Optional; diff --git a/src/main/java/graphql/servlet/core/ApolloScalars.java b/src/main/java/graphql/servlet/core/ApolloScalars.java deleted file mode 100644 index 19dbf406..00000000 --- a/src/main/java/graphql/servlet/core/ApolloScalars.java +++ /dev/null @@ -1,41 +0,0 @@ -package graphql.servlet.core; - -import graphql.schema.Coercing; -import graphql.schema.CoercingParseLiteralException; -import graphql.schema.CoercingParseValueException; -import graphql.schema.CoercingSerializeException; -import graphql.schema.GraphQLScalarType; - -import javax.servlet.http.Part; - -public class ApolloScalars { - public static final GraphQLScalarType Upload = - new GraphQLScalarType("Upload", - "A file part in a multipart request", - new Coercing() { - @Override - public Void serialize(Object dataFetcherResult) { - throw new CoercingSerializeException("Upload is an input-only type"); - } - - @Override - public Part parseValue(Object input) { - if (input instanceof Part) { - return (Part) input; - } else if (null == input) { - return null; - } else { - throw new CoercingParseValueException("Expected type " + - Part.class.getName() + - " but was " + - input.getClass().getName()); - } - } - - @Override - public Part parseLiteral(Object input) { - throw new CoercingParseLiteralException( - "Must use variables to specify Upload values"); - } - }); -} diff --git a/src/main/java/graphql/servlet/core/DefaultGraphQLErrorHandler.java b/src/main/java/graphql/servlet/core/DefaultGraphQLErrorHandler.java deleted file mode 100644 index 3d2524e0..00000000 --- a/src/main/java/graphql/servlet/core/DefaultGraphQLErrorHandler.java +++ /dev/null @@ -1,66 +0,0 @@ -package graphql.servlet.core; - -import graphql.ExceptionWhileDataFetching; -import graphql.GraphQLError; -import graphql.execution.NonNullableFieldWasNullError; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * @author Andrew Potter - */ -public class DefaultGraphQLErrorHandler implements GraphQLErrorHandler { - - private static final Logger log = LoggerFactory.getLogger(DefaultGraphQLErrorHandler.class); - - @Override - public List processErrors(List errors) { - final List clientErrors = filterGraphQLErrors(errors); - if (clientErrors.size() < errors.size()) { - - // Some errors were filtered out to hide implementation - put a generic error in place. - clientErrors.add(new GenericGraphQLError("Internal Server Error(s) while executing query")); - - errors.stream() - .filter(error -> !isClientError(error)) - .forEach(this::logError); - } - - return clientErrors; - } - - protected void logError(GraphQLError error) { - if (error instanceof Throwable) { - log.error("Error executing query!", (Throwable) error); - } else if (error instanceof ExceptionWhileDataFetching) { - log.error("Error executing query {}", error.getMessage(), ((ExceptionWhileDataFetching) error).getException()); - } else { - log.error("Error executing query ({}): {}", error.getClass().getSimpleName(), error.getMessage()); - } - } - - protected List filterGraphQLErrors(List errors) { - return errors.stream() - .filter(this::isClientError) - .map(this::replaceNonNullableFieldWasNullError) - .collect(Collectors.toList()); - } - - protected boolean isClientError(GraphQLError error) { - if (error instanceof ExceptionWhileDataFetching) { - return ((ExceptionWhileDataFetching) error).getException() instanceof GraphQLError; - } - return true; - } - - private GraphQLError replaceNonNullableFieldWasNullError(GraphQLError error) { - if (error instanceof NonNullableFieldWasNullError) { - return new RenderableNonNullableFieldWasNullError((NonNullableFieldWasNullError) error); - } else { - return error; - } - } -} diff --git a/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java b/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java index c40a8372..1e632481 100644 --- a/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java +++ b/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java @@ -1,20 +1,25 @@ package graphql.servlet.core; +import static java.util.stream.Collectors.toList; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.MappingIterator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; -import graphql.*; +import graphql.DeferredExecutionResult; +import graphql.DeferredExecutionResultImpl; +import graphql.ExecutionResult; +import graphql.ExecutionResultImpl; +import graphql.GraphQLError; import graphql.execution.ExecutionPath; -import graphql.servlet.config.ConfiguringObjectMapperProvider; -import graphql.servlet.config.ObjectMapperConfigurer; -import graphql.servlet.config.ObjectMapperProvider; +import graphql.kickstart.execution.error.DefaultGraphQLErrorHandler; +import graphql.kickstart.execution.error.GraphQLErrorHandler; +import graphql.kickstart.execution.config.ConfiguringObjectMapperProvider; +import graphql.kickstart.execution.config.ObjectMapperConfigurer; +import graphql.kickstart.execution.config.ObjectMapperProvider; import graphql.servlet.core.internal.GraphQLRequest; import graphql.servlet.core.internal.VariablesDeserializer; - -import java.util.stream.Stream; -import javax.servlet.http.Part; import java.io.IOException; import java.io.InputStream; import java.io.Writer; @@ -23,195 +28,198 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; - -import static java.util.stream.Collectors.toList; +import javax.servlet.http.Part; /** * @author Andrew Potter */ public class GraphQLObjectMapper { - private static final TypeReference>> - MULTIPART_MAP_TYPE_REFERENCE = new TypeReference>>() { - }; - private final ObjectMapperProvider objectMapperProvider; - private final Supplier graphQLErrorHandlerSupplier; - - private volatile ObjectMapper mapper; - - protected GraphQLObjectMapper(ObjectMapperProvider objectMapperProvider, Supplier graphQLErrorHandlerSupplier) { - this.objectMapperProvider = objectMapperProvider; - this.graphQLErrorHandlerSupplier = graphQLErrorHandlerSupplier; - } - - // Double-check idiom for lazy initialization of instance fields. - public ObjectMapper getJacksonMapper() { - ObjectMapper result = mapper; - if (result == null) { // First check (no locking) - synchronized (this) { - result = mapper; - if (result == null) { // Second check (with locking) - mapper = result = objectMapperProvider.provide(); - } - } - } - return result; - } + private static final TypeReference>> + MULTIPART_MAP_TYPE_REFERENCE = new TypeReference>>() { + }; + private final ObjectMapperProvider objectMapperProvider; + private final Supplier graphQLErrorHandlerSupplier; - /** - * @return an {@link ObjectReader} for deserializing {@link GraphQLRequest} - */ - public ObjectReader getGraphQLRequestMapper() { - return getJacksonMapper().reader().forType(GraphQLRequest.class); - } + private volatile ObjectMapper mapper; - public GraphQLRequest readGraphQLRequest(InputStream inputStream) throws IOException { - return getGraphQLRequestMapper().readValue(inputStream); - } + protected GraphQLObjectMapper(ObjectMapperProvider objectMapperProvider, + Supplier graphQLErrorHandlerSupplier) { + this.objectMapperProvider = objectMapperProvider; + this.graphQLErrorHandlerSupplier = graphQLErrorHandlerSupplier; + } + + public static Builder newBuilder() { + return new Builder(); + } - public GraphQLRequest readGraphQLRequest(String text) throws IOException { - return getGraphQLRequestMapper().readValue(text); + // Double-check idiom for lazy initialization of instance fields. + public ObjectMapper getJacksonMapper() { + ObjectMapper result = mapper; + if (result == null) { // First check (no locking) + synchronized (this) { + result = mapper; + if (result == null) { // Second check (with locking) + mapper = result = objectMapperProvider.provide(); + } + } } - public List readBatchedGraphQLRequest(InputStream inputStream) throws IOException { - MappingIterator iterator = getGraphQLRequestMapper().readValues(inputStream); - List requests = new ArrayList<>(); + return result; + } - while (iterator.hasNext()) { - requests.add(iterator.next()); - } + /** + * @return an {@link ObjectReader} for deserializing {@link GraphQLRequest} + */ + public ObjectReader getGraphQLRequestMapper() { + return getJacksonMapper().reader().forType(GraphQLRequest.class); + } - return requests; - } + public GraphQLRequest readGraphQLRequest(InputStream inputStream) throws IOException { + return getGraphQLRequestMapper().readValue(inputStream); + } - public List readBatchedGraphQLRequest(String query) throws IOException { - MappingIterator iterator = getGraphQLRequestMapper().readValues(query); - List requests = new ArrayList<>(); + public GraphQLRequest readGraphQLRequest(String text) throws IOException { + return getGraphQLRequestMapper().readValue(text); + } - while (iterator.hasNext()) { - requests.add(iterator.next()); - } + public List readBatchedGraphQLRequest(InputStream inputStream) throws IOException { + MappingIterator iterator = getGraphQLRequestMapper().readValues(inputStream); + List requests = new ArrayList<>(); - return requests; + while (iterator.hasNext()) { + requests.add(iterator.next()); } - public String serializeResultAsJson(ExecutionResult executionResult) { - try { - return getJacksonMapper().writeValueAsString(createResultFromExecutionResult(executionResult)); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } + return requests; + } - public void serializeResultAsJson(Writer writer, ExecutionResult executionResult) throws IOException { - getJacksonMapper().writeValue(writer, createResultFromExecutionResult(executionResult)); - } + public List readBatchedGraphQLRequest(String query) throws IOException { + MappingIterator iterator = getGraphQLRequestMapper().readValues(query); + List requests = new ArrayList<>(); - public boolean areErrorsPresent(ExecutionResult executionResult) { - return graphQLErrorHandlerSupplier.get().errorsPresent(executionResult.getErrors()); + while (iterator.hasNext()) { + requests.add(iterator.next()); } - public ExecutionResult sanitizeErrors(ExecutionResult executionResult) { - Object data = executionResult.getData(); - Map extensions = executionResult.getExtensions(); - List errors = executionResult.getErrors(); + return requests; + } - GraphQLErrorHandler errorHandler = graphQLErrorHandlerSupplier.get(); - if (errorHandler.errorsPresent(errors)) { - errors = errorHandler.processErrors(errors); - } else { - errors = null; - } - return new ExecutionResultImpl(data, errors, extensions); + public String serializeResultAsJson(ExecutionResult executionResult) { + try { + return getJacksonMapper().writeValueAsString(createResultFromExecutionResult(executionResult)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); } + } - public Map createResultFromExecutionResult(ExecutionResult executionResult) { - ExecutionResult sanitizedExecutionResult = sanitizeErrors(executionResult); - if (executionResult instanceof DeferredExecutionResult) { - sanitizedExecutionResult = DeferredExecutionResultImpl - .newDeferredExecutionResult() - .from(executionResult) - .path(ExecutionPath.fromList(((DeferredExecutionResult) executionResult).getPath())) - .build(); - } - return convertSanitizedExecutionResult(sanitizedExecutionResult); - } + public void serializeResultAsJson(Writer writer, ExecutionResult executionResult) throws IOException { + getJacksonMapper().writeValue(writer, createResultFromExecutionResult(executionResult)); + } + + public boolean areErrorsPresent(ExecutionResult executionResult) { + return graphQLErrorHandlerSupplier.get().errorsPresent(executionResult.getErrors()); + } - public Map convertSanitizedExecutionResult(ExecutionResult executionResult) { - return convertSanitizedExecutionResult(executionResult, true); + public ExecutionResult sanitizeErrors(ExecutionResult executionResult) { + Object data = executionResult.getData(); + Map extensions = executionResult.getExtensions(); + List errors = executionResult.getErrors(); + + GraphQLErrorHandler errorHandler = graphQLErrorHandlerSupplier.get(); + if (errorHandler.errorsPresent(errors)) { + errors = errorHandler.processErrors(errors); + } else { + errors = null; } + return new ExecutionResultImpl(data, errors, extensions); + } - public Map convertSanitizedExecutionResult(ExecutionResult executionResult, boolean includeData) { - final Map result = new LinkedHashMap<>(); + public Map createResultFromExecutionResult(ExecutionResult executionResult) { + ExecutionResult sanitizedExecutionResult = sanitizeErrors(executionResult); + if (executionResult instanceof DeferredExecutionResult) { + sanitizedExecutionResult = DeferredExecutionResultImpl + .newDeferredExecutionResult() + .from(executionResult) + .path(ExecutionPath.fromList(((DeferredExecutionResult) executionResult).getPath())) + .build(); + } + return convertSanitizedExecutionResult(sanitizedExecutionResult); + } - if (areErrorsPresent(executionResult)) { - result.put("errors", executionResult.getErrors().stream().map(GraphQLError::toSpecification).collect(toList())); - } + public Map convertSanitizedExecutionResult(ExecutionResult executionResult) { + return convertSanitizedExecutionResult(executionResult, true); + } - if (executionResult.getExtensions() != null && !executionResult.getExtensions().isEmpty()) { - result.put("extensions", executionResult.getExtensions()); - } + public Map convertSanitizedExecutionResult(ExecutionResult executionResult, boolean includeData) { + final Map result = new LinkedHashMap<>(); - if (includeData) { - result.put("data", executionResult.getData()); - } + if (areErrorsPresent(executionResult)) { + result.put("errors", executionResult.getErrors().stream().map(GraphQLError::toSpecification).collect(toList())); + } - if (executionResult instanceof DeferredExecutionResult) { - result.put("path", ((DeferredExecutionResult) executionResult).getPath()); - } + if (executionResult.getExtensions() != null && !executionResult.getExtensions().isEmpty()) { + result.put("extensions", executionResult.getExtensions()); + } - return result; + if (includeData) { + result.put("data", executionResult.getData()); } - public Map deserializeVariables(String variables) { - try { - return VariablesDeserializer.deserializeVariablesObject(getJacksonMapper().readValue(variables, Object.class), getJacksonMapper()); - } catch (IOException e) { - throw new RuntimeException(e); - } + if (executionResult instanceof DeferredExecutionResult) { + result.put("path", ((DeferredExecutionResult) executionResult).getPath()); } - public Map> deserializeMultipartMap(Part part) { - try { - return getJacksonMapper().readValue(part.getInputStream(), MULTIPART_MAP_TYPE_REFERENCE); - } catch (IOException e) { - throw new RuntimeException(e); - } + return result; + } + + public Map deserializeVariables(String variables) { + try { + return VariablesDeserializer + .deserializeVariablesObject(getJacksonMapper().readValue(variables, Object.class), getJacksonMapper()); + } catch (IOException e) { + throw new RuntimeException(e); } + } - public static Builder newBuilder() { - return new Builder(); + public Map> deserializeMultipartMap(Part part) { + try { + return getJacksonMapper().readValue(part.getInputStream(), MULTIPART_MAP_TYPE_REFERENCE); + } catch (IOException e) { + throw new RuntimeException(e); } + } - public static class Builder { - private ObjectMapperProvider objectMapperProvider = new ConfiguringObjectMapperProvider(); - private Supplier graphQLErrorHandler = DefaultGraphQLErrorHandler::new; + public static class Builder { - public Builder withObjectMapperConfigurer(ObjectMapperConfigurer objectMapperConfigurer) { - return withObjectMapperConfigurer(() -> objectMapperConfigurer); - } + private ObjectMapperProvider objectMapperProvider = new ConfiguringObjectMapperProvider(); + private Supplier graphQLErrorHandler = DefaultGraphQLErrorHandler::new; - public Builder withObjectMapperConfigurer(Supplier objectMapperConfigurer) { - this.objectMapperProvider = new ConfiguringObjectMapperProvider(objectMapperConfigurer.get()); - return this; - } + public Builder withObjectMapperConfigurer(ObjectMapperConfigurer objectMapperConfigurer) { + return withObjectMapperConfigurer(() -> objectMapperConfigurer); + } - public Builder withObjectMapperProvider(ObjectMapperProvider objectMapperProvider) { - this.objectMapperProvider = objectMapperProvider; - return this; - } + public Builder withObjectMapperConfigurer(Supplier objectMapperConfigurer) { + this.objectMapperProvider = new ConfiguringObjectMapperProvider(objectMapperConfigurer.get()); + return this; + } - public Builder withGraphQLErrorHandler(GraphQLErrorHandler graphQLErrorHandler) { - return withGraphQLErrorHandler(() -> graphQLErrorHandler); - } + public Builder withObjectMapperProvider(ObjectMapperProvider objectMapperProvider) { + this.objectMapperProvider = objectMapperProvider; + return this; + } - public Builder withGraphQLErrorHandler(Supplier graphQLErrorHandler) { - this.graphQLErrorHandler = graphQLErrorHandler; - return this; - } + public Builder withGraphQLErrorHandler(GraphQLErrorHandler graphQLErrorHandler) { + return withGraphQLErrorHandler(() -> graphQLErrorHandler); + } - public GraphQLObjectMapper build() { - return new GraphQLObjectMapper(objectMapperProvider, graphQLErrorHandler); - } + public Builder withGraphQLErrorHandler(Supplier graphQLErrorHandler) { + this.graphQLErrorHandler = graphQLErrorHandler; + return this; + } + + public GraphQLObjectMapper build() { + return new GraphQLObjectMapper(objectMapperProvider, graphQLErrorHandler); } + } } diff --git a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java b/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java index 25b40410..9f9400bd 100644 --- a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java +++ b/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java @@ -1,6 +1,6 @@ package graphql.servlet.core.internal; -import graphql.servlet.core.ApolloSubscriptionConnectionListener; +import graphql.servlet.apollo.ApolloSubscriptionConnectionListener; /** * @author Andrew Potter diff --git a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java b/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java index 4818c5e1..9bcfc306 100644 --- a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java +++ b/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java @@ -4,9 +4,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonValue; import graphql.ExecutionResult; -import graphql.servlet.core.ApolloSubscriptionConnectionListener; -import graphql.servlet.input.GraphQLSingleInvocationInput; -import graphql.servlet.core.SubscriptionException; +import graphql.servlet.apollo.ApolloSubscriptionConnectionListener; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import graphql.kickstart.execution.subscription.SubscriptionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java b/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java index 909423b2..cffbc2ff 100644 --- a/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java +++ b/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java @@ -1,6 +1,6 @@ package graphql.servlet.core.internal; -import graphql.servlet.input.GraphQLSingleInvocationInput; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; diff --git a/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java b/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java index 22b1d850..6414f20d 100644 --- a/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java +++ b/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java @@ -2,8 +2,8 @@ import graphql.servlet.input.GraphQLInvocationInputFactory; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.GraphQLQueryInvoker; -import graphql.servlet.core.SubscriptionConnectionListener; +import graphql.kickstart.execution.GraphQLQueryInvoker; +import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; import java.util.Optional; diff --git a/src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java b/src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java index 8b86f7e8..d60bda3a 100644 --- a/src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java +++ b/src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java @@ -1,7 +1,9 @@ package graphql.servlet.input; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; + /** - * Wraps the result of pre processing a batch. Allows customization of the response code and message if the batch isn't to be executed. + * Wraps the result of pre processing a batch. Allows customization of the response code and message if the batch isn't to be executed. */ public class BatchInputPreProcessResult { diff --git a/src/main/java/graphql/servlet/input/BatchInputPreProcessor.java b/src/main/java/graphql/servlet/input/BatchInputPreProcessor.java index 39d7c50a..cacfc4fe 100644 --- a/src/main/java/graphql/servlet/input/BatchInputPreProcessor.java +++ b/src/main/java/graphql/servlet/input/BatchInputPreProcessor.java @@ -1,5 +1,6 @@ package graphql.servlet.input; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java b/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java index 9dd39f19..c0857264 100644 --- a/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java +++ b/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java @@ -1,152 +1,164 @@ package graphql.servlet.input; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import graphql.schema.GraphQLSchema; import graphql.servlet.config.DefaultGraphQLSchemaProvider; import graphql.servlet.config.GraphQLSchemaProvider; -import graphql.servlet.context.ContextSetting; +import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.context.DefaultGraphQLContextBuilder; import graphql.servlet.context.GraphQLContextBuilder; import graphql.servlet.core.DefaultGraphQLRootObjectBuilder; import graphql.servlet.core.GraphQLRootObjectBuilder; import graphql.servlet.core.internal.GraphQLRequest; - +import java.util.List; +import java.util.function.Supplier; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; -import java.util.List; -import java.util.function.Supplier; /** * @author Andrew Potter */ public class GraphQLInvocationInputFactory { - private final Supplier schemaProviderSupplier; - private final Supplier contextBuilderSupplier; - private final Supplier rootObjectBuilderSupplier; - - protected GraphQLInvocationInputFactory(Supplier schemaProviderSupplier, Supplier contextBuilderSupplier, Supplier rootObjectBuilderSupplier) { - this.schemaProviderSupplier = schemaProviderSupplier; - this.contextBuilderSupplier = contextBuilderSupplier; - this.rootObjectBuilderSupplier = rootObjectBuilderSupplier; - } - - public GraphQLSchemaProvider getSchemaProvider() { - return schemaProviderSupplier.get(); - } - - - public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, HttpServletRequest request, HttpServletResponse response) { - return create(graphQLRequest, request, response, false); - } - - public GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List graphQLRequests, HttpServletRequest request, - HttpServletResponse response) { - return create(contextSetting, graphQLRequests, request, response, false); - } - + private final Supplier schemaProviderSupplier; + private final Supplier contextBuilderSupplier; + private final Supplier rootObjectBuilderSupplier; + + protected GraphQLInvocationInputFactory(Supplier schemaProviderSupplier, + Supplier contextBuilderSupplier, + Supplier rootObjectBuilderSupplier) { + this.schemaProviderSupplier = schemaProviderSupplier; + this.contextBuilderSupplier = contextBuilderSupplier; + this.rootObjectBuilderSupplier = rootObjectBuilderSupplier; + } + + public static Builder newBuilder(GraphQLSchema schema) { + return new Builder(new DefaultGraphQLSchemaProvider(schema)); + } + + public static Builder newBuilder(GraphQLSchemaProvider schemaProvider) { + return new Builder(schemaProvider); + } + + public static Builder newBuilder(Supplier schemaProviderSupplier) { + return new Builder(schemaProviderSupplier); + } + + public GraphQLSchemaProvider getSchemaProvider() { + return schemaProviderSupplier.get(); + } + + public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, HttpServletRequest request, + HttpServletResponse response) { + return create(graphQLRequest, request, response, false); + } + + public GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List graphQLRequests, + HttpServletRequest request, + HttpServletResponse response) { + return create(contextSetting, graphQLRequests, request, response, false); + } + + public GraphQLSingleInvocationInput createReadOnly(GraphQLRequest graphQLRequest, HttpServletRequest request, + HttpServletResponse response) { + return create(graphQLRequest, request, response, true); + } + + public GraphQLBatchedInvocationInput createReadOnly(ContextSetting contextSetting, + List graphQLRequests, HttpServletRequest request, HttpServletResponse response) { + return create(contextSetting, graphQLRequests, request, response, true); + } + + public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest) { + return new GraphQLSingleInvocationInput( + graphQLRequest, + schemaProviderSupplier.get().getSchema(), + contextBuilderSupplier.get().build(), + rootObjectBuilderSupplier.get().build() + ); + } + + private GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, HttpServletRequest request, + HttpServletResponse response, + boolean readOnly) { + return new GraphQLSingleInvocationInput( + graphQLRequest, + readOnly ? schemaProviderSupplier.get().getReadOnlySchema(request) + : schemaProviderSupplier.get().getSchema(request), + contextBuilderSupplier.get().build(request, response), + rootObjectBuilderSupplier.get().build(request) + ); + } + + private GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List graphQLRequests, + HttpServletRequest request, + HttpServletResponse response, boolean readOnly) { + return contextSetting.getBatch( + graphQLRequests, + readOnly ? schemaProviderSupplier.get().getReadOnlySchema(request) + : schemaProviderSupplier.get().getSchema(request), + () -> contextBuilderSupplier.get().build(request, response), + rootObjectBuilderSupplier.get().build(request) + ); + } + + public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, Session session, HandshakeRequest request) { + return new GraphQLSingleInvocationInput( + graphQLRequest, + schemaProviderSupplier.get().getSchema(request), + contextBuilderSupplier.get().build(session, request), + rootObjectBuilderSupplier.get().build(request) + ); + } + + public GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List graphQLRequest, + Session session, HandshakeRequest request) { + return contextSetting.getBatch( + graphQLRequest, + schemaProviderSupplier.get().getSchema(request), + () -> contextBuilderSupplier.get().build(session, request), + rootObjectBuilderSupplier.get().build(request) + ); + } + + public static class Builder { - public GraphQLSingleInvocationInput createReadOnly(GraphQLRequest graphQLRequest, HttpServletRequest request, HttpServletResponse response) { - return create(graphQLRequest, request, response, true); - } - - public GraphQLBatchedInvocationInput createReadOnly(ContextSetting contextSetting, List graphQLRequests, HttpServletRequest request, HttpServletResponse response) { - return create(contextSetting, graphQLRequests, request, response, true); - } - - public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest) { - return new GraphQLSingleInvocationInput( - graphQLRequest, - schemaProviderSupplier.get().getSchema(), - contextBuilderSupplier.get().build(), - rootObjectBuilderSupplier.get().build() - ); - } - - private GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, HttpServletRequest request, HttpServletResponse response, - boolean readOnly) { - return new GraphQLSingleInvocationInput( - graphQLRequest, - readOnly ? schemaProviderSupplier.get().getReadOnlySchema(request) : schemaProviderSupplier.get().getSchema(request), - contextBuilderSupplier.get().build(request, response), - rootObjectBuilderSupplier.get().build(request) - ); - } + private final Supplier schemaProviderSupplier; + private Supplier contextBuilderSupplier = DefaultGraphQLContextBuilder::new; + private Supplier rootObjectBuilderSupplier = DefaultGraphQLRootObjectBuilder::new; - private GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List graphQLRequests, HttpServletRequest request, - HttpServletResponse response, boolean readOnly) { - return contextSetting.getBatch( - graphQLRequests, - readOnly ? schemaProviderSupplier.get().getReadOnlySchema(request) : schemaProviderSupplier.get().getSchema(request), - () -> contextBuilderSupplier.get().build(request, response), - rootObjectBuilderSupplier.get().build(request) - ); + public Builder(GraphQLSchemaProvider schemaProvider) { + this(() -> schemaProvider); } - public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, Session session, HandshakeRequest request) { - return new GraphQLSingleInvocationInput( - graphQLRequest, - schemaProviderSupplier.get().getSchema(request), - contextBuilderSupplier.get().build(session, request), - rootObjectBuilderSupplier.get().build(request) - ); + public Builder(Supplier schemaProviderSupplier) { + this.schemaProviderSupplier = schemaProviderSupplier; } - public GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List graphQLRequest, Session session, HandshakeRequest request) { - return contextSetting.getBatch( - graphQLRequest, - schemaProviderSupplier.get().getSchema(request), - () -> contextBuilderSupplier.get().build(session, request), - rootObjectBuilderSupplier.get().build(request) - ); + public Builder withGraphQLContextBuilder(GraphQLContextBuilder contextBuilder) { + return withGraphQLContextBuilder(() -> contextBuilder); } - public static Builder newBuilder(GraphQLSchema schema) { - return new Builder(new DefaultGraphQLSchemaProvider(schema)); + public Builder withGraphQLContextBuilder(Supplier contextBuilderSupplier) { + this.contextBuilderSupplier = contextBuilderSupplier; + return this; } - public static Builder newBuilder(GraphQLSchemaProvider schemaProvider) { - return new Builder(schemaProvider); + public Builder withGraphQLRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { + return withGraphQLRootObjectBuilder(() -> rootObjectBuilder); } - public static Builder newBuilder(Supplier schemaProviderSupplier) { - return new Builder(schemaProviderSupplier); + public Builder withGraphQLRootObjectBuilder(Supplier rootObjectBuilderSupplier) { + this.rootObjectBuilderSupplier = rootObjectBuilderSupplier; + return this; } - public static class Builder { - private final Supplier schemaProviderSupplier; - private Supplier contextBuilderSupplier = DefaultGraphQLContextBuilder::new; - private Supplier rootObjectBuilderSupplier = DefaultGraphQLRootObjectBuilder::new; - - public Builder(GraphQLSchemaProvider schemaProvider) { - this(() -> schemaProvider); - } - - public Builder(Supplier schemaProviderSupplier) { - this.schemaProviderSupplier = schemaProviderSupplier; - } - - public Builder withGraphQLContextBuilder(GraphQLContextBuilder contextBuilder) { - return withGraphQLContextBuilder(() -> contextBuilder); - } - - public Builder withGraphQLContextBuilder(Supplier contextBuilderSupplier) { - this.contextBuilderSupplier = contextBuilderSupplier; - return this; - } - - public Builder withGraphQLRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { - return withGraphQLRootObjectBuilder(() -> rootObjectBuilder); - } - - public Builder withGraphQLRootObjectBuilder(Supplier rootObjectBuilderSupplier) { - this.rootObjectBuilderSupplier = rootObjectBuilderSupplier; - return this; - } - - public GraphQLInvocationInputFactory build() { - return new GraphQLInvocationInputFactory(schemaProviderSupplier, contextBuilderSupplier, rootObjectBuilderSupplier); - } + public GraphQLInvocationInputFactory build() { + return new GraphQLInvocationInputFactory(schemaProviderSupplier, contextBuilderSupplier, + rootObjectBuilderSupplier); } + } } diff --git a/src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java b/src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java deleted file mode 100644 index fb70a8b8..00000000 --- a/src/main/java/graphql/servlet/input/GraphQLSingleInvocationInput.java +++ /dev/null @@ -1,59 +0,0 @@ -package graphql.servlet.input; - -import graphql.ExecutionInput; -import graphql.execution.ExecutionId; -import graphql.schema.GraphQLSchema; -import graphql.servlet.context.GraphQLContext; -import graphql.servlet.core.internal.GraphQLRequest; -import org.dataloader.DataLoaderRegistry; - -import javax.security.auth.Subject; -import java.util.Optional; - -/** - * Represents a single GraphQL execution. - */ -public class GraphQLSingleInvocationInput implements GraphQLInvocationInput { - - private final GraphQLSchema schema; - - private final ExecutionInput executionInput; - - private final Optional subject; - - public GraphQLSingleInvocationInput(GraphQLRequest request, GraphQLSchema schema, GraphQLContext context, Object root) { - this.schema = schema; - this.executionInput = createExecutionInput(request, context, root); - subject = context.getSubject(); - } - - /** - * @return the schema to use to execute this query. - */ - public GraphQLSchema getSchema() { - return schema; - } - - /** - * @return a subject to execute the query as. - */ - public Optional getSubject() { - return subject; - } - - private ExecutionInput createExecutionInput(GraphQLRequest graphQLRequest, GraphQLContext context, Object root) { - return ExecutionInput.newExecutionInput() - .query(graphQLRequest.getQuery()) - .operationName(graphQLRequest.getOperationName()) - .context(context) - .root(root) - .variables(graphQLRequest.getVariables()) - .dataLoaderRegistry(context.getDataLoaderRegistry().orElse(new DataLoaderRegistry())) - .executionId(ExecutionId.generate()) - .build(); - } - - public ExecutionInput getExecutionInput() { - return executionInput; - } -} diff --git a/src/main/java/graphql/servlet/instrumentation/AbstractTrackingApproach.java b/src/main/java/graphql/servlet/instrumentation/AbstractTrackingApproach.java deleted file mode 100644 index 16394254..00000000 --- a/src/main/java/graphql/servlet/instrumentation/AbstractTrackingApproach.java +++ /dev/null @@ -1,225 +0,0 @@ -package graphql.servlet.instrumentation; - -import graphql.ExecutionResult; -import graphql.execution.ExecutionId; -import graphql.execution.ExecutionPath; -import graphql.execution.FieldValueInfo; -import graphql.execution.MergedField; -import graphql.execution.instrumentation.DeferredFieldInstrumentationContext; -import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext; -import graphql.execution.instrumentation.InstrumentationContext; -import graphql.execution.instrumentation.parameters.InstrumentationDeferredFieldParameters; -import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters; -import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; -import graphql.language.Field; -import graphql.language.Selection; -import graphql.language.SelectionSet; -import graphql.schema.GraphQLOutputType; -import org.dataloader.DataLoaderRegistry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -/** - * Handles logic common to tracking approaches. - */ -public abstract class AbstractTrackingApproach implements TrackingApproach { - - private static final Logger log = LoggerFactory.getLogger(AbstractTrackingApproach.class); - - private final DataLoaderRegistry dataLoaderRegistry; - - private final RequestStack stack = new RequestStack(); - - public AbstractTrackingApproach(DataLoaderRegistry dataLoaderRegistry) { - this.dataLoaderRegistry = dataLoaderRegistry; - } - - /** - * @return allows extending classes to modify the stack. - */ - protected RequestStack getStack() { - return stack; - } - - @Override - public ExecutionStrategyInstrumentationContext beginExecutionStrategy(InstrumentationExecutionStrategyParameters parameters) { - ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); - ExecutionPath path = parameters.getExecutionStrategyParameters().getPath(); - int parentLevel = path.getLevel(); - int curLevel = parentLevel + 1; - int fieldCount = parameters.getExecutionStrategyParameters().getFields().size(); - synchronized (stack) { - stack.increaseExpectedFetchCount(executionId, curLevel, fieldCount); - stack.increaseHappenedStrategyCalls(executionId, curLevel); - } - - return new ExecutionStrategyInstrumentationContext() { - @Override - public void onDispatched(CompletableFuture result) { - - } - - @Override - public void onCompleted(ExecutionResult result, Throwable t) { - - } - - @Override - public void onFieldValuesInfo(List fieldValueInfoList) { - synchronized (stack) { - stack.setStatus(executionId, handleOnFieldValuesInfo(fieldValueInfoList, stack, executionId, curLevel)); - if (stack.allReady()) { - dispatchWithoutLocking(); - } - } - } - - @Override - public void onDeferredField(MergedField field) { - // fake fetch count for this field - synchronized (stack) { - stack.increaseFetchCount(executionId, curLevel); - stack.setStatus(executionId, dispatchIfNeeded(stack, executionId, curLevel)); - if (stack.allReady()) { - dispatchWithoutLocking(); - } - } - } - }; - } - - // - // thread safety : called with synchronised(stack) - // - private boolean handleOnFieldValuesInfo(List fieldValueInfoList, RequestStack stack, ExecutionId executionId, int curLevel) { - stack.increaseHappenedOnFieldValueCalls(executionId, curLevel); - int expectedStrategyCalls = 0; - for (FieldValueInfo fieldValueInfo : fieldValueInfoList) { - if (fieldValueInfo.getCompleteValueType() == FieldValueInfo.CompleteValueType.OBJECT) { - expectedStrategyCalls++; - } else if (fieldValueInfo.getCompleteValueType() == FieldValueInfo.CompleteValueType.LIST) { - expectedStrategyCalls += getCountForList(fieldValueInfo); - } - } - stack.increaseExpectedStrategyCalls(executionId, curLevel + 1, expectedStrategyCalls); - return dispatchIfNeeded(stack, executionId, curLevel + 1); - } - - private int getCountForList(FieldValueInfo fieldValueInfo) { - int result = 0; - for (FieldValueInfo cvi : fieldValueInfo.getFieldValueInfos()) { - if (cvi.getCompleteValueType() == FieldValueInfo.CompleteValueType.OBJECT) { - result++; - } else if (cvi.getCompleteValueType() == FieldValueInfo.CompleteValueType.LIST) { - result += getCountForList(cvi); - } - } - return result; - } - - @Override - public DeferredFieldInstrumentationContext beginDeferredField(InstrumentationDeferredFieldParameters parameters) { - ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); - int level = parameters.getExecutionStrategyParameters().getPath().getLevel(); - synchronized (stack) { - stack.clearAndMarkCurrentLevelAsReady(executionId, level); - } - - return new DeferredFieldInstrumentationContext() { - @Override - public void onDispatched(CompletableFuture result) { - - } - - @Override - public void onCompleted(ExecutionResult result, Throwable t) { - } - - @Override - public void onFieldValueInfo(FieldValueInfo fieldValueInfo) { - synchronized (stack) { - stack.setStatus(executionId, handleOnFieldValuesInfo(Collections.singletonList(fieldValueInfo), stack, executionId, level)); - if (stack.allReady()) { - dispatchWithoutLocking(); - } - } - } - }; - } - - @Override - public InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters) { - ExecutionId executionId = parameters.getExecutionContext().getExecutionId(); - ExecutionPath path = parameters.getEnvironment().getExecutionStepInfo().getPath(); - int level = path.getLevel(); - return new InstrumentationContext() { - - @Override - public void onDispatched(CompletableFuture result) { - synchronized (stack) { - stack.increaseFetchCount(executionId, level); - stack.setStatus(executionId, dispatchIfNeeded(stack, executionId, level)); - - if (stack.allReady()) { - dispatchWithoutLocking(); - } - } - } - - @Override - public void onCompleted(Object result, Throwable t) { - } - }; - } - - @Override - public void removeTracking(ExecutionId executionId) { - synchronized (stack) { - stack.removeExecution(executionId); - if (stack.allReady()) { - dispatchWithoutLocking(); - } - } - } - - - // - // thread safety : called with synchronised(stack) - // - private boolean dispatchIfNeeded(RequestStack stack, ExecutionId executionId, int level) { - if (levelReady(stack, executionId, level)) { - return stack.dispatchIfNotDispatchedBefore(executionId, level); - } - return false; - } - - // - // thread safety : called with synchronised(stack) - // - private boolean levelReady(RequestStack stack, ExecutionId executionId, int level) { - if (level == 1) { - // level 1 is special: there is only one strategy call and that's it - return stack.allFetchesHappened(executionId, 1); - } - return (levelReady(stack, executionId, level - 1) && stack.allOnFieldCallsHappened(executionId, level - 1) - && stack.allStrategyCallsHappened(executionId, level) && stack.allFetchesHappened(executionId, level)); - } - - @Override - public void dispatch() { - synchronized (stack) { - dispatchWithoutLocking(); - } - } - - private void dispatchWithoutLocking() { - log.debug("Dispatching data loaders ({})", dataLoaderRegistry.getKeys()); - dataLoaderRegistry.dispatchAll(); - stack.allReset(); - } -} diff --git a/src/main/java/graphql/servlet/config/GraphQLMutationProvider.java b/src/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java similarity index 52% rename from src/main/java/graphql/servlet/config/GraphQLMutationProvider.java rename to src/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java index 60e0ddff..93b1a2c6 100644 --- a/src/main/java/graphql/servlet/config/GraphQLMutationProvider.java +++ b/src/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java @@ -1,10 +1,10 @@ -package graphql.servlet.config; +package graphql.servlet.osgi; import graphql.schema.GraphQLFieldDefinition; -import graphql.servlet.config.GraphQLProvider; - import java.util.Collection; public interface GraphQLMutationProvider extends GraphQLProvider { - Collection getMutations(); + + Collection getMutations(); + } diff --git a/src/main/java/graphql/servlet/config/GraphQLProvider.java b/src/main/java/graphql/servlet/osgi/GraphQLProvider.java similarity index 54% rename from src/main/java/graphql/servlet/config/GraphQLProvider.java rename to src/main/java/graphql/servlet/osgi/GraphQLProvider.java index 54535a9c..3168abb4 100644 --- a/src/main/java/graphql/servlet/config/GraphQLProvider.java +++ b/src/main/java/graphql/servlet/osgi/GraphQLProvider.java @@ -1,4 +1,4 @@ -package graphql.servlet.config; +package graphql.servlet.osgi; public interface GraphQLProvider { } diff --git a/src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java b/src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java new file mode 100644 index 00000000..646263b9 --- /dev/null +++ b/src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java @@ -0,0 +1,16 @@ +package graphql.servlet.osgi; + +import graphql.schema.GraphQLFieldDefinition; +import java.util.Collection; + +/** + * This interface is used by OSGi bundles to plugin new field into the root query type + */ +public interface GraphQLQueryProvider extends GraphQLProvider { + + /** + * @return a collection of field definitions that will be added to the root query type. + */ + Collection getQueries(); + +} diff --git a/src/main/java/graphql/servlet/config/GraphQLSubscriptionProvider.java b/src/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java similarity index 72% rename from src/main/java/graphql/servlet/config/GraphQLSubscriptionProvider.java rename to src/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java index 046dd123..75f398f4 100644 --- a/src/main/java/graphql/servlet/config/GraphQLSubscriptionProvider.java +++ b/src/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java @@ -1,7 +1,7 @@ -package graphql.servlet.config; +package graphql.servlet.osgi; import graphql.schema.GraphQLFieldDefinition; -import graphql.servlet.config.GraphQLProvider; +import graphql.servlet.osgi.GraphQLProvider; import java.util.Collection; diff --git a/src/main/java/graphql/servlet/config/GraphQLTypesProvider.java b/src/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java similarity index 52% rename from src/main/java/graphql/servlet/config/GraphQLTypesProvider.java rename to src/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java index 61ff7065..22f9006e 100644 --- a/src/main/java/graphql/servlet/config/GraphQLTypesProvider.java +++ b/src/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java @@ -1,10 +1,9 @@ -package graphql.servlet.config; +package graphql.servlet.osgi; import graphql.schema.GraphQLType; -import graphql.servlet.config.GraphQLProvider; - import java.util.Collection; public interface GraphQLTypesProvider extends GraphQLProvider { - Collection getTypes(); + + Collection getTypes(); } diff --git a/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy b/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy index 6c9da55b..9940366e 100644 --- a/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy +++ b/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy @@ -8,11 +8,11 @@ import graphql.execution.instrumentation.SimpleInstrumentation import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment -import graphql.servlet.context.ContextSetting -import graphql.servlet.context.DefaultGraphQLContext -import graphql.servlet.context.GraphQLContext +import graphql.kickstart.execution.context.ContextSetting +import graphql.kickstart.execution.context.DefaultGraphQLContext +import graphql.kickstart.execution.context.GraphQLContext import graphql.servlet.context.GraphQLContextBuilder -import graphql.servlet.instrumentation.ConfigurableDispatchInstrumentation +import graphql.kickstart.execution.instrumentation.ConfigurableDispatchInstrumentation import org.dataloader.BatchLoader import org.dataloader.DataLoader import org.dataloader.DataLoaderRegistry diff --git a/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy b/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy index 1b111ab2..75d62477 100644 --- a/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy +++ b/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy @@ -7,10 +7,10 @@ import graphql.annotations.processor.GraphQLAnnotations import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLInterfaceType -import graphql.servlet.config.GraphQLCodeRegistryProvider -import graphql.servlet.config.GraphQLMutationProvider -import graphql.servlet.config.GraphQLQueryProvider -import graphql.servlet.config.GraphQLSubscriptionProvider +import graphql.kickstart.execution.config.GraphQLCodeRegistryProvider +import graphql.servlet.osgi.GraphQLMutationProvider +import graphql.servlet.osgi.GraphQLQueryProvider +import graphql.servlet.osgi.GraphQLSubscriptionProvider import spock.lang.Ignore import spock.lang.Specification diff --git a/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java b/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java index ef271181..ef23c53f 100644 --- a/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java +++ b/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java @@ -2,7 +2,7 @@ import graphql.servlet.input.BatchInputPreProcessResult; import graphql.servlet.input.BatchInputPreProcessor; -import graphql.servlet.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/src/test/groovy/graphql/servlet/TestUtils.groovy b/src/test/groovy/graphql/servlet/TestUtils.groovy index ec79d188..2fa1e84f 100644 --- a/src/test/groovy/graphql/servlet/TestUtils.groovy +++ b/src/test/groovy/graphql/servlet/TestUtils.groovy @@ -11,12 +11,11 @@ import graphql.schema.idl.SchemaParser import graphql.schema.idl.TypeRuntimeWiring import graphql.schema.idl.errors.SchemaProblem import graphql.servlet.context.GraphQLContextBuilder -import graphql.servlet.config.GraphQLConfiguration -import graphql.servlet.core.ApolloScalars +import graphql.kickstart.execution.config.GraphQLConfiguration +import graphql.servlet.apollo.ApolloScalars import graphql.servlet.input.BatchInputPreProcessor -import graphql.servlet.context.ContextSetting +import graphql.kickstart.execution.context.ContextSetting -import java.util.concurrent.CompletableFuture import java.util.concurrent.atomic.AtomicReference class TestUtils { From 37c62fc7521262446fe1a9b4cbe1eb3d2b57d60c Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 17 Nov 2019 11:58:02 +0100 Subject: [PATCH 09/26] Extract core functionality into its own module --- build.gradle | 350 +++++++++--------- gradle.properties | 13 + graphql-java-kickstart/build.gradle | 10 + .../execution/DecoratedExecutionResult.java | 0 .../execution/GraphQLBatchedQueryResult.java | 0 .../execution/GraphQLErrorQueryResult.java | 0 .../execution/GraphQLQueryInvoker.java | 0 .../execution/GraphQLQueryResult.java | 0 .../kickstart/execution}/GraphQLRequest.java | 2 +- .../execution/GraphQLSingleQueryResult.java | 0 .../execution}/VariablesDeserializer.java | 2 +- .../ConfiguringObjectMapperProvider.java | 0 .../DefaultExecutionStrategyProvider.java | 0 .../config/ExecutionStrategyProvider.java | 0 .../config/InstrumentationProvider.java | 0 .../config/ObjectMapperConfigurer.java | 0 .../config/ObjectMapperProvider.java | 0 .../execution/context/ContextSetting.java | 98 +++++ .../context/DefaultGraphQLContext.java | 0 .../execution/context/GraphQLContext.java | 0 .../error/DefaultGraphQLErrorHandler.java | 0 .../error/DefaultObjectMapperConfigurer.java | 0 .../execution/error/GenericGraphQLError.java | 0 .../execution/error/GraphQLErrorHandler.java | 0 ...enderableNonNullableFieldWasNullError.java | 0 .../input/GraphQLBatchedInvocationInput.java | 0 .../input/GraphQLInvocationInput.java | 0 .../input/GraphQLSingleInvocationInput.java | 4 +- .../input/PerQueryBatchedInvocationInput.java | 2 +- .../PerRequestBatchedInvocationInput.java | 29 ++ .../AbstractTrackingApproach.java | 0 .../ConfigurableDispatchInstrumentation.java | 0 ...aLoaderDispatcherInstrumentationState.java | 0 .../FieldLevelTrackingApproach.java | 0 .../NoOpInstrumentationProvider.java | 0 .../RequestLevelTrackingApproach.java | 0 .../instrumentation/RequestStack.java | 0 .../instrumentation/TrackingApproach.java | 0 .../SubscriptionConnectionListener.java | 0 .../subscription/SubscriptionException.java | 0 graphql-java-servlet/build.gradle | 52 +++ .../servlet/AbstractGraphQLHttpServlet.java | 3 +- .../AbstractGraphQLInvocationInputParser.java | 0 .../servlet/BatchedQueryResponseWriter.java | 0 .../servlet/ConfiguredGraphQLHttpServlet.java | 2 - .../servlet/DefaultGraphQLServlet.java | 0 .../servlet/ErrorQueryResponseWriter.java | 0 .../servlet/ExecutionResultSubscriber.java | 0 .../servlet}/GraphQLConfiguration.java | 4 +- .../GraphQLGetInvocationInputParser.java | 2 +- .../graphql/servlet/GraphQLHttpServlet.java | 1 - .../servlet/GraphQLInvocationInputParser.java | 0 ...GraphQLMultipartInvocationInputParser.java | 2 +- .../GraphQLPostInvocationInputParser.java | 7 +- .../servlet/GraphQLWebsocketServlet.java | 0 .../graphql/servlet/HttpRequestHandler.java | 2 +- .../servlet/HttpRequestHandlerImpl.java | 1 - .../servlet/OsgiGraphQLHttpServlet.java | 2 +- .../graphql/servlet/QueryResponseWriter.java | 0 .../servlet/SimpleGraphQLHttpServlet.java | 1 - ...SingleAsynchronousQueryResponseWriter.java | 0 .../servlet/SingleQueryResponseWriter.java | 0 .../graphql/servlet/StaticDataPublisher.java | 0 .../servlet/SubscriptionAsyncListener.java | 0 .../graphql/servlet/apollo/ApolloScalars.java | 0 .../ApolloSubscriptionConnectionListener.java | 0 .../config/DefaultGraphQLSchemaProvider.java | 0 .../servlet/config/GraphQLSchemaProvider.java | 0 .../context/DefaultGraphQLContextBuilder.java | 0 .../context/DefaultGraphQLServletContext.java | 0 .../DefaultGraphQLWebSocketContext.java | 0 .../context/GraphQLContextBuilder.java | 0 .../context/GraphQLServletContext.java | 0 .../context/GraphQLWebSocketContext.java | 0 .../core/DefaultGraphQLRootObjectBuilder.java | 0 .../graphql/servlet/core/GraphQLMBean.java | 0 .../servlet/core/GraphQLObjectMapper.java | 4 +- .../core/GraphQLRootObjectBuilder.java | 0 .../servlet/core/GraphQLServletListener.java | 0 .../core/StaticGraphQLRootObjectBuilder.java | 0 .../ApolloSubscriptionKeepAliveRunner.java | 0 .../ApolloSubscriptionProtocolFactory.java | 0 .../ApolloSubscriptionProtocolHandler.java | 1 + .../FallbackSubscriptionProtocolFactory.java | 0 .../FallbackSubscriptionProtocolHandler.java | 1 + .../core/internal/GraphQLThreadFactory.java | 0 .../internal/SubscriptionHandlerInput.java | 0 .../internal/SubscriptionProtocolFactory.java | 0 .../internal/SubscriptionProtocolHandler.java | 0 .../core/internal/SubscriptionSender.java | 0 .../servlet/core/internal/VariableMapper.java | 0 .../core/internal/WsSessionSubscriptions.java | 0 .../input/BatchInputPreProcessResult.java | 0 .../servlet/input/BatchInputPreProcessor.java | 0 .../input/GraphQLInvocationInputFactory.java | 2 +- .../input/NoOpBatchInputPreProcessor.java | 3 +- .../osgi}/GraphQLCodeRegistryProvider.java | 2 +- .../servlet/osgi/GraphQLMutationProvider.java | 0 .../graphql/servlet/osgi/GraphQLProvider.java | 0 .../servlet/osgi/GraphQLQueryProvider.java | 0 .../osgi/GraphQLSubscriptionProvider.java | 0 .../servlet/osgi/GraphQLTypesProvider.java | 0 .../AbstractGraphQLHttpServletSpec.groovy | 0 .../servlet/DataLoaderDispatchingSpec.groovy | 0 .../servlet/OsgiGraphQLHttpServletSpec.groovy | 2 +- .../servlet/TestBatchInputPreProcessor.java | 0 .../graphql/servlet/TestException.groovy | 0 .../servlet/TestGraphQLErrorException.groovy | 0 .../graphql/servlet/TestMultipartPart.groovy | 0 .../groovy/graphql/servlet/TestUtils.groovy | 1 - settings.gradle | 3 + .../execution/context/ContextSetting.java | 94 ----- .../PerRequestBatchedInvocationInput.java | 28 -- 113 files changed, 409 insertions(+), 321 deletions(-) create mode 100644 graphql-java-kickstart/build.gradle rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/GraphQLQueryResult.java (100%) rename {src/main/java/graphql/servlet/core/internal => graphql-java-kickstart/src/main/java/graphql/kickstart/execution}/GraphQLRequest.java (97%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java (100%) rename {src/main/java/graphql/servlet/core/internal => graphql-java-kickstart/src/main/java/graphql/kickstart/execution}/VariablesDeserializer.java (97%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java (100%) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/context/GraphQLContext.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java (97%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java (95%) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java (100%) rename {src => graphql-java-kickstart/src}/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java (100%) create mode 100644 graphql-java-servlet/build.gradle rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java (98%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/BatchedQueryResponseWriter.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java (87%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/DefaultGraphQLServlet.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/ErrorQueryResponseWriter.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/ExecutionResultSubscriber.java (100%) rename {src/main/java/graphql/kickstart/execution/config => graphql-java-servlet/src/main/java/graphql/servlet}/GraphQLConfiguration.java (98%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java (97%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/GraphQLHttpServlet.java (95%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/GraphQLInvocationInputParser.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java (99%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java (93%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/GraphQLWebsocketServlet.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/HttpRequestHandler.java (89%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/HttpRequestHandlerImpl.java (98%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java (99%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/QueryResponseWriter.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java (98%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/SingleQueryResponseWriter.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/StaticDataPublisher.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/SubscriptionAsyncListener.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/apollo/ApolloScalars.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/config/GraphQLSchemaProvider.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/context/GraphQLContextBuilder.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/context/GraphQLServletContext.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/context/GraphQLWebSocketContext.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/GraphQLMBean.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/GraphQLObjectMapper.java (98%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/GraphQLServletListener.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java (99%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java (97%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/GraphQLThreadFactory.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/SubscriptionSender.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/VariableMapper.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/input/BatchInputPreProcessResult.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/input/BatchInputPreProcessor.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java (99%) rename {src/main/java/graphql/kickstart/execution => graphql-java-servlet/src/main/java/graphql/servlet}/input/NoOpBatchInputPreProcessor.java (86%) rename {src/main/java/graphql/kickstart/execution/config => graphql-java-servlet/src/main/java/graphql/servlet/osgi}/GraphQLCodeRegistryProvider.java (82%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/osgi/GraphQLProvider.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java (100%) rename {src => graphql-java-servlet/src}/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java (100%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy (100%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy (100%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy (98%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java (100%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/TestException.groovy (100%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/TestGraphQLErrorException.groovy (100%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/TestMultipartPart.groovy (100%) rename {src => graphql-java-servlet/src}/test/groovy/graphql/servlet/TestUtils.groovy (99%) delete mode 100644 src/main/java/graphql/kickstart/execution/context/ContextSetting.java delete mode 100644 src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java diff --git a/build.gradle b/build.gradle index a7e908c6..9f150579 100644 --- a/build.gradle +++ b/build.gradle @@ -1,213 +1,221 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 oEmbedler Inc. and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + buildscript { repositories { - jcenter() + mavenLocal() mavenCentral() + jcenter() + maven { url "https://dl.bintray.com/graphql-java-kickstart/releases" } + maven { url "https://plugins.gradle.org/m2/" } + maven { url 'https://repo.spring.io/plugins-release' } } dependencies { - classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:3.1.0' + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.+" + classpath 'net.researchgate:gradle-release:2.7.0' } } + plugins { - id "com.jfrog.bintray" version "1.8.4" - id "com.jfrog.artifactory" version "4.8.1" id 'net.researchgate.release' version '2.7.0' - id 'io.franzbecker.gradle-lombok' version '3.1.0' + id 'io.franzbecker.gradle-lombok' version '3.1.0' apply false + id "com.jfrog.artifactory" version "4.8.1" apply false } -apply plugin: 'java' -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +subprojects { + apply plugin: 'idea' + apply plugin: 'java' + apply plugin: 'maven' + apply plugin: 'maven-publish' + apply plugin: "com.jfrog.bintray" + apply plugin: 'io.franzbecker.gradle-lombok' + apply plugin: 'com.jfrog.artifactory' + + repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { url "https://dl.bintray.com/graphql-java-kickstart/releases" } + maven { url "https://oss.jfrog.org/artifactory/oss-snapshot-local" } + maven { url "https://repo.spring.io/libs-milestone" } + } -// Tests -apply plugin: 'groovy' + idea { + module { + downloadJavadoc = true + downloadSources = true + } + } -repositories { - mavenLocal() - mavenCentral() -} + compileJava { + sourceCompatibility = SOURCE_COMPATIBILITY + targetCompatibility = TARGET_COMPATIBILITY + } -dependencies { - compile 'org.slf4j:slf4j-api:1.7.21' - - // Useful utilities - compile 'com.google.guava:guava:24.1.1-jre' - - // Unit testing - testCompile "org.codehaus.groovy:groovy-all:2.4.1" - testCompile "org.spockframework:spock-core:1.1-groovy-2.4-rc-3" - testRuntime "cglib:cglib-nodep:3.2.4" - testRuntime "org.objenesis:objenesis:2.5.1" - testCompile 'org.slf4j:slf4j-simple:1.7.24' - testCompile 'org.springframework:spring-test:4.3.7.RELEASE' - testRuntime 'org.springframework:spring-web:4.3.7.RELEASE' - - // OSGi - compileOnly 'org.osgi:org.osgi.core:6.0.0' - compileOnly 'org.osgi:org.osgi.service.cm:1.5.0' - compileOnly 'org.osgi:org.osgi.service.component:1.3.0' - compileOnly 'biz.aQute.bnd:biz.aQute.bndlib:3.1.0' - - // Servlet - compile 'javax.servlet:javax.servlet-api:3.1.0' - compile 'javax.websocket:javax.websocket-api:1.1' - - // GraphQL - compile "com.graphql-java:graphql-java:$LIB_GRAPHQL_JAVA_VER" - - testCompile 'io.github.graphql-java:graphql-java-annotations:5.2' - - // JSON - compile "com.fasterxml.jackson.core:jackson-core:$LIB_JACKSON_VER" - compile "com.fasterxml.jackson.core:jackson-annotations:$LIB_JACKSON_VER" - compile "com.fasterxml.jackson.core:jackson-databind:$LIB_JACKSON_VER" - compile "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$LIB_JACKSON_VER" -} + compileJava.dependsOn(processResources) -apply plugin: 'osgi' -apply plugin: 'java-library-distribution' -apply plugin: 'biz.aQute.bnd.builder' -apply plugin: 'com.jfrog.bintray' -apply plugin: 'maven-publish' -apply plugin: 'idea' -apply plugin: 'maven' - -jar { - manifest { - instruction 'Require-Capability', 'osgi.extender' + lombok { + version = "1.18.4" + sha256 = "" } -} -// custom tasks for creating source/javadoc jars -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' - from sourceSets.main.allSource -} -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} + if (!it.name.startsWith('example')) { -publishing { - publications { - maven(MavenPublication) { - from components.java - groupId 'com.graphql-java-kickstart' - artifactId project.name - version project.version - - artifact sourcesJar - artifact javadocJar - - pom.withXml { - asNode().children().last() + { - resolveStrategy = Closure.DELEGATE_FIRST - name 'graphql-java-servlet' - description 'relay.js-compatible GraphQL servlet' - url 'https://github.com/graphql-java-kickstart/graphql-java-servlet' - inceptionYear '2016' - - scm { - url 'https://github.com/graphql-java-kickstart/graphql-java-servlet' - connection 'scm:https://github.com/graphql-java-kickstart/graphql-java-servlet.git' - developerConnection 'scm:git://github.com/graphql-java-kickstart/graphql-java-servlet.git' - } + jar { + from "LICENSE.md" + } - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution 'repo' - } + task sourcesJar(type: Jar) { + dependsOn classes + classifier 'sources' + from sourceSets.main.allSource + } + + task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir + } + + artifacts { + archives sourcesJar + archives javadocJar + } + + publishing { + publications { + mainProjectPublication(MavenPublication) { + version version + from components.java + + artifact sourcesJar { + classifier "sources" + } + artifact javadocJar { + classifier "javadoc" } - developers { - developer { - id 'yrashk' - name 'Yurii Rashkovskii' - email 'yrashk@gmail.com' - } - developer { - id 'apottere' - name 'Andrew Potter' - email 'apottere@gmail.com' + pom.withXml { + asNode().children().last() + { + resolveStrategy = Closure.DELEGATE_FIRST + name 'graphql-java-servlet' + description 'relay.js-compatible GraphQL servlet' + url 'https://github.com/graphql-java-kickstart/graphql-java-servlet' + inceptionYear '2016' + + scm { + url 'https://github.com/graphql-java-kickstart/graphql-java-servlet' + connection 'scm:https://github.com/graphql-java-kickstart/graphql-java-servlet.git' + developerConnection 'scm:git://github.com/graphql-java-kickstart/graphql-java-servlet.git' + } + + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + + developers { + developer { + id 'yrashk' + name 'Yurii Rashkovskii' + email 'yrashk@gmail.com' + } + developer { + id 'apottere' + name 'Andrew Potter' + email 'apottere@gmail.com' + } + } } + // https://discuss.gradle.org/t/maven-publish-plugin-generated-pom-making-dependency-scope-runtime/7494/10 + asNode().dependencies.'*'.findAll() { + it.scope.text() == 'runtime' && project.configurations.compile.allDependencies.find { dep -> + dep.name == it.artifactId.text() + } + }.each { it.scope*.value = 'compile'} } } - // https://discuss.gradle.org/t/maven-publish-plugin-generated-pom-making-dependency-scope-runtime/7494/10 - asNode().dependencies.'*'.findAll() { - it.scope.text() == 'runtime' && project.configurations.compile.allDependencies.find { dep -> - dep.name == it.artifactId.text() - } - }.each { it.scope*.value = 'compile'} } } - } -} - -release { - tagTemplate = 'v${version}' - failOnPublishNeeded = false - ignoredSnapshotDependencies = ['com.graphql-java-kickstart:graphql-java-servlet'] -} -afterReleaseBuild.dependsOn bintrayUpload - -bintray { - user = System.env.BINTRAY_USER ?: project.findProperty('BINTRAY_USER') ?: '' - key = System.env.BINTRAY_PASS ?: project.findProperty('BINTRAY_PASS') ?: '' - publications = ['maven'] - publish = true - pkg { - repo = 'releases' - name = project.name - licenses = ['Apache-2.0'] - vcsUrl = 'https://github.com/graphql-java-kickstart/graphql-java-servlet' - userOrg = 'graphql-java-kickstart' - version { - name = project.version - mavenCentralSync { - close = '1' + bintray { + user = System.env.BINTRAY_USER ?: project.findProperty('BINTRAY_USER') ?: '' + key = System.env.BINTRAY_PASS ?: project.findProperty('BINTRAY_PASS') ?: '' + publications = ['mainProjectPublication'] + publish = true + pkg { + repo = 'releases' + name = PROJECT_NAME + desc = PROJECT_DESC + licenses = [PROJECT_LICENSE] + vcsUrl = PROJECT_GIT_REPO_URL + userOrg = 'graphql-java-kickstart' + version { + name = project.version + mavenCentralSync { + close = '1' + } + } } } - } -} -artifactory { - contextUrl = 'https://oss.jfrog.org' - publish { - repository { - repoKey = 'oss-snapshot-local' + artifactory { + contextUrl = 'https://oss.jfrog.org' + publish { + repository { + repoKey = 'oss-snapshot-local' - username = System.env.BINTRAY_USER ?: System.getProperty('BINTRAY_USER') - password = System.env.BINTRAY_PASS ?: System.getProperty('BINTRAY_PASS') + username = System.env.BINTRAY_USER ?: System.getProperty('BINTRAY_USER') + password = System.env.BINTRAY_PASS ?: System.getProperty('BINTRAY_PASS') - maven = true - } - defaults { - publications 'maven' - publishArtifacts = true - publishPom = true - } - } - resolve { - repository { - repoKey = 'jcenter' + maven = true + } + defaults { + publications 'maven' + publishArtifacts = true + publishPom = true + } + } + resolve { + repository { + repoKey = 'jcenter' + } + } } } } -idea { - project { - languageLevel = '11' - vcs = 'Git' - } +release { + tagTemplate = 'v${version}' + failOnPublishNeeded = false + ignoredSnapshotDependencies = ['com.graphql-java-kickstart:graphql-java-servlet'] } -wrapper { - gradleVersion = '4.10.3' +task build { + dependsOn subprojects.findResults { it.tasks.findByName('assemble') } + dependsOn subprojects.findResults { it.tasks.findByName('check') } + dependsOn subprojects.findResults { it.tasks.findByName('bintray') } } -lombok { - version = '1.18.10' +task wrapper(type: Wrapper) { + gradleVersion = "${GRADLE_WRAPPER_VER}" } diff --git a/gradle.properties b/gradle.properties index 4ed5737d..09b0a2bc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,18 @@ version = 8.0.1-SNAPSHOT group = com.graphql-java-kickstart +PROJECT_NAME = graphql-java-kickstart +PROJECT_DESC = GraphQL Java Kickstart +PROJECT_GIT_REPO_URL = https://github.com/graphql-java-kickstart/graphql-spring-boot +PROJECT_LICENSE = MIT +PROJECT_LICENSE_URL = https://github.com/graphql-java-kickstart/spring-boot-graphql/blob/master/LICENSE.md +PROJECT_DEV_ID = apottere +PROJECT_DEV_NAME = Andrew Potter + LIB_GRAPHQL_JAVA_VER = 13.0 LIB_JACKSON_VER = 2.9.9 + +SOURCE_COMPATIBILITY = 1.8 +TARGET_COMPATIBILITY = 1.8 + +GRADLE_WRAPPER_VER = 4.10.3 diff --git a/graphql-java-kickstart/build.gradle b/graphql-java-kickstart/build.gradle new file mode 100644 index 00000000..cf44bc07 --- /dev/null +++ b/graphql-java-kickstart/build.gradle @@ -0,0 +1,10 @@ +dependencies { + // GraphQL + compile "com.graphql-java:graphql-java:$LIB_GRAPHQL_JAVA_VER" + + // JSON + compile "com.fasterxml.jackson.core:jackson-core:$LIB_JACKSON_VER" + compile "com.fasterxml.jackson.core:jackson-annotations:$LIB_JACKSON_VER" + compile "com.fasterxml.jackson.core:jackson-databind:$LIB_JACKSON_VER" + compile "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$LIB_JACKSON_VER" +} diff --git a/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DecoratedExecutionResult.java diff --git a/src/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLBatchedQueryResult.java diff --git a/src/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLErrorQueryResult.java diff --git a/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java diff --git a/src/main/java/graphql/kickstart/execution/GraphQLQueryResult.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryResult.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/GraphQLQueryResult.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryResult.java diff --git a/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLRequest.java similarity index 97% rename from src/main/java/graphql/servlet/core/internal/GraphQLRequest.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLRequest.java index 8a2ad61d..caf093d3 100644 --- a/src/main/java/graphql/servlet/core/internal/GraphQLRequest.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLRequest.java @@ -1,4 +1,4 @@ -package graphql.servlet.core.internal; +package graphql.kickstart.execution; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/src/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLSingleQueryResult.java diff --git a/src/main/java/graphql/servlet/core/internal/VariablesDeserializer.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/VariablesDeserializer.java similarity index 97% rename from src/main/java/graphql/servlet/core/internal/VariablesDeserializer.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/VariablesDeserializer.java index c7bc1925..fdd3a3ff 100644 --- a/src/main/java/graphql/servlet/core/internal/VariablesDeserializer.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/VariablesDeserializer.java @@ -1,4 +1,4 @@ -package graphql.servlet.core.internal; +package graphql.kickstart.execution; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.ObjectCodec; diff --git a/src/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ConfiguringObjectMapperProvider.java diff --git a/src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/DefaultExecutionStrategyProvider.java diff --git a/src/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ExecutionStrategyProvider.java diff --git a/src/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/InstrumentationProvider.java diff --git a/src/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ObjectMapperConfigurer.java diff --git a/src/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/ObjectMapperProvider.java diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java new file mode 100644 index 00000000..d1f508d5 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java @@ -0,0 +1,98 @@ +package graphql.kickstart.execution.context; + +import graphql.ExecutionInput; +import graphql.execution.ExecutionId; +import graphql.execution.instrumentation.ChainedInstrumentation; +import graphql.execution.instrumentation.Instrumentation; +import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions; +import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.PerQueryBatchedInvocationInput; +import graphql.kickstart.execution.input.PerRequestBatchedInvocationInput; +import graphql.kickstart.execution.instrumentation.ConfigurableDispatchInstrumentation; +import graphql.kickstart.execution.instrumentation.FieldLevelTrackingApproach; +import graphql.kickstart.execution.instrumentation.RequestLevelTrackingApproach; +import graphql.schema.GraphQLSchema; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.dataloader.DataLoaderRegistry; + +/** + * An enum representing possible context settings. These are modeled after Apollo's link settings. + */ +public enum ContextSetting { + + /** + * A context object, and therefor dataloader registry and subject, should be shared between all GraphQL executions in + * a http request. + */ + PER_REQUEST_WITH_INSTRUMENTATION, + PER_REQUEST_WITHOUT_INSTRUMENTATION, + /** + * Each GraphQL execution should always have its own context. + */ + PER_QUERY_WITH_INSTRUMENTATION, + PER_QUERY_WITHOUT_INSTRUMENTATION; + + /** + * Creates a set of inputs with the correct context based on the setting. + * + * @param requests the GraphQL requests to execute. + * @param schema the GraphQL schema to execute the requests against. + * @param contextSupplier method that returns the context to use for each execution or for the request as a whole. + * @param root the root object to use for each execution. + * @return a configured batch input. + */ + public GraphQLBatchedInvocationInput getBatch(List requests, GraphQLSchema schema, + Supplier contextSupplier, Object root) { + switch (this) { + case PER_QUERY_WITH_INSTRUMENTATION: + //Intentional fallthrough + case PER_QUERY_WITHOUT_INSTRUMENTATION: + return new PerQueryBatchedInvocationInput(requests, schema, contextSupplier, root, this); + case PER_REQUEST_WITHOUT_INSTRUMENTATION: + //Intentional fallthrough + case PER_REQUEST_WITH_INSTRUMENTATION: + return new PerRequestBatchedInvocationInput(requests, schema, contextSupplier, root, this); + default: + throw new RuntimeException("Unconfigured context setting type"); + } + } + + /** + * Augments the provided instrumentation supplier to also supply the correct dispatching instrumentation. + * + * @param instrumentation the instrumentation supplier to augment + * @param executionInputs the inputs that will be dispatched by the instrumentation + * @param options the DataLoader dispatching instrumentation options that will be used. + * @return augmented instrumentation supplier. + */ + public Supplier configureInstrumentationForContext(Supplier instrumentation, + List executionInputs, + DataLoaderDispatcherInstrumentationOptions options) { + ConfigurableDispatchInstrumentation dispatchInstrumentation; + switch (this) { + case PER_REQUEST_WITH_INSTRUMENTATION: + DataLoaderRegistry registry = executionInputs.stream().findFirst().map(ExecutionInput::getDataLoaderRegistry) + .orElseThrow(IllegalArgumentException::new); + List executionIds = executionInputs.stream().map(ExecutionInput::getExecutionId) + .collect(Collectors.toList()); + RequestLevelTrackingApproach requestTrackingApproach = new RequestLevelTrackingApproach(executionIds, registry); + dispatchInstrumentation = new ConfigurableDispatchInstrumentation(options, + (dataLoaderRegistry -> requestTrackingApproach)); + break; + case PER_QUERY_WITH_INSTRUMENTATION: + dispatchInstrumentation = new ConfigurableDispatchInstrumentation(options, FieldLevelTrackingApproach::new); + break; + case PER_REQUEST_WITHOUT_INSTRUMENTATION: + //Intentional fallthrough + case PER_QUERY_WITHOUT_INSTRUMENTATION: + return instrumentation::get; + default: + throw new RuntimeException("Unconfigured context setting type"); + } + return () -> new ChainedInstrumentation(Arrays.asList(dispatchInstrumentation, instrumentation.get())); + } +} diff --git a/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContext.java diff --git a/src/main/java/graphql/kickstart/execution/context/GraphQLContext.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/GraphQLContext.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/context/GraphQLContext.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/GraphQLContext.java diff --git a/src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/DefaultGraphQLErrorHandler.java diff --git a/src/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/DefaultObjectMapperConfigurer.java diff --git a/src/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/GenericGraphQLError.java diff --git a/src/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/GraphQLErrorHandler.java diff --git a/src/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/error/RenderableNonNullableFieldWasNullError.java diff --git a/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java diff --git a/src/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLInvocationInput.java diff --git a/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java similarity index 97% rename from src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java index 93a4a847..7159e176 100644 --- a/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLSingleInvocationInput.java @@ -2,9 +2,9 @@ import graphql.ExecutionInput; import graphql.execution.ExecutionId; -import graphql.schema.GraphQLSchema; +import graphql.kickstart.execution.GraphQLRequest; import graphql.kickstart.execution.context.GraphQLContext; -import graphql.servlet.core.internal.GraphQLRequest; +import graphql.schema.GraphQLSchema; import java.util.Optional; import javax.security.auth.Subject; import org.dataloader.DataLoaderRegistry; diff --git a/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java similarity index 95% rename from src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java index 87948528..c6c24365 100644 --- a/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java @@ -1,9 +1,9 @@ package graphql.kickstart.execution.input; +import graphql.kickstart.execution.GraphQLRequest; import graphql.schema.GraphQLSchema; import graphql.kickstart.execution.context.ContextSetting; import graphql.kickstart.execution.context.GraphQLContext; -import graphql.servlet.core.internal.GraphQLRequest; import java.util.List; import java.util.function.Supplier; diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java new file mode 100644 index 00000000..d0f6876e --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java @@ -0,0 +1,29 @@ +package graphql.kickstart.execution.input; + +import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.execution.context.GraphQLContext; +import graphql.schema.GraphQLSchema; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import lombok.Getter; + +/** + * A collection of GraphQLSingleInvocationInputs that share a context object. + */ +@Getter +public class PerRequestBatchedInvocationInput implements GraphQLBatchedInvocationInput { + + private final List executionInputs; + private final ContextSetting contextSetting; + + public PerRequestBatchedInvocationInput(List requests, GraphQLSchema schema, + Supplier contextSupplier, Object root, ContextSetting contextSetting) { + GraphQLContext context = contextSupplier.get(); + executionInputs = requests.stream().map(request -> new GraphQLSingleInvocationInput(request, schema, context, root)) + .collect(Collectors.toList()); + this.contextSetting = contextSetting; + } + +} diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/AbstractTrackingApproach.java diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/ConfigurableDispatchInstrumentation.java diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/DataLoaderDispatcherInstrumentationState.java diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/FieldLevelTrackingApproach.java diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/NoOpInstrumentationProvider.java diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/RequestLevelTrackingApproach.java diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/RequestStack.java diff --git a/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/instrumentation/TrackingApproach.java diff --git a/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java diff --git a/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java similarity index 100% rename from src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java diff --git a/graphql-java-servlet/build.gradle b/graphql-java-servlet/build.gradle new file mode 100644 index 00000000..e2829bea --- /dev/null +++ b/graphql-java-servlet/build.gradle @@ -0,0 +1,52 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:3.1.0' + } +} + +apply plugin: 'groovy' +apply plugin: 'osgi' +apply plugin: 'java-library-distribution' +apply plugin: 'biz.aQute.bnd.builder' + +jar { + manifest { + instruction 'Require-Capability', 'osgi.extender' + } +} + +//afterReleaseBuild.dependsOn bintrayUpload + +dependencies { + compile(project(':graphql-java-kickstart')) + +// compile 'org.slf4j:slf4j-api:1.7.21' + + // Useful utilities + compile 'com.google.guava:guava:24.1.1-jre' + + // Servlet + compile 'javax.servlet:javax.servlet-api:3.1.0' + compile 'javax.websocket:javax.websocket-api:1.1' + + // OSGi + compileOnly 'org.osgi:org.osgi.core:6.0.0' + compileOnly 'org.osgi:org.osgi.service.cm:1.5.0' + compileOnly 'org.osgi:org.osgi.service.component:1.3.0' + compileOnly 'biz.aQute.bnd:biz.aQute.bndlib:3.1.0' + + testCompile 'io.github.graphql-java:graphql-java-annotations:5.2' + + // Unit testing + testCompile "org.codehaus.groovy:groovy-all:2.4.1" + testCompile "org.spockframework:spock-core:1.1-groovy-2.4-rc-3" + testRuntime "cglib:cglib-nodep:3.2.4" + testRuntime "org.objenesis:objenesis:2.5.1" + testCompile 'org.slf4j:slf4j-simple:1.7.24' + testCompile 'org.springframework:spring-test:4.3.7.RELEASE' + testRuntime 'org.springframework:spring-web:4.3.7.RELEASE' +} diff --git a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java similarity index 98% rename from src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java rename to graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java index e94fe446..fbcbb5d1 100644 --- a/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java @@ -1,12 +1,11 @@ package graphql.servlet; import graphql.schema.GraphQLFieldDefinition; -import graphql.kickstart.execution.config.GraphQLConfiguration; import graphql.servlet.core.GraphQLMBean; import graphql.servlet.core.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.core.GraphQLServletListener; -import graphql.servlet.core.internal.GraphQLRequest; +import graphql.kickstart.execution.GraphQLRequest; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java similarity index 100% rename from src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java rename to graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java diff --git a/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java similarity index 100% rename from src/main/java/graphql/servlet/BatchedQueryResponseWriter.java rename to graphql-java-servlet/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java diff --git a/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java similarity index 87% rename from src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java rename to graphql-java-servlet/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java index 24a671c3..c00fdf1c 100644 --- a/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java @@ -1,7 +1,5 @@ package graphql.servlet; -import graphql.kickstart.execution.config.GraphQLConfiguration; - import java.util.Objects; class ConfiguredGraphQLHttpServlet extends GraphQLHttpServlet { diff --git a/src/main/java/graphql/servlet/DefaultGraphQLServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/DefaultGraphQLServlet.java similarity index 100% rename from src/main/java/graphql/servlet/DefaultGraphQLServlet.java rename to graphql-java-servlet/src/main/java/graphql/servlet/DefaultGraphQLServlet.java diff --git a/src/main/java/graphql/servlet/ErrorQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/ErrorQueryResponseWriter.java similarity index 100% rename from src/main/java/graphql/servlet/ErrorQueryResponseWriter.java rename to graphql-java-servlet/src/main/java/graphql/servlet/ErrorQueryResponseWriter.java diff --git a/src/main/java/graphql/servlet/ExecutionResultSubscriber.java b/graphql-java-servlet/src/main/java/graphql/servlet/ExecutionResultSubscriber.java similarity index 100% rename from src/main/java/graphql/servlet/ExecutionResultSubscriber.java rename to graphql-java-servlet/src/main/java/graphql/servlet/ExecutionResultSubscriber.java diff --git a/src/main/java/graphql/kickstart/execution/config/GraphQLConfiguration.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java similarity index 98% rename from src/main/java/graphql/kickstart/execution/config/GraphQLConfiguration.java rename to graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java index b1b6cfe1..f3cfca36 100644 --- a/src/main/java/graphql/kickstart/execution/config/GraphQLConfiguration.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java @@ -1,4 +1,4 @@ -package graphql.kickstart.execution.config; +package graphql.servlet; import graphql.schema.GraphQLSchema; import graphql.servlet.config.DefaultGraphQLSchemaProvider; @@ -12,7 +12,7 @@ import graphql.servlet.input.BatchInputPreProcessor; import graphql.servlet.input.GraphQLInvocationInputFactory; import graphql.servlet.core.internal.GraphQLThreadFactory; -import graphql.kickstart.execution.input.NoOpBatchInputPreProcessor; +import graphql.servlet.input.NoOpBatchInputPreProcessor; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java similarity index 97% rename from src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java rename to graphql-java-servlet/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java index 0e47795b..f51a97e0 100644 --- a/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java @@ -3,7 +3,7 @@ import graphql.GraphQLException; import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.internal.GraphQLRequest; +import graphql.kickstart.execution.GraphQLRequest; import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; diff --git a/src/main/java/graphql/servlet/GraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLHttpServlet.java similarity index 95% rename from src/main/java/graphql/servlet/GraphQLHttpServlet.java rename to graphql-java-servlet/src/main/java/graphql/servlet/GraphQLHttpServlet.java index 2709d046..b0ba217a 100644 --- a/src/main/java/graphql/servlet/GraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLHttpServlet.java @@ -3,7 +3,6 @@ import graphql.schema.GraphQLSchema; import graphql.servlet.core.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.kickstart.execution.config.GraphQLConfiguration; import graphql.servlet.input.GraphQLInvocationInputFactory; /** diff --git a/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java similarity index 100% rename from src/main/java/graphql/servlet/GraphQLInvocationInputParser.java rename to graphql-java-servlet/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java diff --git a/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java similarity index 99% rename from src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java rename to graphql-java-servlet/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java index 1fc5db23..d40e8511 100644 --- a/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java @@ -5,7 +5,7 @@ import graphql.GraphQLException; import graphql.kickstart.execution.context.ContextSetting; import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.internal.GraphQLRequest; +import graphql.kickstart.execution.GraphQLRequest; import graphql.servlet.core.internal.VariableMapper; import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; diff --git a/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java similarity index 93% rename from src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java rename to graphql-java-servlet/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java index 7b73d6d2..53aa9688 100644 --- a/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java @@ -1,13 +1,12 @@ package graphql.servlet; -import static graphql.servlet.HttpRequestHandler.APPLICATION_GRAPHQL; import static java.util.stream.Collectors.joining; import graphql.GraphQLException; +import graphql.kickstart.execution.GraphQLRequest; import graphql.kickstart.execution.context.ContextSetting; -import graphql.servlet.core.GraphQLObjectMapper; -import graphql.servlet.core.internal.GraphQLRequest; import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.servlet.core.GraphQLObjectMapper; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; import java.util.List; @@ -16,6 +15,8 @@ class GraphQLPostInvocationInputParser extends AbstractGraphQLInvocationInputParser { + private static final String APPLICATION_GRAPHQL = "application/graphql"; + GraphQLPostInvocationInputParser(GraphQLInvocationInputFactory invocationInputFactory, GraphQLObjectMapper graphQLObjectMapper, ContextSetting contextSetting) { super(invocationInputFactory, graphQLObjectMapper, contextSetting); diff --git a/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java similarity index 100% rename from src/main/java/graphql/servlet/GraphQLWebsocketServlet.java rename to graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java diff --git a/src/main/java/graphql/servlet/HttpRequestHandler.java b/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandler.java similarity index 89% rename from src/main/java/graphql/servlet/HttpRequestHandler.java rename to graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandler.java index 81e05805..3e0bb385 100644 --- a/src/main/java/graphql/servlet/HttpRequestHandler.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandler.java @@ -7,7 +7,7 @@ interface HttpRequestHandler { String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8"; String APPLICATION_EVENT_STREAM_UTF8 = "text/event-stream;charset=UTF-8"; - String APPLICATION_GRAPHQL = "application/graphql"; + int STATUS_OK = 200; int STATUS_BAD_REQUEST = 400; diff --git a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java b/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java similarity index 98% rename from src/main/java/graphql/servlet/HttpRequestHandlerImpl.java rename to graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java index 6c1b2fc4..caa08930 100644 --- a/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java @@ -4,7 +4,6 @@ import graphql.GraphQLException; import graphql.kickstart.execution.GraphQLQueryResult; -import graphql.kickstart.execution.config.GraphQLConfiguration; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.input.BatchInputPreProcessResult; import graphql.servlet.input.BatchInputPreProcessor; diff --git a/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java similarity index 99% rename from src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java rename to graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java index 6a743658..384434b6 100644 --- a/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java @@ -11,7 +11,7 @@ import graphql.kickstart.execution.config.DefaultExecutionStrategyProvider; import graphql.servlet.config.DefaultGraphQLSchemaProvider; import graphql.kickstart.execution.config.ExecutionStrategyProvider; -import graphql.kickstart.execution.config.GraphQLCodeRegistryProvider; +import graphql.servlet.osgi.GraphQLCodeRegistryProvider; import graphql.servlet.osgi.GraphQLMutationProvider; import graphql.servlet.osgi.GraphQLProvider; import graphql.servlet.osgi.GraphQLQueryProvider; diff --git a/src/main/java/graphql/servlet/QueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/QueryResponseWriter.java similarity index 100% rename from src/main/java/graphql/servlet/QueryResponseWriter.java rename to graphql-java-servlet/src/main/java/graphql/servlet/QueryResponseWriter.java diff --git a/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java similarity index 98% rename from src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java rename to graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java index 15db66ec..4444b959 100644 --- a/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java @@ -5,7 +5,6 @@ import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.config.GraphQLSchemaProvider; import graphql.servlet.core.GraphQLServletListener; -import graphql.kickstart.execution.config.GraphQLConfiguration; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.util.ArrayList; diff --git a/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java similarity index 100% rename from src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java rename to graphql-java-servlet/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java diff --git a/src/main/java/graphql/servlet/SingleQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/SingleQueryResponseWriter.java similarity index 100% rename from src/main/java/graphql/servlet/SingleQueryResponseWriter.java rename to graphql-java-servlet/src/main/java/graphql/servlet/SingleQueryResponseWriter.java diff --git a/src/main/java/graphql/servlet/StaticDataPublisher.java b/graphql-java-servlet/src/main/java/graphql/servlet/StaticDataPublisher.java similarity index 100% rename from src/main/java/graphql/servlet/StaticDataPublisher.java rename to graphql-java-servlet/src/main/java/graphql/servlet/StaticDataPublisher.java diff --git a/src/main/java/graphql/servlet/SubscriptionAsyncListener.java b/graphql-java-servlet/src/main/java/graphql/servlet/SubscriptionAsyncListener.java similarity index 100% rename from src/main/java/graphql/servlet/SubscriptionAsyncListener.java rename to graphql-java-servlet/src/main/java/graphql/servlet/SubscriptionAsyncListener.java diff --git a/src/main/java/graphql/servlet/apollo/ApolloScalars.java b/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloScalars.java similarity index 100% rename from src/main/java/graphql/servlet/apollo/ApolloScalars.java rename to graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloScalars.java diff --git a/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java b/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java similarity index 100% rename from src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java rename to graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java diff --git a/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java similarity index 100% rename from src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java diff --git a/src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java similarity index 100% rename from src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java diff --git a/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java similarity index 100% rename from src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java rename to graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java diff --git a/src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java similarity index 100% rename from src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java rename to graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContext.java diff --git a/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java similarity index 100% rename from src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java rename to graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java diff --git a/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java similarity index 100% rename from src/main/java/graphql/servlet/context/GraphQLContextBuilder.java rename to graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java diff --git a/src/main/java/graphql/servlet/context/GraphQLServletContext.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLServletContext.java similarity index 100% rename from src/main/java/graphql/servlet/context/GraphQLServletContext.java rename to graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLServletContext.java diff --git a/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java similarity index 100% rename from src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java rename to graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java diff --git a/src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java similarity index 100% rename from src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java diff --git a/src/main/java/graphql/servlet/core/GraphQLMBean.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLMBean.java similarity index 100% rename from src/main/java/graphql/servlet/core/GraphQLMBean.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLMBean.java diff --git a/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java similarity index 98% rename from src/main/java/graphql/servlet/core/GraphQLObjectMapper.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java index 1e632481..add9fc02 100644 --- a/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java @@ -18,8 +18,8 @@ import graphql.kickstart.execution.config.ConfiguringObjectMapperProvider; import graphql.kickstart.execution.config.ObjectMapperConfigurer; import graphql.kickstart.execution.config.ObjectMapperProvider; -import graphql.servlet.core.internal.GraphQLRequest; -import graphql.servlet.core.internal.VariablesDeserializer; +import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.VariablesDeserializer; import java.io.IOException; import java.io.InputStream; import java.io.Writer; diff --git a/src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java similarity index 100% rename from src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java diff --git a/src/main/java/graphql/servlet/core/GraphQLServletListener.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLServletListener.java similarity index 100% rename from src/main/java/graphql/servlet/core/GraphQLServletListener.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLServletListener.java diff --git a/src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java similarity index 100% rename from src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java diff --git a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java diff --git a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java diff --git a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java similarity index 99% rename from src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java index 9bcfc306..6f064f50 100644 --- a/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonValue; import graphql.ExecutionResult; +import graphql.kickstart.execution.GraphQLRequest; import graphql.servlet.apollo.ApolloSubscriptionConnectionListener; import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import graphql.kickstart.execution.subscription.SubscriptionException; diff --git a/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java diff --git a/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java similarity index 97% rename from src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java index cffbc2ff..fa4a9903 100644 --- a/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java @@ -1,5 +1,6 @@ package graphql.servlet.core.internal; +import graphql.kickstart.execution.GraphQLRequest; import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import javax.websocket.Session; diff --git a/src/main/java/graphql/servlet/core/internal/GraphQLThreadFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/GraphQLThreadFactory.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/GraphQLThreadFactory.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/GraphQLThreadFactory.java diff --git a/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java diff --git a/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java diff --git a/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java diff --git a/src/main/java/graphql/servlet/core/internal/SubscriptionSender.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionSender.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/SubscriptionSender.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionSender.java diff --git a/src/main/java/graphql/servlet/core/internal/VariableMapper.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/VariableMapper.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/VariableMapper.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/VariableMapper.java diff --git a/src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java similarity index 100% rename from src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java rename to graphql-java-servlet/src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java diff --git a/src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java b/graphql-java-servlet/src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java similarity index 100% rename from src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java rename to graphql-java-servlet/src/main/java/graphql/servlet/input/BatchInputPreProcessResult.java diff --git a/src/main/java/graphql/servlet/input/BatchInputPreProcessor.java b/graphql-java-servlet/src/main/java/graphql/servlet/input/BatchInputPreProcessor.java similarity index 100% rename from src/main/java/graphql/servlet/input/BatchInputPreProcessor.java rename to graphql-java-servlet/src/main/java/graphql/servlet/input/BatchInputPreProcessor.java diff --git a/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java similarity index 99% rename from src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java rename to graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java index c0857264..8ee3e33c 100644 --- a/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java @@ -10,7 +10,7 @@ import graphql.servlet.context.GraphQLContextBuilder; import graphql.servlet.core.DefaultGraphQLRootObjectBuilder; import graphql.servlet.core.GraphQLRootObjectBuilder; -import graphql.servlet.core.internal.GraphQLRequest; +import graphql.kickstart.execution.GraphQLRequest; import java.util.List; import java.util.function.Supplier; import javax.servlet.http.HttpServletRequest; diff --git a/src/main/java/graphql/kickstart/execution/input/NoOpBatchInputPreProcessor.java b/graphql-java-servlet/src/main/java/graphql/servlet/input/NoOpBatchInputPreProcessor.java similarity index 86% rename from src/main/java/graphql/kickstart/execution/input/NoOpBatchInputPreProcessor.java rename to graphql-java-servlet/src/main/java/graphql/servlet/input/NoOpBatchInputPreProcessor.java index 4e008f36..e645d413 100644 --- a/src/main/java/graphql/kickstart/execution/input/NoOpBatchInputPreProcessor.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/input/NoOpBatchInputPreProcessor.java @@ -1,5 +1,6 @@ -package graphql.kickstart.execution.input; +package graphql.servlet.input; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import graphql.servlet.input.BatchInputPreProcessResult; import graphql.servlet.input.BatchInputPreProcessor; import javax.servlet.http.HttpServletRequest; diff --git a/src/main/java/graphql/kickstart/execution/config/GraphQLCodeRegistryProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLCodeRegistryProvider.java similarity index 82% rename from src/main/java/graphql/kickstart/execution/config/GraphQLCodeRegistryProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLCodeRegistryProvider.java index af2e16b5..cbfab2e8 100644 --- a/src/main/java/graphql/kickstart/execution/config/GraphQLCodeRegistryProvider.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLCodeRegistryProvider.java @@ -1,4 +1,4 @@ -package graphql.kickstart.execution.config; +package graphql.servlet.osgi; import graphql.schema.GraphQLCodeRegistry; import graphql.servlet.osgi.GraphQLProvider; diff --git a/src/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java similarity index 100% rename from src/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLMutationProvider.java diff --git a/src/main/java/graphql/servlet/osgi/GraphQLProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLProvider.java similarity index 100% rename from src/main/java/graphql/servlet/osgi/GraphQLProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLProvider.java diff --git a/src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java similarity index 100% rename from src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLQueryProvider.java diff --git a/src/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java similarity index 100% rename from src/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLSubscriptionProvider.java diff --git a/src/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java similarity index 100% rename from src/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java rename to graphql-java-servlet/src/main/java/graphql/servlet/osgi/GraphQLTypesProvider.java diff --git a/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy similarity index 100% rename from src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy rename to graphql-java-servlet/src/test/groovy/graphql/servlet/AbstractGraphQLHttpServletSpec.groovy diff --git a/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy similarity index 100% rename from src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy rename to graphql-java-servlet/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy diff --git a/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy similarity index 98% rename from src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy rename to graphql-java-servlet/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy index 75d62477..ab32d145 100644 --- a/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy +++ b/graphql-java-servlet/src/test/groovy/graphql/servlet/OsgiGraphQLHttpServletSpec.groovy @@ -7,7 +7,7 @@ import graphql.annotations.processor.GraphQLAnnotations import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLInterfaceType -import graphql.kickstart.execution.config.GraphQLCodeRegistryProvider +import graphql.servlet.osgi.GraphQLCodeRegistryProvider import graphql.servlet.osgi.GraphQLMutationProvider import graphql.servlet.osgi.GraphQLQueryProvider import graphql.servlet.osgi.GraphQLSubscriptionProvider diff --git a/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java similarity index 100% rename from src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java rename to graphql-java-servlet/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java diff --git a/src/test/groovy/graphql/servlet/TestException.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestException.groovy similarity index 100% rename from src/test/groovy/graphql/servlet/TestException.groovy rename to graphql-java-servlet/src/test/groovy/graphql/servlet/TestException.groovy diff --git a/src/test/groovy/graphql/servlet/TestGraphQLErrorException.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestGraphQLErrorException.groovy similarity index 100% rename from src/test/groovy/graphql/servlet/TestGraphQLErrorException.groovy rename to graphql-java-servlet/src/test/groovy/graphql/servlet/TestGraphQLErrorException.groovy diff --git a/src/test/groovy/graphql/servlet/TestMultipartPart.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestMultipartPart.groovy similarity index 100% rename from src/test/groovy/graphql/servlet/TestMultipartPart.groovy rename to graphql-java-servlet/src/test/groovy/graphql/servlet/TestMultipartPart.groovy diff --git a/src/test/groovy/graphql/servlet/TestUtils.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestUtils.groovy similarity index 99% rename from src/test/groovy/graphql/servlet/TestUtils.groovy rename to graphql-java-servlet/src/test/groovy/graphql/servlet/TestUtils.groovy index 2fa1e84f..401d97fa 100644 --- a/src/test/groovy/graphql/servlet/TestUtils.groovy +++ b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestUtils.groovy @@ -11,7 +11,6 @@ import graphql.schema.idl.SchemaParser import graphql.schema.idl.TypeRuntimeWiring import graphql.schema.idl.errors.SchemaProblem import graphql.servlet.context.GraphQLContextBuilder -import graphql.kickstart.execution.config.GraphQLConfiguration import graphql.servlet.apollo.ApolloScalars import graphql.servlet.input.BatchInputPreProcessor import graphql.kickstart.execution.context.ContextSetting diff --git a/settings.gradle b/settings.gradle index f96cebb6..df6580aa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ rootProject.name = 'graphql-java-servlet' + +include ':graphql-java-kickstart' +include ':graphql-java-servlet' diff --git a/src/main/java/graphql/kickstart/execution/context/ContextSetting.java b/src/main/java/graphql/kickstart/execution/context/ContextSetting.java deleted file mode 100644 index 45f62297..00000000 --- a/src/main/java/graphql/kickstart/execution/context/ContextSetting.java +++ /dev/null @@ -1,94 +0,0 @@ -package graphql.kickstart.execution.context; - -import graphql.ExecutionInput; -import graphql.execution.ExecutionId; -import graphql.execution.instrumentation.ChainedInstrumentation; -import graphql.execution.instrumentation.Instrumentation; -import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions; -import graphql.schema.GraphQLSchema; -import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; -import graphql.kickstart.execution.input.PerQueryBatchedInvocationInput; -import graphql.kickstart.execution.input.PerRequestBatchedInvocationInput; -import graphql.kickstart.execution.instrumentation.ConfigurableDispatchInstrumentation; -import graphql.kickstart.execution.instrumentation.FieldLevelTrackingApproach; -import graphql.kickstart.execution.instrumentation.RequestLevelTrackingApproach; -import graphql.servlet.core.internal.GraphQLRequest; -import org.dataloader.DataLoaderRegistry; - -import java.util.Arrays; -import java.util.List; - -import java.util.function.Supplier; -import java.util.stream.Collectors; - -/** - * An enum representing possible context settings. These are modeled after Apollo's link settings. - */ -public enum ContextSetting { - - /** - * A context object, and therefor dataloader registry and subject, should be shared between all GraphQL executions in a http request. - */ - PER_REQUEST_WITH_INSTRUMENTATION, - PER_REQUEST_WITHOUT_INSTRUMENTATION, - /** - * Each GraphQL execution should always have its own context. - */ - PER_QUERY_WITH_INSTRUMENTATION, - PER_QUERY_WITHOUT_INSTRUMENTATION; - - /** - * Creates a set of inputs with the correct context based on the setting. - * @param requests the GraphQL requests to execute. - * @param schema the GraphQL schema to execute the requests against. - * @param contextSupplier method that returns the context to use for each execution or for the request as a whole. - * @param root the root object to use for each execution. - * @return a configured batch input. - */ - public GraphQLBatchedInvocationInput getBatch(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root) { - switch (this) { - case PER_QUERY_WITH_INSTRUMENTATION: - //Intentional fallthrough - case PER_QUERY_WITHOUT_INSTRUMENTATION: - return new PerQueryBatchedInvocationInput(requests, schema, contextSupplier, root, this); - case PER_REQUEST_WITHOUT_INSTRUMENTATION: - //Intentional fallthrough - case PER_REQUEST_WITH_INSTRUMENTATION: - return new PerRequestBatchedInvocationInput(requests, schema, contextSupplier, root, this); - default: - throw new RuntimeException("Unconfigured context setting type"); - } - } - - /** - * Augments the provided instrumentation supplier to also supply the correct dispatching instrumentation. - * @param instrumentation the instrumentation supplier to augment - * @param executionInputs the inputs that will be dispatched by the instrumentation - * @param options the DataLoader dispatching instrumentation options that will be used. - * @return augmented instrumentation supplier. - */ - public Supplier configureInstrumentationForContext(Supplier instrumentation, List executionInputs, - DataLoaderDispatcherInstrumentationOptions options) { - ConfigurableDispatchInstrumentation dispatchInstrumentation; - switch (this) { - case PER_REQUEST_WITH_INSTRUMENTATION: - DataLoaderRegistry registry = executionInputs.stream().findFirst().map(ExecutionInput::getDataLoaderRegistry) - .orElseThrow(IllegalArgumentException::new); - List executionIds = executionInputs.stream().map(ExecutionInput::getExecutionId).collect(Collectors.toList()); - RequestLevelTrackingApproach requestTrackingApproach = new RequestLevelTrackingApproach(executionIds, registry); - dispatchInstrumentation = new ConfigurableDispatchInstrumentation(options, - (dataLoaderRegistry -> requestTrackingApproach)); - break; - case PER_QUERY_WITH_INSTRUMENTATION: - dispatchInstrumentation = new ConfigurableDispatchInstrumentation(options, FieldLevelTrackingApproach::new); - break; - case PER_REQUEST_WITHOUT_INSTRUMENTATION: - //Intentional fallthrough - case PER_QUERY_WITHOUT_INSTRUMENTATION: - return instrumentation::get; - default: - throw new RuntimeException("Unconfigured context setting type"); - } - return () -> new ChainedInstrumentation(Arrays.asList(dispatchInstrumentation, instrumentation.get())); - } -} diff --git a/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java b/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java deleted file mode 100644 index f03f25fc..00000000 --- a/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java +++ /dev/null @@ -1,28 +0,0 @@ -package graphql.kickstart.execution.input; - -import graphql.schema.GraphQLSchema; -import graphql.kickstart.execution.context.ContextSetting; -import graphql.kickstart.execution.context.GraphQLContext; -import graphql.servlet.core.internal.GraphQLRequest; - -import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import lombok.Getter; - -/** - * A collection of GraphQLSingleInvocationInputs that share a context object. - */ -@Getter -public class PerRequestBatchedInvocationInput implements GraphQLBatchedInvocationInput { - - private final List executionInputs; - private final ContextSetting contextSetting; - - public PerRequestBatchedInvocationInput(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root, ContextSetting contextSetting) { - GraphQLContext context = contextSupplier.get(); - executionInputs = requests.stream().map(request -> new GraphQLSingleInvocationInput(request, schema, context, root)).collect(Collectors.toList()); - this.contextSetting = contextSetting; - } - -} From f17ad46b8d419d93a437d20983e988c7c8c7d533 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 17 Nov 2019 12:05:46 +0100 Subject: [PATCH 10/26] Extract core functionality into its own module --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 09b0a2bc..3dda48bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version = 8.0.1-SNAPSHOT +version = 9.0.0-SNAPSHOT group = com.graphql-java-kickstart PROJECT_NAME = graphql-java-kickstart From 80236b41b9e609afac034157b7d6d7cd0ce24b73 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Mon, 18 Nov 2019 12:47:51 +0100 Subject: [PATCH 11/26] Introduced GraphQLInvoker to allow invocation from webflux version --- build.gradle | 2 +- .../DefaultGraphQLRootObjectBuilder.java | 9 + .../kickstart/execution/GraphQLInvoker.java | 69 ++++ .../execution}/GraphQLObjectMapper.java | 13 +- .../execution/GraphQLQueryInvoker.java | 3 +- .../execution/GraphQLRootObjectBuilder.java | 10 + .../StaticGraphQLRootObjectBuilder.java | 20 ++ .../config/DefaultGraphQLSchemaProvider.java | 18 +- .../execution/config/GraphQLBuilder.java | 61 ++++ .../config/GraphQLSchemaProvider.java | 21 ++ .../execution/context/ContextSetting.java | 2 +- .../context/DefaultGraphQLContext.java | 42 +-- .../context/DefaultGraphQLContextBuilder.java | 13 + .../context/GraphQLContextBuilder.java | 10 + .../servlet/AbstractGraphQLHttpServlet.java | 2 +- .../AbstractGraphQLInvocationInputParser.java | 2 +- .../servlet/BatchedQueryResponseWriter.java | 2 +- .../servlet/DefaultGraphQLServlet.java | 2 +- .../servlet/ExecutionResultSubscriber.java | 2 +- .../graphql/servlet/GraphQLConfiguration.java | 339 +++++++++--------- .../GraphQLGetInvocationInputParser.java | 2 +- .../graphql/servlet/GraphQLHttpServlet.java | 2 +- .../servlet/GraphQLInvocationInputParser.java | 2 +- ...GraphQLMultipartInvocationInputParser.java | 11 +- .../GraphQLPostInvocationInputParser.java | 2 +- .../servlet/GraphQLWebsocketServlet.java | 2 +- .../servlet/OsgiGraphQLHttpServlet.java | 35 +- .../graphql/servlet/QueryResponseWriter.java | 2 +- .../servlet/SimpleGraphQLHttpServlet.java | 7 +- ...SingleAsynchronousQueryResponseWriter.java | 2 +- .../servlet/SingleQueryResponseWriter.java | 2 +- .../DefaultGraphQLSchemaServletProvider.java | 33 ++ .../servlet/config/GraphQLSchemaProvider.java | 40 --- .../config/GraphQLSchemaServletProvider.java | 28 ++ .../context/DefaultGraphQLContextBuilder.java | 29 -- .../DefaultGraphQLServletContextBuilder.java | 27 ++ ...java => GraphQLServletContextBuilder.java} | 8 +- .../core/DefaultGraphQLRootObjectBuilder.java | 22 +- .../core/GraphQLRootObjectBuilder.java | 15 - .../core/GraphQLServletRootObjectBuilder.java | 13 + .../core/StaticGraphQLRootObjectBuilder.java | 28 -- .../internal/SubscriptionHandlerInput.java | 2 +- .../input/GraphQLInvocationInputFactory.java | 51 +-- .../servlet/DataLoaderDispatchingSpec.groovy | 4 +- .../groovy/graphql/servlet/TestUtils.groovy | 4 +- 45 files changed, 611 insertions(+), 404 deletions(-) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DefaultGraphQLRootObjectBuilder.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java rename {graphql-java-servlet/src/main/java/graphql/servlet/core => graphql-java-kickstart/src/main/java/graphql/kickstart/execution}/GraphQLObjectMapper.java (95%) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLRootObjectBuilder.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/StaticGraphQLRootObjectBuilder.java rename {graphql-java-servlet/src/main/java/graphql/servlet => graphql-java-kickstart/src/main/java/graphql/kickstart/execution}/config/DefaultGraphQLSchemaProvider.java (61%) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLSchemaProvider.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContextBuilder.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/GraphQLContextBuilder.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaServletProvider.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaServletProvider.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java rename graphql-java-servlet/src/main/java/graphql/servlet/context/{GraphQLContextBuilder.java => GraphQLServletContextBuilder.java} (74%) delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLServletRootObjectBuilder.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java diff --git a/build.gradle b/build.gradle index 9f150579..d61ac2aa 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ buildscript { plugins { id 'net.researchgate.release' version '2.7.0' - id 'io.franzbecker.gradle-lombok' version '3.1.0' apply false + id 'io.franzbecker.gradle-lombok' version '3.2.0' apply false id "com.jfrog.artifactory" version "4.8.1" apply false } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DefaultGraphQLRootObjectBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DefaultGraphQLRootObjectBuilder.java new file mode 100644 index 00000000..b913e37b --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/DefaultGraphQLRootObjectBuilder.java @@ -0,0 +1,9 @@ +package graphql.kickstart.execution; + +public class DefaultGraphQLRootObjectBuilder extends StaticGraphQLRootObjectBuilder { + + public DefaultGraphQLRootObjectBuilder() { + super(new Object()); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java new file mode 100644 index 00000000..18411fe8 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java @@ -0,0 +1,69 @@ +package graphql.kickstart.execution; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.security.auth.Subject; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GraphQLInvoker { + + private final GraphQL graphQL; + + public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) { + if (invocationInput instanceof GraphQLSingleInvocationInput) { + return GraphQLQueryResult.create(query((GraphQLSingleInvocationInput) invocationInput)); + } + GraphQLBatchedInvocationInput batchedInvocationInput = (GraphQLBatchedInvocationInput) invocationInput; + return GraphQLQueryResult.create(query(batchedInvocationInput.getExecutionInputs(), batchedInvocationInput.getContextSetting())); + } + + private ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) { + return executeAsync(singleInvocationInput).join(); + } + + public CompletableFuture executeAsync(GraphQLSingleInvocationInput invocationInput) { + if (Subject.getSubject(AccessController.getContext()) == null && invocationInput.getSubject().isPresent()) { + return Subject + .doAs(invocationInput.getSubject().get(), (PrivilegedAction>) () -> { + try { + return query(invocationInput.getExecutionInput()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + return query(invocationInput.getExecutionInput()); + } + + private CompletableFuture query(ExecutionInput executionInput) { + return graphQL.executeAsync(executionInput); + } + + private List query(List batchedInvocationInput, + ContextSetting contextSetting) { +// List executionIds = batchedInvocationInput.stream() +// .map(GraphQLSingleInvocationInput::getExecutionInput) +// .collect(Collectors.toList()); +// Supplier configuredInstrumentation = contextSetting +// .configureInstrumentationForContext(getInstrumentation, executionIds, optionsSupplier.get()); + return batchedInvocationInput.stream() + .map(this::executeAsync) + //We want eager eval + .collect(Collectors.toList()) + .stream() + .map(CompletableFuture::join) + .collect(Collectors.toList()); + } +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLObjectMapper.java similarity index 95% rename from graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLObjectMapper.java index add9fc02..a7fc3408 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLObjectMapper.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLObjectMapper.java @@ -1,4 +1,4 @@ -package graphql.servlet.core; +package graphql.kickstart.execution; import static java.util.stream.Collectors.toList; @@ -13,13 +13,11 @@ import graphql.ExecutionResultImpl; import graphql.GraphQLError; import graphql.execution.ExecutionPath; -import graphql.kickstart.execution.error.DefaultGraphQLErrorHandler; -import graphql.kickstart.execution.error.GraphQLErrorHandler; import graphql.kickstart.execution.config.ConfiguringObjectMapperProvider; import graphql.kickstart.execution.config.ObjectMapperConfigurer; import graphql.kickstart.execution.config.ObjectMapperProvider; -import graphql.kickstart.execution.GraphQLRequest; -import graphql.kickstart.execution.VariablesDeserializer; +import graphql.kickstart.execution.error.DefaultGraphQLErrorHandler; +import graphql.kickstart.execution.error.GraphQLErrorHandler; import java.io.IOException; import java.io.InputStream; import java.io.Writer; @@ -28,7 +26,6 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; -import javax.servlet.http.Part; /** * @author Andrew Potter @@ -182,9 +179,9 @@ public Map deserializeVariables(String variables) { } } - public Map> deserializeMultipartMap(Part part) { + public Map> deserializeMultipartMap(InputStream inputStream) { try { - return getJacksonMapper().readValue(part.getInputStream(), MULTIPART_MAP_TYPE_REFERENCE); + return getJacksonMapper().readValue(inputStream, MULTIPART_MAP_TYPE_REFERENCE); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java index 64d2afcf..cc688ae2 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java @@ -72,8 +72,7 @@ public List query(List batchedInv .map(GraphQLSingleInvocationInput::getExecutionInput) .collect(Collectors.toList()); Supplier configuredInstrumentation = contextSetting - .configureInstrumentationForContext(getInstrumentation, executionIds, - optionsSupplier.get()); + .configureInstrumentationForContext(getInstrumentation, executionIds, optionsSupplier.get()); return batchedInvocationInput.stream() .map(input -> this.queryAsync(input, configuredInstrumentation)) //We want eager eval diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLRootObjectBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLRootObjectBuilder.java new file mode 100644 index 00000000..55e7c763 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLRootObjectBuilder.java @@ -0,0 +1,10 @@ +package graphql.kickstart.execution; + +public interface GraphQLRootObjectBuilder { + + /** + * @return the graphql root object + */ + Object build(); + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/StaticGraphQLRootObjectBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/StaticGraphQLRootObjectBuilder.java new file mode 100644 index 00000000..a780a02c --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/StaticGraphQLRootObjectBuilder.java @@ -0,0 +1,20 @@ +package graphql.kickstart.execution; + +public class StaticGraphQLRootObjectBuilder implements GraphQLRootObjectBuilder { + + private final Object rootObject; + + public StaticGraphQLRootObjectBuilder(Object rootObject) { + this.rootObject = rootObject; + } + + @Override + public Object build() { + return rootObject; + } + + protected Object getRootObject() { + return rootObject; + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/DefaultGraphQLSchemaProvider.java similarity index 61% rename from graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/DefaultGraphQLSchemaProvider.java index d9d8dc49..eca85768 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaProvider.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/DefaultGraphQLSchemaProvider.java @@ -1,8 +1,6 @@ -package graphql.servlet.config; +package graphql.kickstart.execution.config; import graphql.schema.GraphQLSchema; -import javax.servlet.http.HttpServletRequest; -import javax.websocket.server.HandshakeRequest; /** * @author Andrew Potter @@ -21,24 +19,14 @@ public DefaultGraphQLSchemaProvider(GraphQLSchema schema, GraphQLSchema readOnly this.readOnlySchema = readOnlySchema; } - - @Override - public GraphQLSchema getSchema(HttpServletRequest request) { - return getSchema(); - } - - @Override - public GraphQLSchema getSchema(HandshakeRequest request) { - return getSchema(); - } - @Override public GraphQLSchema getSchema() { return schema; } @Override - public GraphQLSchema getReadOnlySchema(HttpServletRequest request) { + public GraphQLSchema getReadOnlySchema() { return readOnlySchema; } + } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java new file mode 100644 index 00000000..6e15d332 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java @@ -0,0 +1,61 @@ +package graphql.kickstart.execution.config; + +import graphql.GraphQL; +import graphql.execution.instrumentation.ChainedInstrumentation; +import graphql.execution.instrumentation.Instrumentation; +import graphql.execution.instrumentation.SimpleInstrumentation; +import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation; +import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; +import graphql.execution.preparsed.PreparsedDocumentProvider; +import graphql.schema.GraphQLSchema; +import java.util.function.Supplier; + +public class GraphQLBuilder { + + private Supplier executionStrategyProviderSupplier = DefaultExecutionStrategyProvider::new; + private Supplier preparsedDocumentProviderSupplier = () -> NoOpPreparsedDocumentProvider.INSTANCE; + private Supplier instrumentationSupplier = () -> SimpleInstrumentation.INSTANCE; + + public GraphQLBuilder executionStrategyProvider(Supplier supplier) { + executionStrategyProviderSupplier = supplier; + return this; + } + + public GraphQLBuilder preparsedDocumentProvider(Supplier supplier) { + preparsedDocumentProviderSupplier = supplier; + return this; + } + + public GraphQLBuilder instrumentation(Supplier supplier) { + instrumentationSupplier = supplier; + return this; + } + + public GraphQL build(GraphQLSchemaProvider schemaProvider) { + return build(schemaProvider.getSchema()); + } + + public GraphQL build(GraphQLSchema schema) { + ExecutionStrategyProvider executionStrategyProvider = executionStrategyProviderSupplier.get(); + GraphQL.Builder builder = GraphQL.newGraphQL(schema) + .queryExecutionStrategy(executionStrategyProvider.getQueryExecutionStrategy()) + .mutationExecutionStrategy(executionStrategyProvider.getMutationExecutionStrategy()) + .subscriptionExecutionStrategy(executionStrategyProvider.getSubscriptionExecutionStrategy()) + .preparsedDocumentProvider(preparsedDocumentProviderSupplier.get()); + Instrumentation instrumentation = instrumentationSupplier.get(); + builder.instrumentation(instrumentation); + if (containsDispatchInstrumentation(instrumentation)) { + builder.doNotAddDefaultInstrumentations(); + } + return builder.build(); + } + + private boolean containsDispatchInstrumentation(Instrumentation instrumentation) { + if (instrumentation instanceof ChainedInstrumentation) { + return ((ChainedInstrumentation) instrumentation).getInstrumentations().stream() + .anyMatch(this::containsDispatchInstrumentation); + } + return instrumentation instanceof DataLoaderDispatcherInstrumentation; + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLSchemaProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLSchemaProvider.java new file mode 100644 index 00000000..e812adfd --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLSchemaProvider.java @@ -0,0 +1,21 @@ +package graphql.kickstart.execution.config; + +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLSchema; + +public interface GraphQLSchemaProvider { + + static GraphQLSchema copyReadOnly(GraphQLSchema schema) { + return GraphQLSchema.newSchema(schema) + .mutation((GraphQLObjectType) null) + .build(); + } + + /** + * @return a schema for handling mbean calls. + */ + GraphQLSchema getSchema(); + + GraphQLSchema getReadOnlySchema(); + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java index d1f508d5..fdb56c64 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/ContextSetting.java @@ -89,7 +89,7 @@ public Supplier configureInstrumentationForContext(Supplier getSubject() { + return Optional.ofNullable(subject); + } - @Override - public Optional getSubject() { - return Optional.ofNullable(subject); - } + @Override + public Optional getDataLoaderRegistry() { + return Optional.ofNullable(dataLoaderRegistry); + } - @Override - public Optional getDataLoaderRegistry() { - return Optional.ofNullable(dataLoaderRegistry); - } } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContextBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContextBuilder.java new file mode 100644 index 00000000..5f5c7a6f --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/DefaultGraphQLContextBuilder.java @@ -0,0 +1,13 @@ +package graphql.kickstart.execution.context; + +/** + * Returns an empty context. + */ +public class DefaultGraphQLContextBuilder implements GraphQLContextBuilder { + + @Override + public GraphQLContext build() { + return new DefaultGraphQLContext(); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/GraphQLContextBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/GraphQLContextBuilder.java new file mode 100644 index 00000000..d62b25ed --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/context/GraphQLContextBuilder.java @@ -0,0 +1,10 @@ +package graphql.kickstart.execution.context; + +public interface GraphQLContextBuilder { + + /** + * @return the graphql context + */ + GraphQLContext build(); + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java index fbcbb5d1..2e23d217 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java @@ -2,7 +2,7 @@ import graphql.schema.GraphQLFieldDefinition; import graphql.servlet.core.GraphQLMBean; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.core.GraphQLServletListener; import graphql.kickstart.execution.GraphQLRequest; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java index 1664cfc3..aa703d0e 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLInvocationInputParser.java @@ -1,7 +1,7 @@ package graphql.servlet; import graphql.kickstart.execution.context.ContextSetting; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.servlet.input.GraphQLInvocationInputFactory; import lombok.RequiredArgsConstructor; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java index 763cb5c0..c69d4244 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/BatchedQueryResponseWriter.java @@ -4,7 +4,7 @@ import static graphql.servlet.HttpRequestHandler.STATUS_OK; import graphql.ExecutionResult; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import java.io.IOException; import java.util.Iterator; import java.util.List; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/DefaultGraphQLServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/DefaultGraphQLServlet.java index f1d22119..bc0370e4 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/DefaultGraphQLServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/DefaultGraphQLServlet.java @@ -1,6 +1,6 @@ package graphql.servlet; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.input.GraphQLInvocationInputFactory; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/ExecutionResultSubscriber.java b/graphql-java-servlet/src/main/java/graphql/servlet/ExecutionResultSubscriber.java index b490eb00..d6342ddf 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/ExecutionResultSubscriber.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/ExecutionResultSubscriber.java @@ -1,7 +1,7 @@ package graphql.servlet; import graphql.ExecutionResult; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import java.io.IOException; import java.io.Writer; import java.util.concurrent.CountDownLatch; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java index f3cfca36..a0fcb711 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java @@ -1,19 +1,18 @@ package graphql.servlet; -import graphql.schema.GraphQLSchema; -import graphql.servlet.config.DefaultGraphQLSchemaProvider; -import graphql.servlet.config.GraphQLSchemaProvider; -import graphql.servlet.context.GraphQLContextBuilder; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.servlet.core.GraphQLRootObjectBuilder; -import graphql.servlet.core.GraphQLServletListener; import graphql.kickstart.execution.context.ContextSetting; +import graphql.schema.GraphQLSchema; +import graphql.servlet.config.DefaultGraphQLSchemaServletProvider; +import graphql.servlet.config.GraphQLSchemaServletProvider; +import graphql.servlet.context.GraphQLServletContextBuilder; +import graphql.servlet.core.GraphQLServletListener; +import graphql.servlet.core.GraphQLServletRootObjectBuilder; +import graphql.servlet.core.internal.GraphQLThreadFactory; import graphql.servlet.input.BatchInputPreProcessor; import graphql.servlet.input.GraphQLInvocationInputFactory; -import graphql.servlet.core.internal.GraphQLThreadFactory; import graphql.servlet.input.NoOpBatchInputPreProcessor; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -22,191 +21,191 @@ public class GraphQLConfiguration { - private final GraphQLInvocationInputFactory invocationInputFactory; - private final Supplier batchInputPreProcessor; - private final GraphQLQueryInvoker queryInvoker; - private final GraphQLObjectMapper objectMapper; - private final List listeners; - private final boolean asyncServletModeEnabled; - private final Executor asyncExecutor; - private final long subscriptionTimeout; - private final ContextSetting contextSetting; - - public static GraphQLConfiguration.Builder with(GraphQLSchema schema) { - return with(new DefaultGraphQLSchemaProvider(schema)); - } - - public static GraphQLConfiguration.Builder with(GraphQLSchemaProvider schemaProvider) { - return new Builder(GraphQLInvocationInputFactory.newBuilder(schemaProvider)); - } - - public static GraphQLConfiguration.Builder with(GraphQLInvocationInputFactory invocationInputFactory) { - return new Builder(invocationInputFactory); - } - - private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, - GraphQLObjectMapper objectMapper, List listeners, boolean asyncServletModeEnabled, - Executor asyncExecutor, long subscriptionTimeout, ContextSetting contextSetting, - Supplier batchInputPreProcessor) { - this.invocationInputFactory = invocationInputFactory; + private final GraphQLInvocationInputFactory invocationInputFactory; + private final Supplier batchInputPreProcessor; + private final GraphQLQueryInvoker queryInvoker; + private final GraphQLObjectMapper objectMapper; + private final List listeners; + private final boolean asyncServletModeEnabled; + private final Executor asyncExecutor; + private final long subscriptionTimeout; + private final ContextSetting contextSetting; + + private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, + GraphQLObjectMapper objectMapper, List listeners, boolean asyncServletModeEnabled, + Executor asyncExecutor, long subscriptionTimeout, ContextSetting contextSetting, + Supplier batchInputPreProcessor) { + this.invocationInputFactory = invocationInputFactory; + this.queryInvoker = queryInvoker; + this.objectMapper = objectMapper; + this.listeners = listeners; + this.asyncServletModeEnabled = asyncServletModeEnabled; + this.asyncExecutor = asyncExecutor; + this.subscriptionTimeout = subscriptionTimeout; + this.contextSetting = contextSetting; + this.batchInputPreProcessor = batchInputPreProcessor; + } + + public static GraphQLConfiguration.Builder with(GraphQLSchema schema) { + return with(new DefaultGraphQLSchemaServletProvider(schema)); + } + + public static GraphQLConfiguration.Builder with(GraphQLSchemaServletProvider schemaProvider) { + return new Builder(GraphQLInvocationInputFactory.newBuilder(schemaProvider)); + } + + public static GraphQLConfiguration.Builder with(GraphQLInvocationInputFactory invocationInputFactory) { + return new Builder(invocationInputFactory); + } + + public GraphQLInvocationInputFactory getInvocationInputFactory() { + return invocationInputFactory; + } + + public GraphQLQueryInvoker getQueryInvoker() { + return queryInvoker; + } + + public GraphQLObjectMapper getObjectMapper() { + return objectMapper; + } + + public List getListeners() { + return new ArrayList<>(listeners); + } + + public boolean isAsyncServletModeEnabled() { + return asyncServletModeEnabled; + } + + public Executor getAsyncExecutor() { + return asyncExecutor; + } + + public void add(GraphQLServletListener listener) { + listeners.add(listener); + } + + public boolean remove(GraphQLServletListener listener) { + return listeners.remove(listener); + } + + public long getSubscriptionTimeout() { + return subscriptionTimeout; + } + + public ContextSetting getContextSetting() { + return contextSetting; + } + + public BatchInputPreProcessor getBatchInputPreProcessor() { + return batchInputPreProcessor.get(); + } + + public static class Builder { + + private GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder; + private GraphQLInvocationInputFactory invocationInputFactory; + private GraphQLQueryInvoker queryInvoker = GraphQLQueryInvoker.newBuilder().build(); + private GraphQLObjectMapper objectMapper = GraphQLObjectMapper.newBuilder().build(); + private List listeners = new ArrayList<>(); + private boolean asyncServletModeEnabled = false; + private Executor asyncExecutor = Executors.newCachedThreadPool(new GraphQLThreadFactory()); + private long subscriptionTimeout = 0; + private ContextSetting contextSetting = ContextSetting.PER_QUERY_WITH_INSTRUMENTATION; + private Supplier batchInputPreProcessorSupplier = () -> new NoOpBatchInputPreProcessor(); + + private Builder(GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder) { + this.invocationInputFactoryBuilder = invocationInputFactoryBuilder; + } + + private Builder(GraphQLInvocationInputFactory invocationInputFactory) { + this.invocationInputFactory = invocationInputFactory; + } + + public Builder with(GraphQLQueryInvoker queryInvoker) { + if (queryInvoker != null) { this.queryInvoker = queryInvoker; - this.objectMapper = objectMapper; - this.listeners = listeners; - this.asyncServletModeEnabled = asyncServletModeEnabled; - this.asyncExecutor = asyncExecutor; - this.subscriptionTimeout = subscriptionTimeout; - this.contextSetting = contextSetting; - this.batchInputPreProcessor = batchInputPreProcessor; + } + return this; } - public GraphQLInvocationInputFactory getInvocationInputFactory() { - return invocationInputFactory; + public Builder with(GraphQLObjectMapper objectMapper) { + if (objectMapper != null) { + this.objectMapper = objectMapper; + } + return this; } - public GraphQLQueryInvoker getQueryInvoker() { - return queryInvoker; + public Builder with(List listeners) { + if (listeners != null) { + this.listeners = listeners; + } + return this; } - public GraphQLObjectMapper getObjectMapper() { - return objectMapper; + public Builder with(boolean asyncServletModeEnabled) { + this.asyncServletModeEnabled = asyncServletModeEnabled; + return this; } - public List getListeners() { - return new ArrayList<>(listeners); + public Builder with(Executor asyncExecutor) { + if (asyncExecutor != null) { + this.asyncExecutor = asyncExecutor; + } + return this; } - public boolean isAsyncServletModeEnabled() { - return asyncServletModeEnabled; + public Builder with(GraphQLServletContextBuilder contextBuilder) { + this.invocationInputFactoryBuilder.withGraphQLContextBuilder(contextBuilder); + return this; } - public Executor getAsyncExecutor() { - return asyncExecutor; + public Builder with(GraphQLServletRootObjectBuilder rootObjectBuilder) { + this.invocationInputFactoryBuilder.withGraphQLRootObjectBuilder(rootObjectBuilder); + return this; } - public void add(GraphQLServletListener listener) { - listeners.add(listener); + public Builder with(long subscriptionTimeout) { + this.subscriptionTimeout = subscriptionTimeout; + return this; } - public boolean remove(GraphQLServletListener listener) { - return listeners.remove(listener); + public Builder with(ContextSetting contextSetting) { + if (contextSetting != null) { + this.contextSetting = contextSetting; + } + return this; } - public long getSubscriptionTimeout() { - return subscriptionTimeout; + public Builder with(BatchInputPreProcessor batchInputPreProcessor) { + if (batchInputPreProcessor != null) { + this.batchInputPreProcessorSupplier = () -> batchInputPreProcessor; + } + return this; } - public ContextSetting getContextSetting() { - return contextSetting; + public Builder with(Supplier batchInputPreProcessor) { + if (batchInputPreProcessor != null) { + this.batchInputPreProcessorSupplier = batchInputPreProcessor; + } + return this; } - public BatchInputPreProcessor getBatchInputPreProcessor() { - return batchInputPreProcessor.get(); + public GraphQLConfiguration build() { + return new GraphQLConfiguration( + this.invocationInputFactory != null ? this.invocationInputFactory : invocationInputFactoryBuilder.build(), + queryInvoker, + objectMapper, + listeners, + asyncServletModeEnabled, + asyncExecutor, + subscriptionTimeout, + contextSetting, + batchInputPreProcessorSupplier + ); } - public static class Builder { - - private GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder; - private GraphQLInvocationInputFactory invocationInputFactory; - private GraphQLQueryInvoker queryInvoker = GraphQLQueryInvoker.newBuilder().build(); - private GraphQLObjectMapper objectMapper = GraphQLObjectMapper.newBuilder().build(); - private List listeners = new ArrayList<>(); - private boolean asyncServletModeEnabled = false; - private Executor asyncExecutor = Executors.newCachedThreadPool(new GraphQLThreadFactory()); - private long subscriptionTimeout = 0; - private ContextSetting contextSetting = ContextSetting.PER_QUERY_WITH_INSTRUMENTATION; - private Supplier batchInputPreProcessorSupplier = () -> new NoOpBatchInputPreProcessor(); - - private Builder(GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder) { - this.invocationInputFactoryBuilder = invocationInputFactoryBuilder; - } - - private Builder(GraphQLInvocationInputFactory invocationInputFactory) { - this.invocationInputFactory = invocationInputFactory; - } - - public Builder with(GraphQLQueryInvoker queryInvoker) { - if (queryInvoker != null) { - this.queryInvoker = queryInvoker; - } - return this; - } - - public Builder with(GraphQLObjectMapper objectMapper) { - if (objectMapper != null) { - this.objectMapper = objectMapper; - } - return this; - } - - public Builder with(List listeners) { - if (listeners != null) { - this.listeners = listeners; - } - return this; - } - - public Builder with(boolean asyncServletModeEnabled) { - this.asyncServletModeEnabled = asyncServletModeEnabled; - return this; - } - - public Builder with(Executor asyncExecutor) { - if (asyncExecutor != null) { - this.asyncExecutor = asyncExecutor; - } - return this; - } - - public Builder with(GraphQLContextBuilder contextBuilder) { - this.invocationInputFactoryBuilder.withGraphQLContextBuilder(contextBuilder); - return this; - } - - public Builder with(GraphQLRootObjectBuilder rootObjectBuilder) { - this.invocationInputFactoryBuilder.withGraphQLRootObjectBuilder(rootObjectBuilder); - return this; - } - - public Builder with(long subscriptionTimeout) { - this.subscriptionTimeout = subscriptionTimeout; - return this; - } - - public Builder with(ContextSetting contextSetting) { - if (contextSetting != null) { - this.contextSetting = contextSetting; - } - return this; - } - - public Builder with(BatchInputPreProcessor batchInputPreProcessor) { - if (batchInputPreProcessor != null) { - this.batchInputPreProcessorSupplier = () -> batchInputPreProcessor; - } - return this; - } - - public Builder with(Supplier batchInputPreProcessor) { - if (batchInputPreProcessor != null) { - this.batchInputPreProcessorSupplier = batchInputPreProcessor; - } - return this; - } - - public GraphQLConfiguration build() { - return new GraphQLConfiguration( - this.invocationInputFactory != null ? this.invocationInputFactory : invocationInputFactoryBuilder.build(), - queryInvoker, - objectMapper, - listeners, - asyncServletModeEnabled, - asyncExecutor, - subscriptionTimeout, - contextSetting, - batchInputPreProcessorSupplier - ); - } - - } + } } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java index f51a97e0..f8a53599 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLGetInvocationInputParser.java @@ -2,7 +2,7 @@ import graphql.GraphQLException; import graphql.kickstart.execution.context.ContextSetting; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLRequest; import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLHttpServlet.java index b0ba217a..161f6db8 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLHttpServlet.java @@ -1,7 +1,7 @@ package graphql.servlet; import graphql.schema.GraphQLSchema; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.input.GraphQLInvocationInputFactory; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java index 1232504a..32292f83 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLInvocationInputParser.java @@ -1,7 +1,7 @@ package graphql.servlet; import graphql.kickstart.execution.context.ContextSetting; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java index d40e8511..99b4f967 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLMultipartInvocationInputParser.java @@ -4,7 +4,7 @@ import graphql.GraphQLException; import graphql.kickstart.execution.context.ContextSetting; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLRequest; import graphql.servlet.core.internal.VariableMapper; import graphql.kickstart.execution.input.GraphQLInvocationInput; @@ -57,7 +57,14 @@ public GraphQLInvocationInput getGraphQLInvocationInput(HttpServletRequest reque InputStream inputStream = queryItem.get().getInputStream(); final Optional>> variablesMap = - getPart(parts, "map").map(graphQLObjectMapper::deserializeMultipartMap); + getPart(parts, "map") + .map(part -> { + try (InputStream is = part.getInputStream()) { + return graphQLObjectMapper.deserializeMultipartMap(is); + } catch (IOException e) { + throw new RuntimeException("Unable to read input stream from part", e); + } + }); String query = read(inputStream); if ("query".equals(key) && isSingleQuery(query)) { diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java index 53aa9688..cea0a2e7 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLPostInvocationInputParser.java @@ -6,7 +6,7 @@ import graphql.kickstart.execution.GraphQLRequest; import graphql.kickstart.execution.context.ContextSetting; import graphql.kickstart.execution.input.GraphQLInvocationInput; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.io.IOException; import java.util.List; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java index 4c4c87da..d7d3608c 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java @@ -1,6 +1,6 @@ package graphql.servlet; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; import graphql.servlet.input.GraphQLInvocationInputFactory; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java index 384434b6..364dfbaf 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java @@ -9,24 +9,25 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLType; import graphql.kickstart.execution.config.DefaultExecutionStrategyProvider; -import graphql.servlet.config.DefaultGraphQLSchemaProvider; +import graphql.servlet.config.DefaultGraphQLSchemaServletProvider; import graphql.kickstart.execution.config.ExecutionStrategyProvider; +import graphql.servlet.config.GraphQLSchemaServletProvider; +import graphql.servlet.core.GraphQLServletRootObjectBuilder; import graphql.servlet.osgi.GraphQLCodeRegistryProvider; import graphql.servlet.osgi.GraphQLMutationProvider; import graphql.servlet.osgi.GraphQLProvider; import graphql.servlet.osgi.GraphQLQueryProvider; -import graphql.servlet.config.GraphQLSchemaProvider; import graphql.servlet.osgi.GraphQLSubscriptionProvider; import graphql.servlet.osgi.GraphQLTypesProvider; import graphql.kickstart.execution.config.InstrumentationProvider; -import graphql.servlet.context.DefaultGraphQLContextBuilder; -import graphql.servlet.context.GraphQLContextBuilder; +import graphql.servlet.context.DefaultGraphQLServletContextBuilder; +import graphql.servlet.context.GraphQLServletContextBuilder; import graphql.kickstart.execution.error.DefaultGraphQLErrorHandler; import graphql.servlet.core.DefaultGraphQLRootObjectBuilder; import graphql.kickstart.execution.error.GraphQLErrorHandler; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.servlet.core.GraphQLRootObjectBuilder; +import graphql.kickstart.execution.GraphQLRootObjectBuilder; import graphql.servlet.core.GraphQLServletListener; import graphql.servlet.input.GraphQLInvocationInputFactory; import graphql.kickstart.execution.instrumentation.NoOpInstrumentationProvider; @@ -61,15 +62,15 @@ public class OsgiGraphQLHttpServlet extends AbstractGraphQLHttpServlet { private final GraphQLInvocationInputFactory invocationInputFactory; private final GraphQLObjectMapper graphQLObjectMapper; - private GraphQLContextBuilder contextBuilder = new DefaultGraphQLContextBuilder(); - private GraphQLRootObjectBuilder rootObjectBuilder = new DefaultGraphQLRootObjectBuilder(); + private GraphQLServletContextBuilder contextBuilder = new DefaultGraphQLServletContextBuilder(); + private GraphQLServletRootObjectBuilder rootObjectBuilder = new DefaultGraphQLRootObjectBuilder(); private ExecutionStrategyProvider executionStrategyProvider = new DefaultExecutionStrategyProvider(); private InstrumentationProvider instrumentationProvider = new NoOpInstrumentationProvider(); private GraphQLErrorHandler errorHandler = new DefaultGraphQLErrorHandler(); private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE; private GraphQLCodeRegistryProvider codeRegistryProvider = () -> GraphQLCodeRegistry.newCodeRegistry().build(); - private GraphQLSchemaProvider schemaProvider; + private GraphQLSchemaServletProvider schemaProvider; private ScheduledExecutorService executor; private ScheduledFuture updateFuture; @@ -189,7 +190,7 @@ private void doUpdateSchema() { } } - this.schemaProvider = new DefaultGraphQLSchemaProvider(newSchema().query(queryTypeBuilder.build()) + this.schemaProvider = new DefaultGraphQLSchemaServletProvider(newSchema().query(queryTypeBuilder.build()) .mutation(mutationType) .subscription(subscriptionType) .additionalTypes(types) @@ -290,12 +291,12 @@ public void unbindServletListener(GraphQLServletListener listener) { } @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) - public void setContextProvider(GraphQLContextBuilder contextBuilder) { + public void setContextProvider(GraphQLServletContextBuilder contextBuilder) { this.contextBuilder = contextBuilder; } - public void unsetContextProvider(GraphQLContextBuilder contextBuilder) { - this.contextBuilder = new DefaultGraphQLContextBuilder(); + public void unsetContextProvider(GraphQLServletContextBuilder contextBuilder) { + this.contextBuilder = new DefaultGraphQLServletContextBuilder(); } public void unsetRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { @@ -329,16 +330,16 @@ public void unbindCodeRegistryProvider(GraphQLCodeRegistryProvider graphQLCodeRe updateSchema(); } - public GraphQLContextBuilder getContextBuilder() { + public GraphQLServletContextBuilder getContextBuilder() { return contextBuilder; } - public GraphQLRootObjectBuilder getRootObjectBuilder() { + public GraphQLServletRootObjectBuilder getRootObjectBuilder() { return rootObjectBuilder; } @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) - public void setRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { + public void setRootObjectBuilder(GraphQLServletRootObjectBuilder rootObjectBuilder) { this.rootObjectBuilder = rootObjectBuilder; } @@ -378,7 +379,7 @@ public void setPreparsedDocumentProvider(PreparsedDocumentProvider preparsedDocu this.preparsedDocumentProvider = preparsedDocumentProvider; } - public GraphQLSchemaProvider getSchemaProvider() { + public GraphQLSchemaServletProvider getSchemaProvider() { return schemaProvider; } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/QueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/QueryResponseWriter.java index d35420da..907c1a02 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/QueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/QueryResponseWriter.java @@ -1,7 +1,7 @@ package graphql.servlet; import graphql.kickstart.execution.GraphQLQueryResult; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import java.io.IOException; import java.util.Objects; import javax.servlet.http.HttpServletRequest; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java index 4444b959..84936aa9 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java @@ -1,9 +1,10 @@ package graphql.servlet; import graphql.schema.GraphQLSchema; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.servlet.config.GraphQLSchemaProvider; +import graphql.kickstart.execution.config.GraphQLSchemaProvider; +import graphql.servlet.config.GraphQLSchemaServletProvider; import graphql.servlet.core.GraphQLServletListener; import graphql.servlet.input.GraphQLInvocationInputFactory; @@ -83,7 +84,7 @@ public static Builder newBuilder(GraphQLSchema schema) { return new Builder(GraphQLInvocationInputFactory.newBuilder(schema).build()); } - public static Builder newBuilder(GraphQLSchemaProvider schemaProvider) { + public static Builder newBuilder(GraphQLSchemaServletProvider schemaProvider) { return new Builder(GraphQLInvocationInputFactory.newBuilder(schemaProvider).build()); } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java index cf85cf73..da30d0d0 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/SingleAsynchronousQueryResponseWriter.java @@ -5,7 +5,7 @@ import graphql.ExecutionResult; import graphql.GraphQL; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/SingleQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/servlet/SingleQueryResponseWriter.java index 1b62661b..33475a11 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/SingleQueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/SingleQueryResponseWriter.java @@ -4,7 +4,7 @@ import static graphql.servlet.HttpRequestHandler.STATUS_OK; import graphql.ExecutionResult; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaServletProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaServletProvider.java new file mode 100644 index 00000000..1dc9f4ed --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/config/DefaultGraphQLSchemaServletProvider.java @@ -0,0 +1,33 @@ +package graphql.servlet.config; + +import graphql.kickstart.execution.config.DefaultGraphQLSchemaProvider; +import graphql.schema.GraphQLSchema; +import javax.servlet.http.HttpServletRequest; +import javax.websocket.server.HandshakeRequest; + +/** + * @author Andrew Potter + */ +public class DefaultGraphQLSchemaServletProvider extends DefaultGraphQLSchemaProvider implements + GraphQLSchemaServletProvider { + + public DefaultGraphQLSchemaServletProvider(GraphQLSchema schema) { + super(schema); + } + + @Override + public GraphQLSchema getSchema(HttpServletRequest request) { + return getSchema(); + } + + @Override + public GraphQLSchema getSchema(HandshakeRequest request) { + return getSchema(); + } + + @Override + public GraphQLSchema getReadOnlySchema(HttpServletRequest request) { + return getReadOnlySchema(); + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java deleted file mode 100644 index 386a0285..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -package graphql.servlet.config; - -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLSchema; - -import javax.servlet.http.HttpServletRequest; -import javax.websocket.server.HandshakeRequest; - -public interface GraphQLSchemaProvider { - - static GraphQLSchema copyReadOnly(GraphQLSchema schema) { - return GraphQLSchema.newSchema(schema) - .mutation((GraphQLObjectType) null) - .build(); - } - - /** - * @param request the http request - * @return a schema based on the request (auth, etc). - */ - GraphQLSchema getSchema(HttpServletRequest request); - - /** - * @param request the http request used to create a websocket - * @return a schema based on the request (auth, etc). - */ - GraphQLSchema getSchema(HandshakeRequest request); - - /** - * @return a schema for handling mbean calls. - */ - GraphQLSchema getSchema(); - - /** - * @param request the http request - * @return a read-only schema based on the request (auth, etc). Should return the same schema (query/subscription-only version) as {@link #getSchema(HttpServletRequest)} for a given request. - */ - GraphQLSchema getReadOnlySchema(HttpServletRequest request); - -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaServletProvider.java b/graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaServletProvider.java new file mode 100644 index 00000000..305bf34e --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/config/GraphQLSchemaServletProvider.java @@ -0,0 +1,28 @@ +package graphql.servlet.config; + +import graphql.kickstart.execution.config.GraphQLSchemaProvider; +import graphql.schema.GraphQLSchema; +import javax.servlet.http.HttpServletRequest; +import javax.websocket.server.HandshakeRequest; + +public interface GraphQLSchemaServletProvider extends GraphQLSchemaProvider { + + /** + * @param request the http request + * @return a schema based on the request (auth, etc). + */ + GraphQLSchema getSchema(HttpServletRequest request); + + /** + * @param request the http request used to create a websocket + * @return a schema based on the request (auth, etc). + */ + GraphQLSchema getSchema(HandshakeRequest request); + + /** + * @param request the http request + * @return a read-only schema based on the request (auth, etc). Should return the same schema (query/subscription-only version) as {@link #getSchema(HttpServletRequest)} for a given request. + */ + GraphQLSchema getReadOnlySchema(HttpServletRequest request); + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java deleted file mode 100644 index 85491170..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLContextBuilder.java +++ /dev/null @@ -1,29 +0,0 @@ -package graphql.servlet.context; - -import graphql.kickstart.execution.context.DefaultGraphQLContext; -import graphql.kickstart.execution.context.GraphQLContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.websocket.Session; -import javax.websocket.server.HandshakeRequest; - -/** - * Returns an empty context. - */ -public class DefaultGraphQLContextBuilder implements GraphQLContextBuilder { - - @Override - public GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { - return DefaultGraphQLServletContext.createServletContext().with(httpServletRequest).with(httpServletResponse).build(); - } - - @Override - public GraphQLContext build(Session session, HandshakeRequest handshakeRequest) { - return DefaultGraphQLWebSocketContext.createWebSocketContext().with(session).with(handshakeRequest).build(); - } - - @Override - public GraphQLContext build() { - return new DefaultGraphQLContext(); - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java new file mode 100644 index 00000000..5ac73142 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java @@ -0,0 +1,27 @@ +package graphql.servlet.context; + +import graphql.kickstart.execution.context.DefaultGraphQLContextBuilder; +import graphql.kickstart.execution.context.GraphQLContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.websocket.Session; +import javax.websocket.server.HandshakeRequest; + +/** + * Returns an empty context. + */ +public class DefaultGraphQLServletContextBuilder extends DefaultGraphQLContextBuilder implements + GraphQLServletContextBuilder { + + @Override + public GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { + return DefaultGraphQLServletContext.createServletContext().with(httpServletRequest).with(httpServletResponse) + .build(); + } + + @Override + public GraphQLContext build(Session session, HandshakeRequest handshakeRequest) { + return DefaultGraphQLWebSocketContext.createWebSocketContext().with(session).with(handshakeRequest).build(); + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLServletContextBuilder.java similarity index 74% rename from graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java rename to graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLServletContextBuilder.java index b0960575..5c319460 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLServletContextBuilder.java @@ -1,20 +1,16 @@ package graphql.servlet.context; import graphql.kickstart.execution.context.GraphQLContext; +import graphql.kickstart.execution.context.GraphQLContextBuilder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; -public interface GraphQLContextBuilder { +public interface GraphQLServletContextBuilder extends GraphQLContextBuilder { GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse); GraphQLContext build(Session session, HandshakeRequest handshakeRequest); - /** - * Only used for MBean calls. - * @return the graphql context - */ - GraphQLContext build(); } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java index 2c5e3c8b..b07a3b89 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/core/DefaultGraphQLRootObjectBuilder.java @@ -1,7 +1,23 @@ package graphql.servlet.core; -public class DefaultGraphQLRootObjectBuilder extends StaticGraphQLRootObjectBuilder { - public DefaultGraphQLRootObjectBuilder() { - super(new Object()); +import graphql.kickstart.execution.StaticGraphQLRootObjectBuilder; +import javax.servlet.http.HttpServletRequest; +import javax.websocket.server.HandshakeRequest; + +public class DefaultGraphQLRootObjectBuilder extends StaticGraphQLRootObjectBuilder implements GraphQLServletRootObjectBuilder { + + public DefaultGraphQLRootObjectBuilder() { + super(new Object()); + } + + @Override + public Object build(HttpServletRequest req) { + return getRootObject(); } + + @Override + public Object build(HandshakeRequest req) { + return getRootObject(); + } + } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java deleted file mode 100644 index e4022730..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLRootObjectBuilder.java +++ /dev/null @@ -1,15 +0,0 @@ -package graphql.servlet.core; - -import javax.servlet.http.HttpServletRequest; -import javax.websocket.server.HandshakeRequest; - -public interface GraphQLRootObjectBuilder { - Object build(HttpServletRequest req); - Object build(HandshakeRequest req); - - /** - * Only used for MBean calls. - * @return the graphql root object - */ - Object build(); -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLServletRootObjectBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLServletRootObjectBuilder.java new file mode 100644 index 00000000..5d350647 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/core/GraphQLServletRootObjectBuilder.java @@ -0,0 +1,13 @@ +package graphql.servlet.core; + +import graphql.kickstart.execution.GraphQLRootObjectBuilder; +import javax.servlet.http.HttpServletRequest; +import javax.websocket.server.HandshakeRequest; + +public interface GraphQLServletRootObjectBuilder extends GraphQLRootObjectBuilder { + + Object build(HttpServletRequest req); + + Object build(HandshakeRequest req); + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java deleted file mode 100644 index 8790a9a0..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/StaticGraphQLRootObjectBuilder.java +++ /dev/null @@ -1,28 +0,0 @@ -package graphql.servlet.core; - -import javax.servlet.http.HttpServletRequest; -import javax.websocket.server.HandshakeRequest; - -public class StaticGraphQLRootObjectBuilder implements GraphQLRootObjectBuilder { - - private final Object rootObject; - - public StaticGraphQLRootObjectBuilder(Object rootObject) { - this.rootObject = rootObject; - } - - @Override - public Object build(HttpServletRequest req) { - return rootObject; - } - - @Override - public Object build(HandshakeRequest req) { - return rootObject; - } - - @Override - public Object build() { - return rootObject; - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java index 6414f20d..4cf7d270 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java @@ -1,7 +1,7 @@ package graphql.servlet.core.internal; import graphql.servlet.input.GraphQLInvocationInputFactory; -import graphql.servlet.core.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java index 8ee3e33c..28435a2d 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java @@ -1,16 +1,17 @@ package graphql.servlet.input; +import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.config.GraphQLSchemaProvider; +import graphql.kickstart.execution.context.ContextSetting; import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import graphql.schema.GraphQLSchema; -import graphql.servlet.config.DefaultGraphQLSchemaProvider; -import graphql.servlet.config.GraphQLSchemaProvider; -import graphql.kickstart.execution.context.ContextSetting; -import graphql.servlet.context.DefaultGraphQLContextBuilder; -import graphql.servlet.context.GraphQLContextBuilder; +import graphql.servlet.config.DefaultGraphQLSchemaServletProvider; +import graphql.servlet.config.GraphQLSchemaServletProvider; +import graphql.servlet.context.DefaultGraphQLServletContextBuilder; +import graphql.servlet.context.GraphQLServletContextBuilder; import graphql.servlet.core.DefaultGraphQLRootObjectBuilder; -import graphql.servlet.core.GraphQLRootObjectBuilder; -import graphql.kickstart.execution.GraphQLRequest; +import graphql.servlet.core.GraphQLServletRootObjectBuilder; import java.util.List; import java.util.function.Supplier; import javax.servlet.http.HttpServletRequest; @@ -23,27 +24,27 @@ */ public class GraphQLInvocationInputFactory { - private final Supplier schemaProviderSupplier; - private final Supplier contextBuilderSupplier; - private final Supplier rootObjectBuilderSupplier; + private final Supplier schemaProviderSupplier; + private final Supplier contextBuilderSupplier; + private final Supplier rootObjectBuilderSupplier; - protected GraphQLInvocationInputFactory(Supplier schemaProviderSupplier, - Supplier contextBuilderSupplier, - Supplier rootObjectBuilderSupplier) { + protected GraphQLInvocationInputFactory(Supplier schemaProviderSupplier, + Supplier contextBuilderSupplier, + Supplier rootObjectBuilderSupplier) { this.schemaProviderSupplier = schemaProviderSupplier; this.contextBuilderSupplier = contextBuilderSupplier; this.rootObjectBuilderSupplier = rootObjectBuilderSupplier; } public static Builder newBuilder(GraphQLSchema schema) { - return new Builder(new DefaultGraphQLSchemaProvider(schema)); + return new Builder(new DefaultGraphQLSchemaServletProvider(schema)); } - public static Builder newBuilder(GraphQLSchemaProvider schemaProvider) { + public static Builder newBuilder(GraphQLSchemaServletProvider schemaProvider) { return new Builder(schemaProvider); } - public static Builder newBuilder(Supplier schemaProviderSupplier) { + public static Builder newBuilder(Supplier schemaProviderSupplier) { return new Builder(schemaProviderSupplier); } @@ -126,32 +127,32 @@ public GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List< public static class Builder { - private final Supplier schemaProviderSupplier; - private Supplier contextBuilderSupplier = DefaultGraphQLContextBuilder::new; - private Supplier rootObjectBuilderSupplier = DefaultGraphQLRootObjectBuilder::new; + private final Supplier schemaProviderSupplier; + private Supplier contextBuilderSupplier = DefaultGraphQLServletContextBuilder::new; + private Supplier rootObjectBuilderSupplier = DefaultGraphQLRootObjectBuilder::new; - public Builder(GraphQLSchemaProvider schemaProvider) { + public Builder(GraphQLSchemaServletProvider schemaProvider) { this(() -> schemaProvider); } - public Builder(Supplier schemaProviderSupplier) { + public Builder(Supplier schemaProviderSupplier) { this.schemaProviderSupplier = schemaProviderSupplier; } - public Builder withGraphQLContextBuilder(GraphQLContextBuilder contextBuilder) { + public Builder withGraphQLContextBuilder(GraphQLServletContextBuilder contextBuilder) { return withGraphQLContextBuilder(() -> contextBuilder); } - public Builder withGraphQLContextBuilder(Supplier contextBuilderSupplier) { + public Builder withGraphQLContextBuilder(Supplier contextBuilderSupplier) { this.contextBuilderSupplier = contextBuilderSupplier; return this; } - public Builder withGraphQLRootObjectBuilder(GraphQLRootObjectBuilder rootObjectBuilder) { + public Builder withGraphQLRootObjectBuilder(GraphQLServletRootObjectBuilder rootObjectBuilder) { return withGraphQLRootObjectBuilder(() -> rootObjectBuilder); } - public Builder withGraphQLRootObjectBuilder(Supplier rootObjectBuilderSupplier) { + public Builder withGraphQLRootObjectBuilder(Supplier rootObjectBuilderSupplier) { this.rootObjectBuilderSupplier = rootObjectBuilderSupplier; return this; } diff --git a/graphql-java-servlet/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy index 9940366e..6278d428 100644 --- a/graphql-java-servlet/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy +++ b/graphql-java-servlet/src/test/groovy/graphql/servlet/DataLoaderDispatchingSpec.groovy @@ -11,7 +11,7 @@ import graphql.schema.DataFetchingEnvironment import graphql.kickstart.execution.context.ContextSetting import graphql.kickstart.execution.context.DefaultGraphQLContext import graphql.kickstart.execution.context.GraphQLContext -import graphql.servlet.context.GraphQLContextBuilder +import graphql.servlet.context.GraphQLServletContextBuilder import graphql.kickstart.execution.instrumentation.ConfigurableDispatchInstrumentation import org.dataloader.BatchLoader import org.dataloader.DataLoader @@ -84,7 +84,7 @@ class DataLoaderDispatchingSpec extends Specification { } def contextBuilder() { - return new GraphQLContextBuilder() { + return new GraphQLServletContextBuilder() { @Override GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { new DefaultGraphQLContext(registry(), null) diff --git a/graphql-java-servlet/src/test/groovy/graphql/servlet/TestUtils.groovy b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestUtils.groovy index 401d97fa..71d7258b 100644 --- a/graphql-java-servlet/src/test/groovy/graphql/servlet/TestUtils.groovy +++ b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestUtils.groovy @@ -10,7 +10,7 @@ import graphql.schema.idl.SchemaGenerator import graphql.schema.idl.SchemaParser import graphql.schema.idl.TypeRuntimeWiring import graphql.schema.idl.errors.SchemaProblem -import graphql.servlet.context.GraphQLContextBuilder +import graphql.servlet.context.GraphQLServletContextBuilder import graphql.servlet.apollo.ApolloScalars import graphql.servlet.input.BatchInputPreProcessor import graphql.kickstart.execution.context.ContextSetting @@ -49,7 +49,7 @@ class TestUtils { DataFetcher fieldDataFetcher = { env -> env.arguments.arg }, DataFetcher otherDataFetcher, boolean asyncServletModeEnabled = false, ContextSetting contextSetting, - GraphQLContextBuilder contextBuilder) { + GraphQLServletContextBuilder contextBuilder) { GraphQLSchema schema = createGraphQlSchemaWithTwoLevels(queryDataFetcher, fieldDataFetcher, otherDataFetcher) GraphQLHttpServlet servlet = GraphQLHttpServlet.with(GraphQLConfiguration .with(schema) From 2cf964bf87e5ac80478a236480c87ba31afe27e8 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Tue, 19 Nov 2019 10:46:42 +0100 Subject: [PATCH 12/26] Moved subject proxy out of the core logic --- .../kickstart/execution/GraphQLInvoker.java | 33 +++++-------------- .../execution/GraphQLInvokerProxy.java | 12 +++++++ .../execution/GraphQLInvokerSubjectProxy.java | 29 ++++++++++++++++ 3 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerProxy.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java index 18411fe8..27d916f1 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java @@ -1,56 +1,41 @@ package graphql.kickstart.execution; -import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.GraphQL; import graphql.kickstart.execution.context.ContextSetting; import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import javax.security.auth.Subject; +import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; +@AllArgsConstructor @RequiredArgsConstructor public class GraphQLInvoker { private final GraphQL graphQL; + private GraphQLInvokerProxy proxy = GraphQL::executeAsync; + + public CompletableFuture executeAsync(GraphQLSingleInvocationInput invocationInput) { + return proxy.executeAsync(graphQL, invocationInput.getExecutionInput()); + } public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) { if (invocationInput instanceof GraphQLSingleInvocationInput) { return GraphQLQueryResult.create(query((GraphQLSingleInvocationInput) invocationInput)); } GraphQLBatchedInvocationInput batchedInvocationInput = (GraphQLBatchedInvocationInput) invocationInput; - return GraphQLQueryResult.create(query(batchedInvocationInput.getExecutionInputs(), batchedInvocationInput.getContextSetting())); + return GraphQLQueryResult + .create(query(batchedInvocationInput.getExecutionInputs(), batchedInvocationInput.getContextSetting())); } private ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) { return executeAsync(singleInvocationInput).join(); } - public CompletableFuture executeAsync(GraphQLSingleInvocationInput invocationInput) { - if (Subject.getSubject(AccessController.getContext()) == null && invocationInput.getSubject().isPresent()) { - return Subject - .doAs(invocationInput.getSubject().get(), (PrivilegedAction>) () -> { - try { - return query(invocationInput.getExecutionInput()); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - return query(invocationInput.getExecutionInput()); - } - - private CompletableFuture query(ExecutionInput executionInput) { - return graphQL.executeAsync(executionInput); - } - private List query(List batchedInvocationInput, ContextSetting contextSetting) { // List executionIds = batchedInvocationInput.stream() diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerProxy.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerProxy.java new file mode 100644 index 00000000..a09e1681 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerProxy.java @@ -0,0 +1,12 @@ +package graphql.kickstart.execution; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.GraphQL; +import java.util.concurrent.CompletableFuture; + +public interface GraphQLInvokerProxy { + + CompletableFuture executeAsync(GraphQL graphQL, ExecutionInput executionInput); + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java new file mode 100644 index 00000000..a9991f29 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java @@ -0,0 +1,29 @@ +package graphql.kickstart.execution; + +import graphql.ExecutionInput; +import graphql.ExecutionResult; +import graphql.kickstart.execution.context.GraphQLContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.CompletableFuture; +import javax.security.auth.Subject; + +public class GraphQLInvokerSubjectProxy implements GraphQLInvokerProxy { + + @Override + public CompletableFuture executeAsync(GraphQLInvoker graphQLInvoker, ExecutionInput executionInput) { + GraphQLContext context = (GraphQLContext) executionInput.getContext(); + if (Subject.getSubject(AccessController.getContext()) == null && context.getSubject().isPresent()) { + return Subject + .doAs(context.getSubject().get(), (PrivilegedAction>) () -> { + try { + return graphQLInvoker.executeAsync(executionInput); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + return graphQLInvoker.executeAsync(executionInput); + } + +} From 3dbad111015df8ea8b2b7625f6da11cefe85a97a Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Wed, 20 Nov 2019 08:10:37 +0100 Subject: [PATCH 13/26] Fix build error --- .../kickstart/execution/GraphQLInvokerSubjectProxy.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java index a9991f29..5995adab 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvokerSubjectProxy.java @@ -2,6 +2,7 @@ import graphql.ExecutionInput; import graphql.ExecutionResult; +import graphql.GraphQL; import graphql.kickstart.execution.context.GraphQLContext; import java.security.AccessController; import java.security.PrivilegedAction; @@ -11,19 +12,19 @@ public class GraphQLInvokerSubjectProxy implements GraphQLInvokerProxy { @Override - public CompletableFuture executeAsync(GraphQLInvoker graphQLInvoker, ExecutionInput executionInput) { + public CompletableFuture executeAsync(GraphQL graphQL, ExecutionInput executionInput) { GraphQLContext context = (GraphQLContext) executionInput.getContext(); if (Subject.getSubject(AccessController.getContext()) == null && context.getSubject().isPresent()) { return Subject .doAs(context.getSubject().get(), (PrivilegedAction>) () -> { try { - return graphQLInvoker.executeAsync(executionInput); + return graphQL.executeAsync(executionInput); } catch (Exception e) { throw new RuntimeException(e); } }); } - return graphQLInvoker.executeAsync(executionInput); + return graphQL.executeAsync(executionInput); } } From e0fa40be27c0724e41899d32a81448b43b2a3508 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 24 Nov 2019 11:58:01 +0100 Subject: [PATCH 14/26] Refactor subscriptions --- gradle.properties | 2 +- .../AtomicSubscriptionSubscription.java | 27 ++ .../DefaultSubscriptionSession.java | 102 +++++ ...hQLSubscriptionInvocationInputFactory.java | 10 + .../GraphQLSubscriptionMapper.java | 39 ++ .../subscriptions/SessionSubscriber.java | 50 +++ .../subscriptions/SessionSubscriptions.java | 56 +++ .../SubscriptionConnectionListener.java | 2 +- .../SubscriptionException.java | 2 +- .../subscriptions/SubscriptionHandler.java | 5 + .../SubscriptionProtocolFactory.java | 22 ++ .../subscriptions/SubscriptionSession.java | 49 +++ .../apollo/ApolloCommandProvider.java | 34 ++ .../ApolloSubscriptionConnectionListener.java | 24 ++ .../apollo/ApolloSubscriptionConsumer.java | 31 ++ .../ApolloSubscriptionKeepAliveRunner.java | 58 +++ .../ApolloSubscriptionProtocolFactory.java | 84 +++++ .../apollo/ApolloSubscriptionSession.java | 30 ++ ...epAliveSubscriptionConnectionListener.java | 38 ++ .../apollo/OperationMessage.java | 77 ++++ .../apollo/SubscriptionCommand.java | 9 + .../SubscriptionConnectionInitCommand.java | 23 ++ ...ubscriptionConnectionTerminateCommand.java | 22 ++ .../apollo/SubscriptionStartCommand.java | 50 +++ .../apollo/SubscriptionStopCommand.java | 18 + .../servlet/GraphQLWebsocketServlet.java | 351 ++++++++++-------- .../ApolloSubscriptionConnectionListener.java | 44 --- ...oWebSocketSubscriptionProtocolFactory.java | 50 +++ .../ApolloWebSocketSubscriptionSession.java | 38 ++ .../DefaultGraphQLServletContextBuilder.java | 5 +- .../DefaultGraphQLWebSocketContext.java | 113 +++--- .../context/GraphQLWebSocketContext.java | 2 - .../ApolloSubscriptionKeepAliveRunner.java | 64 ---- .../ApolloSubscriptionProtocolFactory.java | 30 -- .../ApolloSubscriptionProtocolHandler.java | 228 ------------ .../FallbackSubscriptionProtocolFactory.java | 18 - .../FallbackSubscriptionProtocolHandler.java | 57 --- .../internal/SubscriptionHandlerInput.java | 39 -- .../internal/SubscriptionProtocolFactory.java | 18 - .../internal/SubscriptionProtocolHandler.java | 95 ----- .../core/internal/SubscriptionSender.java | 24 -- .../servlet/core/internal/VariableMapper.java | 112 +++--- .../core/internal/WsSessionSubscriptions.java | 55 --- .../input/GraphQLInvocationInputFactory.java | 21 +- .../FallbackSubscriptionConsumer.java | 51 +++ .../FallbackSubscriptionProtocolFactory.java | 40 ++ .../WebSocketSendSubscriber.java | 57 +++ .../WebSocketSubscriptionProtocolFactory.java | 13 + .../WebSocketSubscriptionSession.java | 34 ++ 49 files changed, 1471 insertions(+), 952 deletions(-) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/AtomicSubscriptionSubscription.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionInvocationInputFactory.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionMapper.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriptions.java rename graphql-java-kickstart/src/main/java/graphql/kickstart/execution/{subscription => subscriptions}/SubscriptionConnectionListener.java (61%) rename graphql-java-kickstart/src/main/java/graphql/kickstart/execution/{subscription => subscriptions}/SubscriptionException.java (84%) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionHandler.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionProtocolFactory.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionSession.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloCommandProvider.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConnectionListener.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConsumer.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionKeepAliveRunner.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionProtocolFactory.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionSession.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/KeepAliveSubscriptionConnectionListener.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/OperationMessage.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionCommand.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionTerminateCommand.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStopCommand.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionProtocolFactory.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionSession.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionSender.java delete mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionConsumer.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionProtocolFactory.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionProtocolFactory.java create mode 100644 graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionSession.java diff --git a/gradle.properties b/gradle.properties index 3dda48bf..a2404f57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ PROJECT_DEV_ID = apottere PROJECT_DEV_NAME = Andrew Potter LIB_GRAPHQL_JAVA_VER = 13.0 -LIB_JACKSON_VER = 2.9.9 +LIB_JACKSON_VER = 2.10.0 SOURCE_COMPATIBILITY = 1.8 TARGET_COMPATIBILITY = 1.8 diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/AtomicSubscriptionSubscription.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/AtomicSubscriptionSubscription.java new file mode 100644 index 00000000..f60faac8 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/AtomicSubscriptionSubscription.java @@ -0,0 +1,27 @@ +package graphql.kickstart.execution.subscriptions; + +import java.util.concurrent.atomic.AtomicReference; +import org.reactivestreams.Subscription; + +public class AtomicSubscriptionSubscription { + + private final AtomicReference reference = new AtomicReference<>(null); + + public void set(Subscription subscription) { + if (reference.get() != null) { + throw new IllegalStateException("Cannot overwrite subscription!"); + } + + reference.set(subscription); + } + + public Subscription get() { + Subscription subscription = reference.get(); + if (subscription == null) { + throw new IllegalStateException("Subscription has not been initialized yet!"); + } + + return subscription; + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java new file mode 100644 index 00000000..ea61c7e0 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java @@ -0,0 +1,102 @@ +package graphql.kickstart.execution.subscriptions; + +import graphql.ExecutionResult; +import graphql.execution.reactive.SingleSubscriberPublisher; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; + +@Slf4j +@RequiredArgsConstructor +public class DefaultSubscriptionSession implements SubscriptionSession { + + @Getter + private final GraphQLSubscriptionMapper mapper; + private SingleSubscriberPublisher publisher = new SingleSubscriberPublisher<>(); + private SessionSubscriptions subscriptions = new SessionSubscriptions(); + + @Override + public void send(String message) { + Objects.requireNonNull(message, "message is required"); + log.info("Offer message: {}", message); + publisher.offer(message); + } + + @Override + public void sendMessage(Object payload) { + Objects.requireNonNull(payload, "payload is required"); + log.info("Send message: {}", payload); + send(mapper.serialize(payload)); + } + + @Override + public void subscribe(String id, Publisher dataPublisher) { + dataPublisher.subscribe(new SessionSubscriber(this, id)); + } + + @Override + public void add(String id, Subscription subscription) { + subscriptions.add(id, subscription); + } + + @Override + public void unsubscribe(String id) { + subscriptions.cancel(id); + } + + @Override + public void sendDataMessage(String id, Object payload) { + send(mapper.serialize(payload)); + } + + @Override + public void sendErrorMessage(String id) { + + } + + @Override + public void sendCompleteMessage(String id) { + + } + + @Override + public void close(String reason) { + publisher.noMoreData(); + } + + @Override + public Map getUserProperties() { + return new HashMap<>(); + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public String getId() { + return null; + } + + @Override + public SessionSubscriptions getSubscriptions() { + return subscriptions; + } + + @Override + public Object unwrap() { + throw new UnsupportedOperationException(); + } + + @Override + public Publisher getPublisher() { + return publisher; + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionInvocationInputFactory.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionInvocationInputFactory.java new file mode 100644 index 00000000..89d83a76 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionInvocationInputFactory.java @@ -0,0 +1,10 @@ +package graphql.kickstart.execution.subscriptions; + +import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; + +public interface GraphQLSubscriptionInvocationInputFactory { + + GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, SubscriptionSession session); + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionMapper.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionMapper.java new file mode 100644 index 00000000..1e4c8459 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/GraphQLSubscriptionMapper.java @@ -0,0 +1,39 @@ +package graphql.kickstart.execution.subscriptions; + +import com.fasterxml.jackson.core.JsonProcessingException; +import graphql.ExecutionResult; +import graphql.kickstart.execution.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLRequest; +import java.util.Map; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GraphQLSubscriptionMapper { + + private final GraphQLObjectMapper graphQLObjectMapper; + + public GraphQLRequest readGraphQLRequest(Object payload) { + return graphQLObjectMapper.getJacksonMapper().convertValue(payload, GraphQLRequest.class); + } + + public ExecutionResult sanitizeErrors(ExecutionResult executionResult) { + return graphQLObjectMapper.sanitizeErrors(executionResult); + } + + public boolean areErrorsPresent(ExecutionResult executionResult) { + return graphQLObjectMapper.areErrorsPresent(executionResult); + } + + public Map convertSanitizedExecutionResult(ExecutionResult executionResult) { + return graphQLObjectMapper.convertSanitizedExecutionResult(executionResult, false); + } + + public String serialize(Object payload) { + try { + return graphQLObjectMapper.getJacksonMapper().writeValueAsString(payload); + } catch (JsonProcessingException e) { + return e.getMessage(); + } + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java new file mode 100644 index 00000000..dd2f7e99 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java @@ -0,0 +1,50 @@ +package graphql.kickstart.execution.subscriptions; + +import graphql.ExecutionResult; +import java.util.HashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +@Slf4j +@RequiredArgsConstructor +class SessionSubscriber implements Subscriber { + + private final SubscriptionSession session; + private final String id; + private AtomicSubscriptionSubscription subscriptionReference = new AtomicSubscriptionSubscription(); + + @Override + public void onSubscribe(Subscription subscription) { + log.info("Subscribe to execution result: {}", subscription); + subscriptionReference.set(subscription); + subscriptionReference.get().request(1); + + session.add(id, subscriptionReference.get()); + } + + @Override + public void onNext(ExecutionResult executionResult) { + log.info("Next execution result: {}", executionResult); + Map result = new HashMap<>(); + result.put("data", executionResult.getData()); + session.sendDataMessage(id, result); + subscriptionReference.get().request(1); + } + + @Override + public void onError(Throwable throwable) { + log.error("Subscription error", throwable); + session.unsubscribe(id); + session.sendErrorMessage(id); + } + + @Override + public void onComplete() { + session.unsubscribe(id); + session.sendCompleteMessage(id); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriptions.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriptions.java new file mode 100644 index 00000000..72d67953 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriptions.java @@ -0,0 +1,56 @@ +package graphql.kickstart.execution.subscriptions; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.reactivestreams.Subscription; + +/** + * @author Andrew Potter + */ +public class SessionSubscriptions { + + private final Object lock = new Object(); + + private boolean closed = false; + private Map subscriptions = new ConcurrentHashMap<>(); + + public void add(Subscription subscription) { + add(getImplicitId(subscription), subscription); + } + + public void add(String id, Subscription subscription) { + synchronized (lock) { + if (closed) { + throw new IllegalStateException("Websocket was already closed!"); + } + subscriptions.put(id, subscription); + } + } + + public void cancel(Subscription subscription) { + cancel(getImplicitId(subscription)); + } + + public void cancel(String id) { + Subscription subscription = subscriptions.remove(id); + if (subscription != null) { + subscription.cancel(); + } + } + + public void close() { + synchronized (lock) { + closed = true; + subscriptions.forEach((k, v) -> v.cancel()); + subscriptions.clear(); + } + } + + private String getImplicitId(Subscription subscription) { + return String.valueOf(subscription.hashCode()); + } + + public int getSubscriptionCount() { + return subscriptions.size(); + } +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionConnectionListener.java similarity index 61% rename from graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionConnectionListener.java index 33f6294a..f806e169 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionConnectionListener.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionConnectionListener.java @@ -1,4 +1,4 @@ -package graphql.kickstart.execution.subscription; +package graphql.kickstart.execution.subscriptions; /** * Marker interface diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionException.java similarity index 84% rename from graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java rename to graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionException.java index 7bc19906..05040d69 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscription/SubscriptionException.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionException.java @@ -1,4 +1,4 @@ -package graphql.kickstart.execution.subscription; +package graphql.kickstart.execution.subscriptions; public class SubscriptionException extends Exception { diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionHandler.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionHandler.java new file mode 100644 index 00000000..0847ad27 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionHandler.java @@ -0,0 +1,5 @@ +package graphql.kickstart.execution.subscriptions; + +public class SubscriptionHandler { + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionProtocolFactory.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionProtocolFactory.java new file mode 100644 index 00000000..c1789ad2 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionProtocolFactory.java @@ -0,0 +1,22 @@ +package graphql.kickstart.execution.subscriptions; + +import java.util.function.Consumer; + +/** + * @author Andrew Potter + */ +public abstract class SubscriptionProtocolFactory { + + private final String protocol; + + public SubscriptionProtocolFactory(String protocol) { + this.protocol = protocol; + } + + public String getProtocol() { + return protocol; + } + + public abstract Consumer createConsumer(SubscriptionSession session); + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionSession.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionSession.java new file mode 100644 index 00000000..6b45e51a --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SubscriptionSession.java @@ -0,0 +1,49 @@ +package graphql.kickstart.execution.subscriptions; + +import graphql.ExecutionResult; +import java.util.Map; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; + +public interface SubscriptionSession { + + void subscribe(String id, Publisher data); + + void add(String id, Subscription subscription); + + void unsubscribe(String id); + + void send(String message); + + void sendMessage(Object payload); + + void sendDataMessage(String id, Object payload); + + void sendErrorMessage(String id); + + void sendCompleteMessage(String id); + + void close(String reason); + + /** + * While the session is open, this method returns a Map that the developer may use to store application specific + * information relating to this session instance. The developer may retrieve information from this Map at any time + * between the opening of the session and during the onClose() method. But outside that time, any information stored + * using this Map may no longer be kept by the container. Web socket applications running on distributed + * implementations of the web container should make any application specific objects stored here java.io.Serializable, + * or the object may not be recreated after a failover. + * + * @return an editable Map of application data. + */ + Map getUserProperties(); + + boolean isOpen(); + + String getId(); + + SessionSubscriptions getSubscriptions(); + + Object unwrap(); + + Publisher getPublisher(); +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloCommandProvider.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloCommandProvider.java new file mode 100644 index 00000000..29993119 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloCommandProvider.java @@ -0,0 +1,34 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.GraphQLInvoker; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.apollo.OperationMessage.Type; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class ApolloCommandProvider { + + private final Map commands = new HashMap<>(); + + public ApolloCommandProvider( + GraphQLSubscriptionMapper mapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker, + Collection connectionListeners + ) { + commands.put(Type.GQL_CONNECTION_INIT, new SubscriptionConnectionInitCommand(connectionListeners)); + commands.put(Type.GQL_START, new SubscriptionStartCommand(mapper, invocationInputFactory, graphQLInvoker, connectionListeners)); + commands.put(Type.GQL_STOP, new SubscriptionStopCommand(connectionListeners)); + commands.put(Type.GQL_CONNECTION_TERMINATE, new SubscriptionConnectionTerminateCommand(connectionListeners)); + } + + public SubscriptionCommand getByType(Type type) { + if (commands.containsKey(type)) { + return commands.get(type); + } + throw new IllegalStateException("No command found for type " + type); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConnectionListener.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConnectionListener.java new file mode 100644 index 00000000..57bf1ce0 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConnectionListener.java @@ -0,0 +1,24 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.subscriptions.SubscriptionConnectionListener; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; + +public interface ApolloSubscriptionConnectionListener extends SubscriptionConnectionListener { + + default void onConnect(SubscriptionSession session, OperationMessage message) { + // do nothing + } + + default void onStart(SubscriptionSession session, OperationMessage message) { + // do nothing + } + + default void onStop(SubscriptionSession session, OperationMessage message) { + // do nothing + } + + default void onTerminate(SubscriptionSession session, OperationMessage message) { + // do nothing + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConsumer.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConsumer.java new file mode 100644 index 00000000..a7860494 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionConsumer.java @@ -0,0 +1,31 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import com.fasterxml.jackson.core.JsonProcessingException; +import graphql.kickstart.execution.GraphQLObjectMapper; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import graphql.kickstart.execution.subscriptions.apollo.OperationMessage.Type; +import java.util.function.Consumer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +public class ApolloSubscriptionConsumer implements Consumer { + + private final SubscriptionSession session; + private final GraphQLObjectMapper objectMapper; + private final ApolloCommandProvider commandProvider; + + @Override + public void accept(String request) { + try { + OperationMessage message = objectMapper.getJacksonMapper().readValue(request, OperationMessage.class); + SubscriptionCommand command = commandProvider.getByType(message.getType()); + command.apply(session, message); + } catch (JsonProcessingException e) { + log.error("Cannot read subscription command '{}'", request, e); + session.sendMessage(new OperationMessage(Type.GQL_CONNECTION_ERROR, null, e.getMessage())); + } + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionKeepAliveRunner.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionKeepAliveRunner.java new file mode 100644 index 00000000..c78e9076 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionKeepAliveRunner.java @@ -0,0 +1,58 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class ApolloSubscriptionKeepAliveRunner { + + private static final int EXECUTOR_POOL_SIZE = 10; + + private final ScheduledExecutorService executor; + private final OperationMessage keepAliveMessage; + private final Map> futures; + private final long keepAliveIntervalSeconds; + + ApolloSubscriptionKeepAliveRunner(Duration keepAliveInterval) { + this.keepAliveMessage = OperationMessage.newKeepAliveMessage(); + this.executor = Executors.newScheduledThreadPool(EXECUTOR_POOL_SIZE); + this.futures = new ConcurrentHashMap<>(); + this.keepAliveIntervalSeconds = keepAliveInterval.getSeconds(); + } + + void keepAlive(SubscriptionSession session) { + futures.computeIfAbsent(session, this::startKeepAlive); + } + + private ScheduledFuture startKeepAlive(SubscriptionSession session) { + return executor.scheduleAtFixedRate(() -> { + try { + if (session.isOpen()) { + session.sendMessage(keepAliveMessage); + } else { + log.debug("Session {} appears to be closed. Aborting keep alive", session.getId()); + abort(session); + } + } catch (Throwable t) { + log.error("Cannot send keep alive message to session {}. Aborting keep alive", session.getId(), t); + abort(session); + } + }, 0, keepAliveIntervalSeconds, TimeUnit.SECONDS); + } + + void abort(SubscriptionSession session) { + Future future = futures.remove(session); + if (future != null) { + future.cancel(true); + } + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionProtocolFactory.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionProtocolFactory.java new file mode 100644 index 00000000..02af1fcf --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionProtocolFactory.java @@ -0,0 +1,84 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.GraphQLInvoker; +import graphql.kickstart.execution.GraphQLObjectMapper; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.SubscriptionProtocolFactory; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.time.Duration; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import lombok.Getter; + +/** + * @author Andrew Potter + */ +public class ApolloSubscriptionProtocolFactory extends SubscriptionProtocolFactory { + + public static final int KEEP_ALIVE_INTERVAL = 15; + @Getter + private final GraphQLObjectMapper objectMapper; + private final ApolloCommandProvider commandProvider; + + public ApolloSubscriptionProtocolFactory( + GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker + ) { + this(objectMapper, invocationInputFactory, graphQLInvoker, Duration.ofSeconds(KEEP_ALIVE_INTERVAL)); + } + + public ApolloSubscriptionProtocolFactory( + GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker, + Duration keepAliveInterval) { + this(objectMapper, invocationInputFactory, graphQLInvoker, null, keepAliveInterval); + } + + public ApolloSubscriptionProtocolFactory( + GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker, + Collection connectionListeners) { + this( + objectMapper, + invocationInputFactory, + graphQLInvoker, + connectionListeners, + Duration.ofSeconds(KEEP_ALIVE_INTERVAL) + ); + } + + public ApolloSubscriptionProtocolFactory( + GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker, + Collection connectionListeners, + Duration keepAliveInterval) { + super("graphql-ws"); + this.objectMapper = objectMapper; + Set listeners = new HashSet<>(); + if (connectionListeners != null) { + listeners.addAll(connectionListeners); + } + if (keepAliveInterval != null && + listeners.stream().noneMatch(KeepAliveSubscriptionConnectionListener.class::isInstance)) { + listeners.add(new KeepAliveSubscriptionConnectionListener(keepAliveInterval)); + } + commandProvider = new ApolloCommandProvider( + new GraphQLSubscriptionMapper(objectMapper), + invocationInputFactory, + graphQLInvoker, + listeners); + } + + @Override + public Consumer createConsumer(SubscriptionSession session) { + return new ApolloSubscriptionConsumer(session, objectMapper, commandProvider); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionSession.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionSession.java new file mode 100644 index 00000000..db1918d6 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/ApolloSubscriptionSession.java @@ -0,0 +1,30 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.subscriptions.DefaultSubscriptionSession; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.apollo.OperationMessage.Type; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ApolloSubscriptionSession extends DefaultSubscriptionSession { + + public ApolloSubscriptionSession(GraphQLSubscriptionMapper mapper) { + super(mapper); + } + + @Override + public void sendDataMessage(String id, Object payload) { + sendMessage(new OperationMessage(Type.GQL_DATA, id, payload)); + } + + @Override + public void sendErrorMessage(String id) { + sendMessage(new OperationMessage(Type.GQL_ERROR, id, null)); + } + + @Override + public void sendCompleteMessage(String id) { + sendMessage(new OperationMessage(Type.GQL_COMPLETE, id, null)); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/KeepAliveSubscriptionConnectionListener.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/KeepAliveSubscriptionConnectionListener.java new file mode 100644 index 00000000..9f03e85c --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/KeepAliveSubscriptionConnectionListener.java @@ -0,0 +1,38 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.time.Duration; + +public class KeepAliveSubscriptionConnectionListener implements ApolloSubscriptionConnectionListener { + + private final ApolloSubscriptionKeepAliveRunner keepAliveRunner; + + public KeepAliveSubscriptionConnectionListener() { + this(Duration.ofSeconds(15)); + } + + public KeepAliveSubscriptionConnectionListener(Duration keepAliveInterval) { + keepAliveRunner = new ApolloSubscriptionKeepAliveRunner(keepAliveInterval); + } + + @Override + public void onConnect(SubscriptionSession session, OperationMessage message) { + keepAliveRunner.keepAlive(session); + } + + @Override + public void onStart(SubscriptionSession session, OperationMessage message) { + // do nothing + } + + @Override + public void onStop(SubscriptionSession session, OperationMessage message) { + // do nothing + } + + @Override + public void onTerminate(SubscriptionSession session, OperationMessage message) { + keepAliveRunner.abort(session); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/OperationMessage.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/OperationMessage.java new file mode 100644 index 00000000..79bad5fa --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/OperationMessage.java @@ -0,0 +1,77 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.HashMap; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class OperationMessage { + + private Type type; + private String id; + private Object payload; + + public static OperationMessage newKeepAliveMessage() { + return new OperationMessage(Type.GQL_CONNECTION_KEEP_ALIVE, null, null); + } + + public Type getType() { + return type; + } + + public String getId() { + return id; + } + + public Object getPayload() { + return payload; + } + + public enum Type { + + // Server Messages + GQL_CONNECTION_ACK("connection_ack"), + GQL_CONNECTION_ERROR("connection_error"), + GQL_CONNECTION_KEEP_ALIVE("ka"), + GQL_DATA("data"), + GQL_ERROR("error"), + GQL_COMPLETE("complete"), + + // Client Messages + GQL_CONNECTION_INIT("connection_init"), + GQL_CONNECTION_TERMINATE("connection_terminate"), + GQL_START("start"), + GQL_STOP("stop"); + + private static final Map reverseLookup = new HashMap<>(); + + static { + for (Type type : Type.values()) { + reverseLookup.put(type.getType(), type); + } + } + + private final String type; + + Type(String type) { + this.type = type; + } + + @JsonCreator + public static Type findType(String type) { + return reverseLookup.get(type); + } + + @JsonValue + public String getType() { + return type; + } + + } +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionCommand.java new file mode 100644 index 00000000..64195f76 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionCommand.java @@ -0,0 +1,9 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.subscriptions.SubscriptionSession; + +interface SubscriptionCommand { + + void apply(SubscriptionSession session, OperationMessage message); + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java new file mode 100644 index 00000000..9fee5d51 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java @@ -0,0 +1,23 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import graphql.kickstart.execution.subscriptions.apollo.OperationMessage.Type; +import java.util.Collection; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class SubscriptionConnectionInitCommand implements SubscriptionCommand { + + private final Collection connectionListeners; + + @Override + public void apply(SubscriptionSession session, OperationMessage message) { + try { + connectionListeners.forEach(it -> it.onConnect(session, message)); + session.sendMessage(new OperationMessage(Type.GQL_CONNECTION_ACK, message.getId(), null)); + } catch (Throwable t) { + session.sendMessage(new OperationMessage(Type.GQL_CONNECTION_ERROR, message.getId(), t.getMessage())); + } + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionTerminateCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionTerminateCommand.java new file mode 100644 index 00000000..16255c5f --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionTerminateCommand.java @@ -0,0 +1,22 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import static graphql.kickstart.execution.subscriptions.apollo.OperationMessage.Type.GQL_CONNECTION_TERMINATE; + +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +class SubscriptionConnectionTerminateCommand implements SubscriptionCommand { + + private final Collection connectionListeners; + + @Override + public void apply(SubscriptionSession session, OperationMessage message) { + connectionListeners.forEach(it -> it.onTerminate(session, message)); + session.close("client requested " + GQL_CONNECTION_TERMINATE.getType()); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java new file mode 100644 index 00000000..79b7415a --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java @@ -0,0 +1,50 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import static graphql.kickstart.execution.subscriptions.apollo.OperationMessage.Type.GQL_ERROR; + +import graphql.ExecutionResult; +import graphql.kickstart.execution.GraphQLInvoker; +import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.util.Collection; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class SubscriptionStartCommand implements SubscriptionCommand { + + private final GraphQLSubscriptionMapper mapper; + private final GraphQLSubscriptionInvocationInputFactory invocationInputFactory; + private final GraphQLInvoker graphQLInvoker; + private final Collection connectionListeners; + + @Override + public void apply(SubscriptionSession session, OperationMessage message) { + connectionListeners.forEach(it -> it.onStart(session, message)); + CompletableFuture executionResult = executeAsync(message.getPayload(), session); + executionResult.thenAccept(result -> handleSubscriptionStart(session, message.getId(), result)); + } + + private CompletableFuture executeAsync(Object payload, SubscriptionSession session) { + Objects.requireNonNull(payload, "Payload is required"); + GraphQLRequest graphQLRequest = mapper.readGraphQLRequest(payload); + + GraphQLSingleInvocationInput invocationInput = invocationInputFactory.create(graphQLRequest, session); + return graphQLInvoker.executeAsync(invocationInput); + } + + private void handleSubscriptionStart(SubscriptionSession session, String id, ExecutionResult executionResult) { + ExecutionResult sanitizedExecutionResult = mapper.sanitizeErrors(executionResult); + if (!mapper.areErrorsPresent(sanitizedExecutionResult)) { + session.subscribe(id, sanitizedExecutionResult.getData()); + } else { + Object payload = mapper.convertSanitizedExecutionResult(sanitizedExecutionResult); + session.sendMessage(new OperationMessage(GQL_ERROR, id, payload)); + } + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStopCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStopCommand.java new file mode 100644 index 00000000..71de4be5 --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStopCommand.java @@ -0,0 +1,18 @@ +package graphql.kickstart.execution.subscriptions.apollo; + +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.util.Collection; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class SubscriptionStopCommand implements SubscriptionCommand { + + private final Collection connectionListeners; + + @Override + public void apply(SubscriptionSession session, OperationMessage message) { + connectionListeners.forEach(it -> it.onStop(session, message)); + session.unsubscribe(message.getId()); + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java index d7d3608c..acf404ce 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLWebsocketServlet.java @@ -1,200 +1,243 @@ package graphql.servlet; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; + +import graphql.kickstart.execution.GraphQLInvoker; import graphql.kickstart.execution.GraphQLObjectMapper; -import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; -import graphql.servlet.input.GraphQLInvocationInputFactory; -import graphql.servlet.core.internal.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.*; -import javax.websocket.server.HandshakeRequest; -import javax.websocket.server.ServerEndpointConfig; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.SessionSubscriptions; +import graphql.kickstart.execution.subscriptions.SubscriptionConnectionListener; +import graphql.kickstart.execution.subscriptions.SubscriptionProtocolFactory; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import graphql.kickstart.execution.subscriptions.apollo.ApolloSubscriptionConnectionListener; +import graphql.servlet.apollo.ApolloWebSocketSubscriptionProtocolFactory; +import graphql.servlet.subscriptions.FallbackSubscriptionProtocolFactory; +import graphql.servlet.subscriptions.WebSocketSendSubscriber; +import graphql.servlet.subscriptions.WebSocketSubscriptionProtocolFactory; import java.io.EOFException; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; +import java.util.function.Consumer; import java.util.stream.Stream; +import javax.websocket.CloseReason; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.HandshakeResponse; +import javax.websocket.MessageHandler; +import javax.websocket.Session; +import javax.websocket.server.HandshakeRequest; +import javax.websocket.server.ServerEndpointConfig; +import lombok.extern.slf4j.Slf4j; /** * Must be used with {@link #modifyHandshake(ServerEndpointConfig, HandshakeRequest, HandshakeResponse)} * * @author Andrew Potter */ +@Slf4j public class GraphQLWebsocketServlet extends Endpoint { - private static final Logger log = LoggerFactory.getLogger(GraphQLWebsocketServlet.class); - - private static final String HANDSHAKE_REQUEST_KEY = HandshakeRequest.class.getName(); - private static final String PROTOCOL_HANDLER_REQUEST_KEY = SubscriptionProtocolHandler.class.getName(); - private static final CloseReason ERROR_CLOSE_REASON = new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "Internal Server Error"); - private static final CloseReason SHUTDOWN_CLOSE_REASON = new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "Server Shut Down"); - - private final List subscriptionProtocolFactories; - private final SubscriptionProtocolFactory fallbackSubscriptionProtocolFactory; - private final List allSubscriptionProtocols; - - private final Map sessionSubscriptionCache = new ConcurrentHashMap<>(); - private final SubscriptionHandlerInput subscriptionHandlerInput; - private final AtomicBoolean isShuttingDown = new AtomicBoolean(false); - private final AtomicBoolean isShutDown = new AtomicBoolean(false); - private final Object cacheLock = new Object(); - - public GraphQLWebsocketServlet(GraphQLQueryInvoker queryInvoker, GraphQLInvocationInputFactory invocationInputFactory, GraphQLObjectMapper graphQLObjectMapper) { - this(queryInvoker, invocationInputFactory, graphQLObjectMapper, null); + private static final String HANDSHAKE_REQUEST_KEY = HandshakeRequest.class.getName(); + private static final String PROTOCOL_FACTORY_REQUEST_KEY = SubscriptionProtocolFactory.class.getName(); + private static final CloseReason ERROR_CLOSE_REASON = new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, + "Internal Server Error"); + private static final CloseReason SHUTDOWN_CLOSE_REASON = new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, + "Server Shut Down"); + + private final List subscriptionProtocolFactories; + private final SubscriptionProtocolFactory fallbackSubscriptionProtocolFactory; + private final List allSubscriptionProtocols; + + private final Map sessionSubscriptionCache = new ConcurrentHashMap<>(); + private final AtomicBoolean isShuttingDown = new AtomicBoolean(false); + private final AtomicBoolean isShutDown = new AtomicBoolean(false); + private final Object cacheLock = new Object(); + + public GraphQLWebsocketServlet( + GraphQLInvoker graphQLInvoker, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLObjectMapper graphQLObjectMapper) { + this(graphQLInvoker, invocationInputFactory, graphQLObjectMapper, null); + } + + public GraphQLWebsocketServlet( + GraphQLInvoker graphQLInvoker, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLObjectMapper graphQLObjectMapper, + Collection connectionListeners) { + List listeners = new ArrayList<>(); + if (connectionListeners != null) { + connectionListeners.stream() + .filter(ApolloSubscriptionConnectionListener.class::isInstance) + .map(ApolloSubscriptionConnectionListener.class::cast) + .forEach(listeners::add); } - - public GraphQLWebsocketServlet(GraphQLQueryInvoker queryInvoker, GraphQLInvocationInputFactory invocationInputFactory, GraphQLObjectMapper graphQLObjectMapper, SubscriptionConnectionListener subscriptionConnectionListener) { - this.subscriptionHandlerInput = new SubscriptionHandlerInput(invocationInputFactory, queryInvoker, graphQLObjectMapper, subscriptionConnectionListener); - - subscriptionProtocolFactories = Collections.singletonList(new ApolloSubscriptionProtocolFactory(subscriptionHandlerInput)); - fallbackSubscriptionProtocolFactory = new FallbackSubscriptionProtocolFactory(subscriptionHandlerInput); - allSubscriptionProtocols = Stream.concat(subscriptionProtocolFactories.stream(), Stream.of(fallbackSubscriptionProtocolFactory)) - .map(SubscriptionProtocolFactory::getProtocol) - .collect(Collectors.toList()); + subscriptionProtocolFactories = singletonList(new ApolloWebSocketSubscriptionProtocolFactory( + graphQLObjectMapper, + invocationInputFactory, + graphQLInvoker, + listeners + )); + fallbackSubscriptionProtocolFactory = new FallbackSubscriptionProtocolFactory( + new GraphQLSubscriptionMapper(graphQLObjectMapper), + invocationInputFactory, + graphQLInvoker + ); + allSubscriptionProtocols = Stream + .concat(subscriptionProtocolFactories.stream(), Stream.of(fallbackSubscriptionProtocolFactory)) + .map(SubscriptionProtocolFactory::getProtocol) + .collect(toList()); + } + + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + final WebSocketSubscriptionProtocolFactory subscriptionProtocolFactory = + (WebSocketSubscriptionProtocolFactory) endpointConfig.getUserProperties().get(PROTOCOL_FACTORY_REQUEST_KEY); + + // todo: create apollo version of it through SubscriptionProtocolFactory + SubscriptionSession subscriptionSession = subscriptionProtocolFactory.createSession(session); + synchronized (cacheLock) { + if (isShuttingDown.get()) { + throw new IllegalStateException("Server is shutting down!"); + } + + sessionSubscriptionCache.put(session, subscriptionSession.getSubscriptions()); } - @Override - public void onOpen(Session session, EndpointConfig endpointConfig) { - final WsSessionSubscriptions subscriptions = new WsSessionSubscriptions(); - final HandshakeRequest request = (HandshakeRequest) endpointConfig.getUserProperties().get(HANDSHAKE_REQUEST_KEY); - final SubscriptionProtocolHandler subscriptionProtocolHandler = (SubscriptionProtocolHandler) endpointConfig.getUserProperties().get(PROTOCOL_HANDLER_REQUEST_KEY); + subscriptionSession.getPublisher().subscribe(new WebSocketSendSubscriber(session)); - synchronized (cacheLock) { - if (isShuttingDown.get()) { - throw new IllegalStateException("Server is shutting down!"); - } + log.debug("Session opened: {}, {}", session.getId(), endpointConfig); + Consumer consumer = subscriptionProtocolFactory.createConsumer(subscriptionSession); - sessionSubscriptionCache.put(session, subscriptions); + // This *cannot* be a lambda because of the way undertow checks the class... + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String text) { + try { + consumer.accept(text); + } catch (Throwable t) { + log.error("Error executing websocket query for session: {}", session.getId(), t); + closeUnexpectedly(session, t); } - - log.debug("Session opened: {}, {}", session.getId(), endpointConfig); - - // This *cannot* be a lambda because of the way undertow checks the class... - session.addMessageHandler(new MessageHandler.Whole() { - @Override - public void onMessage(String text) { - try { - subscriptionProtocolHandler.onMessage(request, session, subscriptions, text); - } catch (Throwable t) { - log.error("Error executing websocket query for session: {}", session.getId(), t); - closeUnexpectedly(session, t); - } - } - }); + } + }); + } + + @Override + public void onClose(Session session, CloseReason closeReason) { + log.debug("Session closed: {}, {}", session.getId(), closeReason); + SessionSubscriptions subscriptions; + synchronized (cacheLock) { + subscriptions = sessionSubscriptionCache.remove(session); } - - @Override - public void onClose(Session session, CloseReason closeReason) { - log.debug("Session closed: {}, {}", session.getId(), closeReason); - WsSessionSubscriptions subscriptions; - synchronized (cacheLock) { - subscriptions = sessionSubscriptionCache.remove(session); - } - if (subscriptions != null) { - subscriptions.close(); - } + if (subscriptions != null) { + subscriptions.close(); } - - @Override - public void onError(Session session, Throwable thr) { - if (thr instanceof EOFException) { - log.warn("Session {} was killed abruptly without calling onClose. Cleaning up session", session.getId()); - onClose(session, ERROR_CLOSE_REASON); - } else { - log.error("Error in websocket session: {}", session.getId(), thr); - closeUnexpectedly(session, thr); - } + } + + @Override + public void onError(Session session, Throwable thr) { + if (thr instanceof EOFException) { + log.warn("Session {} was killed abruptly without calling onClose. Cleaning up session", session.getId()); + onClose(session, ERROR_CLOSE_REASON); + } else { + log.error("Error in websocket session: {}", session.getId(), thr); + closeUnexpectedly(session, thr); } + } - private void closeUnexpectedly(Session session, Throwable t) { - try { - session.close(ERROR_CLOSE_REASON); - } catch (IOException e) { - log.error("Error closing websocket session for session: {}", session.getId(), t); - } + private void closeUnexpectedly(Session session, Throwable t) { + try { + session.close(ERROR_CLOSE_REASON); + } catch (IOException e) { + log.error("Error closing websocket session for session: {}", session.getId(), t); } + } - public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { - sec.getUserProperties().put(HANDSHAKE_REQUEST_KEY, request); + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { + sec.getUserProperties().put(HANDSHAKE_REQUEST_KEY, request); - List protocol = request.getHeaders().get(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL); - if (protocol == null) { - protocol = Collections.emptyList(); - } + List protocol = request.getHeaders().get(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL); + if (protocol == null) { + protocol = Collections.emptyList(); + } - SubscriptionProtocolFactory subscriptionProtocolFactory = getSubscriptionProtocolFactory(protocol); - sec.getUserProperties().put(PROTOCOL_HANDLER_REQUEST_KEY, subscriptionProtocolFactory.createHandler()); + SubscriptionProtocolFactory subscriptionProtocolFactory = getSubscriptionProtocolFactory(protocol); + sec.getUserProperties().put(PROTOCOL_FACTORY_REQUEST_KEY, subscriptionProtocolFactory); - if (request.getHeaders().get(HandshakeResponse.SEC_WEBSOCKET_ACCEPT) != null) { - response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, allSubscriptionProtocols); - } - if (!protocol.isEmpty()) { - response.getHeaders().put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, Collections.singletonList(subscriptionProtocolFactory.getProtocol())); - } + if (request.getHeaders().get(HandshakeResponse.SEC_WEBSOCKET_ACCEPT) != null) { + response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, allSubscriptionProtocols); } - - /** - * Stops accepting connections and closes all existing connections - */ - public void beginShutDown() { - synchronized (cacheLock) { - isShuttingDown.set(true); - Map copy = new HashMap<>(sessionSubscriptionCache); - - // Prevent comodification exception since #onClose() is called during session.close(), but we can't necessarily rely on that happening so we close subscriptions here anyway. - copy.forEach((session, wsSessionSubscriptions) -> { - wsSessionSubscriptions.close(); - try { - session.close(SHUTDOWN_CLOSE_REASON); - } catch (IOException e) { - log.error("Error closing websocket session!", e); - } - }); - - copy.clear(); - - if(!sessionSubscriptionCache.isEmpty()) { - log.error("GraphQLWebsocketServlet did not shut down cleanly!"); - sessionSubscriptionCache.clear(); - } + if (!protocol.isEmpty()) { + response.getHeaders().put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, + singletonList(subscriptionProtocolFactory.getProtocol())); + } + } + + /** + * Stops accepting connections and closes all existing connections + */ + public void beginShutDown() { + synchronized (cacheLock) { + isShuttingDown.set(true); + Map copy = new HashMap<>(sessionSubscriptionCache); + + // Prevent comodification exception since #onClose() is called during session.close(), but we can't necessarily rely on that happening so we close subscriptions here anyway. + copy.forEach((session, wsSessionSubscriptions) -> { + wsSessionSubscriptions.close(); + try { + session.close(SHUTDOWN_CLOSE_REASON); + } catch (IOException e) { + log.error("Error closing websocket session!", e); } + }); - isShutDown.set(true); - } + copy.clear(); - /** - * @return true when shutdown is complete - */ - public boolean isShutDown() { - return isShutDown.get(); + if (!sessionSubscriptionCache.isEmpty()) { + log.error("GraphQLWebsocketServlet did not shut down cleanly!"); + sessionSubscriptionCache.clear(); + } } - private SubscriptionProtocolFactory getSubscriptionProtocolFactory(List accept) { - for (String protocol : accept) { - for (SubscriptionProtocolFactory subscriptionProtocolFactory : subscriptionProtocolFactories) { - if (subscriptionProtocolFactory.getProtocol().equals(protocol)) { - return subscriptionProtocolFactory; - } - } + isShutDown.set(true); + } + + /** + * @return true when shutdown is complete + */ + public boolean isShutDown() { + return isShutDown.get(); + } + + private SubscriptionProtocolFactory getSubscriptionProtocolFactory(List accept) { + for (String protocol : accept) { + for (SubscriptionProtocolFactory subscriptionProtocolFactory : subscriptionProtocolFactories) { + if (subscriptionProtocolFactory.getProtocol().equals(protocol)) { + return subscriptionProtocolFactory; } - - return fallbackSubscriptionProtocolFactory; + } } - public int getSessionCount() { - return sessionSubscriptionCache.size(); - } + return fallbackSubscriptionProtocolFactory; + } - public int getSubscriptionCount() { - return sessionSubscriptionCache.values().stream() - .mapToInt(WsSessionSubscriptions::getSubscriptionCount) - .sum(); - } + public int getSessionCount() { + return sessionSubscriptionCache.size(); + } + + public int getSubscriptionCount() { + return sessionSubscriptionCache.values().stream() + .mapToInt(SessionSubscriptions::getSubscriptionCount) + .sum(); + } } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java b/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java deleted file mode 100644 index 25f01dd5..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloSubscriptionConnectionListener.java +++ /dev/null @@ -1,44 +0,0 @@ -package graphql.servlet.apollo; - -import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; -import graphql.kickstart.execution.subscription.SubscriptionException; -import java.time.Duration; -import java.util.Optional; - -public interface ApolloSubscriptionConnectionListener extends SubscriptionConnectionListener { - - long KEEP_ALIVE_INTERVAL_SEC = 15; - - String CONNECT_RESULT_KEY = "CONNECT_RESULT"; - - default boolean isKeepAliveEnabled() { - return true; - } - - default Optional onConnect(Object payload) throws SubscriptionException { - return Optional.empty(); - } - - default Duration getKeepAliveInterval() { - return Duration.ofSeconds(KEEP_ALIVE_INTERVAL_SEC); - } - - static ApolloSubscriptionConnectionListener createWithKeepAliveDisabled() { - return new ApolloSubscriptionConnectionListener() { - @Override - public boolean isKeepAliveEnabled() { - return false; - } - }; - } - - static ApolloSubscriptionConnectionListener createWithKeepAliveInterval(Duration interval) { - return new ApolloSubscriptionConnectionListener() { - @Override - public Duration getKeepAliveInterval() { - return interval; - } - }; - } - -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionProtocolFactory.java new file mode 100644 index 00000000..ebfe7272 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionProtocolFactory.java @@ -0,0 +1,50 @@ +package graphql.servlet.apollo; + +import graphql.kickstart.execution.GraphQLInvoker; +import graphql.kickstart.execution.GraphQLObjectMapper; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import graphql.kickstart.execution.subscriptions.apollo.ApolloSubscriptionConnectionListener; +import graphql.kickstart.execution.subscriptions.apollo.ApolloSubscriptionProtocolFactory; +import graphql.servlet.subscriptions.WebSocketSubscriptionProtocolFactory; +import java.time.Duration; +import java.util.Collection; +import javax.websocket.Session; + +public class ApolloWebSocketSubscriptionProtocolFactory + extends ApolloSubscriptionProtocolFactory + implements WebSocketSubscriptionProtocolFactory { + + public ApolloWebSocketSubscriptionProtocolFactory(GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker) { + super(objectMapper, invocationInputFactory, graphQLInvoker); + } + + public ApolloWebSocketSubscriptionProtocolFactory(GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker, Duration keepAliveInterval) { + super(objectMapper, invocationInputFactory, graphQLInvoker, keepAliveInterval); + } + + public ApolloWebSocketSubscriptionProtocolFactory(GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker, + Collection connectionListeners) { + super(objectMapper, invocationInputFactory, graphQLInvoker, connectionListeners); + } + + public ApolloWebSocketSubscriptionProtocolFactory(GraphQLObjectMapper objectMapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker, + Collection connectionListeners, Duration keepAliveInterval) { + super(objectMapper, invocationInputFactory, graphQLInvoker, connectionListeners, keepAliveInterval); + } + + @Override + public SubscriptionSession createSession(Session session) { + return new ApolloWebSocketSubscriptionSession(new GraphQLSubscriptionMapper(getObjectMapper()), session); + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionSession.java b/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionSession.java new file mode 100644 index 00000000..e745b7da --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/apollo/ApolloWebSocketSubscriptionSession.java @@ -0,0 +1,38 @@ +package graphql.servlet.apollo; + +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.apollo.ApolloSubscriptionSession; +import graphql.servlet.subscriptions.WebSocketSubscriptionSession; +import java.util.Map; +import javax.websocket.Session; + +public class ApolloWebSocketSubscriptionSession extends ApolloSubscriptionSession { + + private final WebSocketSubscriptionSession webSocketSubscriptionSession; + + public ApolloWebSocketSubscriptionSession(GraphQLSubscriptionMapper mapper, Session session) { + super(mapper); + webSocketSubscriptionSession = new WebSocketSubscriptionSession(mapper, session); + } + + @Override + public boolean isOpen() { + return webSocketSubscriptionSession.isOpen(); + } + + @Override + public Map getUserProperties() { + return webSocketSubscriptionSession.getUserProperties(); + } + + @Override + public String getId() { + return webSocketSubscriptionSession.getId(); + } + + @Override + public Session unwrap() { + return webSocketSubscriptionSession.unwrap(); + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java index 5ac73142..5270b062 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLServletContextBuilder.java @@ -14,9 +14,8 @@ public class DefaultGraphQLServletContextBuilder extends DefaultGraphQLContextBu GraphQLServletContextBuilder { @Override - public GraphQLContext build(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { - return DefaultGraphQLServletContext.createServletContext().with(httpServletRequest).with(httpServletResponse) - .build(); + public GraphQLContext build(HttpServletRequest request, HttpServletResponse response) { + return DefaultGraphQLServletContext.createServletContext().with(request).with(response).build(); } @Override diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java index 983d288c..fdad98f7 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/context/DefaultGraphQLWebSocketContext.java @@ -1,83 +1,76 @@ package graphql.servlet.context; import graphql.kickstart.execution.context.DefaultGraphQLContext; -import graphql.servlet.apollo.ApolloSubscriptionConnectionListener; -import org.dataloader.DataLoaderRegistry; - import javax.security.auth.Subject; import javax.websocket.Session; import javax.websocket.server.HandshakeRequest; -import java.util.Optional; +import org.dataloader.DataLoaderRegistry; public class DefaultGraphQLWebSocketContext extends DefaultGraphQLContext implements GraphQLWebSocketContext { - private final Session session; - private final HandshakeRequest handshakeRequest; + private final Session session; + private final HandshakeRequest handshakeRequest; - private DefaultGraphQLWebSocketContext(DataLoaderRegistry dataLoaderRegistry, Subject subject, - Session session, HandshakeRequest handshakeRequest) { - super(dataLoaderRegistry, subject); - this.session = session; - this.handshakeRequest = handshakeRequest; - } + private DefaultGraphQLWebSocketContext(DataLoaderRegistry dataLoaderRegistry, Subject subject, + Session session, HandshakeRequest handshakeRequest) { + super(dataLoaderRegistry, subject); + this.session = session; + this.handshakeRequest = handshakeRequest; + } - @Override - public Session getSession() { - return session; - } + public static Builder createWebSocketContext(DataLoaderRegistry registry, Subject subject) { + return new Builder(registry, subject); + } + + public static Builder createWebSocketContext() { + return new Builder(new DataLoaderRegistry(), null); + } - @Override - public Optional getConnectResult() { - return Optional.of(session).map(session -> session.getUserProperties().get(ApolloSubscriptionConnectionListener.CONNECT_RESULT_KEY)); + @Override + public Session getSession() { + return session; + } + + @Override + public HandshakeRequest getHandshakeRequest() { + return handshakeRequest; + } + + public static class Builder { + + private Session session; + private HandshakeRequest handshakeRequest; + private DataLoaderRegistry dataLoaderRegistry; + private Subject subject; + + private Builder(DataLoaderRegistry dataLoaderRegistry, Subject subject) { + this.dataLoaderRegistry = dataLoaderRegistry; + this.subject = subject; } - @Override - public HandshakeRequest getHandshakeRequest() { - return handshakeRequest; + public DefaultGraphQLWebSocketContext build() { + return new DefaultGraphQLWebSocketContext(dataLoaderRegistry, subject, session, handshakeRequest); } - public static Builder createWebSocketContext(DataLoaderRegistry registry, Subject subject) { - return new Builder(registry, subject); + public Builder with(Session session) { + this.session = session; + return this; } - public static Builder createWebSocketContext() { - return new Builder(new DataLoaderRegistry(), null); + public Builder with(HandshakeRequest handshakeRequest) { + this.handshakeRequest = handshakeRequest; + return this; } - public static class Builder { - private Session session; - private HandshakeRequest handshakeRequest; - private DataLoaderRegistry dataLoaderRegistry; - private Subject subject; - - private Builder(DataLoaderRegistry dataLoaderRegistry, Subject subject) { - this.dataLoaderRegistry = dataLoaderRegistry; - this.subject = subject; - } - - public DefaultGraphQLWebSocketContext build() { - return new DefaultGraphQLWebSocketContext(dataLoaderRegistry, subject, session, handshakeRequest); - } - - public Builder with(Session session) { - this.session = session; - return this; - } - - public Builder with(HandshakeRequest handshakeRequest) { - this.handshakeRequest = handshakeRequest; - return this; - } - - public Builder with(DataLoaderRegistry dataLoaderRegistry) { - this.dataLoaderRegistry = dataLoaderRegistry; - return this; - } - - public Builder with(Subject subject) { - this.subject = subject; - return this; - } + public Builder with(DataLoaderRegistry dataLoaderRegistry) { + this.dataLoaderRegistry = dataLoaderRegistry; + return this; + } + public Builder with(Subject subject) { + this.subject = subject; + return this; } + } + } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java b/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java index 0b7193a7..4ab956a3 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/context/GraphQLWebSocketContext.java @@ -9,8 +9,6 @@ public interface GraphQLWebSocketContext extends GraphQLContext { Session getSession(); - Optional getConnectResult(); - HandshakeRequest getHandshakeRequest(); } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java deleted file mode 100644 index 2c60f9cf..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionKeepAliveRunner.java +++ /dev/null @@ -1,64 +0,0 @@ -package graphql.servlet.core.internal; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.Session; -import java.time.Duration; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -class ApolloSubscriptionKeepAliveRunner { - - private static final Logger LOG = LoggerFactory.getLogger(ApolloSubscriptionKeepAliveRunner.class); - - private static final int EXECUTOR_POOL_SIZE = 10; - - private final ScheduledExecutorService executor; - private final SubscriptionSender sender; - private final ApolloSubscriptionProtocolHandler.OperationMessage keepAliveMessage; - private final Map> futures; - private final long keepAliveIntervalSeconds; - - ApolloSubscriptionKeepAliveRunner(SubscriptionSender sender, Duration keepAliveInterval) { - this.sender = Objects.requireNonNull(sender); - this.keepAliveMessage = ApolloSubscriptionProtocolHandler.OperationMessage.newKeepAliveMessage(); - this.executor = Executors.newScheduledThreadPool(EXECUTOR_POOL_SIZE); - this.futures = new ConcurrentHashMap<>(); - this.keepAliveIntervalSeconds = keepAliveInterval.getSeconds(); - } - - void keepAlive(Session session) { - futures.computeIfAbsent(session, this::startKeepAlive); - } - - private ScheduledFuture startKeepAlive(Session session) { - return executor.scheduleAtFixedRate(() -> { - try { - if (session.isOpen()) { - sender.send(session, keepAliveMessage); - } else { - LOG.debug("Session {} appears to be closed. Aborting keep alive", session.getId()); - abort(session); - } - } catch (Throwable t) { - LOG.error("Cannot send keep alive message to session {}. Aborting keep alive", session.getId(), t); - abort(session); - } - }, 0, keepAliveIntervalSeconds, TimeUnit.SECONDS); - } - - void abort(Session session) { - Future future = futures.remove(session); - if (future != null) { - future.cancel(true); - } - } - -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java deleted file mode 100644 index 9f9400bd..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package graphql.servlet.core.internal; - -import graphql.servlet.apollo.ApolloSubscriptionConnectionListener; - -/** - * @author Andrew Potter - */ -public class ApolloSubscriptionProtocolFactory extends SubscriptionProtocolFactory { - private final SubscriptionHandlerInput subscriptionHandlerInput; - private final SubscriptionSender subscriptionSender; - private final ApolloSubscriptionKeepAliveRunner keepAliveRunner; - private final ApolloSubscriptionConnectionListener connectionListener; - - public ApolloSubscriptionProtocolFactory(SubscriptionHandlerInput subscriptionHandlerInput) { - super("graphql-ws"); - this.subscriptionHandlerInput = subscriptionHandlerInput; - this.connectionListener = subscriptionHandlerInput.getSubscriptionConnectionListener() - .filter(ApolloSubscriptionConnectionListener.class::isInstance) - .map(ApolloSubscriptionConnectionListener.class::cast) - .orElse(new ApolloSubscriptionConnectionListener() {}); - subscriptionSender = - new SubscriptionSender(subscriptionHandlerInput.getGraphQLObjectMapper().getJacksonMapper()); - keepAliveRunner = new ApolloSubscriptionKeepAliveRunner(subscriptionSender, connectionListener.getKeepAliveInterval()); - } - - @Override - public SubscriptionProtocolHandler createHandler() { - return new ApolloSubscriptionProtocolHandler(subscriptionHandlerInput, connectionListener, subscriptionSender, keepAliveRunner); - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java deleted file mode 100644 index 6f064f50..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/ApolloSubscriptionProtocolHandler.java +++ /dev/null @@ -1,228 +0,0 @@ -package graphql.servlet.core.internal; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonValue; -import graphql.ExecutionResult; -import graphql.kickstart.execution.GraphQLRequest; -import graphql.servlet.apollo.ApolloSubscriptionConnectionListener; -import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; -import graphql.kickstart.execution.subscription.SubscriptionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.CloseReason; -import javax.websocket.Session; -import javax.websocket.server.HandshakeRequest; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static graphql.servlet.core.internal.ApolloSubscriptionProtocolHandler.OperationMessage.Type.GQL_COMPLETE; -import static graphql.servlet.core.internal.ApolloSubscriptionProtocolHandler.OperationMessage.Type.GQL_CONNECTION_TERMINATE; -import static graphql.servlet.core.internal.ApolloSubscriptionProtocolHandler.OperationMessage.Type.GQL_DATA; -import static graphql.servlet.core.internal.ApolloSubscriptionProtocolHandler.OperationMessage.Type.GQL_ERROR; - -/** - * https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md - * - * @author Andrew Potter - */ -public class ApolloSubscriptionProtocolHandler extends SubscriptionProtocolHandler { - - private static final Logger log = LoggerFactory.getLogger(ApolloSubscriptionProtocolHandler.class); - private static final CloseReason TERMINATE_CLOSE_REASON = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "client requested " + GQL_CONNECTION_TERMINATE.getType()); - - private final SubscriptionHandlerInput input; - private final SubscriptionSender sender; - private final ApolloSubscriptionKeepAliveRunner keepAliveRunner; - private final ApolloSubscriptionConnectionListener connectionListener; - - public ApolloSubscriptionProtocolHandler(SubscriptionHandlerInput subscriptionHandlerInput, - ApolloSubscriptionConnectionListener connectionListener, - SubscriptionSender subscriptionSender, - ApolloSubscriptionKeepAliveRunner keepAliveRunner) { - this.input = subscriptionHandlerInput; - this.connectionListener = connectionListener; - this.sender = subscriptionSender; - this.keepAliveRunner = keepAliveRunner; - } - - @Override - public void onMessage(HandshakeRequest request, Session session, WsSessionSubscriptions subscriptions, String text) { - OperationMessage message; - try { - message = input.getGraphQLObjectMapper().getJacksonMapper().readValue(text, OperationMessage.class); - } catch(Throwable t) { - log.warn("Error parsing message", t); - sendMessage(session, OperationMessage.Type.GQL_CONNECTION_ERROR, null); - return; - } - - switch (message.getType()) { - case GQL_CONNECTION_INIT: - try { - Optional connectionResponse = connectionListener.onConnect(message.getPayload()); - connectionResponse.ifPresent(it -> session.getUserProperties().put(ApolloSubscriptionConnectionListener.CONNECT_RESULT_KEY, it)); - } catch (SubscriptionException e) { - sendMessage(session, OperationMessage.Type.GQL_CONNECTION_ERROR, message.getId(), e.getPayload()); - return; - } - - sendMessage(session, OperationMessage.Type.GQL_CONNECTION_ACK, message.getId()); - - if (connectionListener.isKeepAliveEnabled()) { - keepAliveRunner.keepAlive(session); - } - break; - - case GQL_START: - GraphQLSingleInvocationInput graphQLSingleInvocationInput = createInvocationInput(session, message); - handleSubscriptionStart( - session, - subscriptions, - message.id, - input.getQueryInvoker().query(graphQLSingleInvocationInput) - ); - break; - - case GQL_STOP: - unsubscribe(subscriptions, message.id); - break; - - case GQL_CONNECTION_TERMINATE: - keepAliveRunner.abort(session); - try { - session.close(TERMINATE_CLOSE_REASON); - } catch (IOException e) { - log.error("Error closing websocket session!", e); - } - break; - - default: - throw new IllegalArgumentException("Unknown message type: " + message.getType()); - } - } - - private GraphQLSingleInvocationInput createInvocationInput(Session session, OperationMessage message) { - GraphQLRequest graphQLRequest = input.getGraphQLObjectMapper() - .getJacksonMapper() - .convertValue(message.getPayload(), GraphQLRequest.class); - HandshakeRequest handshakeRequest = (HandshakeRequest) session.getUserProperties() - .get(HandshakeRequest.class.getName()); - - return input.getInvocationInputFactory().create(graphQLRequest, session, handshakeRequest); - } - - @SuppressWarnings("unchecked") - private void handleSubscriptionStart(Session session, WsSessionSubscriptions subscriptions, String id, ExecutionResult executionResult) { - executionResult = input.getGraphQLObjectMapper().sanitizeErrors(executionResult); - - if (input.getGraphQLObjectMapper().areErrorsPresent(executionResult)) { - sendMessage(session, OperationMessage.Type.GQL_ERROR, id, input.getGraphQLObjectMapper().convertSanitizedExecutionResult(executionResult, false)); - return; - } - - subscribe(session, executionResult, subscriptions, id); - } - - @Override - protected void sendDataMessage(Session session, String id, Object payload) { - sendMessage(session, GQL_DATA, id, payload); - } - - @Override - protected void sendErrorMessage(Session session, String id) { - keepAliveRunner.abort(session); - sendMessage(session, GQL_ERROR, id); - } - - @Override - protected void sendCompleteMessage(Session session, String id) { - keepAliveRunner.abort(session); - sendMessage(session, GQL_COMPLETE, id); - } - - private void sendMessage(Session session, OperationMessage.Type type, String id) { - sendMessage(session, type, id, null); - } - - private void sendMessage(Session session, OperationMessage.Type type, String id, Object payload) { - sender.send(session, new OperationMessage(type, id, payload)); - } - - @JsonInclude(JsonInclude.Include.NON_NULL) - public static class OperationMessage { - private Type type; - private String id; - private Object payload; - - public OperationMessage() { - } - - public OperationMessage(Type type, String id, Object payload) { - this.type = type; - this.id = id; - this.payload = payload; - } - - static OperationMessage newKeepAliveMessage() { - return new OperationMessage(Type.GQL_CONNECTION_KEEP_ALIVE, null, null); - } - - public Type getType() { - return type; - } - - public String getId() { - return id; - } - - public Object getPayload() { - return payload; - } - - public enum Type { - - // Server Messages - GQL_CONNECTION_ACK("connection_ack"), - GQL_CONNECTION_ERROR("connection_error"), - GQL_CONNECTION_KEEP_ALIVE("ka"), - GQL_DATA("data"), - GQL_ERROR("error"), - GQL_COMPLETE("complete"), - - // Client Messages - GQL_CONNECTION_INIT("connection_init"), - GQL_CONNECTION_TERMINATE("connection_terminate"), - GQL_START("start"), - GQL_STOP("stop"); - - private static final Map reverseLookup = new HashMap<>(); - - static { - for(Type type: Type.values()) { - reverseLookup.put(type.getType(), type); - } - } - - private final String type; - - Type(String type) { - this.type = type; - } - - @JsonCreator - public static Type findType(String type) { - return reverseLookup.get(type); - } - - @JsonValue - public String getType() { - return type; - } - } - } - -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java deleted file mode 100644 index b5566293..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package graphql.servlet.core.internal; - -/** - * @author Andrew Potter - */ -public class FallbackSubscriptionProtocolFactory extends SubscriptionProtocolFactory { - private final SubscriptionHandlerInput subscriptionHandlerInput; - - public FallbackSubscriptionProtocolFactory(SubscriptionHandlerInput subscriptionHandlerInput) { - super(""); - this.subscriptionHandlerInput = subscriptionHandlerInput; - } - - @Override - public SubscriptionProtocolHandler createHandler() { - return new FallbackSubscriptionProtocolHandler(subscriptionHandlerInput); - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java deleted file mode 100644 index fa4a9903..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/FallbackSubscriptionProtocolHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -package graphql.servlet.core.internal; - -import graphql.kickstart.execution.GraphQLRequest; -import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; - -import javax.websocket.Session; -import javax.websocket.server.HandshakeRequest; -import java.io.IOException; -import java.util.UUID; - -/** - * @author Andrew Potter - */ -public class FallbackSubscriptionProtocolHandler extends SubscriptionProtocolHandler { - - private final SubscriptionHandlerInput input; - private final SubscriptionSender sender; - - public FallbackSubscriptionProtocolHandler(SubscriptionHandlerInput subscriptionHandlerInput) { - this.input = subscriptionHandlerInput; - sender = new SubscriptionSender(subscriptionHandlerInput.getGraphQLObjectMapper().getJacksonMapper()); - } - - @Override - public void onMessage(HandshakeRequest request, Session session, WsSessionSubscriptions subscriptions, String text) throws Exception { - GraphQLSingleInvocationInput graphQLSingleInvocationInput = createInvocationInput(session, text); - subscribe( - session, - input.getQueryInvoker().query(graphQLSingleInvocationInput), - subscriptions, - UUID.randomUUID().toString() - ); - } - - private GraphQLSingleInvocationInput createInvocationInput(Session session, String text) throws IOException { - GraphQLRequest graphQLRequest = input.getGraphQLObjectMapper().readGraphQLRequest(text); - HandshakeRequest handshakeRequest = (HandshakeRequest) session.getUserProperties() - .get(HandshakeRequest.class.getName()); - - return input.getInvocationInputFactory().create(graphQLRequest, session, handshakeRequest); - } - - @Override - protected void sendDataMessage(Session session, String id, Object payload) { - sender.send(session, payload); - } - - @Override - protected void sendErrorMessage(Session session, String id) { - - } - - @Override - protected void sendCompleteMessage(Session session, String id) { - - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java deleted file mode 100644 index 4cf7d270..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionHandlerInput.java +++ /dev/null @@ -1,39 +0,0 @@ -package graphql.servlet.core.internal; - -import graphql.servlet.input.GraphQLInvocationInputFactory; -import graphql.kickstart.execution.GraphQLObjectMapper; -import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.kickstart.execution.subscription.SubscriptionConnectionListener; - -import java.util.Optional; - -public class SubscriptionHandlerInput { - - private final GraphQLInvocationInputFactory invocationInputFactory; - private final GraphQLQueryInvoker queryInvoker; - private final GraphQLObjectMapper graphQLObjectMapper; - private final SubscriptionConnectionListener subscriptionConnectionListener; - - public SubscriptionHandlerInput(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, SubscriptionConnectionListener subscriptionConnectionListener) { - this.invocationInputFactory = invocationInputFactory; - this.queryInvoker = queryInvoker; - this.graphQLObjectMapper = graphQLObjectMapper; - this.subscriptionConnectionListener = subscriptionConnectionListener; - } - - public GraphQLInvocationInputFactory getInvocationInputFactory() { - return invocationInputFactory; - } - - public GraphQLQueryInvoker getQueryInvoker() { - return queryInvoker; - } - - public GraphQLObjectMapper getGraphQLObjectMapper() { - return graphQLObjectMapper; - } - - public Optional getSubscriptionConnectionListener() { - return Optional.ofNullable(subscriptionConnectionListener); - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java deleted file mode 100644 index f82fa912..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package graphql.servlet.core.internal; - -/** - * @author Andrew Potter - */ -public abstract class SubscriptionProtocolFactory { - private final String protocol; - - public SubscriptionProtocolFactory(String protocol) { - this.protocol = protocol; - } - - public String getProtocol() { - return protocol; - } - - public abstract SubscriptionProtocolHandler createHandler(); -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java deleted file mode 100644 index b75babf9..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionProtocolHandler.java +++ /dev/null @@ -1,95 +0,0 @@ -package graphql.servlet.core.internal; - -import graphql.ExecutionResult; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.reactivestreams.Subscription; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.websocket.Session; -import javax.websocket.server.HandshakeRequest; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -/** - * @author Andrew Potter - */ -public abstract class SubscriptionProtocolHandler { - - private static final Logger log = LoggerFactory.getLogger(SubscriptionProtocolHandler.class); - - public abstract void onMessage(HandshakeRequest request, Session session, WsSessionSubscriptions subscriptions, String text) throws Exception; - - protected abstract void sendDataMessage(Session session, String id, Object payload); - - protected abstract void sendErrorMessage(Session session, String id); - - protected abstract void sendCompleteMessage(Session session, String id); - - protected void subscribe(Session session, ExecutionResult executionResult, WsSessionSubscriptions subscriptions, String id) { - final Object data = executionResult.getData(); - - if (data instanceof Publisher) { - @SuppressWarnings("unchecked") final Publisher publisher = (Publisher) data; - final AtomicSubscriptionReference subscriptionReference = new AtomicSubscriptionReference(); - - publisher.subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription subscription) { - subscriptionReference.set(subscription); - subscriptionReference.get().request(1); - - subscriptions.add(id, subscriptionReference.get()); - } - - @Override - public void onNext(ExecutionResult executionResult) { - subscriptionReference.get().request(1); - Map result = new HashMap<>(); - result.put("data", executionResult.getData()); - sendDataMessage(session, id, result); - } - - @Override - public void onError(Throwable throwable) { - log.error("Subscription error", throwable); - unsubscribe(subscriptions, id); - sendErrorMessage(session, id); - } - - @Override - public void onComplete() { - unsubscribe(subscriptions, id); - sendCompleteMessage(session, id); - } - }); - } - } - - protected void unsubscribe(WsSessionSubscriptions subscriptions, String id) { - subscriptions.cancel(id); - } - - static class AtomicSubscriptionReference { - private final AtomicReference reference = new AtomicReference<>(null); - - public void set(Subscription subscription) { - if(reference.get() != null) { - throw new IllegalStateException("Cannot overwrite subscription!"); - } - - reference.set(subscription); - } - - public Subscription get() { - Subscription subscription = reference.get(); - if(subscription == null) { - throw new IllegalStateException("Subscription has not been initialized yet!"); - } - - return subscription; - } - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionSender.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionSender.java deleted file mode 100644 index 89c5d117..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/SubscriptionSender.java +++ /dev/null @@ -1,24 +0,0 @@ -package graphql.servlet.core.internal; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import javax.websocket.Session; -import java.io.IOException; -import java.io.UncheckedIOException; - -class SubscriptionSender { - - private final ObjectMapper objectMapper; - - SubscriptionSender(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } - - void send(Session session, Object payload) { - try { - session.getBasicRemote().sendText(objectMapper.writeValueAsString(payload)); - } catch (IOException e) { - throw new UncheckedIOException("Error sending subscription response", e); - } - } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/VariableMapper.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/VariableMapper.java index 1a3b058f..23764047 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/VariableMapper.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/VariableMapper.java @@ -1,76 +1,78 @@ package graphql.servlet.core.internal; -import javax.servlet.http.Part; import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import javax.servlet.http.Part; public class VariableMapper { - private static final Pattern PERIOD = Pattern.compile("\\."); - private static final Mapper> MAP_MAPPER = new Mapper>() { - @Override - public Object set(Map location, String target, Part value) { - return location.put(target, value); - } - - @Override - public Object recurse(Map location, String target) { - return location.get(target); - } - }; - private static final Mapper> LIST_MAPPER = new Mapper>() { - @Override - public Object set(List location, String target, Part value) { - return location.set(Integer.parseInt(target), value); - } + private static final Pattern PERIOD = Pattern.compile("\\."); - @Override - public Object recurse(List location, String target) { - return location.get(Integer.parseInt(target)); - } - }; + private static final Mapper> MAP_MAPPER = new Mapper>() { + @Override + public Object set(Map location, String target, Part value) { + return location.put(target, value); + } - public static void mapVariable(String objectPath, Map variables, Part part) { - String[] segments = PERIOD.split(objectPath); + @Override + public Object recurse(Map location, String target) { + return location.get(target); + } + }; + private static final Mapper> LIST_MAPPER = new Mapper>() { + @Override + public Object set(List location, String target, Part value) { + return location.set(Integer.parseInt(target), value); + } - if (segments.length < 2) { - throw new RuntimeException("object-path in map must have at least two segments"); - } else if (!"variables".equals(segments[0])) { - throw new RuntimeException("can only map into variables"); - } + @Override + public Object recurse(List location, String target) { + return location.get(Integer.parseInt(target)); + } + }; - Object currentLocation = variables; - for (int i = 1; i < segments.length; i++) { - String segmentName = segments[i]; - Mapper mapper = determineMapper(currentLocation, objectPath, segmentName); + public static void mapVariable(String objectPath, Map variables, Part part) { + String[] segments = PERIOD.split(objectPath); - if (i == segments.length - 1) { - if (null != mapper.set(currentLocation, segmentName, part)) { - throw new RuntimeException("expected null value when mapping " + objectPath); - } - } else { - currentLocation = mapper.recurse(currentLocation, segmentName); - if (null == currentLocation) { - throw new RuntimeException("found null intermediate value when trying to map " + objectPath); - } - } - } + if (segments.length < 2) { + throw new RuntimeException("object-path in map must have at least two segments"); + } else if (!"variables".equals(segments[0])) { + throw new RuntimeException("can only map into variables"); } - private static Mapper determineMapper(Object currentLocation, String objectPath, String segmentName) { - if (currentLocation instanceof Map) { - return MAP_MAPPER; - } else if (currentLocation instanceof List) { - return LIST_MAPPER; + Object currentLocation = variables; + for (int i = 1; i < segments.length; i++) { + String segmentName = segments[i]; + Mapper mapper = determineMapper(currentLocation, objectPath, segmentName); + + if (i == segments.length - 1) { + if (null != mapper.set(currentLocation, segmentName, part)) { + throw new RuntimeException("expected null value when mapping " + objectPath); + } + } else { + currentLocation = mapper.recurse(currentLocation, segmentName); + if (null == currentLocation) { + throw new RuntimeException("found null intermediate value when trying to map " + objectPath); } + } + } + } - throw new RuntimeException("expected a map or list at " + segmentName + " when trying to map " + objectPath); + private static Mapper determineMapper(Object currentLocation, String objectPath, String segmentName) { + if (currentLocation instanceof Map) { + return MAP_MAPPER; + } else if (currentLocation instanceof List) { + return LIST_MAPPER; } - interface Mapper { - Object set(T location, String target, Part value); + throw new RuntimeException("expected a map or list at " + segmentName + " when trying to map " + objectPath); + } - Object recurse(T location, String target); - } + interface Mapper { + + Object set(T location, String target, Part value); + + Object recurse(T location, String target); + } } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java b/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java deleted file mode 100644 index 98cf770d..00000000 --- a/graphql-java-servlet/src/main/java/graphql/servlet/core/internal/WsSessionSubscriptions.java +++ /dev/null @@ -1,55 +0,0 @@ -package graphql.servlet.core.internal; - -import org.reactivestreams.Subscription; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author Andrew Potter - */ -public class WsSessionSubscriptions { - - private final Object lock = new Object(); - - private boolean closed = false; - private Map subscriptions = new ConcurrentHashMap<>(); - - public void add(Subscription subscription) { - add(getImplicitId(subscription), subscription); - } - - public void add(String id, Subscription subscription) { - synchronized (lock) { - if (closed) { - throw new IllegalStateException("Websocket was already closed!"); - } - subscriptions.put(id, subscription); - } - } - - public void cancel(Subscription subscription) { - cancel(getImplicitId(subscription)); - } - - public void cancel(String id) { - Subscription subscription = subscriptions.remove(id); - if(subscription != null) { - subscription.cancel(); - } - } - - public void close() { - synchronized (lock) { - closed = true; - subscriptions.forEach((k, v) -> v.cancel()); - subscriptions.clear(); - } - } - - private String getImplicitId(Subscription subscription) { - return String.valueOf(subscription.hashCode()); - } - - public int getSubscriptionCount() { return subscriptions.size(); } -} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java index 28435a2d..32b2a90f 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java @@ -5,6 +5,8 @@ import graphql.kickstart.execution.context.ContextSetting; import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; import graphql.schema.GraphQLSchema; import graphql.servlet.config.DefaultGraphQLSchemaServletProvider; import graphql.servlet.config.GraphQLSchemaServletProvider; @@ -22,7 +24,7 @@ /** * @author Andrew Potter */ -public class GraphQLInvocationInputFactory { +public class GraphQLInvocationInputFactory implements GraphQLSubscriptionInvocationInputFactory { private final Supplier schemaProviderSupplier; private final Supplier contextBuilderSupplier; @@ -106,7 +108,8 @@ private GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List ); } - public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, Session session, HandshakeRequest request) { + public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, Session session) { + HandshakeRequest request = (HandshakeRequest) session.getUserProperties().get(HandshakeRequest.class.getName()); return new GraphQLSingleInvocationInput( graphQLRequest, schemaProviderSupplier.get().getSchema(request), @@ -115,8 +118,20 @@ public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, Sessio ); } + @Override + public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, SubscriptionSession session) { + HandshakeRequest request = (HandshakeRequest) session.getUserProperties().get(HandshakeRequest.class.getName()); + return new GraphQLSingleInvocationInput( + graphQLRequest, + schemaProviderSupplier.get().getSchema(request), + contextBuilderSupplier.get().build((Session) session.unwrap(), request), + rootObjectBuilderSupplier.get().build(request) + ); + } + public GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List graphQLRequest, - Session session, HandshakeRequest request) { + Session session) { + HandshakeRequest request = (HandshakeRequest) session.getUserProperties().get(HandshakeRequest.class.getName()); return contextSetting.getBatch( graphQLRequest, schemaProviderSupplier.get().getSchema(request), diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionConsumer.java b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionConsumer.java new file mode 100644 index 00000000..e65dc209 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionConsumer.java @@ -0,0 +1,51 @@ +package graphql.servlet.subscriptions; + +import graphql.ExecutionResult; +import graphql.kickstart.execution.GraphQLInvoker; +import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import lombok.RequiredArgsConstructor; + +/** + * @author Andrew Potter + */ +@RequiredArgsConstructor +public class FallbackSubscriptionConsumer implements Consumer { + + private final SubscriptionSession session; + private final GraphQLSubscriptionMapper mapper; + private final GraphQLSubscriptionInvocationInputFactory invocationInputFactory; + private final GraphQLInvoker graphQLInvoker; + + @Override + public void accept(String text) { + CompletableFuture executionResult = executeAsync(text, session); + executionResult.thenAccept(result -> handleSubscriptionStart(session, UUID.randomUUID().toString(), result)); + } + + private CompletableFuture executeAsync(Object payload, SubscriptionSession session) { + Objects.requireNonNull(payload, "Payload is required"); + GraphQLRequest graphQLRequest = mapper.readGraphQLRequest(payload); + + GraphQLSingleInvocationInput invocationInput = invocationInputFactory.create(graphQLRequest, session); + return graphQLInvoker.executeAsync(invocationInput); + } + + private void handleSubscriptionStart(SubscriptionSession session, String id, ExecutionResult executionResult) { + ExecutionResult sanitizedExecutionResult = mapper.sanitizeErrors(executionResult); + if (!mapper.areErrorsPresent(sanitizedExecutionResult)) { + session.subscribe(id, sanitizedExecutionResult.getData()); + } else { + Object payload = mapper.convertSanitizedExecutionResult(sanitizedExecutionResult); + session.sendDataMessage(id, payload); + } + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionProtocolFactory.java new file mode 100644 index 00000000..471f8f8f --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/FallbackSubscriptionProtocolFactory.java @@ -0,0 +1,40 @@ +package graphql.servlet.subscriptions; + +import graphql.kickstart.execution.GraphQLInvoker; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionInvocationInputFactory; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import graphql.kickstart.execution.subscriptions.SubscriptionProtocolFactory; +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.util.function.Consumer; +import javax.websocket.Session; + +/** + * @author Andrew Potter + */ +public class FallbackSubscriptionProtocolFactory extends SubscriptionProtocolFactory implements + WebSocketSubscriptionProtocolFactory { + + private final GraphQLSubscriptionMapper mapper; + private final GraphQLSubscriptionInvocationInputFactory invocationInputFactory; + private final GraphQLInvoker graphQLInvoker; + + public FallbackSubscriptionProtocolFactory( + GraphQLSubscriptionMapper mapper, + GraphQLSubscriptionInvocationInputFactory invocationInputFactory, + GraphQLInvoker graphQLInvoker) { + super(""); + this.mapper = mapper; + this.invocationInputFactory = invocationInputFactory; + this.graphQLInvoker = graphQLInvoker; + } + + @Override + public Consumer createConsumer(SubscriptionSession session) { + return new FallbackSubscriptionConsumer(session, mapper, invocationInputFactory, graphQLInvoker); + } + + @Override + public SubscriptionSession createSession(Session session) { + return new WebSocketSubscriptionSession(mapper, session); + } +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java new file mode 100644 index 00000000..bd155f96 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java @@ -0,0 +1,57 @@ +package graphql.servlet.subscriptions; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; +import javax.websocket.Session; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +@Slf4j +@RequiredArgsConstructor +public class WebSocketSendSubscriber implements Subscriber { + + private final Session session; + private AtomicReference subscriptionRef = new AtomicReference<>(); + + @Override + public void onSubscribe(Subscription subscription) { + log.info("Subscribe to publisher"); + subscriptionRef.set(subscription); + subscriptionRef.get().request(1); + } + + @Override + public void onNext(String message) { + subscriptionRef.get().request(1); + log.info("Send message: {}", message); + if (session.isOpen()) { + try { + session.getBasicRemote().sendText(message); + } catch (IOException e) { + log.error("Cannot send message {}", message, e); + } + } + } + + @Override + public void onError(Throwable t) { + + } + + @Override + public void onComplete() { + subscriptionRef.get().request(1); + if (session.isOpen()) { + try { + log.debug("Closing session"); + session.close(); + } catch (IOException e) { + log.error("Cannot close session", e); + } + } + subscriptionRef.get().cancel(); + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionProtocolFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionProtocolFactory.java new file mode 100644 index 00000000..224757aa --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionProtocolFactory.java @@ -0,0 +1,13 @@ +package graphql.servlet.subscriptions; + +import graphql.kickstart.execution.subscriptions.SubscriptionSession; +import java.util.function.Consumer; +import javax.websocket.Session; + +public interface WebSocketSubscriptionProtocolFactory { + + Consumer createConsumer(SubscriptionSession session); + + SubscriptionSession createSession(Session session); + +} diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionSession.java b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionSession.java new file mode 100644 index 00000000..4f6eacfa --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSubscriptionSession.java @@ -0,0 +1,34 @@ +package graphql.servlet.subscriptions; + +import graphql.kickstart.execution.subscriptions.DefaultSubscriptionSession; +import graphql.kickstart.execution.subscriptions.GraphQLSubscriptionMapper; +import java.util.Map; +import javax.websocket.Session; + +public class WebSocketSubscriptionSession extends DefaultSubscriptionSession { + + private final Session session; + + public WebSocketSubscriptionSession(GraphQLSubscriptionMapper mapper, Session session) { + super(mapper); + this.session = session; + } + + public boolean isOpen() { + return session.isOpen(); + } + + public Map getUserProperties() { + return session.getUserProperties(); + } + + public String getId() { + return session.getId(); + } + + @Override + public Session unwrap() { + return session; + } + +} From 9a47e63b219dee395a5056d1988ea304a540bb19 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 24 Nov 2019 20:16:35 +0100 Subject: [PATCH 15/26] Refactor subscriptions --- .../subscriptions/DefaultSubscriptionSession.java | 9 +++++++-- .../execution/subscriptions/SessionSubscriber.java | 3 +-- .../apollo/SubscriptionConnectionInitCommand.java | 3 +++ .../subscriptions/apollo/SubscriptionStartCommand.java | 3 +++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java index ea61c7e0..c8f17cf2 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/DefaultSubscriptionSession.java @@ -23,14 +23,12 @@ public class DefaultSubscriptionSession implements SubscriptionSession { @Override public void send(String message) { Objects.requireNonNull(message, "message is required"); - log.info("Offer message: {}", message); publisher.offer(message); } @Override public void sendMessage(Object payload) { Objects.requireNonNull(payload, "payload is required"); - log.info("Send message: {}", payload); send(mapper.serialize(payload)); } @@ -66,6 +64,8 @@ public void sendCompleteMessage(String id) { @Override public void close(String reason) { + log.debug("Closing subscription session {}", getId()); + subscriptions.close(); publisher.noMoreData(); } @@ -99,4 +99,9 @@ public Publisher getPublisher() { return publisher; } + @Override + public String toString() { + return getId(); + } + } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java index dd2f7e99..12cfa157 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/SessionSubscriber.java @@ -18,7 +18,7 @@ class SessionSubscriber implements Subscriber { @Override public void onSubscribe(Subscription subscription) { - log.info("Subscribe to execution result: {}", subscription); + log.debug("Subscribe to execution result: {}", subscription); subscriptionReference.set(subscription); subscriptionReference.get().request(1); @@ -27,7 +27,6 @@ public void onSubscribe(Subscription subscription) { @Override public void onNext(ExecutionResult executionResult) { - log.info("Next execution result: {}", executionResult); Map result = new HashMap<>(); result.put("data", executionResult.getData()); session.sendDataMessage(id, result); diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java index 9fee5d51..f66a05f0 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java @@ -4,7 +4,9 @@ import graphql.kickstart.execution.subscriptions.apollo.OperationMessage.Type; import java.util.Collection; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RequiredArgsConstructor class SubscriptionConnectionInitCommand implements SubscriptionCommand { @@ -12,6 +14,7 @@ class SubscriptionConnectionInitCommand implements SubscriptionCommand { @Override public void apply(SubscriptionSession session, OperationMessage message) { + log.info("Apollo subscription connection init: {}", session); try { connectionListeners.forEach(it -> it.onConnect(session, message)); session.sendMessage(new OperationMessage(Type.GQL_CONNECTION_ACK, message.getId(), null)); diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java index 79b7415a..bd84972f 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java @@ -13,7 +13,9 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RequiredArgsConstructor class SubscriptionStartCommand implements SubscriptionCommand { @@ -24,6 +26,7 @@ class SubscriptionStartCommand implements SubscriptionCommand { @Override public void apply(SubscriptionSession session, OperationMessage message) { + log.info("Apollo subscription start: {} --> {}", session, message.getPayload()); connectionListeners.forEach(it -> it.onStart(session, message)); CompletableFuture executionResult = executeAsync(message.getPayload(), session); executionResult.thenAccept(result -> handleSubscriptionStart(session, message.getId(), result)); From 8bac91ae006dd4745d4641071e1bb034337fbc15 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 24 Nov 2019 22:05:17 +0100 Subject: [PATCH 16/26] Refactor subscriptions --- .../subscriptions/apollo/SubscriptionConnectionInitCommand.java | 2 +- .../subscriptions/apollo/SubscriptionStartCommand.java | 2 +- .../graphql/servlet/subscriptions/WebSocketSendSubscriber.java | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java index f66a05f0..3d666ec7 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionConnectionInitCommand.java @@ -14,7 +14,7 @@ class SubscriptionConnectionInitCommand implements SubscriptionCommand { @Override public void apply(SubscriptionSession session, OperationMessage message) { - log.info("Apollo subscription connection init: {}", session); + log.debug("Apollo subscription connection init: {}", session); try { connectionListeners.forEach(it -> it.onConnect(session, message)); session.sendMessage(new OperationMessage(Type.GQL_CONNECTION_ACK, message.getId(), null)); diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java index bd84972f..e7d0db3f 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/subscriptions/apollo/SubscriptionStartCommand.java @@ -26,7 +26,7 @@ class SubscriptionStartCommand implements SubscriptionCommand { @Override public void apply(SubscriptionSession session, OperationMessage message) { - log.info("Apollo subscription start: {} --> {}", session, message.getPayload()); + log.debug("Apollo subscription start: {} --> {}", session, message.getPayload()); connectionListeners.forEach(it -> it.onStart(session, message)); CompletableFuture executionResult = executeAsync(message.getPayload(), session); executionResult.thenAccept(result -> handleSubscriptionStart(session, message.getId(), result)); diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java index bd155f96..99774e47 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/subscriptions/WebSocketSendSubscriber.java @@ -17,7 +17,6 @@ public class WebSocketSendSubscriber implements Subscriber { @Override public void onSubscribe(Subscription subscription) { - log.info("Subscribe to publisher"); subscriptionRef.set(subscription); subscriptionRef.get().request(1); } @@ -25,7 +24,6 @@ public void onSubscribe(Subscription subscription) { @Override public void onNext(String message) { subscriptionRef.get().request(1); - log.info("Send message: {}", message); if (session.isOpen()) { try { session.getBasicRemote().sendText(message); From f589fa59b7c2218512198272dc1715bf772e936f Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 24 Nov 2019 22:34:05 +0100 Subject: [PATCH 17/26] Refactor subscriptions --- .../java/graphql/kickstart/execution/GraphQLInvoker.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java index 27d916f1..7572703c 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java @@ -1,5 +1,7 @@ package graphql.kickstart.execution; +import static java.util.stream.Collectors.toList; + import graphql.ExecutionResult; import graphql.GraphQL; import graphql.kickstart.execution.context.ContextSetting; @@ -45,10 +47,7 @@ private List query(List batchedIn // .configureInstrumentationForContext(getInstrumentation, executionIds, optionsSupplier.get()); return batchedInvocationInput.stream() .map(this::executeAsync) - //We want eager eval - .collect(Collectors.toList()) - .stream() .map(CompletableFuture::join) - .collect(Collectors.toList()); + .collect(toList()); } } From eebc9afa5d9d7fa2e488edfa23eff84eef3fe8c7 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Sun, 24 Nov 2019 23:25:51 +0100 Subject: [PATCH 18/26] Fix build deprecations --- build.gradle | 3 ++- graphql-java-servlet/build.gradle | 11 +---------- settings.gradle | 2 ++ 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index d61ac2aa..e0166a43 100644 --- a/build.gradle +++ b/build.gradle @@ -36,6 +36,7 @@ plugins { id 'net.researchgate.release' version '2.7.0' id 'io.franzbecker.gradle-lombok' version '3.2.0' apply false id "com.jfrog.artifactory" version "4.8.1" apply false + id "biz.aQute.bnd" version "4.3.1" apply false } subprojects { @@ -216,6 +217,6 @@ task build { dependsOn subprojects.findResults { it.tasks.findByName('bintray') } } -task wrapper(type: Wrapper) { +wrapper { gradleVersion = "${GRADLE_WRAPPER_VER}" } diff --git a/graphql-java-servlet/build.gradle b/graphql-java-servlet/build.gradle index e2829bea..6854cf1c 100644 --- a/graphql-java-servlet/build.gradle +++ b/graphql-java-servlet/build.gradle @@ -3,9 +3,6 @@ buildscript { jcenter() mavenCentral() } - dependencies { - classpath 'biz.aQute.bnd:biz.aQute.bnd.gradle:3.1.0' - } } apply plugin: 'groovy' @@ -14,18 +11,12 @@ apply plugin: 'java-library-distribution' apply plugin: 'biz.aQute.bnd.builder' jar { - manifest { - instruction 'Require-Capability', 'osgi.extender' - } + bnd ('Require-Capability': 'osgi.extender') } -//afterReleaseBuild.dependsOn bintrayUpload - dependencies { compile(project(':graphql-java-kickstart')) -// compile 'org.slf4j:slf4j-api:1.7.21' - // Useful utilities compile 'com.google.guava:guava:24.1.1-jre' diff --git a/settings.gradle b/settings.gradle index df6580aa..0b1d0018 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ +enableFeaturePreview('STABLE_PUBLISHING') + rootProject.name = 'graphql-java-servlet' include ':graphql-java-kickstart' From 2dee089b8895f9807d15ff3b8dc22ac8df319cee Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Mon, 25 Nov 2019 08:43:00 +0100 Subject: [PATCH 19/26] Upgrade gradle to 6.0.1 --- build.gradle | 2 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- graphql-java-servlet/build.gradle | 1 - settings.gradle | 2 -- 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index e0166a43..c8a5746f 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ buildscript { plugins { id 'net.researchgate.release' version '2.7.0' id 'io.franzbecker.gradle-lombok' version '3.2.0' apply false - id "com.jfrog.artifactory" version "4.8.1" apply false + id "com.jfrog.artifactory" version "4.11.0" apply false id "biz.aQute.bnd" version "4.3.1" apply false } diff --git a/gradle.properties b/gradle.properties index a2404f57..425ddf04 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ LIB_JACKSON_VER = 2.10.0 SOURCE_COMPATIBILITY = 1.8 TARGET_COMPATIBILITY = 1.8 -GRADLE_WRAPPER_VER = 4.10.3 +GRADLE_WRAPPER_VER = 6.0.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4c1dfdb..5f8ff30a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Thu Nov 14 18:53:34 CET 2019 -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/graphql-java-servlet/build.gradle b/graphql-java-servlet/build.gradle index 6854cf1c..f7f2bd84 100644 --- a/graphql-java-servlet/build.gradle +++ b/graphql-java-servlet/build.gradle @@ -6,7 +6,6 @@ buildscript { } apply plugin: 'groovy' -apply plugin: 'osgi' apply plugin: 'java-library-distribution' apply plugin: 'biz.aQute.bnd.builder' diff --git a/settings.gradle b/settings.gradle index 0b1d0018..df6580aa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,3 @@ -enableFeaturePreview('STABLE_PUBLISHING') - rootProject.name = 'graphql-java-servlet' include ':graphql-java-kickstart' From 16071f6e03e0d3e91f20654e2a78b1fc6224e6ee Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Mon, 25 Nov 2019 08:51:51 +0100 Subject: [PATCH 20/26] Upgrade gradle to 6.0.1 --- build.gradle | 1 - graphql-java-servlet/build.gradle | 2 +- .../graphql/servlet/OsgiGraphQLHttpServlet.java | 14 +++++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index c8a5746f..60e4869a 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,6 @@ plugins { subprojects { apply plugin: 'idea' apply plugin: 'java' - apply plugin: 'maven' apply plugin: 'maven-publish' apply plugin: "com.jfrog.bintray" apply plugin: 'io.franzbecker.gradle-lombok' diff --git a/graphql-java-servlet/build.gradle b/graphql-java-servlet/build.gradle index f7f2bd84..f28fa854 100644 --- a/graphql-java-servlet/build.gradle +++ b/graphql-java-servlet/build.gradle @@ -27,7 +27,7 @@ dependencies { compileOnly 'org.osgi:org.osgi.core:6.0.0' compileOnly 'org.osgi:org.osgi.service.cm:1.5.0' compileOnly 'org.osgi:org.osgi.service.component:1.3.0' - compileOnly 'biz.aQute.bnd:biz.aQute.bndlib:3.1.0' + compileOnly 'biz.aQute.bnd:biz.aQute.bndlib:4.3.1' testCompile 'io.github.graphql-java:graphql-java-annotations:5.2' diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java index 364dfbaf..29ae4bb9 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java @@ -3,6 +3,13 @@ import static graphql.schema.GraphQLObjectType.newObject; import static graphql.schema.GraphQLSchema.newSchema; +import aQute.bnd.component.annotations.Activate; +import aQute.bnd.component.annotations.Component; +import aQute.bnd.component.annotations.Deactivate; +import aQute.bnd.component.annotations.Reference; +import aQute.bnd.component.annotations.ReferenceCardinality; +import aQute.bnd.component.annotations.ReferencePolicy; +import aQute.bnd.component.annotations.ReferencePolicyOption; import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; import graphql.execution.preparsed.PreparsedDocumentProvider; import graphql.schema.GraphQLCodeRegistry; @@ -39,13 +46,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.component.annotations.ReferencePolicyOption; @Component( service = {javax.servlet.http.HttpServlet.class, javax.servlet.Servlet.class}, From 6465fa814cc8c765ed988f96244a1d4154bf1525 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Mon, 25 Nov 2019 09:00:16 +0100 Subject: [PATCH 21/26] Upgrade gradle to 6.0.1 --- graphql-java-servlet/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql-java-servlet/build.gradle b/graphql-java-servlet/build.gradle index f28fa854..17dd8726 100644 --- a/graphql-java-servlet/build.gradle +++ b/graphql-java-servlet/build.gradle @@ -14,7 +14,7 @@ jar { } dependencies { - compile(project(':graphql-java-kickstart')) + implementation(project(':graphql-java-kickstart')) // Useful utilities compile 'com.google.guava:guava:24.1.1-jre' From a0c0f49f1094c7fb674587f8a13af21ae5f727cb Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Mon, 25 Nov 2019 19:48:13 +0100 Subject: [PATCH 22/26] Build GraphQL on the fly after all --- .../BatchedDataLoaderGraphQLBuilder.java | 42 +++++++++++++++++++ .../kickstart/execution/GraphQLInvoker.java | 24 +++++------ .../execution/GraphQLQueryInvoker.java | 2 +- .../execution/config/GraphQLBuilder.java | 20 +++++++-- .../input/GraphQLBatchedInvocationInput.java | 21 ++++++---- .../input/PerQueryBatchedInvocationInput.java | 5 ++- .../PerRequestBatchedInvocationInput.java | 4 +- .../servlet/TestBatchInputPreProcessor.java | 26 ++++++------ 8 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 graphql-java-kickstart/src/main/java/graphql/kickstart/execution/BatchedDataLoaderGraphQLBuilder.java diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/BatchedDataLoaderGraphQLBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/BatchedDataLoaderGraphQLBuilder.java new file mode 100644 index 00000000..ba66dcdc --- /dev/null +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/BatchedDataLoaderGraphQLBuilder.java @@ -0,0 +1,42 @@ +package graphql.kickstart.execution; + +import graphql.ExecutionInput; +import graphql.GraphQL; +import graphql.execution.instrumentation.Instrumentation; +import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions; +import graphql.kickstart.execution.config.GraphQLBuilder; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import java.util.List; +import java.util.function.Supplier; + +public class BatchedDataLoaderGraphQLBuilder { + + private final Supplier optionsSupplier; + + public BatchedDataLoaderGraphQLBuilder(Supplier optionsSupplier) { + if (optionsSupplier != null) { + this.optionsSupplier = optionsSupplier; + } else { + this.optionsSupplier = DataLoaderDispatcherInstrumentationOptions::newOptions; + } + } + + GraphQL newGraphQL(GraphQLBatchedInvocationInput invocationInput, GraphQLBuilder graphQLBuilder) { + Supplier supplier = augment(invocationInput, graphQLBuilder.getInstrumentationSupplier()); + return invocationInput.getInvocationInputs().stream().findFirst() + .map(GraphQLSingleInvocationInput::getSchema) + .map(schema -> graphQLBuilder.build(schema, supplier)) + .orElseThrow(() -> new IllegalArgumentException("Batched invocation input must contain at least one query")); + } + + private Supplier augment( + GraphQLBatchedInvocationInput batchedInvocationInput, + Supplier instrumentationSupplier + ) { + List executionInputs = batchedInvocationInput.getExecutionInputs(); + return batchedInvocationInput.getContextSetting() + .configureInstrumentationForContext(instrumentationSupplier, executionInputs, optionsSupplier.get()); + } + +} diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java index 7572703c..a3a0cfb1 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java @@ -4,13 +4,12 @@ import graphql.ExecutionResult; import graphql.GraphQL; -import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.execution.config.GraphQLBuilder; import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import graphql.kickstart.execution.input.GraphQLInvocationInput; import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; @@ -18,10 +17,12 @@ @RequiredArgsConstructor public class GraphQLInvoker { - private final GraphQL graphQL; + private final GraphQLBuilder graphQLBuilder; + private final BatchedDataLoaderGraphQLBuilder batchedDataLoaderGraphQLBuilder; private GraphQLInvokerProxy proxy = GraphQL::executeAsync; public CompletableFuture executeAsync(GraphQLSingleInvocationInput invocationInput) { + GraphQL graphQL = graphQLBuilder.build(invocationInput.getSchema()); return proxy.executeAsync(graphQL, invocationInput.getExecutionInput()); } @@ -30,24 +31,19 @@ public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) { return GraphQLQueryResult.create(query((GraphQLSingleInvocationInput) invocationInput)); } GraphQLBatchedInvocationInput batchedInvocationInput = (GraphQLBatchedInvocationInput) invocationInput; - return GraphQLQueryResult - .create(query(batchedInvocationInput.getExecutionInputs(), batchedInvocationInput.getContextSetting())); + return GraphQLQueryResult.create(query(batchedInvocationInput)); } private ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) { return executeAsync(singleInvocationInput).join(); } - private List query(List batchedInvocationInput, - ContextSetting contextSetting) { -// List executionIds = batchedInvocationInput.stream() -// .map(GraphQLSingleInvocationInput::getExecutionInput) -// .collect(Collectors.toList()); -// Supplier configuredInstrumentation = contextSetting -// .configureInstrumentationForContext(getInstrumentation, executionIds, optionsSupplier.get()); - return batchedInvocationInput.stream() - .map(this::executeAsync) + private List query(GraphQLBatchedInvocationInput batchedInvocationInput) { + GraphQL graphQL = batchedDataLoaderGraphQLBuilder.newGraphQL(batchedInvocationInput, graphQLBuilder); + return batchedInvocationInput.getExecutionInputs().stream() + .map(executionInput -> proxy.executeAsync(graphQL, executionInput)) .map(CompletableFuture::join) .collect(toList()); } } + diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java index cc688ae2..1a01eb8f 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java @@ -54,7 +54,7 @@ public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) { return GraphQLQueryResult.create(query((GraphQLSingleInvocationInput) invocationInput)); } GraphQLBatchedInvocationInput batchedInvocationInput = (GraphQLBatchedInvocationInput) invocationInput; - return GraphQLQueryResult.create(query(batchedInvocationInput.getExecutionInputs(), batchedInvocationInput.getContextSetting())); + return GraphQLQueryResult.create(query(batchedInvocationInput.getInvocationInputs(), batchedInvocationInput.getContextSetting())); } public ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) { diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java index 6e15d332..e8411249 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/config/GraphQLBuilder.java @@ -9,25 +9,33 @@ import graphql.execution.preparsed.PreparsedDocumentProvider; import graphql.schema.GraphQLSchema; import java.util.function.Supplier; +import lombok.Getter; public class GraphQLBuilder { private Supplier executionStrategyProviderSupplier = DefaultExecutionStrategyProvider::new; private Supplier preparsedDocumentProviderSupplier = () -> NoOpPreparsedDocumentProvider.INSTANCE; + @Getter private Supplier instrumentationSupplier = () -> SimpleInstrumentation.INSTANCE; public GraphQLBuilder executionStrategyProvider(Supplier supplier) { - executionStrategyProviderSupplier = supplier; + if (supplier != null) { + executionStrategyProviderSupplier = supplier; + } return this; } public GraphQLBuilder preparsedDocumentProvider(Supplier supplier) { - preparsedDocumentProviderSupplier = supplier; + if (supplier != null) { + preparsedDocumentProviderSupplier = supplier; + } return this; } public GraphQLBuilder instrumentation(Supplier supplier) { - instrumentationSupplier = supplier; + if (supplier != null) { + instrumentationSupplier = supplier; + } return this; } @@ -36,13 +44,17 @@ public GraphQL build(GraphQLSchemaProvider schemaProvider) { } public GraphQL build(GraphQLSchema schema) { + return build(schema, instrumentationSupplier); + } + + public GraphQL build(GraphQLSchema schema, Supplier configuredInstrumentationSupplier) { ExecutionStrategyProvider executionStrategyProvider = executionStrategyProviderSupplier.get(); GraphQL.Builder builder = GraphQL.newGraphQL(schema) .queryExecutionStrategy(executionStrategyProvider.getQueryExecutionStrategy()) .mutationExecutionStrategy(executionStrategyProvider.getMutationExecutionStrategy()) .subscriptionExecutionStrategy(executionStrategyProvider.getSubscriptionExecutionStrategy()) .preparsedDocumentProvider(preparsedDocumentProviderSupplier.get()); - Instrumentation instrumentation = instrumentationSupplier.get(); + Instrumentation instrumentation = configuredInstrumentationSupplier.get(); builder.instrumentation(instrumentation); if (containsDispatchInstrumentation(instrumentation)) { builder.doNotAddDefaultInstrumentations(); diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java index 95182256..4fc8b70d 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/GraphQLBatchedInvocationInput.java @@ -1,8 +1,9 @@ package graphql.kickstart.execution.input; +import static java.util.stream.Collectors.toList; + +import graphql.ExecutionInput; import graphql.kickstart.execution.context.ContextSetting; -import graphql.kickstart.execution.input.GraphQLInvocationInput; -import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; import java.util.List; /** @@ -10,11 +11,17 @@ */ public interface GraphQLBatchedInvocationInput extends GraphQLInvocationInput { - /** - * @return each individual input in the batch, configured with a context. - */ - List getExecutionInputs(); + /** + * @return each individual input in the batch, configured with a context. + */ + List getInvocationInputs(); + + default List getExecutionInputs() { + return getInvocationInputs().stream() + .map(GraphQLSingleInvocationInput::getExecutionInput) + .collect(toList()); + } - ContextSetting getContextSetting(); + ContextSetting getContextSetting(); } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java index c6c24365..3cea428b 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerQueryBatchedInvocationInput.java @@ -15,11 +15,12 @@ */ @Getter public class PerQueryBatchedInvocationInput implements GraphQLBatchedInvocationInput { - private final List executionInputs; + + private final List invocationInputs; private final ContextSetting contextSetting; public PerQueryBatchedInvocationInput(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root, ContextSetting contextSetting) { - executionInputs = requests.stream() + invocationInputs = requests.stream() .map(request -> new GraphQLSingleInvocationInput(request, schema, contextSupplier.get(), root)).collect(Collectors.toList()); this.contextSetting = contextSetting; } diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java index d0f6876e..a50e0cdf 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/input/PerRequestBatchedInvocationInput.java @@ -15,13 +15,13 @@ @Getter public class PerRequestBatchedInvocationInput implements GraphQLBatchedInvocationInput { - private final List executionInputs; + private final List invocationInputs; private final ContextSetting contextSetting; public PerRequestBatchedInvocationInput(List requests, GraphQLSchema schema, Supplier contextSupplier, Object root, ContextSetting contextSetting) { GraphQLContext context = contextSupplier.get(); - executionInputs = requests.stream().map(request -> new GraphQLSingleInvocationInput(request, schema, context, root)) + invocationInputs = requests.stream().map(request -> new GraphQLSingleInvocationInput(request, schema, context, root)) .collect(Collectors.toList()); this.contextSetting = contextSetting; } diff --git a/graphql-java-servlet/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java index ef23c53f..66c9108f 100644 --- a/graphql-java-servlet/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java +++ b/graphql-java-servlet/src/test/groovy/graphql/servlet/TestBatchInputPreProcessor.java @@ -1,25 +1,25 @@ package graphql.servlet; +import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; import graphql.servlet.input.BatchInputPreProcessResult; import graphql.servlet.input.BatchInputPreProcessor; -import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class TestBatchInputPreProcessor implements BatchInputPreProcessor { - public static String BATCH_ERROR_MESSAGE = "Batch limit exceeded"; + public static String BATCH_ERROR_MESSAGE = "Batch limit exceeded"; - @Override - public BatchInputPreProcessResult preProcessBatch(GraphQLBatchedInvocationInput batchedInvocationInput, HttpServletRequest request, - HttpServletResponse response) { - BatchInputPreProcessResult preProcessResult; - if (batchedInvocationInput.getExecutionInputs().size() > 2) { - preProcessResult = new BatchInputPreProcessResult(400, BATCH_ERROR_MESSAGE); - } else { - preProcessResult = new BatchInputPreProcessResult(batchedInvocationInput); - } - return preProcessResult; + @Override + public BatchInputPreProcessResult preProcessBatch(GraphQLBatchedInvocationInput batchedInvocationInput, + HttpServletRequest request, + HttpServletResponse response) { + BatchInputPreProcessResult preProcessResult; + if (batchedInvocationInput.getExecutionInputs().size() > 2) { + preProcessResult = new BatchInputPreProcessResult(400, BATCH_ERROR_MESSAGE); + } else { + preProcessResult = new BatchInputPreProcessResult(batchedInvocationInput); } + return preProcessResult; + } } From 497adf0d157480b2e3412877449319a27f02230c Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Mon, 25 Nov 2019 19:51:54 +0100 Subject: [PATCH 23/26] Build GraphQL on the fly after all --- graphql-java-servlet/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql-java-servlet/build.gradle b/graphql-java-servlet/build.gradle index 17dd8726..b7bd4fc6 100644 --- a/graphql-java-servlet/build.gradle +++ b/graphql-java-servlet/build.gradle @@ -14,7 +14,7 @@ jar { } dependencies { - implementation(project(':graphql-java-kickstart')) + api(project(':graphql-java-kickstart')) // Useful utilities compile 'com.google.guava:guava:24.1.1-jre' From f2bb4e0aefc72951587d795636dbd0a3a48ecab7 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Tue, 26 Nov 2019 09:14:29 +0100 Subject: [PATCH 24/26] Limit use of GraphQLQueryInvoker --- .../execution/GraphQLQueryInvoker.java | 96 ++----------------- .../servlet/AbstractGraphQLHttpServlet.java | 17 ++-- .../graphql/servlet/GraphQLConfiguration.java | 12 ++- .../servlet/HttpRequestHandlerImpl.java | 10 +- .../input/GraphQLInvocationInputFactory.java | 10 -- 5 files changed, 32 insertions(+), 113 deletions(-) diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java index 1a01eb8f..1d3f6a59 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLQueryInvoker.java @@ -1,29 +1,16 @@ package graphql.kickstart.execution; -import graphql.ExecutionInput; -import graphql.ExecutionResult; -import graphql.GraphQL; import graphql.execution.instrumentation.ChainedInstrumentation; import graphql.execution.instrumentation.Instrumentation; import graphql.execution.instrumentation.SimpleInstrumentation; -import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation; import graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentationOptions; import graphql.execution.preparsed.NoOpPreparsedDocumentProvider; import graphql.execution.preparsed.PreparsedDocumentProvider; -import graphql.schema.GraphQLSchema; import graphql.kickstart.execution.config.DefaultExecutionStrategyProvider; import graphql.kickstart.execution.config.ExecutionStrategyProvider; -import graphql.kickstart.execution.context.ContextSetting; -import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; -import graphql.kickstart.execution.input.GraphQLInvocationInput; -import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; -import java.security.AccessController; -import java.security.PrivilegedAction; +import graphql.kickstart.execution.config.GraphQLBuilder; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; -import java.util.stream.Collectors; -import javax.security.auth.Subject; /** * @author Andrew Potter @@ -49,81 +36,12 @@ public static Builder newBuilder() { return new Builder(); } - public GraphQLQueryResult query(GraphQLInvocationInput invocationInput) { - if (invocationInput instanceof GraphQLSingleInvocationInput) { - return GraphQLQueryResult.create(query((GraphQLSingleInvocationInput) invocationInput)); - } - GraphQLBatchedInvocationInput batchedInvocationInput = (GraphQLBatchedInvocationInput) invocationInput; - return GraphQLQueryResult.create(query(batchedInvocationInput.getInvocationInputs(), batchedInvocationInput.getContextSetting())); - } - - public ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput) { - return queryAsync(singleInvocationInput, getInstrumentation).join(); - } - - private CompletableFuture queryAsync(GraphQLSingleInvocationInput singleInvocationInput, - Supplier configuredInstrumentation) { - return query(singleInvocationInput, configuredInstrumentation, singleInvocationInput.getExecutionInput()); - } - - public List query(List batchedInvocationInput, - ContextSetting contextSetting) { - List executionIds = batchedInvocationInput.stream() - .map(GraphQLSingleInvocationInput::getExecutionInput) - .collect(Collectors.toList()); - Supplier configuredInstrumentation = contextSetting - .configureInstrumentationForContext(getInstrumentation, executionIds, optionsSupplier.get()); - return batchedInvocationInput.stream() - .map(input -> this.queryAsync(input, configuredInstrumentation)) - //We want eager eval - .collect(Collectors.toList()) - .stream() - .map(CompletableFuture::join) - .collect(Collectors.toList()); - } - - private GraphQL newGraphQL(GraphQLSchema schema, Supplier configuredInstrumentation) { - ExecutionStrategyProvider executionStrategyProvider = getExecutionStrategyProvider.get(); - GraphQL.Builder builder = GraphQL.newGraphQL(schema) - .queryExecutionStrategy(executionStrategyProvider.getQueryExecutionStrategy()) - .mutationExecutionStrategy(executionStrategyProvider.getMutationExecutionStrategy()) - .subscriptionExecutionStrategy(executionStrategyProvider.getSubscriptionExecutionStrategy()) - .preparsedDocumentProvider(getPreparsedDocumentProvider.get()); - Instrumentation instrumentation = configuredInstrumentation.get(); - builder.instrumentation(instrumentation); - if (containsDispatchInstrumentation(instrumentation)) { - builder.doNotAddDefaultInstrumentations(); - } - return builder.build(); - } - - private boolean containsDispatchInstrumentation(Instrumentation instrumentation) { - if (instrumentation instanceof ChainedInstrumentation) { - return ((ChainedInstrumentation) instrumentation).getInstrumentations().stream() - .anyMatch(this::containsDispatchInstrumentation); - } - return instrumentation instanceof DataLoaderDispatcherInstrumentation; - } - - private CompletableFuture query(GraphQLSingleInvocationInput invocationInput, - Supplier configuredInstrumentation, ExecutionInput executionInput) { - if (Subject.getSubject(AccessController.getContext()) == null && invocationInput.getSubject().isPresent()) { - return Subject - .doAs(invocationInput.getSubject().get(), (PrivilegedAction>) () -> { - try { - return query(invocationInput.getSchema(), executionInput, configuredInstrumentation); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - return query(invocationInput.getSchema(), executionInput, configuredInstrumentation); - } - - private CompletableFuture query(GraphQLSchema schema, ExecutionInput executionInput, - Supplier configuredInstrumentation) { - return newGraphQL(schema, configuredInstrumentation).executeAsync(executionInput); + public GraphQLInvoker toGraphQLInvoker() { + GraphQLBuilder graphQLBuilder = new GraphQLBuilder() + .executionStrategyProvider(getExecutionStrategyProvider) + .instrumentation(getInstrumentation) + .preparsedDocumentProvider(getPreparsedDocumentProvider); + return new GraphQLInvoker(graphQLBuilder, new BatchedDataLoaderGraphQLBuilder(optionsSupplier)); } public static class Builder { diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java index 2e23d217..19523bde 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java @@ -1,14 +1,17 @@ package graphql.servlet; -import graphql.schema.GraphQLFieldDefinition; -import graphql.servlet.core.GraphQLMBean; +import static graphql.kickstart.execution.GraphQLRequest.createQueryOnlyRequest; + +import graphql.ExecutionResult; import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.servlet.core.GraphQLServletListener; import graphql.kickstart.execution.GraphQLRequest; +import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import graphql.schema.GraphQLFieldDefinition; +import graphql.servlet.core.GraphQLMBean; +import graphql.servlet.core.GraphQLServletListener; import graphql.servlet.input.GraphQLInvocationInputFactory; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -115,8 +118,10 @@ public String[] getMutations() { @Override public String executeQuery(String query) { try { - return configuration.getObjectMapper().serializeResultAsJson(configuration.getQueryInvoker() - .query(configuration.getInvocationInputFactory().create(new GraphQLRequest(query, new HashMap<>(), null)))); + GraphQLRequest graphQLRequest = createQueryOnlyRequest(query); + GraphQLSingleInvocationInput invocationInput = configuration.getInvocationInputFactory().create(graphQLRequest); + ExecutionResult result = configuration.getGraphQLInvoker().query(invocationInput).getResult(); + return configuration.getObjectMapper().serializeResultAsJson(result); } catch (Exception e) { return e.getMessage(); } diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java index a0fcb711..1f7a0530 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/GraphQLConfiguration.java @@ -1,5 +1,6 @@ package graphql.servlet; +import graphql.kickstart.execution.GraphQLInvoker; import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.kickstart.execution.context.ContextSetting; @@ -24,6 +25,7 @@ public class GraphQLConfiguration { private final GraphQLInvocationInputFactory invocationInputFactory; private final Supplier batchInputPreProcessor; private final GraphQLQueryInvoker queryInvoker; + private final GraphQLInvoker graphQLInvoker; private final GraphQLObjectMapper objectMapper; private final List listeners; private final boolean asyncServletModeEnabled; @@ -31,12 +33,14 @@ public class GraphQLConfiguration { private final long subscriptionTimeout; private final ContextSetting contextSetting; - private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, + private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, + GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout, ContextSetting contextSetting, Supplier batchInputPreProcessor) { this.invocationInputFactory = invocationInputFactory; this.queryInvoker = queryInvoker; + this.graphQLInvoker = queryInvoker.toGraphQLInvoker(); this.objectMapper = objectMapper; this.listeners = listeners; this.asyncServletModeEnabled = asyncServletModeEnabled; @@ -62,8 +66,10 @@ public GraphQLInvocationInputFactory getInvocationInputFactory() { return invocationInputFactory; } - public GraphQLQueryInvoker getQueryInvoker() { - return queryInvoker; + public GraphQLQueryInvoker getQueryInvoker() { return queryInvoker; } + + public GraphQLInvoker getGraphQLInvoker() { + return graphQLInvoker; } public GraphQLObjectMapper getObjectMapper() { diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java b/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java index caa08930..170d2295 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/HttpRequestHandlerImpl.java @@ -3,8 +3,8 @@ import static graphql.servlet.QueryResponseWriter.createWriter; import graphql.GraphQLException; +import graphql.kickstart.execution.GraphQLInvoker; import graphql.kickstart.execution.GraphQLQueryResult; -import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.servlet.input.BatchInputPreProcessResult; import graphql.servlet.input.BatchInputPreProcessor; import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput; @@ -19,11 +19,11 @@ class HttpRequestHandlerImpl implements HttpRequestHandler { private final GraphQLConfiguration configuration; - private final GraphQLQueryInvoker queryInvoker; + private final GraphQLInvoker graphQLInvoker; HttpRequestHandlerImpl(GraphQLConfiguration configuration) { this.configuration = configuration; - queryInvoker = configuration.getQueryInvoker(); + graphQLInvoker = configuration.getGraphQLInvoker(); } @Override @@ -65,7 +65,7 @@ private void execute(GraphQLInvocationInput invocationInput, HttpServletRequest private GraphQLQueryResult invoke(GraphQLInvocationInput invocationInput, HttpServletRequest request, HttpServletResponse response) { if (invocationInput instanceof GraphQLSingleInvocationInput) { - return queryInvoker.query(invocationInput); + return graphQLInvoker.query(invocationInput); } return invokeBatched((GraphQLBatchedInvocationInput) invocationInput, request, response); } @@ -76,7 +76,7 @@ private GraphQLQueryResult invokeBatched(GraphQLBatchedInvocationInput batchedIn BatchInputPreProcessor preprocessor = configuration.getBatchInputPreProcessor(); BatchInputPreProcessResult result = preprocessor.preProcessBatch(batchedInvocationInput, request, response); if (result.isExecutable()) { - return queryInvoker.query(result.getBatchedInvocationInput()); + return graphQLInvoker.query(result.getBatchedInvocationInput()); } return GraphQLQueryResult.createError(result.getStatusCode(), result.getStatusMessage()); diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java index 32b2a90f..cb9dc80d 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/input/GraphQLInvocationInputFactory.java @@ -108,16 +108,6 @@ private GraphQLBatchedInvocationInput create(ContextSetting contextSetting, List ); } - public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, Session session) { - HandshakeRequest request = (HandshakeRequest) session.getUserProperties().get(HandshakeRequest.class.getName()); - return new GraphQLSingleInvocationInput( - graphQLRequest, - schemaProviderSupplier.get().getSchema(request), - contextBuilderSupplier.get().build(session, request), - rootObjectBuilderSupplier.get().build(request) - ); - } - @Override public GraphQLSingleInvocationInput create(GraphQLRequest graphQLRequest, SubscriptionSession session) { HandshakeRequest request = (HandshakeRequest) session.getUserProperties().get(HandshakeRequest.class.getName()); From eae8120ada70d31861470762c2611672a0e5d60b Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Thu, 28 Nov 2019 19:38:31 +0100 Subject: [PATCH 25/26] Use gradle 5.6.4 instead of 6.0.1 --- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../servlet/SimpleGraphQLHttpServlet.java | 231 +++++++++--------- 3 files changed, 119 insertions(+), 116 deletions(-) diff --git a/gradle.properties b/gradle.properties index 425ddf04..884be6c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ LIB_JACKSON_VER = 2.10.0 SOURCE_COMPATIBILITY = 1.8 TARGET_COMPATIBILITY = 1.8 -GRADLE_WRAPPER_VER = 6.0.1 +GRADLE_WRAPPER_VER = 5.6.4 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5f8ff30a..533c49c2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Thu Nov 14 18:53:34 CET 2019 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java index 84936aa9..009642df 100644 --- a/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java @@ -1,13 +1,11 @@ package graphql.servlet; -import graphql.schema.GraphQLSchema; import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; -import graphql.kickstart.execution.config.GraphQLSchemaProvider; +import graphql.schema.GraphQLSchema; import graphql.servlet.config.GraphQLSchemaServletProvider; import graphql.servlet.core.GraphQLServletListener; import graphql.servlet.input.GraphQLInvocationInputFactory; - import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -17,128 +15,133 @@ */ public class SimpleGraphQLHttpServlet extends AbstractGraphQLHttpServlet { - private GraphQLConfiguration configuration; - - public SimpleGraphQLHttpServlet() { - } - - /** - * @deprecated use {@link GraphQLHttpServlet} instead - */ - @Deprecated - public SimpleGraphQLHttpServlet(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, List listeners, boolean asyncServletMode) { - super(listeners); - this.configuration = GraphQLConfiguration.with(invocationInputFactory) - .with(queryInvoker) - .with(graphQLObjectMapper) - .with(listeners != null ? listeners : new ArrayList<>()) - .with(asyncServletMode) - .build(); - } - - /** - * @deprecated use {@link GraphQLHttpServlet} instead - */ - @Deprecated - public SimpleGraphQLHttpServlet(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, List listeners, boolean asyncServletMode, long subscriptionTimeout) { - super(listeners); - this.configuration = GraphQLConfiguration.with(invocationInputFactory) - .with(queryInvoker) - .with(graphQLObjectMapper) - .with(listeners != null ? listeners : new ArrayList<>()) - .with(asyncServletMode) - .with(subscriptionTimeout) - .build(); + private GraphQLConfiguration configuration; + + public SimpleGraphQLHttpServlet() { + } + + /** + * @deprecated use {@link GraphQLHttpServlet} instead + */ + @Deprecated + public SimpleGraphQLHttpServlet(GraphQLInvocationInputFactory invocationInputFactory, + GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, List listeners, + boolean asyncServletMode) { + super(listeners); + this.configuration = GraphQLConfiguration.with(invocationInputFactory) + .with(queryInvoker) + .with(graphQLObjectMapper) + .with(listeners != null ? listeners : new ArrayList<>()) + .with(asyncServletMode) + .build(); + } + + /** + * @deprecated use {@link GraphQLHttpServlet} instead + */ + @Deprecated + public SimpleGraphQLHttpServlet(GraphQLInvocationInputFactory invocationInputFactory, + GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, List listeners, + boolean asyncServletMode, long subscriptionTimeout) { + super(listeners); + this.configuration = GraphQLConfiguration.with(invocationInputFactory) + .with(queryInvoker) + .with(graphQLObjectMapper) + .with(listeners != null ? listeners : new ArrayList<>()) + .with(asyncServletMode) + .with(subscriptionTimeout) + .build(); + } + + private SimpleGraphQLHttpServlet(GraphQLConfiguration configuration) { + this.configuration = Objects.requireNonNull(configuration, "configuration is required"); + } + + public static Builder newBuilder(GraphQLSchema schema) { + return new Builder(GraphQLInvocationInputFactory.newBuilder(schema).build()); + } + + public static Builder newBuilder(GraphQLSchemaServletProvider schemaProvider) { + return new Builder(GraphQLInvocationInputFactory.newBuilder(schemaProvider).build()); + } + + public static Builder newBuilder(GraphQLInvocationInputFactory invocationInputFactory) { + return new Builder(invocationInputFactory); + } + + @Override + protected GraphQLConfiguration getConfiguration() { + return configuration; + } + + @Override + protected GraphQLQueryInvoker getQueryInvoker() { + return configuration.getQueryInvoker(); + } + + @Override + protected GraphQLInvocationInputFactory getInvocationInputFactory() { + return configuration.getInvocationInputFactory(); + } + + @Override + protected GraphQLObjectMapper getGraphQLObjectMapper() { + return configuration.getObjectMapper(); + } + + @Override + protected boolean isAsyncServletMode() { + return configuration.isAsyncServletModeEnabled(); + } + + public static class Builder { + + private final GraphQLInvocationInputFactory invocationInputFactory; + private GraphQLQueryInvoker queryInvoker = GraphQLQueryInvoker.newBuilder().build(); + private GraphQLObjectMapper graphQLObjectMapper = GraphQLObjectMapper.newBuilder().build(); + private List listeners; + private boolean asyncServletMode; + private long subscriptionTimeout; + + Builder(GraphQLInvocationInputFactory invocationInputFactory) { + this.invocationInputFactory = invocationInputFactory; } - private SimpleGraphQLHttpServlet(GraphQLConfiguration configuration) { - this.configuration = Objects.requireNonNull(configuration, "configuration is required"); + public Builder withQueryInvoker(GraphQLQueryInvoker queryInvoker) { + this.queryInvoker = queryInvoker; + return this; } - @Override - protected GraphQLConfiguration getConfiguration() { - return configuration; + public Builder withObjectMapper(GraphQLObjectMapper objectMapper) { + this.graphQLObjectMapper = objectMapper; + return this; } - @Override - protected GraphQLQueryInvoker getQueryInvoker() { - return configuration.getQueryInvoker(); + public Builder withAsyncServletMode(boolean asyncServletMode) { + this.asyncServletMode = asyncServletMode; + return this; } - @Override - protected GraphQLInvocationInputFactory getInvocationInputFactory() { - return configuration.getInvocationInputFactory(); + public Builder withListeners(List listeners) { + this.listeners = listeners; + return this; } - @Override - protected GraphQLObjectMapper getGraphQLObjectMapper() { - return configuration.getObjectMapper(); + public Builder withSubscriptionTimeout(long subscriptionTimeout) { + this.subscriptionTimeout = subscriptionTimeout; + return this; } - @Override - protected boolean isAsyncServletMode() { - return configuration.isAsyncServletModeEnabled(); - } - - public static Builder newBuilder(GraphQLSchema schema) { - return new Builder(GraphQLInvocationInputFactory.newBuilder(schema).build()); - } - - public static Builder newBuilder(GraphQLSchemaServletProvider schemaProvider) { - return new Builder(GraphQLInvocationInputFactory.newBuilder(schemaProvider).build()); - } - - public static Builder newBuilder(GraphQLInvocationInputFactory invocationInputFactory) { - return new Builder(invocationInputFactory); - } - - public static class Builder { - private final GraphQLInvocationInputFactory invocationInputFactory; - private GraphQLQueryInvoker queryInvoker = GraphQLQueryInvoker.newBuilder().build(); - private GraphQLObjectMapper graphQLObjectMapper = GraphQLObjectMapper.newBuilder().build(); - private List listeners; - private boolean asyncServletMode; - private long subscriptionTimeout; - - Builder(GraphQLInvocationInputFactory invocationInputFactory) { - this.invocationInputFactory = invocationInputFactory; - } - - public Builder withQueryInvoker(GraphQLQueryInvoker queryInvoker) { - this.queryInvoker = queryInvoker; - return this; - } - - public Builder withObjectMapper(GraphQLObjectMapper objectMapper) { - this.graphQLObjectMapper = objectMapper; - return this; - } - - public Builder withAsyncServletMode(boolean asyncServletMode) { - this.asyncServletMode = asyncServletMode; - return this; - } - - public Builder withListeners(List listeners) { - this.listeners = listeners; - return this; - } - - public Builder withSubscriptionTimeout(long subscriptionTimeout) { - this.subscriptionTimeout = subscriptionTimeout; - return this; - } - - @Deprecated - public SimpleGraphQLHttpServlet build() { - GraphQLConfiguration configuration = GraphQLConfiguration.with(invocationInputFactory) - .with(queryInvoker) - .with(graphQLObjectMapper) - .with(listeners != null ? listeners : new ArrayList<>()) - .with(asyncServletMode) - .with(subscriptionTimeout) - .build(); - return new SimpleGraphQLHttpServlet(configuration); - } + @Deprecated + public SimpleGraphQLHttpServlet build() { + GraphQLConfiguration configuration = GraphQLConfiguration.with(invocationInputFactory) + .with(queryInvoker) + .with(graphQLObjectMapper) + .with(listeners != null ? listeners : new ArrayList<>()) + .with(asyncServletMode) + .with(subscriptionTimeout) + .build(); + return new SimpleGraphQLHttpServlet(configuration); } + } } From 1dd834e639219c3a46d9c2586b1ae7e08500bb6d Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Thu, 28 Nov 2019 20:04:07 +0100 Subject: [PATCH 26/26] Fix batching query --- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../main/java/graphql/kickstart/execution/GraphQLInvoker.java | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 884be6c9..425ddf04 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,4 @@ LIB_JACKSON_VER = 2.10.0 SOURCE_COMPATIBILITY = 1.8 TARGET_COMPATIBILITY = 1.8 -GRADLE_WRAPPER_VER = 5.6.4 +GRADLE_WRAPPER_VER = 6.0.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 533c49c2..5f8ff30a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Thu Nov 14 18:53:34 CET 2019 -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java index a3a0cfb1..27e2f08e 100644 --- a/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java +++ b/graphql-java-kickstart/src/main/java/graphql/kickstart/execution/GraphQLInvoker.java @@ -42,6 +42,8 @@ private List query(GraphQLBatchedInvocationInput batchedInvocat GraphQL graphQL = batchedDataLoaderGraphQLBuilder.newGraphQL(batchedInvocationInput, graphQLBuilder); return batchedInvocationInput.getExecutionInputs().stream() .map(executionInput -> proxy.executeAsync(graphQL, executionInput)) + .collect(toList()) + .stream() .map(CompletableFuture::join) .collect(toList()); }