builderFunction) {
return execute(builderFunction.apply(ExecutionInput.newExecutionInput()).build());
}
/**
* Executes the graphql query using the provided input object
*
* @param executionInput {@link ExecutionInput}
*
* @return an {@link ExecutionResult} which can include errors
*/
public ExecutionResult execute(ExecutionInput executionInput) {
try {
return executeAsync(executionInput).join();
} catch (CompletionException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw e;
}
}
}
/**
* Executes the graphql query using the provided input object builder
*
* This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult}
* which is the result of executing the provided query.
*
* @param executionInputBuilder {@link ExecutionInput.Builder}
*
* @return a promise to an {@link ExecutionResult} which can include errors
*/
public CompletableFuture executeAsync(ExecutionInput.Builder executionInputBuilder) {
return executeAsync(executionInputBuilder.build());
}
/**
* Executes the graphql query using the provided input object builder
*
* This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult}
* which is the result of executing the provided query.
*
* This allows a lambda style like :
*
* {@code
* ExecutionResult result = graphql.execute(input -> input.query("{hello}").root(startingObj).context(contextObj));
* }
*
*
* @param builderFunction a function that is given a {@link ExecutionInput.Builder}
*
* @return a promise to an {@link ExecutionResult} which can include errors
*/
public CompletableFuture executeAsync(UnaryOperator builderFunction) {
return executeAsync(builderFunction.apply(ExecutionInput.newExecutionInput()).build());
}
/**
* Executes the graphql query using the provided input object
*
* This will return a promise (aka {@link CompletableFuture}) to provide a {@link ExecutionResult}
* which is the result of executing the provided query.
*
* @param executionInput {@link ExecutionInput}
*
* @return a promise to an {@link ExecutionResult} which can include errors
*/
public CompletableFuture executeAsync(ExecutionInput executionInput) {
try {
log.debug("Executing request. operation name: '{}'. query: '{}'. variables '{}'", executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables());
InstrumentationState instrumentationState = instrumentation.createState();
InstrumentationExecutionParameters inputInstrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState);
executionInput = instrumentation.instrumentExecutionInput(executionInput, inputInstrumentationParameters);
InstrumentationExecutionParameters instrumentationParameters = new InstrumentationExecutionParameters(executionInput, this.graphQLSchema, instrumentationState);
InstrumentationContext executionInstrumentation = instrumentation.beginExecution(instrumentationParameters);
GraphQLSchema graphQLSchema = instrumentation.instrumentSchema(this.graphQLSchema, instrumentationParameters);
CompletableFuture executionResult = parseValidateAndExecute(executionInput, graphQLSchema, instrumentationState);
//
// finish up instrumentation
executionResult = executionResult.whenComplete(executionInstrumentation::onCompleted);
//
// allow instrumentation to tweak the result
executionResult = executionResult.thenCompose(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters));
return executionResult;
} catch (AbortExecutionException abortException) {
ExecutionResultImpl executionResult = new ExecutionResultImpl(abortException);
if (!abortException.getUnderlyingErrors().isEmpty()) {
executionResult = new ExecutionResultImpl(abortException.getUnderlyingErrors());
}
return CompletableFuture.completedFuture(executionResult);
}
}
private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
AtomicReference executionInputRef = new AtomicReference<>(executionInput);
PreparsedDocumentEntry preparsedDoc = preparsedDocumentProvider.get(executionInput.getQuery(),
transformedQuery -> {
// if they change the original query in the pre-parser, then we want to see it downstream from then on
executionInputRef.set(executionInput.transform(bldr -> bldr.query(transformedQuery)));
return parseAndValidate(executionInputRef.get(), graphQLSchema, instrumentationState);
});
if (preparsedDoc.hasErrors()) {
return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDoc.getErrors()));
}
return execute(executionInputRef.get(), preparsedDoc.getDocument(), graphQLSchema, instrumentationState);
}
private PreparsedDocumentEntry parseAndValidate(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
log.debug("Parsing query: '{}'...", executionInput.getQuery());
ParseResult parseResult = parse(executionInput, graphQLSchema, instrumentationState);
if (parseResult.isFailure()) {
log.warn("Query failed to parse : '{}'", executionInput.getQuery());
return new PreparsedDocumentEntry(toInvalidSyntaxError(parseResult.getException()));
} else {
final Document document = parseResult.getDocument();
log.debug("Validating query: '{}'", executionInput.getQuery());
final List errors = validate(executionInput, document, graphQLSchema, instrumentationState);
if (!errors.isEmpty()) {
log.warn("Query failed to validate : '{}'", executionInput.getQuery());
return new PreparsedDocumentEntry(errors);
}
return new PreparsedDocumentEntry(document);
}
}
private ParseResult parse(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
InstrumentationContext parseInstrumentation = instrumentation.beginParse(new InstrumentationExecutionParameters(executionInput, graphQLSchema, instrumentationState));
Parser parser = new Parser();
Document document;
try {
document = parser.parseDocument(executionInput.getQuery());
} catch (ParseCancellationException e) {
parseInstrumentation.onCompleted(null, e);
return ParseResult.ofError(e);
}
parseInstrumentation.onCompleted(document, null);
return ParseResult.of(document);
}
private List validate(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
InstrumentationContext> validationCtx = instrumentation.beginValidation(new InstrumentationValidationParameters(executionInput, document, graphQLSchema, instrumentationState));
Validator validator = new Validator();
List validationErrors = validator.validateDocument(graphQLSchema, document);
validationCtx.onCompleted(validationErrors, null);
return validationErrors;
}
private CompletableFuture execute(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
String query = executionInput.getQuery();
String operationName = executionInput.getOperationName();
Object context = executionInput.getContext();
Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation);
ExecutionId executionId = idProvider.provide(query, operationName, context);
log.debug("Executing '{}'. operation name: '{}'. query: '{}'. variables '{}'", executionId, executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables());
CompletableFuture future = execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState);
future.whenComplete((result, throwable) -> {
if (throwable != null) {
log.error(String.format("Execution '%s' threw exception when executing : query : '%s'. variables '%s'", executionId, executionInput.getQuery(), executionInput.getVariables()), throwable);
} else {
int errorCount = result.getErrors().size();
if (errorCount > 0) {
log.debug("Execution '{}' completed with '{}' errors", executionId, errorCount);
} else {
log.debug("Execution '{}' completed with zero errors", executionId);
}
}
});
return future;
}
private static class ParseResult {
private final Document document;
private final Exception exception;
private ParseResult(Document document, Exception exception) {
this.document = document;
this.exception = exception;
}
private boolean isFailure() {
return document == null;
}
private Document getDocument() {
return document;
}
private Exception getException() {
return exception;
}
private static ParseResult of(Document document) {
return new ParseResult(document, null);
}
private static ParseResult ofError(Exception e) {
return new ParseResult(null, e);
}
}
}