Skip to content

Commit b007361

Browse files
committed
Initial upgrades to customize batch handling
1 parent 1e9eacd commit b007361

11 files changed

+150
-40
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 GraphQLExecutionResultHandlerFactory getBatchInputHandler();
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(getBatchInputHandler())
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+
GraphQLExecutionResultHandlerFactory batchInputHandlerFactory = configuration.getBatchInputHandlerFactory();
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(batchInputHandlerFactory, 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+
GraphQLExecutionResultHandlerFactory batchInputHandlerFactory = configuration.getBatchInputHandlerFactory();
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(batchInputHandlerFactory, 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(batchInputHandlerFactory, 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(GraphQLExecutionResultHandlerFactory 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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package graphql.servlet;
2+
3+
import graphql.ExecutionResult;
4+
5+
import java.io.IOException;
6+
import java.io.Writer;
7+
import java.util.ArrayList;
8+
import java.util.Iterator;
9+
import java.util.List;
10+
11+
public class DefaultGraphQLExecutionResultHandlerFactory implements GraphQLExecutionResultHandlerFactory {
12+
@Override
13+
public ExecutionResultHandler getBatchHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper) {
14+
return new DefaultGraphQLExecutionResultHandler(respWriter, graphQLObjectMapper);
15+
}
16+
17+
private class DefaultGraphQLExecutionResultHandler implements ExecutionResultHandler {
18+
19+
private final List<ExecutionResult> results = new ArrayList<>();
20+
21+
private final Writer respWriter;
22+
23+
private final GraphQLObjectMapper graphQLObjectMapper;
24+
25+
private DefaultGraphQLExecutionResultHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper) {
26+
this.respWriter = respWriter;
27+
this.graphQLObjectMapper = graphQLObjectMapper;
28+
}
29+
30+
@Override
31+
public void accept(ExecutionResult result) {
32+
results.add(result);
33+
}
34+
35+
@Override
36+
public void finalizeResults() {
37+
try {
38+
respWriter.write("[");
39+
Iterator<ExecutionResult> iterator = results.iterator();
40+
while (iterator.hasNext()) {
41+
respWriter.write(graphQLObjectMapper.serializeResultAsJson(iterator.next()));
42+
if (iterator.hasNext()) {
43+
respWriter.write(",");
44+
}
45+
}
46+
respWriter.write("]");
47+
} catch (IOException e) {
48+
throw new RuntimeException(e);
49+
}
50+
}
51+
}
52+
}

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 GraphQLExecutionResultHandlerFactory getBatchInputHandler() {
30+
return null;
31+
}
32+
2833
@Override
2934
protected boolean isAsyncServletMode() {
3035
return false;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package graphql.servlet;
2+
3+
import graphql.ExecutionInput;
4+
import graphql.ExecutionResult;
5+
6+
import java.util.Iterator;
7+
import java.util.function.Consumer;
8+
9+
/**
10+
* @author Andrew Potter
11+
*/
12+
public interface ExecutionResultHandler extends Consumer<ExecutionResult> {
13+
/**
14+
* Allows the number of queries in a batch to be limited.
15+
* @param executionInputIterator iterator for the current set of requests.
16+
* @return if processing should continue.
17+
*/
18+
default boolean shouldContinue(Iterator<ExecutionInput> executionInputIterator){
19+
return executionInputIterator.hasNext();
20+
}
21+
22+
/**
23+
* Should perform any actions, such as formatting and sending results, to complete a batch.
24+
*/
25+
void finalizeResults();
26+
}
27+

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 GraphQLExecutionResultHandlerFactory batchInputHandlerFactory;
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, GraphQLExecutionResultHandlerFactory batchInputHandlerFactory, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout) {
3435
this.invocationInputFactory = invocationInputFactory;
3536
this.queryInvoker = queryInvoker;
3637
this.objectMapper = objectMapper;
38+
this.batchInputHandlerFactory = batchInputHandlerFactory;
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 GraphQLExecutionResultHandlerFactory getBatchInputHandlerFactory() {
58+
return batchInputHandlerFactory;
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 GraphQLExecutionResultHandlerFactory batchInputHandler = new DefaultGraphQLExecutionResultHandlerFactory();
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(GraphQLExecutionResultHandlerFactory batchInputHandlerFactory) {
120+
if (batchInputHandlerFactory != null) {
121+
this.batchInputHandler = batchInputHandlerFactory;
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+
batchInputHandler,
151166
listeners,
152167
asyncServletModeEnabled,
153168
asyncExecutor,
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 GraphQLExecutionResultHandlerFactory {
9+
/**
10+
* Produces an ExecutionResultHandler 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+
ExecutionResultHandler getBatchHandler(Writer respWriter, GraphQLObjectMapper graphQLObjectMapper);
16+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ protected GraphQLObjectMapper getGraphQLObjectMapper() {
3737
throw new UnsupportedOperationException();
3838
}
3939

40+
@Override
41+
protected GraphQLExecutionResultHandlerFactory getBatchInputHandler() {
42+
throw new UnsupportedOperationException();
43+
}
44+
4045
@Override
4146
protected boolean isAsyncServletMode() {
4247
throw new UnsupportedOperationException();

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
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;
@@ -45,10 +44,12 @@ public ExecutionResult query(GraphQLSingleInvocationInput singleInvocationInput)
4544
public void query(GraphQLBatchedInvocationInput batchedInvocationInput, ExecutionResultHandler executionResultHandler) {
4645
Iterator<ExecutionInput> executionInputIterator = batchedInvocationInput.getExecutionInputs().iterator();
4746

48-
while (executionInputIterator.hasNext()) {
47+
while (executionResultHandler.shouldContinue(executionInputIterator)) {
4948
ExecutionResult result = query(batchedInvocationInput, executionInputIterator.next());
50-
executionResultHandler.accept(result, executionInputIterator.hasNext());
49+
executionResultHandler.accept(result);
5150
}
51+
52+
executionResultHandler.finalizeResults();
5253
}
5354

5455
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 GraphQLExecutionResultHandlerFactory executionResultHandlerFactory = new DefaultGraphQLExecutionResultHandlerFactory();
4950

5051
private GraphQLSchemaProvider schemaProvider;
5152

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

88+
@Override
89+
protected GraphQLExecutionResultHandlerFactory getBatchInputHandler() {
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 GraphQLExecutionResultHandlerFactory getBatchInputHandler() {
74+
return configuration.getBatchInputHandlerFactory();
75+
}
76+
7277
@Override
7378
protected boolean isAsyncServletMode() {
7479
return configuration.isAsyncServletModeEnabled();

0 commit comments

Comments
 (0)