Skip to content

Commit 373869b

Browse files
authored
Merge pull request #192 from jmccaull/query_invoker_refactor
Configurable per query or per request context/dataloader batching
2 parents 207aa35 + e2d590e commit 373869b

File tree

91 files changed

+2185
-656
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+2185
-656
lines changed

README.md

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,10 @@ Here's an example of a GraphQL provider that implements three interfaces at the
233233

234234
* [ExampleGraphQLProvider](examples/osgi/providers/src/main/java/graphql/servlet/examples/osgi/ExampleGraphQLProvider.java)
235235

236-
## Request-scoped DataLoaders
236+
## Context and DataLoader settings
237237

238-
It is possible to use dataloaders in a request scope by customizing [GraphQLContextBuilder](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/master/src/main/java/graphql/servlet/GraphQLContextBuilder.java).
239-
And instantiating a new [DataLoaderRegistry](https://github.com/graphql-java/java-dataloader/blob/master/src/main/java/org/dataloader/DataLoaderRegistry.java) for each GraphQLContext.
238+
It is possible to create context, and consequently dataloaders, in both a request scope and a per query scope by customizing [GraphQLContextBuilder](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/master/src/main/java/graphql/servlet/context/GraphQLContextBuilder.java) and selecting the appropriate [ContextSetting](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/master/src/main/java/graphql/servlet/context/ContextSetting.java) with the provided [GraphQLConfiguration](https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/master/src/main/java/graphql/servlet/config/GraphQLConfiguration.java).
239+
A new [DataLoaderRegistry](https://github.com/graphql-java/java-dataloader/blob/master/src/main/java/org/dataloader/DataLoaderRegistry.java) should be created in each call to the GraphQLContextBuilder, and the servlet will call the builder at the appropriate times.
240240
For eg:
241241
```java
242242
public class CustomGraphQLContextBuilder implements GraphQLContextBuilder {
@@ -249,26 +249,17 @@ public class CustomGraphQLContextBuilder implements GraphQLContextBuilder {
249249

250250
@Override
251251
public GraphQLContext build(HttpServletRequest req) {
252-
GraphQLContext context = new GraphQLContext(req);
253-
context.setDataLoaderRegistry(buildDataLoaderRegistry());
254-
255-
return context;
252+
return new GraphQLContext(buildDataLoaderRegistry());
256253
}
257254

258255
@Override
259256
public GraphQLContext build() {
260-
GraphQLContext context = new GraphQLContext();
261-
context.setDataLoaderRegistry(buildDataLoaderRegistry());
262-
263-
return context;
257+
return new GraphQLContext(buildDataLoaderRegistry());
264258
}
265259

266260
@Override
267261
public GraphQLContext build(HandshakeRequest request) {
268-
GraphQLContext context = new GraphQLContext(request);
269-
context.setDataLoaderRegistry(buildDataLoaderRegistry());
270-
271-
return context;
262+
return new GraphQLContext(buildDataLoaderRegistry());
272263
}
273264

274265
private DataLoaderRegistry buildDataLoaderRegistry() {
@@ -290,3 +281,6 @@ public class CustomGraphQLContextBuilder implements GraphQLContextBuilder {
290281
}
291282

292283
```
284+
If per request is selected this will cause all queries within the http request, if using a batch, to share dataloader caches and batch together load calls as efficently as possible. The dataloaders are dispatched using instrumentation and the correct instrumentation will be selected according to the ContextSetting. The default context setting in GraphQLConfiguration is per query.
285+
286+
Two additional context settings are provided, one for each of the previous settings but without the addition of the Dataloader dispatching instrumentation. This is useful for those not using Dataloaders or wanting to supply their own dispatching instrumentation though the instrumentation supplier within the GraphQLQueryInvoker.

gradle.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
version = 7.5.1-SNAPSHOT
1+
version = 8.0.0-SNAPSHOT
22
group = com.graphql-java-kickstart
33

4-
LIB_GRAPHQL_JAVA_VER = 12.0
5-
LIB_JACKSON_VER = 2.9.8
4+
LIB_GRAPHQL_JAVA_VER = 13.0
5+
LIB_JACKSON_VER = 2.9.9

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

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@
55
import graphql.ExecutionResult;
66
import graphql.introspection.IntrospectionQuery;
77
import graphql.schema.GraphQLFieldDefinition;
8-
import graphql.servlet.internal.GraphQLRequest;
9-
import graphql.servlet.internal.VariableMapper;
8+
import graphql.servlet.core.GraphQLMBean;
9+
import graphql.servlet.core.GraphQLObjectMapper;
10+
import graphql.servlet.core.GraphQLQueryInvoker;
11+
import graphql.servlet.core.GraphQLServletListener;
12+
import graphql.servlet.config.GraphQLConfiguration;
13+
import graphql.servlet.context.ContextSetting;
14+
import graphql.servlet.input.BatchInputPreProcessResult;
15+
import graphql.servlet.input.BatchInputPreProcessor;
16+
import graphql.servlet.input.GraphQLBatchedInvocationInput;
17+
import graphql.servlet.input.GraphQLSingleInvocationInput;
18+
import graphql.servlet.input.GraphQLInvocationInputFactory;
19+
import graphql.servlet.core.internal.GraphQLRequest;
20+
import graphql.servlet.core.internal.VariableMapper;
1021
import org.reactivestreams.Publisher;
1122
import org.reactivestreams.Subscriber;
1223
import org.reactivestreams.Subscription;
@@ -30,6 +41,7 @@
3041
import java.util.ArrayList;
3142
import java.util.Arrays;
3243
import java.util.HashMap;
44+
import java.util.Iterator;
3345
import java.util.List;
3446
import java.util.Map;
3547
import java.util.Objects;
@@ -46,13 +58,13 @@
4658
*/
4759
public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements Servlet, GraphQLMBean {
4860

49-
public static final Logger log = LoggerFactory.getLogger(AbstractGraphQLHttpServlet.class);
61+
private static final Logger log = LoggerFactory.getLogger(AbstractGraphQLHttpServlet.class);
5062

51-
public static final String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8";
52-
public static final String APPLICATION_EVENT_STREAM_UTF8 = "text/event-stream;charset=UTF-8";
53-
public static final String APPLICATION_GRAPHQL = "application/graphql";
54-
public static final int STATUS_OK = 200;
55-
public static final int STATUS_BAD_REQUEST = 400;
63+
private static final String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8";
64+
private static final String APPLICATION_EVENT_STREAM_UTF8 = "text/event-stream;charset=UTF-8";
65+
private static final String APPLICATION_GRAPHQL = "application/graphql";
66+
private static final int STATUS_OK = 200;
67+
private static final int STATUS_BAD_REQUEST = 400;
5668

5769
private static final GraphQLRequest INTROSPECTION_REQUEST = new GraphQLRequest(IntrospectionQuery.INTROSPECTION_QUERY, new HashMap<>(), null);
5870
private static final String[] MULTIPART_KEYS = new String[]{"operations", "graphql", "query"};
@@ -123,13 +135,17 @@ public void init() {
123135
path = request.getServletPath();
124136
}
125137
if (path.contentEquals("/schema.json")) {
126-
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(INTROSPECTION_REQUEST, request, response), response);
138+
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(INTROSPECTION_REQUEST, request, response),
139+
request, response);
127140
} else {
128141
String query = request.getParameter("query");
129142
if (query != null) {
130143

131144
if (isBatchedQuery(query)) {
132-
queryBatched(queryInvoker, graphQLObjectMapper, invocationInputFactory.createReadOnly(graphQLObjectMapper.readBatchedGraphQLRequest(query), request, response), response);
145+
List<GraphQLRequest> requests = graphQLObjectMapper.readBatchedGraphQLRequest(query);
146+
GraphQLBatchedInvocationInput batchedInvocationInput =
147+
invocationInputFactory.createReadOnly(configuration.getContextSetting(), requests, request, response);
148+
queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration);
133149
} else {
134150
final Map<String, Object> variables = new HashMap<>();
135151
if (request.getParameter("variables") != null) {
@@ -138,7 +154,9 @@ public void init() {
138154

139155
String operationName = request.getParameter("operationName");
140156

141-
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.createReadOnly(new GraphQLRequest(query, variables, operationName), request, response), response);
157+
query(queryInvoker, graphQLObjectMapper,
158+
invocationInputFactory.createReadOnly(new GraphQLRequest(query, variables, operationName), request, response),
159+
request, response);
142160
}
143161
} else {
144162
response.setStatus(STATUS_BAD_REQUEST);
@@ -155,7 +173,9 @@ public void init() {
155173
try {
156174
if (APPLICATION_GRAPHQL.equals(request.getContentType())) {
157175
String query = CharStreams.toString(request.getReader());
158-
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(new GraphQLRequest(query, null, null), request, response), response);
176+
query(queryInvoker, graphQLObjectMapper,
177+
invocationInputFactory.create(new GraphQLRequest(query, null, null), request, response),
178+
request, response);
159179
} else if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data") && !request.getParts().isEmpty()) {
160180
final Map<String, List<Part>> fileItems = request.getParts()
161181
.stream()
@@ -182,10 +202,9 @@ public void init() {
182202
List<GraphQLRequest> graphQLRequests =
183203
graphQLObjectMapper.readBatchedGraphQLRequest(inputStream);
184204
variablesMap.ifPresent(map -> graphQLRequests.forEach(r -> mapMultipartVariables(r, map, fileItems)));
185-
GraphQLBatchedInvocationInput invocationInput =
186-
invocationInputFactory.create(graphQLRequests, request, response);
187-
invocationInput.getContext().setParts(fileItems);
188-
queryBatched(queryInvoker, graphQLObjectMapper, invocationInput, response);
205+
GraphQLBatchedInvocationInput batchedInvocationInput = invocationInputFactory.create(configuration.getContextSetting(),
206+
graphQLRequests, request, response);
207+
queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration);
189208
return;
190209
} else {
191210
GraphQLRequest graphQLRequest;
@@ -198,8 +217,7 @@ public void init() {
198217
variablesMap.ifPresent(m -> mapMultipartVariables(graphQLRequest, m, fileItems));
199218
GraphQLSingleInvocationInput invocationInput =
200219
invocationInputFactory.create(graphQLRequest, request, response);
201-
invocationInput.getContext().setParts(fileItems);
202-
query(queryInvoker, graphQLObjectMapper, invocationInput, response);
220+
query(queryInvoker, graphQLObjectMapper, invocationInput, request, response);
203221
return;
204222
}
205223
}
@@ -211,9 +229,12 @@ public void init() {
211229
InputStream inputStream = asMarkableInputStream(request.getInputStream());
212230

213231
if (isBatchedQuery(inputStream)) {
214-
queryBatched(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readBatchedGraphQLRequest(inputStream), request, response), response);
232+
List<GraphQLRequest> requests = graphQLObjectMapper.readBatchedGraphQLRequest(inputStream);
233+
GraphQLBatchedInvocationInput batchedInvocationInput =
234+
invocationInputFactory.create(configuration.getContextSetting(), requests, request, response);
235+
queryBatched(queryInvoker, batchedInvocationInput, request, response, configuration);
215236
} else {
216-
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readGraphQLRequest(inputStream), request, response), response);
237+
query(queryInvoker, graphQLObjectMapper, invocationInputFactory.create(graphQLObjectMapper.readGraphQLRequest(inputStream), request, response), request, response);
217238
}
218239
}
219240
} catch (Exception e) {
@@ -348,18 +369,21 @@ private Optional<Part> getFileItem(Map<String, List<Part>> fileItems, String nam
348369
return Optional.ofNullable(fileItems.get(name)).filter(list -> !list.isEmpty()).map(list -> list.get(0));
349370
}
350371

351-
private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLSingleInvocationInput invocationInput, HttpServletResponse resp) throws IOException {
372+
private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLSingleInvocationInput invocationInput,
373+
HttpServletRequest req, HttpServletResponse resp) throws IOException {
352374
ExecutionResult result = queryInvoker.query(invocationInput);
353375

354376
if (!(result.getData() instanceof Publisher)) {
355377
resp.setContentType(APPLICATION_JSON_UTF8);
356378
resp.setStatus(STATUS_OK);
357379
resp.getWriter().write(graphQLObjectMapper.serializeResultAsJson(result));
358380
} else {
381+
if (req == null) {
382+
throw new IllegalStateException("Http servlet request can not be null");
383+
}
359384
resp.setContentType(APPLICATION_EVENT_STREAM_UTF8);
360385
resp.setStatus(STATUS_OK);
361386

362-
HttpServletRequest req = invocationInput.getContext().getHttpServletRequest().orElseThrow(IllegalStateException::new);
363387
boolean isInAsyncThread = req.isAsyncStarted();
364388
AsyncContext asyncContext = isInAsyncThread ? req.getAsyncContext() : req.startAsync(req, resp);
365389
asyncContext.setTimeout(configuration.getSubscriptionTimeout());
@@ -378,8 +402,30 @@ private void query(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQL
378402
}
379403
}
380404

381-
private void queryBatched(GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper graphQLObjectMapper, GraphQLBatchedInvocationInput invocationInput, HttpServletResponse resp) throws Exception {
382-
queryInvoker.query(invocationInput, resp, graphQLObjectMapper);
405+
private void queryBatched(GraphQLQueryInvoker queryInvoker, GraphQLBatchedInvocationInput batchedInvocationInput, HttpServletRequest request,
406+
HttpServletResponse response, GraphQLConfiguration configuration) throws IOException {
407+
BatchInputPreProcessor batchInputPreProcessor = configuration.getBatchInputPreProcessor();
408+
ContextSetting contextSetting = configuration.getContextSetting();
409+
BatchInputPreProcessResult batchInputPreProcessResult = batchInputPreProcessor.preProcessBatch(batchedInvocationInput, request, response);
410+
if (batchInputPreProcessResult.isExecutable()) {
411+
List<ExecutionResult> results = queryInvoker.query(batchInputPreProcessResult.getBatchedInvocationInput().getExecutionInputs(),
412+
contextSetting);
413+
response.setContentType(AbstractGraphQLHttpServlet.APPLICATION_JSON_UTF8);
414+
response.setStatus(AbstractGraphQLHttpServlet.STATUS_OK);
415+
Writer writer = response.getWriter();
416+
Iterator<ExecutionResult> executionInputIterator = results.iterator();
417+
writer.write("[");
418+
GraphQLObjectMapper graphQLObjectMapper = configuration.getObjectMapper();
419+
while (executionInputIterator.hasNext()) {
420+
writer.write(graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next()));
421+
if (executionInputIterator.hasNext()) {
422+
writer.write(",");
423+
}
424+
}
425+
writer.write("]");
426+
} else {
427+
response.sendError(batchInputPreProcessResult.getStatusCode(), batchInputPreProcessResult.getStatusMessage());
428+
}
383429
}
384430

385431
private <R> List<R> runListeners(Function<? super GraphQLServletListener, R> action) {

src/main/java/graphql/servlet/BatchExecutionHandler.java

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/main/java/graphql/servlet/ConfiguredGraphQLHttpServlet.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package graphql.servlet;
22

3+
import graphql.servlet.config.GraphQLConfiguration;
4+
35
import java.util.Objects;
46

57
class ConfiguredGraphQLHttpServlet extends GraphQLHttpServlet {

src/main/java/graphql/servlet/DefaultBatchExecutionHandler.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package graphql.servlet;
22

3+
import graphql.servlet.core.GraphQLObjectMapper;
4+
import graphql.servlet.core.GraphQLQueryInvoker;
5+
import graphql.servlet.input.GraphQLInvocationInputFactory;
6+
37
public class DefaultGraphQLServlet extends AbstractGraphQLHttpServlet {
48

59
@Override

src/main/java/graphql/servlet/GraphQLBatchedInvocationInput.java

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)