graphql.nextgen.GraphQL Maven / Gradle / Ivy
package graphql.nextgen;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.Internal;
import graphql.ParseAndValidate;
import graphql.ParseAndValidateResult;
import graphql.execution.AbortExecutionException;
import graphql.execution.ExecutionId;
import graphql.execution.ExecutionIdProvider;
import graphql.execution.instrumentation.DocumentAndVariables;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.nextgen.Instrumentation;
import graphql.execution.instrumentation.nextgen.InstrumentationCreateStateParameters;
import graphql.execution.instrumentation.nextgen.InstrumentationExecutionParameters;
import graphql.execution.instrumentation.nextgen.InstrumentationValidationParameters;
import graphql.execution.nextgen.DefaultExecutionStrategy;
import graphql.execution.nextgen.Execution;
import graphql.execution.nextgen.ExecutionStrategy;
import graphql.execution.preparsed.NoOpPreparsedDocumentProvider;
import graphql.execution.preparsed.PreparsedDocumentEntry;
import graphql.execution.preparsed.PreparsedDocumentProvider;
import graphql.language.Document;
import graphql.schema.GraphQLSchema;
import graphql.util.LogKit;
import graphql.validation.ValidationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import static graphql.Assert.assertNotNull;
/**
*
* @deprecated Jan 2022 - We have decided to deprecate the NextGen engine, and it will be removed in a future release.
*/
@Deprecated
@SuppressWarnings("Duplicates")
@Internal
public class GraphQL {
private static final Logger log = LoggerFactory.getLogger(graphql.GraphQL.class);
private static final Logger logNotSafe = LogKit.getNotPrivacySafeLogger(ExecutionStrategy.class);
private final GraphQLSchema graphQLSchema;
private final ExecutionStrategy executionStrategy;
private final ExecutionIdProvider idProvider;
private final Instrumentation instrumentation;
private final PreparsedDocumentProvider preparsedDocumentProvider;
public GraphQL(Builder builder) {
this.graphQLSchema = builder.graphQLSchema;
this.executionStrategy = builder.executionStrategy;
this.idProvider = builder.idProvider;
this.preparsedDocumentProvider = builder.preparsedDocumentProvider;
this.instrumentation = builder.instrumentation;
}
/**
* Executes the graphql query using the provided input object builder
*
* This will return a completed {@link ExecutionResult}
* which is the result of executing the provided query.
*
* @param executionInputBuilder {@link ExecutionInput.Builder}
*
* @return an {@link ExecutionResult} which can include errors
*/
public ExecutionResult execute(ExecutionInput.Builder executionInputBuilder) {
return executeAsync(executionInputBuilder.build()).join();
}
/**
* Executes the graphql query using the provided input object builder
*
* This will return a completed {@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 execute(UnaryOperator builderFunction) {
return executeAsync(builderFunction.apply(ExecutionInput.newExecutionInput()).build());
}
/**
* Executes the graphql query using the provided input object
*
* This will return a completed {@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 ExecutionResult execute(ExecutionInput executionInput) {
return executeAsync(executionInput).join();
}
/**
* 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.executeAsync(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 {
if (logNotSafe.isDebugEnabled()) {
logNotSafe.debug("Executing request. operation name: '{}'. query: '{}'. variables '{}'", executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables());
}
InstrumentationState instrumentationState = instrumentation.createState(new InstrumentationCreateStateParameters(this.graphQLSchema, executionInput));
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.thenApply(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters));
return executionResult;
} catch (AbortExecutionException abortException) {
return CompletableFuture.completedFuture(abortException.toExecutionResult());
}
}
private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
AtomicReference executionInputRef = new AtomicReference<>(executionInput);
Function computeFunction = transformedInput -> {
// if they change the original query in the pre-parser, then we want to see it downstream from then on
executionInputRef.set(transformedInput);
return parseAndValidate(executionInputRef, graphQLSchema, instrumentationState);
};
CompletableFuture preparsedDoc = preparsedDocumentProvider.getDocumentAsync(executionInput, computeFunction);
return preparsedDoc.thenCompose(preparsedDocumentEntry -> {
if (preparsedDocumentEntry.hasErrors()) {
return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDocumentEntry.getErrors()));
}
try {
return execute(executionInputRef.get(), preparsedDocumentEntry.getDocument(), graphQLSchema, instrumentationState);
} catch (AbortExecutionException e) {
return CompletableFuture.completedFuture(e.toExecutionResult());
}
});
}
private PreparsedDocumentEntry parseAndValidate(AtomicReference executionInputRef, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
ExecutionInput executionInput = executionInputRef.get();
String query = executionInput.getQuery();
if (logNotSafe.isDebugEnabled()) {
logNotSafe.debug("Parsing query: '{}'...", query);
}
ParseAndValidateResult parseResult = parse(executionInput, graphQLSchema, instrumentationState);
if (parseResult.isFailure()) {
logNotSafe.warn("Query did not parse : '{}'", executionInput.getQuery());
return new PreparsedDocumentEntry(parseResult.getSyntaxException().toInvalidSyntaxError());
} else {
final Document document = parseResult.getDocument();
// they may have changed the document and the variables via instrumentation so update the reference to it
executionInput = executionInput.transform(builder -> builder.variables(parseResult.getVariables()));
executionInputRef.set(executionInput);
if (logNotSafe.isDebugEnabled()) {
logNotSafe.debug("Validating query: '{}'", query);
}
final List errors = validate(executionInput, document, graphQLSchema, instrumentationState);
if (!errors.isEmpty()) {
logNotSafe.warn("Query did not validate : '{}'", query);
return new PreparsedDocumentEntry(errors);
}
return new PreparsedDocumentEntry(document);
}
}
private ParseAndValidateResult parse(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
InstrumentationExecutionParameters parameters = new InstrumentationExecutionParameters(executionInput, graphQLSchema, instrumentationState);
InstrumentationContext parseInstrumentation = instrumentation.beginParse(parameters);
CompletableFuture documentCF = new CompletableFuture<>();
parseInstrumentation.onDispatched(documentCF);
ParseAndValidateResult parseResult = ParseAndValidate.parse(executionInput);
if (parseResult.isFailure()) {
parseInstrumentation.onCompleted(null, parseResult.getSyntaxException());
return parseResult;
} else {
documentCF.complete(parseResult.getDocument());
parseInstrumentation.onCompleted(parseResult.getDocument(), null);
DocumentAndVariables documentAndVariables = parseResult.getDocumentAndVariables();
documentAndVariables = instrumentation.instrumentDocumentAndVariables(documentAndVariables, parameters);
return ParseAndValidateResult.newResult()
.document(documentAndVariables.getDocument()).variables(documentAndVariables.getVariables()).build();
}
}
private List validate(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
InstrumentationContext> validationCtx = instrumentation.beginValidation(new InstrumentationValidationParameters(executionInput, document, graphQLSchema, instrumentationState));
CompletableFuture> cf = new CompletableFuture<>();
validationCtx.onDispatched(cf);
Predicate> validationRulePredicate = executionInput.getGraphQLContext().getOrDefault(ParseAndValidate.INTERNAL_VALIDATION_PREDICATE_HINT, r -> true);
List validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate);
validationCtx.onCompleted(validationErrors, null);
cf.complete(validationErrors);
return validationErrors;
}
private CompletableFuture execute(ExecutionInput executionInput, Document document, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
String query = executionInput.getQuery();
String operationName = executionInput.getOperationName();
Object context = executionInput.getGraphQLContext();
Execution execution = new Execution();
ExecutionId executionId = idProvider.provide(query, operationName, context);
if (logNotSafe.isDebugEnabled()) {
logNotSafe.debug("Executing '{}'. operation name: '{}'. query: '{}'. variables '{}'", executionId, executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables());
}
CompletableFuture future = execution.execute(executionStrategy, document, graphQLSchema, executionId, executionInput, instrumentationState);
future = 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 {
if (log.isDebugEnabled()) {
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;
}
/**
* Helps you build a GraphQL object ready to execute queries
*
* @param graphQLSchema the schema to use
*
* @return a builder of GraphQL objects
*/
public static Builder newGraphQL(GraphQLSchema graphQLSchema) {
return new Builder(graphQLSchema);
}
/**
* This helps you transform the current GraphQL object into another one by starting a builder with all
* the current values and allows you to transform it how you want.
*
* @param builderConsumer the consumer code that will be given a builder to transform
*
* @return a new GraphQL object based on calling build on that builder
*/
public GraphQL transform(Consumer builderConsumer) {
Builder builder = new Builder(this);
builderConsumer.accept(builder);
return builder.build();
}
public static class Builder {
private GraphQLSchema graphQLSchema;
private ExecutionStrategy executionStrategy = new DefaultExecutionStrategy();
private ExecutionIdProvider idProvider = ExecutionIdProvider.DEFAULT_EXECUTION_ID_PROVIDER;
private Instrumentation instrumentation = new Instrumentation() {
};
private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE;
public Builder(GraphQLSchema graphQLSchema) {
this.graphQLSchema = graphQLSchema;
}
public Builder(GraphQL graphQL) {
this.graphQLSchema = graphQL.graphQLSchema;
this.executionStrategy = graphQL.executionStrategy;
this.idProvider = graphQL.idProvider;
this.instrumentation = graphQL.instrumentation;
}
public Builder schema(GraphQLSchema graphQLSchema) {
this.graphQLSchema = assertNotNull(graphQLSchema, () -> "GraphQLSchema must be non null");
return this;
}
public Builder executionStrategy(ExecutionStrategy executionStrategy) {
this.executionStrategy = assertNotNull(executionStrategy, () -> "ExecutionStrategy must be non null");
return this;
}
public Builder instrumentation(Instrumentation instrumentation) {
this.instrumentation = assertNotNull(instrumentation, () -> "Instrumentation must be non null");
return this;
}
public Builder preparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) {
this.preparsedDocumentProvider = assertNotNull(preparsedDocumentProvider, () -> "PreparsedDocumentProvider must be non null");
return this;
}
public Builder executionIdProvider(ExecutionIdProvider executionIdProvider) {
this.idProvider = assertNotNull(executionIdProvider, () -> "ExecutionIdProvider must be non null");
return this;
}
public GraphQL build() {
assertNotNull(graphQLSchema, () -> "graphQLSchema must be non null");
return new GraphQL(this);
}
}
}