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

graphql.schema.DataFetchingFieldSelectionSetImpl Maven / Gradle / Ivy

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

import graphql.Internal;
import graphql.execution.ExecutionContext;
import graphql.execution.FieldCollector;
import graphql.execution.FieldCollectorParameters;
import graphql.execution.ValuesResolver;
import graphql.introspection.Introspection;
import graphql.language.Field;
import graphql.language.FragmentDefinition;

import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;

@Internal
public class DataFetchingFieldSelectionSetImpl implements DataFetchingFieldSelectionSet {

    private final static DataFetchingFieldSelectionSet NOOP = new DataFetchingFieldSelectionSet() {
        @Override
        public Map> get() {
            return emptyMap();
        }

        @Override
        public Map> getArguments() {
            return emptyMap();
        }

        @Override
        public Map getDefinitions() {
            return emptyMap();
        }

        @Override
        public boolean contains(String fieldGlobPattern) {
            return false;
        }

        @Override
        public SelectedField getField(String fieldName) {
            return null;
        }

        @Override
        public List getFields() {
            return emptyList();
        }

        @Override
        public List getFields(String fieldGlobPattern) {
            return emptyList();
        }
    };

    public static DataFetchingFieldSelectionSet newCollector(ExecutionContext executionContext, GraphQLType fieldType, List fields) {
        GraphQLType unwrappedType = GraphQLTypeUtil.unwrapAll(fieldType);
        if (unwrappedType instanceof GraphQLFieldsContainer) {
            return new DataFetchingFieldSelectionSetImpl(executionContext, (GraphQLFieldsContainer) unwrappedType, fields);
        } else {
            // we can only collect fields on object types and interfaces.  Scalars, Unions etc... cant be done.
            return NOOP;
        }
    }

    private static GraphQLObjectType asObjectTypeOrNull(GraphQLType unwrappedType) {
        return unwrappedType instanceof GraphQLObjectType ? (GraphQLObjectType) unwrappedType : null;
    }

    private final List parentFields;
    private final GraphQLSchema graphQLSchema;
    private final GraphQLFieldsContainer parentFieldType;
    private final Map variables;
    private final Map fragmentsByName;

    private Map> selectionSetFields;
    private Map selectionSetFieldDefinitions;
    private Map> selectionSetFieldArgs;
    private Set flattenedFields;

    private DataFetchingFieldSelectionSetImpl(ExecutionContext executionContext, GraphQLFieldsContainer parentFieldType, List parentFields) {
        this(parentFields, parentFieldType, executionContext.getGraphQLSchema(), executionContext.getVariables(), executionContext.getFragmentsByName());
    }

    public DataFetchingFieldSelectionSetImpl(List parentFields, GraphQLFieldsContainer parentFieldType, GraphQLSchema graphQLSchema, Map variables, Map fragmentsByName) {
        this.parentFields = parentFields;
        this.graphQLSchema = graphQLSchema;
        this.parentFieldType = parentFieldType;
        this.variables = variables;
        this.fragmentsByName = fragmentsByName;
    }

    @Override
    public Map> get() {
        // by having a .get() method we get lazy evaluation.
        computeValuesLazily();
        return selectionSetFields;
    }

    @Override
    public Map> getArguments() {
        computeValuesLazily();
        return selectionSetFieldArgs;
    }

    @Override
    public Map getDefinitions() {
        computeValuesLazily();
        return selectionSetFieldDefinitions;
    }

    @Override
    public boolean contains(String fieldGlobPattern) {
        if (fieldGlobPattern == null || fieldGlobPattern.isEmpty()) {
            return false;
        }
        computeValuesLazily();
        PathMatcher globMatcher = globMatcher(fieldGlobPattern);
        for (String flattenedField : flattenedFields) {
            Path path = Paths.get(flattenedField);
            if (globMatcher.matches(path)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public SelectedField getField(String fqFieldName) {
        computeValuesLazily();

        List fields = selectionSetFields.get(fqFieldName);
        if (fields == null) {
            return null;
        }
        GraphQLFieldDefinition fieldDefinition = selectionSetFieldDefinitions.get(fqFieldName);
        Map arguments = selectionSetFieldArgs.get(fqFieldName);
        arguments = arguments == null ? emptyMap() : arguments;
        return new SelectedFieldImpl(fqFieldName, fields, fieldDefinition, arguments);
    }

    @Override
    public List getFields(String fieldGlobPattern) {
        if (fieldGlobPattern == null || fieldGlobPattern.isEmpty()) {
            return emptyList();
        }
        computeValuesLazily();

        List targetNames = new ArrayList<>();
        PathMatcher globMatcher = globMatcher(fieldGlobPattern);
        for (String flattenedField : flattenedFields) {
            Path path = Paths.get(flattenedField);
            if (globMatcher.matches(path)) {
                targetNames.add(flattenedField);
            }
        }
        return targetNames.stream()
                .map(this::getField)
                .collect(Collectors.toList());
    }

    @Override
    public List getFields() {
        computeValuesLazily();

        return flattenedFields.stream()
                .map(this::getField)
                .collect(Collectors.toList());
    }

    private class SelectedFieldImpl implements SelectedField {
        private final String qualifiedName;
        private final String name;
        private final GraphQLFieldDefinition fieldDefinition;
        private final DataFetchingFieldSelectionSet selectionSet;
        private final Map arguments;

        private SelectedFieldImpl(String qualifiedName, List parentFields, GraphQLFieldDefinition fieldDefinition, Map arguments) {
            this.qualifiedName = qualifiedName;
            this.name = parentFields.get(0).getName();
            this.fieldDefinition = fieldDefinition;
            this.arguments = arguments;
            GraphQLType unwrappedType = GraphQLTypeUtil.unwrapAll(fieldDefinition.getType());
            if (unwrappedType instanceof GraphQLFieldsContainer) {
                this.selectionSet = new DataFetchingFieldSelectionSetImpl(parentFields, (GraphQLFieldsContainer) unwrappedType, graphQLSchema, variables, fragmentsByName);
            } else {
                this.selectionSet = NOOP;
            }
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public String getQualifiedName() {
            return qualifiedName;
        }

        @Override
        public GraphQLFieldDefinition getFieldDefinition() {
            return fieldDefinition;
        }

        @Override
        public Map getArguments() {
            return arguments;
        }

        @Override
        public DataFetchingFieldSelectionSet getSelectionSet() {
            return selectionSet;
        }
    }

    private PathMatcher globMatcher(String fieldGlobPattern) {
        return FileSystems.getDefault().getPathMatcher("glob:" + fieldGlobPattern);
    }

    private void computeValuesLazily() {
        synchronized (this) {
            if (selectionSetFields != null) {
                return;
            }

            selectionSetFields = new LinkedHashMap<>();
            selectionSetFieldDefinitions = new LinkedHashMap<>();
            selectionSetFieldArgs = new LinkedHashMap<>();
            flattenedFields = new LinkedHashSet<>();

            traverseFields(parentFields, parentFieldType, "");
        }
    }

    private final static String SEP = "/";


    private void traverseFields(List fieldList, GraphQLFieldsContainer parentFieldType, String fieldPrefix) {
        FieldCollector fieldCollector = new FieldCollector();
        ValuesResolver valuesResolver = new ValuesResolver();

        FieldCollectorParameters parameters = FieldCollectorParameters.newParameters()
                .schema(graphQLSchema)
                .objectType(asObjectTypeOrNull(parentFieldType))
                .fragments(fragmentsByName)
                .variables(variables)
                .build();

        Map> collectedFields = fieldCollector.collectFields(parameters, fieldList);
        for (Map.Entry> entry : collectedFields.entrySet()) {
            String fieldName = mkFieldName(fieldPrefix, entry.getKey());
            List collectedFieldList = entry.getValue();
            selectionSetFields.put(fieldName, collectedFieldList);

            Field field = collectedFieldList.get(0);
            GraphQLFieldDefinition fieldDef = Introspection.getFieldDef(graphQLSchema, parentFieldType, field.getName());
            GraphQLType unwrappedType = GraphQLTypeUtil.unwrapAll(fieldDef.getType());
            Map argumentValues = valuesResolver.getArgumentValues(fieldDef.getArguments(), field.getArguments(), variables);

            selectionSetFieldArgs.put(fieldName, argumentValues);
            selectionSetFieldDefinitions.put(fieldName, fieldDef);
            flattenedFields.add(fieldName);

            if (unwrappedType instanceof GraphQLFieldsContainer) {
                traverseFields(collectedFieldList, (GraphQLFieldsContainer) unwrappedType, fieldName);
            }
        }
    }

    private String mkFieldName(String fieldPrefix, String fieldName) {
        return (!fieldPrefix.isEmpty() ? fieldPrefix + SEP : "") + fieldName;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy