Skip to content

Commit

Permalink
Merge pull request #149 from jmccaull/batch_handling
Browse files Browse the repository at this point in the history
Upgrades to customize batch handling
  • Loading branch information
oliemansm committed Feb 26, 2019
2 parents 1e9eacd + 240f81b commit 0217361
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 60 deletions.
27 changes: 14 additions & 13 deletions src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements
@Deprecated
protected abstract GraphQLObjectMapper getGraphQLObjectMapper();

/**
* @deprecated override {@link #getConfiguration()} instead
*/
@Deprecated
protected abstract GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory();

/**
* @deprecated override {@link #getConfiguration()} instead
*/
Expand All @@ -85,6 +91,7 @@ protected GraphQLConfiguration getConfiguration() {
.with(getGraphQLObjectMapper())
.with(isAsyncServletMode())
.with(listeners)
.with(getBatchExecutionHandlerFactory())
.build();
}

Expand Down Expand Up @@ -113,6 +120,7 @@ public void init(ServletConfig servletConfig) {
GraphQLInvocationInputFactory invocationInputFactory = configuration.getInvocationInputFactory();
GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper();
GraphQLQueryInvoker queryInvoker = configuration.getQueryInvoker();
GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory = configuration.getBatchExecutionHandlerFactory();

String path = request.getPathInfo();
if (path == null) {
Expand All @@ -125,7 +133,7 @@ public void init(ServletConfig servletConfig) {
if (query != null) {

if (isBatchedQuery(query)) {
queryBatched(queryInvoker, graphQLObjectMapper, invocationInputFactory.createReadOnly(graphQLObjectMapper.readBatchedGraphQLRequest(query), request, response), response);
queryBatched(batchExecutionHandlerFactory, queryInvoker, graphQLObjectMapper, invocationInputFactory.createReadOnly(graphQLObjectMapper.readBatchedGraphQLRequest(query), request, response), response);
} else {
final Map<String, Object> variables = new HashMap<>();
if (request.getParameter("variables") != null) {
Expand All @@ -147,6 +155,7 @@ public void init(ServletConfig servletConfig) {
GraphQLInvocationInputFactory invocationInputFactory = configuration.getInvocationInputFactory();
GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper();
GraphQLQueryInvoker queryInvoker = configuration.getQueryInvoker();
GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory = configuration.getBatchExecutionHandlerFactory();

try {
if (APPLICATION_GRAPHQL.equals(request.getContentType())) {
Expand Down Expand Up @@ -181,7 +190,7 @@ public void init(ServletConfig servletConfig) {
GraphQLBatchedInvocationInput invocationInput =
invocationInputFactory.create(graphQLRequests, request, response);
invocationInput.getContext().setParts(fileItems);
queryBatched(queryInvoker, graphQLObjectMapper, invocationInput, response);
queryBatched(batchExecutionHandlerFactory, queryInvoker, graphQLObjectMapper, invocationInput, response);
return;
} else {
GraphQLRequest graphQLRequest;
Expand All @@ -207,7 +216,7 @@ public void init(ServletConfig servletConfig) {
InputStream inputStream = asMarkableInputStream(request.getInputStream());

if (isBatchedQuery(inputStream)) {
queryBatched(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readBatchedGraphQLRequest(inputStream), request, response), response);
queryBatched(batchExecutionHandlerFactory, queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readBatchedGraphQLRequest(inputStream), request, response), response);
} else {
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readGraphQLRequest(inputStream), request, response), response);
}
Expand Down Expand Up @@ -364,21 +373,13 @@ private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQL
}
}

private void queryBatched(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLBatchedInvocationInput invocationInput, HttpServletResponse resp) throws Exception {
private void queryBatched(GraphQLBatchExecutionHandlerFactory batchInputHandlerFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLBatchedInvocationInput invocationInput, HttpServletResponse resp) throws Exception {
resp.setContentType(APPLICATION_JSON_UTF8);
resp.setStatus(STATUS_OK);

Writer respWriter = resp.getWriter();
respWriter.write('[');

queryInvoker.query(invocationInput, (result, hasNext) -> {
respWriter.write(graphQLObjectMapper.serializeResultAsJson(result));
if (hasNext) {
respWriter.write(',');
}
});

respWriter.write(']');
queryInvoker.query(invocationInput, batchInputHandlerFactory.getBatchHandler(respWriter, graphQLObjectMapper));
}

private <R> List<R> runListeners(Function<? super GraphQLServletListener, R> action) {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/graphql/servlet/BatchExecutionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package graphql.servlet;

import graphql.ExecutionInput;
import graphql.ExecutionResult;

import java.util.function.BiFunction;

/**
* @author Andrew Potter
*/
public interface BatchExecutionHandler {
/**
* Allows separating the logic of handling batch queries from how each individual query is resolved.
* @param batchedInvocationInput the batch query input
* @param queryFunction Function to produce query results.
*/
void handleBatch(GraphQLBatchedInvocationInput batchedInvocationInput, BiFunction<GraphQLInvocationInput, ExecutionInput, ExecutionResult> queryFunction);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package graphql.servlet;

import graphql.ExecutionInput;
import graphql.ExecutionResult;

import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.function.BiFunction;

public class DefaultGraphQLBatchExecutionHandlerFactory implements GraphQLBatchExecutionHandlerFactory {
@Override
public BatchExecutionHandler getBatchHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper) {
return new DefaultGraphQLBatchExecutionHandler(respWriter, graphQLObjectMapper);
}

private class DefaultGraphQLBatchExecutionHandler implements BatchExecutionHandler {

private final Writer respWriter;

private final GraphQLObjectMapper graphQLObjectMapper;

private DefaultGraphQLBatchExecutionHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper) {
this.respWriter = respWriter;
this.graphQLObjectMapper = graphQLObjectMapper;
}

@Override
public void handleBatch(GraphQLBatchedInvocationInput batchedInvocationInput, BiFunction<GraphQLInvocationInput, ExecutionInput,
ExecutionResult> queryFunction) {
Iterator<ExecutionInput> executionInputIterator = batchedInvocationInput.getExecutionInputs().iterator();
try {
respWriter.write("[");
while (executionInputIterator.hasNext()) {
ExecutionResult result = queryFunction.apply(batchedInvocationInput, executionInputIterator.next());
respWriter.write(graphQLObjectMapper.serializeResultAsJson(result));
if (executionInputIterator.hasNext()) {
respWriter.write(",");
}
}
respWriter.write("]");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
5 changes: 5 additions & 0 deletions src/main/java/graphql/servlet/DefaultGraphQLServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
return GraphQLObjectMapper.newBuilder().build();
}

@Override
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
return null;
}

@Override
protected boolean isAsyncServletMode() {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package graphql.servlet;

import java.io.Writer;

/**
* Interface to allow customization of how batched queries are handled.
*/
public interface GraphQLBatchExecutionHandlerFactory {
/**
* Produces an BatchExecutionHandler instance for a specific response. Can maintain state across each request within a batch.
* @param respWriter to send the response back
* @param graphQLObjectMapper to serialize results.
* @return a handler instance
*/
BatchExecutionHandler getBatchHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper);
}
17 changes: 16 additions & 1 deletion src/main/java/graphql/servlet/GraphQLConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class GraphQLConfiguration {
private GraphQLInvocationInputFactory invocationInputFactory;
private GraphQLQueryInvoker queryInvoker;
private GraphQLObjectMapper objectMapper;
private GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory;
private List<GraphQLServletListener> listeners;
private boolean asyncServletModeEnabled;
private Executor asyncExecutor;
Expand All @@ -30,10 +31,11 @@ public static GraphQLConfiguration.Builder with(GraphQLInvocationInputFactory in
return new Builder(invocationInputFactory);
}

private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout) {
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout) {
this.invocationInputFactory = invocationInputFactory;
this.queryInvoker = queryInvoker;
this.objectMapper = objectMapper;
this.batchExecutionHandlerFactory = batchExecutionHandlerFactory;
this.listeners = listeners;
this.asyncServletModeEnabled = asyncServletModeEnabled;
this.asyncExecutor = asyncExecutor;
Expand All @@ -52,6 +54,10 @@ public GraphQLObjectMapper getObjectMapper() {
return objectMapper;
}

public GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
return batchExecutionHandlerFactory;
}

public List<GraphQLServletListener> getListeners() {
return new ArrayList<>(listeners);
}
Expand Down Expand Up @@ -82,6 +88,7 @@ public static class Builder {
private GraphQLInvocationInputFactory invocationInputFactory;
private GraphQLQueryInvoker queryInvoker = GraphQLQueryInvoker.newBuilder().build();
private GraphQLObjectMapper objectMapper = GraphQLObjectMapper.newBuilder().build();
private GraphQLBatchExecutionHandlerFactory graphQLBatchExecutionHandlerFactory = new DefaultGraphQLBatchExecutionHandlerFactory();
private List<GraphQLServletListener> listeners = new ArrayList<>();
private boolean asyncServletModeEnabled = false;
private Executor asyncExecutor = Executors.newCachedThreadPool(new GraphQLThreadFactory());
Expand Down Expand Up @@ -109,6 +116,13 @@ public Builder with(GraphQLObjectMapper objectMapper) {
return this;
}

public Builder with(GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory) {
if (batchExecutionHandlerFactory != null) {
this.graphQLBatchExecutionHandlerFactory = batchExecutionHandlerFactory;
}
return this;
}

public Builder with(List<GraphQLServletListener> listeners) {
if (listeners != null) {
this.listeners = listeners;
Expand Down Expand Up @@ -148,6 +162,7 @@ public GraphQLConfiguration build() {
this.invocationInputFactory != null ? this.invocationInputFactory : invocationInputFactoryBuilder.build(),
queryInvoker,
objectMapper,
graphQLBatchExecutionHandlerFactory,
listeners,
asyncServletModeEnabled,
asyncExecutor,
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/graphql/servlet/GraphQLHttpServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

import graphql.schema.GraphQLSchema;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
* @author Michiel Oliemans
*/
Expand Down Expand Up @@ -37,6 +33,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
throw new UnsupportedOperationException();
}

@Override
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
throw new UnsupportedOperationException();
}

@Override
protected boolean isAsyncServletMode() {
throw new UnsupportedOperationException();
Expand Down
11 changes: 2 additions & 9 deletions src/main/java/graphql/servlet/GraphQLQueryInvoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@
import graphql.execution.preparsed.NoOpPreparsedDocumentProvider;
import graphql.execution.preparsed.PreparsedDocumentProvider;
import graphql.schema.GraphQLSchema;
import graphql.servlet.internal.ExecutionResultHandler;

import javax.security.auth.Subject;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;

Expand All @@ -42,13 +40,8 @@ public ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput)
return query(singleInvocationInput, singleInvocationInput.getExecutionInput());
}

public void query(GraphQLBatchedInvocationInput batchedInvocationInput, ExecutionResultHandler executionResultHandler) {
Iterator<ExecutionInput> executionInputIterator = batchedInvocationInput.getExecutionInputs().iterator();

while (executionInputIterator.hasNext()) {
ExecutionResult result = query(batchedInvocationInput, executionInputIterator.next());
executionResultHandler.accept(result, executionInputIterator.hasNext());
}
public void query(GraphQLBatchedInvocationInput batchedInvocationInput, BatchExecutionHandler batchExecutionHandler) {
batchExecutionHandler.handleBatch(batchedInvocationInput, this::query);
}

private GraphQL newGraphQL(GraphQLSchema schema, Object context) {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class OsgiGraphQLHttpServlet extends AbstractGraphQLHttpServlet {
private InstrumentationProvider instrumentationProvider = new NoOpInstrumentationProvider();
private GraphQLErrorHandler errorHandler = new DefaultGraphQLErrorHandler();
private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE;
private GraphQLBatchExecutionHandlerFactory executionResultHandlerFactory = new DefaultGraphQLBatchExecutionHandlerFactory();

private GraphQLSchemaProvider schemaProvider;

Expand Down Expand Up @@ -84,6 +85,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
return graphQLObjectMapper;
}

@Override
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
return executionResultHandlerFactory;
}

@Override
protected boolean isAsyncServletMode() {
return false;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
return configuration.getObjectMapper();
}

@Override
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
return configuration.getBatchExecutionHandlerFactory();
}

@Override
protected boolean isAsyncServletMode() {
return configuration.isAsyncServletModeEnabled();
Expand Down
23 changes: 0 additions & 23 deletions src/main/java/graphql/servlet/internal/ExecutionResultHandler.java

This file was deleted.

Loading

0 comments on commit 0217361

Please sign in to comment.