Skip to content

Commit 0217361

Browse files
authored
Merge pull request #149 from jmccaull/batch_handling
Upgrades to customize batch handling
2 parents 1e9eacd + 240f81b commit 0217361

14 files changed

+270
-60
lines changed

src/main/java/graphql/servlet/AbstractGraphQLHttpServlet.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements
7373
@Deprecated
7474
protected abstract GraphQLObjectMapper getGraphQLObjectMapper();
7575

76+
/**
77+
* @deprecated override {@link #getConfiguration()} instead
78+
*/
79+
@Deprecated
80+
protected abstract GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory();
81+
7682
/**
7783
* @deprecated override {@link #getConfiguration()} instead
7884
*/
@@ -85,6 +91,7 @@ protected GraphQLConfiguration getConfiguration() {
8591
.with(getGraphQLObjectMapper())
8692
.with(isAsyncServletMode())
8793
.with(listeners)
94+
.with(getBatchExecutionHandlerFactory())
8895
.build();
8996
}
9097

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

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

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

151160
try {
152161
if (APPLICATION_GRAPHQL.equals(request.getContentType())) {
@@ -181,7 +190,7 @@ public void init(ServletConfig servletConfig) {
181190
GraphQLBatchedInvocationInput invocationInput =
182191
invocationInputFactory.create(graphQLRequests, request, response);
183192
invocationInput.getContext().setParts(fileItems);
184-
queryBatched(queryInvoker, graphQLObjectMapper, invocationInput, response);
193+
queryBatched(batchExecutionHandlerFactory, queryInvoker, graphQLObjectMapper, invocationInput, response);
185194
return;
186195
} else {
187196
GraphQLRequest graphQLRequest;
@@ -207,7 +216,7 @@ public void init(ServletConfig servletConfig) {
207216
InputStream inputStream = asMarkableInputStream(request.getInputStream());
208217

209218
if (isBatchedQuery(inputStream)) {
210-
queryBatched(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readBatchedGraphQLRequest(inputStream), request, response), response);
219+
queryBatched(batchExecutionHandlerFactory, queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readBatchedGraphQLRequest(inputStream), request, response), response);
211220
} else {
212221
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readGraphQLRequest(inputStream), request, response), response);
213222
}
@@ -364,21 +373,13 @@ private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQL
364373
}
365374
}
366375

367-
private void queryBatched(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLBatchedInvocationInput invocationInput, HttpServletResponse resp) throws Exception {
376+
private void queryBatched(GraphQLBatchExecutionHandlerFactory batchInputHandlerFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLBatchedInvocationInput invocationInput, HttpServletResponse resp) throws Exception {
368377
resp.setContentType(APPLICATION_JSON_UTF8);
369378
resp.setStatus(STATUS_OK);
370379

371380
Writer respWriter = resp.getWriter();
372-
respWriter.write('[');
373-
374-
queryInvoker.query(invocationInput, (result, hasNext) -> {
375-
respWriter.write(graphQLObjectMapper.serializeResultAsJson(result));
376-
if (hasNext) {
377-
respWriter.write(',');
378-
}
379-
});
380381

381-
respWriter.write(']');
382+
queryInvoker.query(invocationInput, batchInputHandlerFactory.getBatchHandler(respWriter, graphQLObjectMapper));
382383
}
383384

384385
private <R> List<R> runListeners(Function<? super GraphQLServletListener, R> action) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package graphql.servlet;
2+
3+
import graphql.ExecutionInput;
4+
import graphql.ExecutionResult;
5+
6+
import java.util.function.BiFunction;
7+
8+
/**
9+
* @author Andrew Potter
10+
*/
11+
public interface BatchExecutionHandler {
12+
/**
13+
* Allows separating the logic of handling batch queries from how each individual query is resolved.
14+
* @param batchedInvocationInput the batch query input
15+
* @param queryFunction Function to produce query results.
16+
*/
17+
void handleBatch(GraphQLBatchedInvocationInput batchedInvocationInput, BiFunction<GraphQLInvocationInput, ExecutionInput, ExecutionResult> queryFunction);
18+
}
19+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package graphql.servlet;
2+
3+
import graphql.ExecutionInput;
4+
import graphql.ExecutionResult;
5+
6+
import java.io.IOException;
7+
import java.io.Writer;
8+
import java.util.Iterator;
9+
import java.util.function.BiFunction;
10+
11+
public class DefaultGraphQLBatchExecutionHandlerFactory implements GraphQLBatchExecutionHandlerFactory {
12+
@Override
13+
public BatchExecutionHandler getBatchHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper) {
14+
return new DefaultGraphQLBatchExecutionHandler(respWriter, graphQLObjectMapper);
15+
}
16+
17+
private class DefaultGraphQLBatchExecutionHandler implements BatchExecutionHandler {
18+
19+
private final Writer respWriter;
20+
21+
private final GraphQLObjectMapper graphQLObjectMapper;
22+
23+
private DefaultGraphQLBatchExecutionHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper) {
24+
this.respWriter = respWriter;
25+
this.graphQLObjectMapper = graphQLObjectMapper;
26+
}
27+
28+
@Override
29+
public void handleBatch(GraphQLBatchedInvocationInput batchedInvocationInput, BiFunction<GraphQLInvocationInput, ExecutionInput,
30+
ExecutionResult> queryFunction) {
31+
Iterator<ExecutionInput> executionInputIterator = batchedInvocationInput.getExecutionInputs().iterator();
32+
try {
33+
respWriter.write("[");
34+
while (executionInputIterator.hasNext()) {
35+
ExecutionResult result = queryFunction.apply(batchedInvocationInput, executionInputIterator.next());
36+
respWriter.write(graphQLObjectMapper.serializeResultAsJson(result));
37+
if (executionInputIterator.hasNext()) {
38+
respWriter.write(",");
39+
}
40+
}
41+
respWriter.write("]");
42+
} catch (IOException e) {
43+
throw new RuntimeException(e);
44+
}
45+
}
46+
}
47+
}

src/main/java/graphql/servlet/DefaultGraphQLServlet.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
2525
return GraphQLObjectMapper.newBuilder().build();
2626
}
2727

28+
@Override
29+
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
30+
return null;
31+
}
32+
2833
@Override
2934
protected boolean isAsyncServletMode() {
3035
return false;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package graphql.servlet;
2+
3+
import java.io.Writer;
4+
5+
/**
6+
* Interface to allow customization of how batched queries are handled.
7+
*/
8+
public interface GraphQLBatchExecutionHandlerFactory {
9+
/**
10+
* Produces an BatchExecutionHandler instance for a specific response. Can maintain state across each request within a batch.
11+
* @param respWriter to send the response back
12+
* @param graphQLObjectMapper to serialize results.
13+
* @return a handler instance
14+
*/
15+
BatchExecutionHandler getBatchHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper);
16+
}

src/main/java/graphql/servlet/GraphQLConfiguration.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class GraphQLConfiguration {
1313
private GraphQLInvocationInputFactory invocationInputFactory;
1414
private GraphQLQueryInvoker queryInvoker;
1515
private GraphQLObjectMapper objectMapper;
16+
private GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory;
1617
private List<GraphQLServletListener> listeners;
1718
private boolean asyncServletModeEnabled;
1819
private Executor asyncExecutor;
@@ -30,10 +31,11 @@ public static GraphQLConfiguration.Builder with(GraphQLInvocationInputFactory in
3031
return new Builder(invocationInputFactory);
3132
}
3233

33-
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout) {
34+
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout) {
3435
this.invocationInputFactory = invocationInputFactory;
3536
this.queryInvoker = queryInvoker;
3637
this.objectMapper = objectMapper;
38+
this.batchExecutionHandlerFactory = batchExecutionHandlerFactory;
3739
this.listeners = listeners;
3840
this.asyncServletModeEnabled = asyncServletModeEnabled;
3941
this.asyncExecutor = asyncExecutor;
@@ -52,6 +54,10 @@ public GraphQLObjectMapper getObjectMapper() {
5254
return objectMapper;
5355
}
5456

57+
public GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
58+
return batchExecutionHandlerFactory;
59+
}
60+
5561
public List<GraphQLServletListener> getListeners() {
5662
return new ArrayList<>(listeners);
5763
}
@@ -82,6 +88,7 @@ public static class Builder {
8288
private GraphQLInvocationInputFactory invocationInputFactory;
8389
private GraphQLQueryInvoker queryInvoker = GraphQLQueryInvoker.newBuilder().build();
8490
private GraphQLObjectMapper objectMapper = GraphQLObjectMapper.newBuilder().build();
91+
private GraphQLBatchExecutionHandlerFactory graphQLBatchExecutionHandlerFactory = new DefaultGraphQLBatchExecutionHandlerFactory();
8592
private List<GraphQLServletListener> listeners = new ArrayList<>();
8693
private boolean asyncServletModeEnabled = false;
8794
private Executor asyncExecutor = Executors.newCachedThreadPool(new GraphQLThreadFactory());
@@ -109,6 +116,13 @@ public Builder with(GraphQLObjectMapper objectMapper) {
109116
return this;
110117
}
111118

119+
public Builder with(GraphQLBatchExecutionHandlerFactory batchExecutionHandlerFactory) {
120+
if (batchExecutionHandlerFactory != null) {
121+
this.graphQLBatchExecutionHandlerFactory = batchExecutionHandlerFactory;
122+
}
123+
return this;
124+
}
125+
112126
public Builder with(List<GraphQLServletListener> listeners) {
113127
if (listeners != null) {
114128
this.listeners = listeners;
@@ -148,6 +162,7 @@ public GraphQLConfiguration build() {
148162
this.invocationInputFactory != null ? this.invocationInputFactory : invocationInputFactoryBuilder.build(),
149163
queryInvoker,
150164
objectMapper,
165+
graphQLBatchExecutionHandlerFactory,
151166
listeners,
152167
asyncServletModeEnabled,
153168
asyncExecutor,

src/main/java/graphql/servlet/GraphQLHttpServlet.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
import graphql.schema.GraphQLSchema;
44

5-
import java.util.ArrayList;
6-
import java.util.List;
7-
import java.util.Objects;
8-
95
/**
106
* @author Michiel Oliemans
117
*/
@@ -37,6 +33,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
3733
throw new UnsupportedOperationException();
3834
}
3935

36+
@Override
37+
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
38+
throw new UnsupportedOperationException();
39+
}
40+
4041
@Override
4142
protected boolean isAsyncServletMode() {
4243
throw new UnsupportedOperationException();

src/main/java/graphql/servlet/GraphQLQueryInvoker.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@
1111
import graphql.execution.preparsed.NoOpPreparsedDocumentProvider;
1212
import graphql.execution.preparsed.PreparsedDocumentProvider;
1313
import graphql.schema.GraphQLSchema;
14-
import graphql.servlet.internal.ExecutionResultHandler;
1514

1615
import javax.security.auth.Subject;
1716
import java.security.AccessController;
1817
import java.security.PrivilegedAction;
1918
import java.util.ArrayList;
20-
import java.util.Iterator;
2119
import java.util.List;
2220
import java.util.function.Supplier;
2321

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

45-
public void query(GraphQLBatchedInvocationInput batchedInvocationInput, ExecutionResultHandler executionResultHandler) {
46-
Iterator<ExecutionInput> executionInputIterator = batchedInvocationInput.getExecutionInputs().iterator();
47-
48-
while (executionInputIterator.hasNext()) {
49-
ExecutionResult result = query(batchedInvocationInput, executionInputIterator.next());
50-
executionResultHandler.accept(result, executionInputIterator.hasNext());
51-
}
43+
public void query(GraphQLBatchedInvocationInput batchedInvocationInput, BatchExecutionHandler batchExecutionHandler) {
44+
batchExecutionHandler.handleBatch(batchedInvocationInput, this::query);
5245
}
5346

5447
private GraphQL newGraphQL(GraphQLSchema schema, Object context) {

src/main/java/graphql/servlet/OsgiGraphQLHttpServlet.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class OsgiGraphQLHttpServlet extends AbstractGraphQLHttpServlet {
4646
private InstrumentationProvider instrumentationProvider = new NoOpInstrumentationProvider();
4747
private GraphQLErrorHandler errorHandler = new DefaultGraphQLErrorHandler();
4848
private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE;
49+
private GraphQLBatchExecutionHandlerFactory executionResultHandlerFactory = new DefaultGraphQLBatchExecutionHandlerFactory();
4950

5051
private GraphQLSchemaProvider schemaProvider;
5152

@@ -84,6 +85,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
8485
return graphQLObjectMapper;
8586
}
8687

88+
@Override
89+
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
90+
return executionResultHandlerFactory;
91+
}
92+
8793
@Override
8894
protected boolean isAsyncServletMode() {
8995
return false;

src/main/java/graphql/servlet/SimpleGraphQLHttpServlet.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
6969
return configuration.getObjectMapper();
7070
}
7171

72+
@Override
73+
protected GraphQLBatchExecutionHandlerFactory getBatchExecutionHandlerFactory() {
74+
return configuration.getBatchExecutionHandlerFactory();
75+
}
76+
7277
@Override
7378
protected boolean isAsyncServletMode() {
7479
return configuration.isAsyncServletModeEnabled();

0 commit comments

Comments
 (0)