All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.nadel.Nadel Maven / Gradle / Ivy

There is a newer version: 2021-03-26T09-09-21-fabd441
Show newest version
package graphql.nadel;

import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.ParseAndValidateResult;
import graphql.PublicApi;
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.preparsed.NoOpPreparsedDocumentProvider;
import graphql.execution.preparsed.PreparsedDocumentEntry;
import graphql.execution.preparsed.PreparsedDocumentProvider;
import graphql.language.Document;
import graphql.nadel.dsl.CommonDefinition;
import graphql.nadel.dsl.ServiceDefinition;
import graphql.nadel.dsl.StitchingDsl;
import graphql.nadel.engine.Execution;
import graphql.nadel.hooks.ServiceExecutionHooks;
import graphql.nadel.instrumentation.NadelInstrumentation;
import graphql.nadel.instrumentation.parameters.NadelInstrumentationCreateStateParameters;
import graphql.nadel.instrumentation.parameters.NadelInstrumentationQueryExecutionParameters;
import graphql.nadel.instrumentation.parameters.NadelNadelInstrumentationQueryValidationParameters;
import graphql.nadel.introspection.DefaultIntrospectionRunner;
import graphql.nadel.introspection.IntrospectionRunner;
import graphql.nadel.schema.NeverWiringFactory;
import graphql.nadel.schema.OverallSchemaGenerator;
import graphql.nadel.schema.SchemaTransformationHook;
import graphql.nadel.schema.UnderlyingSchemaGenerator;
import graphql.nadel.util.LogKit;
import graphql.parser.InvalidSyntaxException;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.ScalarInfo;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.WiringFactory;
import graphql.validation.ValidationError;
import graphql.validation.Validator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.UnaryOperator;

import static graphql.execution.instrumentation.DocumentAndVariables.newDocumentAndVariables;
import static graphql.nadel.util.Util.buildServiceRegistry;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

@PublicApi
public class Nadel {

    private static final Logger logNotSafe = LogKit.getNotPrivacySafeLogger(Nadel.class);
    private static final Logger log = LoggerFactory.getLogger(Nadel.class);

    private final StitchingDsl stitchingDsl;
    private final ServiceExecutionFactory serviceExecutionFactory;
    private final NSDLParser NSDLParser = new NSDLParser();
    private final List services;
    private final GraphQLSchema overallSchema;
    private final NadelInstrumentation instrumentation;
    private final ServiceExecutionHooks serviceExecutionHooks;
    private final PreparsedDocumentProvider preparsedDocumentProvider;
    private final ExecutionIdProvider executionIdProvider;
    private final IntrospectionRunner introspectionRunner;
    private final DefinitionRegistry commonTypes;
    private final WiringFactory overallWiringFactory;
    private final WiringFactory underlyingWiringFactory;
    private final SchemaTransformationHook schemaTransformationHook;
    private final OverallSchemaGenerator overallSchemaGenerator = new OverallSchemaGenerator();

    private Nadel(Reader nsdl,
                  ServiceExecutionFactory serviceExecutionFactory,
                  NadelInstrumentation instrumentation,
                  PreparsedDocumentProvider preparsedDocumentProvider,
                  ExecutionIdProvider executionIdProvider,
                  IntrospectionRunner introspectionRunner,
                  ServiceExecutionHooks serviceExecutionHooks,
                  WiringFactory overallWiringFactory,
                  WiringFactory underlyingWiringFactory,
                  SchemaTransformationHook schemaTransformationHook) {
        this.serviceExecutionFactory = serviceExecutionFactory;
        this.instrumentation = instrumentation;
        this.serviceExecutionHooks = serviceExecutionHooks;
        this.preparsedDocumentProvider = preparsedDocumentProvider;
        this.executionIdProvider = executionIdProvider;
        this.schemaTransformationHook = schemaTransformationHook;

        this.stitchingDsl = this.NSDLParser.parseDSL(nsdl);
        this.introspectionRunner = introspectionRunner;
        this.overallWiringFactory = overallWiringFactory;
        this.underlyingWiringFactory = underlyingWiringFactory;
        this.services = createServices();
        this.commonTypes = createCommonTypes();
        this.overallSchema = createOverallSchema();
    }

    private DefinitionRegistry createCommonTypes() {
        CommonDefinition commonDefinition = stitchingDsl.getCommonDefinition();
        return buildServiceRegistry(commonDefinition);
    }

    private List createServices() {
        List serviceDefinitions = stitchingDsl.getServiceDefinitions();

        UnderlyingSchemaGenerator underlyingSchemaGenerator = new UnderlyingSchemaGenerator();

        List serviceList = new ArrayList<>();
        for (ServiceDefinition serviceDefinition : serviceDefinitions) {
            String serviceName = serviceDefinition.getName();
            ServiceExecution serviceExecution = this.serviceExecutionFactory.getServiceExecution(serviceName);
            TypeDefinitionRegistry underlyingTypeDefinitions = this.serviceExecutionFactory.getUnderlyingTypeDefinitions(serviceName);

            GraphQLSchema underlyingSchema = underlyingSchemaGenerator
                    .buildUnderlyingSchema(serviceName, underlyingTypeDefinitions, underlyingWiringFactory);
            DefinitionRegistry definitionRegistry = buildServiceRegistry(serviceDefinition);

            Service service = new Service(serviceName, underlyingSchema, serviceExecution, serviceDefinition, definitionRegistry);
            serviceList.add(service);
        }

        return serviceList;

    }

    private GraphQLSchema createOverallSchema() {
        List registries = this.services.stream()
                .map(Service::getDefinitionRegistry)
                .collect(toList());
        GraphQLSchema schema = overallSchemaGenerator.buildOverallSchema(registries, commonTypes, overallWiringFactory);

        GraphQLSchema newSchema = schemaTransformationHook.apply(schema);

        //
        // make sure that the overall schema has the standard scalars in it since he underlying may use them EVEN if the overall does not
        // make direct use of them, we still have to map between them
        return newSchema.transform(builder -> ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS.forEach(builder::additionalType));
    }

    public List getServices() {
        return services;
    }

    public GraphQLSchema getOverallSchema() {
        return overallSchema;
    }

    public CompletableFuture execute(NadelExecutionInput.Builder nadelExecutionInput) {
        return execute(nadelExecutionInput.build());
    }

    public CompletableFuture execute(UnaryOperator builderFunction) {
        return execute(builderFunction.apply(NadelExecutionInput.newNadelExecutionInput()).build());
    }

    public CompletableFuture execute(NadelExecutionInput nadelExecutionInput) {
        long startTime = System.currentTimeMillis();
        ExecutionInput executionInput = ExecutionInput.newExecutionInput()
                .query(nadelExecutionInput.getQuery())
                .operationName(nadelExecutionInput.getOperationName())
                .context(nadelExecutionInput.getContext())
                .variables(nadelExecutionInput.getVariables())
                .executionId(nadelExecutionInput.getExecutionId())
                .build();

        NadelExecutionParams nadelExecutionParams = new NadelExecutionParams(nadelExecutionInput.getArtificialFieldsUUID());

        InstrumentationState instrumentationState = instrumentation.createState(new NadelInstrumentationCreateStateParameters(overallSchema, executionInput));
        NadelInstrumentationQueryExecutionParameters instrumentationParameters = new NadelInstrumentationQueryExecutionParameters(executionInput, overallSchema, instrumentationState);
        try {
            logNotSafe.debug("Executing request. operation name: '{}'. query: '{}'. variables '{}'", executionInput.getOperationName(), executionInput.getQuery(), executionInput.getVariables());

            NadelInstrumentationQueryExecutionParameters inputInstrumentationParameters = new NadelInstrumentationQueryExecutionParameters(executionInput, overallSchema, instrumentationState);
            executionInput = instrumentation.instrumentExecutionInput(executionInput, inputInstrumentationParameters);

            InstrumentationContext executionInstrumentation = instrumentation.beginQueryExecution(instrumentationParameters);

            CompletableFuture executionResult = parseValidateAndExecute(executionInput, overallSchema, instrumentationState, nadelExecutionParams);
            //
            // finish up instrumentation
            executionResult = executionResult.whenComplete(executionInstrumentation::onCompleted);
            //
            // allow instrumentation to tweak the result
            executionResult = executionResult.thenCompose(result -> instrumentation.instrumentExecutionResult(result, instrumentationParameters));
            return executionResult.whenComplete((executionResult1, throwable) -> {
                long elapsedTime = System.currentTimeMillis() - startTime;
                log.debug("Finished execution in {} ms, executionId: {}", elapsedTime, nadelExecutionInput.getExecutionId());
            });
        } catch (AbortExecutionException abortException) {
            return instrumentation.instrumentExecutionResult(abortException.toExecutionResult(), instrumentationParameters);
        }
    }

    private CompletableFuture parseValidateAndExecute(ExecutionInput executionInput,
                                                                       GraphQLSchema graphQLSchema,
                                                                       InstrumentationState instrumentationState,
                                                                       NadelExecutionParams nadelExecutionParams) {
        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);
        };
        PreparsedDocumentEntry preparsedDoc = preparsedDocumentProvider.getDocument(executionInput, computeFunction);
        if (preparsedDoc.hasErrors()) {
            return CompletableFuture.completedFuture(new ExecutionResultImpl(preparsedDoc.getErrors()));
        }
        return executeImpl(executionInputRef.get(), preparsedDoc.getDocument(), instrumentationState, nadelExecutionParams);
    }

    private PreparsedDocumentEntry parseAndValidate(AtomicReference executionInputRef, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {

        ExecutionInput executionInput = executionInputRef.get();
        String query = executionInput.getQuery();

        logNotSafe.debug("Parsing query: '{}'...", query);
        ParseAndValidateResult parseResult = parse(executionInput, graphQLSchema, instrumentationState);
        if (parseResult.isFailure()) {
            logNotSafe.warn("Query failed to 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);

            logNotSafe.debug("Validating query: '{}'", query);
            final List errors = validate(executionInput, document, graphQLSchema, instrumentationState);
            if (!errors.isEmpty()) {
                logNotSafe.warn("Query failed to validate : '{}' because of {} ", query, errors);
                return new PreparsedDocumentEntry(errors);
            }

            return new PreparsedDocumentEntry(document);
        }
    }

    private ParseAndValidateResult parse(ExecutionInput executionInput, GraphQLSchema graphQLSchema, InstrumentationState instrumentationState) {
        NadelInstrumentationQueryExecutionParameters parameters = new NadelInstrumentationQueryExecutionParameters(executionInput, graphQLSchema, instrumentationState);
        InstrumentationContext parseInstrumentation = instrumentation.beginParse(parameters);

        Document document;
        DocumentAndVariables documentAndVariables;
        try {
            document = new NadelGraphQLParser().parseDocument(executionInput.getQuery());
            documentAndVariables = newDocumentAndVariables()
                    .document(document).variables(executionInput.getVariables()).build();
            documentAndVariables = instrumentation.instrumentDocumentAndVariables(documentAndVariables, parameters);
        } catch (InvalidSyntaxException e) {
            parseInstrumentation.onCompleted(null, e);
            return ParseAndValidateResult.newResult().syntaxException(e).build();
        }

        parseInstrumentation.onCompleted(documentAndVariables.getDocument(), null);
        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 NadelNadelInstrumentationQueryValidationParameters(executionInput, document, graphQLSchema, instrumentationState));

        Validator validator = new Validator();
        List validationErrors = validator.validateDocument(graphQLSchema, document);

        validationCtx.onCompleted(validationErrors, null);
        return validationErrors;
    }

    private CompletableFuture executeImpl(ExecutionInput executionInput,
                                                           Document document,
                                                           InstrumentationState instrumentationState,
                                                           NadelExecutionParams nadelExecutionParams) {

        String query = executionInput.getQuery();
        String operationName = executionInput.getOperationName();
        Object context = executionInput.getContext();

        ExecutionId executionId = executionInput.getExecutionId();
        if (executionId == null) {
            executionId = executionIdProvider.provide(query, operationName, context);
        }

        if (executionInput.getContext() instanceof BenchmarkContext) {
            BenchmarkContext.ExecutionArgs executionArgs = ((BenchmarkContext) executionInput.getContext()).executionArgs;
            executionArgs.services = getServices();
            executionArgs.overallSchema = overallSchema;
            executionArgs.instrumentation = instrumentation;
            executionArgs.introspectionRunner = introspectionRunner;
            executionArgs.serviceExecutionHooks = serviceExecutionHooks;
            executionArgs.context = executionInput.getContext();
            executionArgs.executionInput = executionInput;
            executionArgs.document = document;
            executionArgs.executionId = executionId;
            executionArgs.instrumentationState = instrumentationState;
            executionArgs.nadelExecutionParams = nadelExecutionParams;
        }

        Execution execution = new Execution(getServices(), overallSchema, instrumentation, introspectionRunner, serviceExecutionHooks, executionInput.getContext());

        return execution.execute(executionInput, document, executionId, instrumentationState, nadelExecutionParams);
    }

    /**
     * @return a builder of Nadel objects
     */
    public static Builder newNadel() {
        return new Builder();
    }

    public static class Builder {
        private Reader nsdl;
        private ServiceExecutionFactory serviceExecutionFactory;
        private NadelInstrumentation instrumentation = new NadelInstrumentation() {
        };
        private ServiceExecutionHooks serviceExecutionHooks = new ServiceExecutionHooks() {
        };
        private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE;
        private ExecutionIdProvider executionIdProvider = ExecutionIdProvider.DEFAULT_EXECUTION_ID_PROVIDER;
        private IntrospectionRunner introspectionRunner = new DefaultIntrospectionRunner();
        private WiringFactory overallWiringFactory = new NeverWiringFactory();
        private WiringFactory underlyingWiringFactory = new NeverWiringFactory();
        private SchemaTransformationHook schemaTransformationHook = SchemaTransformationHook.IDENTITY;


        public Builder dsl(Reader nsdl) {
            this.nsdl = requireNonNull(nsdl);
            return this;
        }

        public Builder dsl(String nsdl) {
            return dsl(new StringReader(requireNonNull(nsdl)));
        }

        public Builder serviceExecutionFactory(ServiceExecutionFactory serviceExecutionFactory) {
            this.serviceExecutionFactory = serviceExecutionFactory;
            return this;
        }

        public Builder instrumentation(NadelInstrumentation instrumentation) {
            this.instrumentation = requireNonNull(instrumentation);
            return this;
        }

        public Builder preparsedDocumentProvider(PreparsedDocumentProvider preparsedDocumentProvider) {
            this.preparsedDocumentProvider = requireNonNull(preparsedDocumentProvider);
            return this;
        }

        public Builder executionIdProvider(ExecutionIdProvider executionIdProvider) {
            this.executionIdProvider = requireNonNull(executionIdProvider);
            return this;
        }

        public Builder introspectionRunner(IntrospectionRunner introspectionRunner) {
            this.introspectionRunner = requireNonNull(introspectionRunner);
            return this;
        }

        public Builder serviceExecutionHooks(ServiceExecutionHooks serviceExecutionHooks) {
            this.serviceExecutionHooks = requireNonNull(serviceExecutionHooks);
            return this;
        }

        public Builder overallWiringFactory(WiringFactory wiringFactory) {
            this.overallWiringFactory = requireNonNull(wiringFactory);
            return this;
        }

        public Builder underlyingWiringFactory(WiringFactory wiringFactory) {
            this.underlyingWiringFactory = requireNonNull(wiringFactory);
            return this;
        }

        public Builder schemaTransformationHook(SchemaTransformationHook hook) {
            this.schemaTransformationHook = hook;
            return this;
        }

        public Nadel build() {
            return new Nadel(
                    nsdl,
                    serviceExecutionFactory,
                    instrumentation,
                    preparsedDocumentProvider,
                    executionIdProvider,
                    introspectionRunner,
                    serviceExecutionHooks,
                    overallWiringFactory,
                    underlyingWiringFactory,
                    schemaTransformationHook);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy