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

graphql.execution.nextgen.BatchedExecutionStrategy Maven / Gradle / Ivy

package graphql.execution.nextgen;

import graphql.Assert;
import graphql.Internal;
import graphql.execution.Async;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionStepInfo;
import graphql.execution.ExecutionStepInfoFactory;
import graphql.execution.MergedField;
import graphql.execution.nextgen.result.ExecutionResultMultiZipper;
import graphql.execution.nextgen.result.ExecutionResultNode;
import graphql.execution.nextgen.result.ExecutionResultZipper;
import graphql.execution.nextgen.result.NamedResultNode;
import graphql.execution.nextgen.result.ObjectExecutionResultNode;
import graphql.execution.nextgen.result.ObjectExecutionResultNode.RootExecutionResultNode;
import graphql.execution.nextgen.result.ResultNodesUtil;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

@Internal
public class BatchedExecutionStrategy implements ExecutionStrategy {

    ExecutionStepInfoFactory executionInfoFactory;
    ValueFetcher valueFetcher;
    ResultNodesCreator resultNodesCreator = new ResultNodesCreator();

    private final ExecutionContext executionContext;
    private FetchedValueAnalyzer fetchedValueAnalyzer;

    public BatchedExecutionStrategy(ExecutionContext executionContext) {
        this.executionContext = executionContext;
        this.fetchedValueAnalyzer = new FetchedValueAnalyzer(executionContext);
        this.valueFetcher = new ValueFetcher(executionContext);
        this.executionInfoFactory = new ExecutionStepInfoFactory();
    }

    public CompletableFuture execute(FieldSubSelection fieldSubSelection) {
        CompletableFuture rootMono = fetchSubSelection(fieldSubSelection).thenApply(RootExecutionResultNode::new);

        return rootMono
                .thenCompose(rootNode -> {
                    ExecutionResultMultiZipper unresolvedNodes = ResultNodesUtil.getUnresolvedNodes(rootNode);
                    return nextStep(unresolvedNodes);
                })
                .thenApply(finalZipper -> finalZipper.toRootNode())
                .thenApply(RootExecutionResultNode.class::cast);
    }

    private CompletableFuture> fetchSubSelection(FieldSubSelection fieldSubSelection) {
        CompletableFuture> fetchedValueAnalysisFlux = fetchAndAnalyze(fieldSubSelection);
        return fetchedValueAnalysisFluxToNodes(fetchedValueAnalysisFlux);
    }

    private CompletableFuture> fetchedValueAnalysisFluxToNodes(CompletableFuture> fetchedValueAnalysisFlux) {
        return Async.map(fetchedValueAnalysisFlux,
                fetchedValueAnalysis -> new NamedResultNode(fetchedValueAnalysis.getName(), resultNodesCreator.createResultNode(fetchedValueAnalysis)));
    }


    private CompletableFuture nextStep(ExecutionResultMultiZipper multizipper) {
        ExecutionResultMultiZipper nextUnresolvedNodes = ResultNodesUtil.getUnresolvedNodes(multizipper.toRootNode());
        if (nextUnresolvedNodes.getZippers().size() == 0) {
            return CompletableFuture.completedFuture(nextUnresolvedNodes);
        }
        List groups = groupNodesIntoBatches(nextUnresolvedNodes);
        return nextStepImpl(groups).thenCompose(this::nextStep);
    }

    // all multizipper have the same root
    private CompletableFuture nextStepImpl(List unresolvedNodes) {
        Assert.assertNotEmpty(unresolvedNodes, "unresolvedNodes can't be empty");
        ExecutionResultNode commonRoot = unresolvedNodes.get(0).getCommonRoot();

        CompletableFuture>> listListCF = Async.flatMap(unresolvedNodes,
                executionResultMultiZipper -> fetchAndAnalyze(executionResultMultiZipper.getZippers()));

        return Common.flatList(listListCF)
                .thenApply(zippers -> new ExecutionResultMultiZipper(commonRoot, zippers));

    }

    private List groupNodesIntoBatches(ExecutionResultMultiZipper unresolvedZipper) {
        Map, List> zipperBySubSelection = unresolvedZipper.getZippers().stream()
                .collect(groupingBy(executionResultZipper -> executionResultZipper.getCurNode().getFetchedValueAnalysis().getFieldSubSelection().getSubFields()));

        return zipperBySubSelection
                .entrySet()
                .stream()
                .map(entry -> new ExecutionResultMultiZipper(unresolvedZipper.getCommonRoot(), entry.getValue()))
                .collect(Collectors.toList());
    }

    //constrain: all fieldSubSelections have the same fields
    private CompletableFuture> fetchAndAnalyze(List unresolvedNodes) {
        Assert.assertTrue(unresolvedNodes.size() > 0, "unresolvedNodes can't be empty");

        List fieldSubSelections = unresolvedNodes.stream()
                .map(zipper -> zipper.getCurNode().getFetchedValueAnalysis().getFieldSubSelection())
                .collect(Collectors.toList());
        List sources = fieldSubSelections.stream().map(fieldSubSelection -> fieldSubSelection.getSource()).collect(Collectors.toList());

        // each field in the subSelection has n sources as input
        List>> fetchedValues = fieldSubSelections
                .get(0)
                .getSubFields()
                .entrySet()
                .stream()
                .map(entry -> {
                    MergedField sameFields = entry.getValue();
                    String name = entry.getKey();

                    List newExecutionStepInfos = fieldSubSelections.stream().map(executionResultNode -> {
                        return executionInfoFactory.newExecutionStepInfoForSubField(executionContext, sameFields, executionResultNode.getExecutionStepInfo());
                    }).collect(Collectors.toList());

                    CompletableFuture> fetchedValueAnalyzis = valueFetcher
                            .fetchBatchedValues(sources, sameFields, newExecutionStepInfos)
                            .thenApply(fetchValue -> analyseValues(fetchValue, name, sameFields, newExecutionStepInfos));
                    return fetchedValueAnalyzis;
                })
                .collect(toList());

        return Async.each(fetchedValues).thenApply(fetchedValuesMatrix -> {
            List result = new ArrayList<>();
            List> newChildsPerNode = Common.transposeMatrix(fetchedValuesMatrix);

            for (int i = 0; i < newChildsPerNode.size(); i++) {
                ExecutionResultZipper unresolvedNodeZipper = unresolvedNodes.get(i);
                List fetchedValuesForNode = newChildsPerNode.get(i);
                ExecutionResultZipper resolvedZipper = resolvedZipper(unresolvedNodeZipper, fetchedValuesForNode);
                result.add(resolvedZipper);
            }
            return result;
        });
    }

    private ExecutionResultZipper resolvedZipper(ExecutionResultZipper unresolvedNodeZipper, List fetchedValuesForNode) {
        ObjectExecutionResultNode.UnresolvedObjectResultNode unresolvedNode = (ObjectExecutionResultNode.UnresolvedObjectResultNode) unresolvedNodeZipper.getCurNode();
        Map newChildren = fetchedValueAnalysisToNodes(fetchedValuesForNode);

        ObjectExecutionResultNode newNode = unresolvedNode.withChildren(newChildren);
        return unresolvedNodeZipper.withNode(newNode);
    }

    private Map fetchedValueAnalysisToNodes(List fetchedValueAnalysisFlux) {
        Map result = new LinkedHashMap<>();
        fetchedValueAnalysisFlux.forEach(fetchedValueAnalysis -> {
            result.put(fetchedValueAnalysis.getName(), resultNodesCreator.createResultNode(fetchedValueAnalysis));
        });
        return result;
    }


    // only used for the root sub selection atm
    private CompletableFuture> fetchAndAnalyze(FieldSubSelection fieldSubSelection) {
        List> fetchedValues = fieldSubSelection.getSubFields().entrySet().stream()
                .map(entry -> {
                    MergedField sameFields = entry.getValue();
                    String name = entry.getKey();
                    ExecutionStepInfo newExecutionStepInfo = executionInfoFactory.newExecutionStepInfoForSubField(executionContext, sameFields, fieldSubSelection.getExecutionStepInfo());
                    return valueFetcher
                            .fetchValue(fieldSubSelection.getSource(), sameFields, newExecutionStepInfo)
                            .thenApply(fetchValue -> analyseValue(fetchValue, name, sameFields, newExecutionStepInfo));
                })
                .collect(toList());

        return Async.each(fetchedValues);
    }

    // only used for the root sub selection atm
    private FetchedValueAnalysis analyseValue(FetchedValue fetchedValue, String name, MergedField field, ExecutionStepInfo executionInfo) {
        FetchedValueAnalysis fetchedValueAnalysis = fetchedValueAnalyzer.analyzeFetchedValue(fetchedValue, name, field, executionInfo);
        return fetchedValueAnalysis;
    }


    private List analyseValues(List fetchedValues, String name, MergedField field, List executionInfos) {
        List result = new ArrayList<>();
        for (int i = 0; i < fetchedValues.size(); i++) {
            FetchedValue fetchedValue = fetchedValues.get(i);
            ExecutionStepInfo executionStepInfo = executionInfos.get(i);
            FetchedValueAnalysis fetchedValueAnalysis = fetchedValueAnalyzer.analyzeFetchedValue(fetchedValue, name, field, executionStepInfo);
            result.add(fetchedValueAnalysis);
        }
        return result;
    }
}