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

graphql.execution.incremental.DeferredExecutionSupport Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.execution.incremental;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.Internal;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionStrategyParameters;
import graphql.execution.FieldValueInfo;
import graphql.execution.MergedField;
import graphql.execution.MergedSelectionSet;
import graphql.execution.instrumentation.Instrumentation;
import graphql.incremental.IncrementalPayload;
import graphql.util.FpKit;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * The purpose of this class hierarchy is to encapsulate most of the logic for deferring field execution, thus
 * keeping the main execution strategy code clean and focused on the main execution logic.
 * 

* The {@link NoOp} instance should be used when incremental support is not enabled for the current execution. The * methods in this class will return empty or no-op results, that should not impact the main execution. *

* {@link DeferredExecutionSupportImpl} is the actual implementation that will be used when incremental support is enabled. */ @Internal public interface DeferredExecutionSupport { boolean isDeferredField(MergedField mergedField); int deferredFieldsCount(); List getNonDeferredFieldNames(List allFieldNames); Set> createCalls(ExecutionStrategyParameters executionStrategyParameters); DeferredExecutionSupport NOOP = new DeferredExecutionSupport.NoOp(); /** * An implementation that actually executes the deferred fields. */ class DeferredExecutionSupportImpl implements DeferredExecutionSupport { private final ImmutableListMultimap deferredExecutionToFields; private final ImmutableSet deferredFields; private final ImmutableList nonDeferredFieldNames; private final ExecutionStrategyParameters parameters; private final ExecutionContext executionContext; private final BiFunction> resolveFieldWithInfoFn; private final Map>> dfCache = new HashMap<>(); public DeferredExecutionSupportImpl( MergedSelectionSet mergedSelectionSet, ExecutionStrategyParameters parameters, ExecutionContext executionContext, BiFunction> resolveFieldWithInfoFn ) { this.executionContext = executionContext; this.resolveFieldWithInfoFn = resolveFieldWithInfoFn; ImmutableListMultimap.Builder deferredExecutionToFieldsBuilder = ImmutableListMultimap.builder(); ImmutableSet.Builder deferredFieldsBuilder = ImmutableSet.builder(); ImmutableList.Builder nonDeferredFieldNamesBuilder = ImmutableList.builder(); mergedSelectionSet.getSubFields().values().forEach(mergedField -> { mergedField.getDeferredExecutions().forEach(de -> { deferredExecutionToFieldsBuilder.put(de, mergedField); deferredFieldsBuilder.add(mergedField); }); if (mergedField.getDeferredExecutions().isEmpty()) { nonDeferredFieldNamesBuilder.add(mergedField.getSingleField().getResultKey()); } }); this.deferredExecutionToFields = deferredExecutionToFieldsBuilder.build(); this.deferredFields = deferredFieldsBuilder.build(); this.parameters = parameters; this.nonDeferredFieldNames = nonDeferredFieldNamesBuilder.build(); } @Override public boolean isDeferredField(MergedField mergedField) { return deferredFields.contains(mergedField); } @Override public int deferredFieldsCount() { return deferredFields.size(); } @Override public List getNonDeferredFieldNames(List allFieldNames) { return this.nonDeferredFieldNames; } @Override public Set> createCalls(ExecutionStrategyParameters executionStrategyParameters) { return deferredExecutionToFields.keySet().stream() .map(deferredExecution -> this.createDeferredFragmentCall(deferredExecution, executionStrategyParameters)) .collect(Collectors.toSet()); } private DeferredFragmentCall createDeferredFragmentCall(DeferredExecution deferredExecution, ExecutionStrategyParameters executionStrategyParameters) { DeferredCallContext deferredCallContext = new DeferredCallContext(); List mergedFields = deferredExecutionToFields.get(deferredExecution); List>> calls = mergedFields.stream() .map(currentField -> this.createResultSupplier(currentField, deferredCallContext, executionStrategyParameters)) .collect(Collectors.toList()); return new DeferredFragmentCall( deferredExecution.getLabel(), this.parameters.getPath(), calls, deferredCallContext ); } private Supplier> createResultSupplier( MergedField currentField, DeferredCallContext deferredCallContext, ExecutionStrategyParameters executionStrategyParameters ) { Map fields = new LinkedHashMap<>(); fields.put(currentField.getResultKey(), currentField); ExecutionStrategyParameters callParameters = parameters.transform(builder -> { MergedSelectionSet mergedSelectionSet = MergedSelectionSet.newMergedSelectionSet().subFields(fields).build(); builder.deferredCallContext(deferredCallContext) .field(currentField) .fields(mergedSelectionSet) .path(parameters.getPath().segment(currentField.getResultKey())) .parent(null); // this is a break in the parent -> child chain - it's a new start effectively } ); Instrumentation instrumentation = executionContext.getInstrumentation(); instrumentation.beginDeferredField(executionContext.getInstrumentationState()); return dfCache.computeIfAbsent( currentField.getResultKey(), // The same field can be associated with multiple defer executions, so // we memoize the field resolution to avoid multiple calls to the same data fetcher key -> FpKit.interThreadMemoize(() -> { CompletableFuture fieldValueResult = resolveFieldWithInfoFn .apply(executionContext, callParameters); CompletableFuture executionResultCF = fieldValueResult .thenCompose(fvi -> { executionContext.getDataLoaderDispatcherStrategy().executeDeferredOnFieldValueInfo(fvi, executionStrategyParameters); return fvi .getFieldValueFuture() .thenApply(fv -> ExecutionResultImpl.newExecutionResult().data(fv).build()); } ); return executionResultCF .thenApply(executionResult -> new DeferredFragmentCall.FieldWithExecutionResult(currentField.getResultKey(), executionResult) ); } ) ); } } /** * A no-op implementation that should be used when incremental support is not enabled for the current execution. */ class NoOp implements DeferredExecutionSupport { @Override public boolean isDeferredField(MergedField mergedField) { return false; } @Override public int deferredFieldsCount() { return 0; } @Override public List getNonDeferredFieldNames(List allFieldNames) { return allFieldNames; } @Override public Set> createCalls(ExecutionStrategyParameters executionStrategyParameters) { return Collections.emptySet(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy