From 09fa9f691433a81f454562af66ee8df3ac88315f Mon Sep 17 00:00:00 2001 From: Todd Berman Date: Sun, 6 Sep 2015 14:24:37 -0700 Subject: [PATCH 1/2] Initial execution strategy work --- src/main/java/graphql/GraphQL.java | 9 +- .../java/graphql/execution/Execution.java | 174 +----------------- .../graphql/execution/ExecutionContext.java | 9 + .../execution/ExecutionContextBuilder.java | 3 +- .../graphql/execution/ExecutionStrategy.java | 142 ++++++++++++++ .../ExecutorServiceExecutionStrategy.java | 52 ++++++ .../execution/SimpleExecutionStrategy.java | 23 +++ 7 files changed, 243 insertions(+), 169 deletions(-) create mode 100644 src/main/java/graphql/execution/ExecutionStrategy.java create mode 100644 src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java create mode 100644 src/main/java/graphql/execution/SimpleExecutionStrategy.java diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index d9d4da6ed5..828566e35c 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -2,6 +2,7 @@ import graphql.execution.Execution; +import graphql.execution.ExecutionStrategy; import graphql.language.Document; import graphql.language.SourceLocation; import graphql.parser.Parser; @@ -24,7 +25,7 @@ public class GraphQL { private final GraphQLSchema graphQLSchema; - private final ExecutorService executorService; + private final ExecutionStrategy executionStrategy; private static final Logger log = LoggerFactory.getLogger(GraphQL.class); @@ -33,9 +34,9 @@ public GraphQL(GraphQLSchema graphQLSchema) { } - public GraphQL(GraphQLSchema graphQLSchema, ExecutorService executorService) { + public GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy executionStrategy) { this.graphQLSchema = graphQLSchema; - this.executorService = executorService; + this.executionStrategy = executionStrategy; } public ExecutionResult execute(String requestString) { @@ -72,7 +73,7 @@ public ExecutionResult execute(String requestString, String operationName, Objec if (validationErrors.size() > 0) { return new ExecutionResultImpl(validationErrors); } - Execution execution = new Execution(executorService); + Execution execution = new Execution(executionStrategy); return execution.execute(graphQLSchema, context, document, operationName, arguments); } diff --git a/src/main/java/graphql/execution/Execution.java b/src/main/java/graphql/execution/Execution.java index b687a946a7..a7b245d8d6 100644 --- a/src/main/java/graphql/execution/Execution.java +++ b/src/main/java/graphql/execution/Execution.java @@ -25,22 +25,20 @@ public class Execution { - private FieldCollector fieldCollector; - private ValuesResolver valuesResolver; - private ExecutorService executorService; + private FieldCollector fieldCollector = new FieldCollector(); + private ExecutionStrategy strategy; + public Execution(ExecutionStrategy strategy) { + this.strategy = strategy; - private static final Logger log = LoggerFactory.getLogger(Execution.class); - - public Execution(ExecutorService executorService) { - fieldCollector = new FieldCollector(); - valuesResolver = new ValuesResolver(); - this.executorService = executorService; + if (this.strategy == null) { + this.strategy = new SimpleExecutionStrategy(); + } } public ExecutionResult execute(GraphQLSchema graphQLSchema, Object root, Document document, String operationName, Map args) { ExecutionContextBuilder executionContextBuilder = new ExecutionContextBuilder(new ValuesResolver()); - ExecutionContext executionContext = executionContextBuilder.build(graphQLSchema, root, document, operationName, args); + ExecutionContext executionContext = executionContextBuilder.build(graphQLSchema, strategy, root, document, operationName, args); return executeOperation(executionContext, root, executionContext.getOperationDefinition()); } @@ -65,162 +63,10 @@ private ExecutionResult executeOperation( Map> fields = new LinkedHashMap<>(); fieldCollector.collectFields(executionContext, operationRootType, operationDefinition.getSelectionSet(), new ArrayList(), fields); - Map result; if (operationDefinition.getOperation() == OperationDefinition.Operation.MUTATION) { - result = executeFieldsSerially(executionContext, operationRootType, root, fields); + return new SimpleExecutionStrategy().execute(executionContext, operationRootType, root, fields); } else { - result = executeFieldsParallel(executionContext, operationRootType, root, fields); + return strategy.execute(executionContext, operationRootType, root, fields); } - return new ExecutionResultImpl(result, executionContext.getErrors()); - } - - private Map executeFieldsSerially(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map> fields) { - Map results = new LinkedHashMap<>(); - for (String fieldName : fields.keySet()) { - List fieldList = fields.get(fieldName); - Object resolvedResult = resolveField(executionContext, parentType, source, fieldList); - results.put(fieldName, resolvedResult); - } - return results; - } - - - private Map executeFieldsParallel(final ExecutionContext executionContext, final GraphQLObjectType parentType, final Object source, Map> fields) { - if (executorService == null) return executeFieldsSerially(executionContext, parentType, source, fields); - - Map> futures = new LinkedHashMap<>(); - for (String fieldName : fields.keySet()) { - final List fieldList = fields.get(fieldName); - Callable resolveField = new Callable() { - @Override - public Object call() throws Exception { - return resolveField(executionContext, parentType, source, fieldList); - - } - }; - futures.put(fieldName, executorService.submit(resolveField)); - } - try { - Map results = new LinkedHashMap<>(); - for (String fieldName : futures.keySet()) { - results.put(fieldName, futures.get(fieldName).get()); - } - return results; - } catch (InterruptedException | ExecutionException e) { - throw new GraphQLException(e); - } - } - - private Object resolveField(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, List fields) { - GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, fields.get(0)); - if (fieldDef == null) return null; - - Map argumentValues = valuesResolver.getArgumentValues(fieldDef.getArguments(), fields.get(0).getArguments(), executionContext.getVariables()); - DataFetchingEnvironment environment = new DataFetchingEnvironment( - source, - argumentValues, - executionContext.getRoot(), - fields, - fieldDef.getType(), - parentType, - executionContext.getGraphQLSchema() - ); - - Object resolvedValue = null; - try { - resolvedValue = fieldDef.getDataFetcher().get(environment); - } catch (Exception e) { - log.info("Exception while fetching data", e); - executionContext.addError(new ExceptionWhileDataFetching(e)); - } - - - return completeValue(executionContext, fieldDef.getType(), fields, resolvedValue); - } - - private Object completeValue(ExecutionContext executionContext, GraphQLType fieldType, List fields, Object result) { - if (fieldType instanceof GraphQLNonNull) { - GraphQLNonNull graphQLNonNull = (GraphQLNonNull) fieldType; - Object completed = completeValue(executionContext, graphQLNonNull.getWrappedType(), fields, result); - if (completed == null) throw new GraphQLException("Cannot return null for non-nullable type: " + fields); - return completed; - - } else if (result == null) { - return null; - } else if (fieldType instanceof GraphQLList) { - return completeValueForList(executionContext, (GraphQLList) fieldType, fields, (List) result); - } else if (fieldType instanceof GraphQLScalarType) { - return completeValueForScalar((GraphQLScalarType) fieldType, result); - } else if (fieldType instanceof GraphQLEnumType) { - return completeValueForEnum((GraphQLEnumType) fieldType, result); - } - - - GraphQLObjectType resolvedType; - if (fieldType instanceof GraphQLInterfaceType) { - resolvedType = resolveType((GraphQLInterfaceType) fieldType, result); - } else if (fieldType instanceof GraphQLUnionType) { - resolvedType = resolveType((GraphQLUnionType) fieldType, result); - } else { - resolvedType = (GraphQLObjectType) fieldType; - } - - Map> subFields = new LinkedHashMap<>(); - List visitedFragments = new ArrayList<>(); - for (Field field : fields) { - if (field.getSelectionSet() == null) continue; - fieldCollector.collectFields(executionContext, resolvedType, field.getSelectionSet(), visitedFragments, subFields); - } - return executeFieldsParallel(executionContext, resolvedType, result, subFields); - } - - private GraphQLObjectType resolveType(GraphQLInterfaceType graphQLInterfaceType, Object value) { - GraphQLObjectType result = graphQLInterfaceType.getTypeResolver().getType(value); - if (result == null) throw new GraphQLException("could not determine type"); - return result; - } - - private GraphQLObjectType resolveType(GraphQLUnionType graphQLUnionType, Object value) { - GraphQLObjectType result = graphQLUnionType.getTypeResolver().getType(value); - if (result == null) throw new GraphQLException("could not determine type"); - return result; - } - - - private Object completeValueForEnum(GraphQLEnumType enumType, Object result) { - return enumType.getCoercing().coerce(result); - } - - private Object completeValueForScalar(GraphQLScalarType scalarType, Object result) { - return scalarType.getCoercing().coerce(result); - } - - private Object completeValueForList(ExecutionContext executionContext, GraphQLList fieldType, List fields, List result) { - List completedResults = new ArrayList<>(); - for (Object item : result) { - completedResults.add(completeValue(executionContext, fieldType.getWrappedType(), fields, item)); - } - return completedResults; - } - - private GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLObjectType parentType, Field field) { - if (schema.getQueryType() == parentType) { - if (field.getName().equals(SchemaMetaFieldDef.getName())) { - return SchemaMetaFieldDef; - } - if (field.getName().equals(TypeMetaFieldDef.getName())) { - return TypeMetaFieldDef; - } - } - if (field.getName().equals(TypeNameMetaFieldDef.getName())) { - return TypeNameMetaFieldDef; - } - - GraphQLFieldDefinition fieldDefinition = parentType.getFieldDefinition(field.getName()); - if (fieldDefinition == null) throw new GraphQLException("unknown field " + field.getName()); - return fieldDefinition; - } - - } diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index 05a7130052..6a30b0f555 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -14,6 +14,7 @@ public class ExecutionContext { private GraphQLSchema graphQLSchema; + private ExecutionStrategy executionStrategy; private Map fragmentsByName = new LinkedHashMap<>(); private OperationDefinition operationDefinition; private Map variables = new LinkedHashMap<>(); @@ -71,4 +72,12 @@ public void addError(GraphQLError error) { public List getErrors(){ return errors; } + + public ExecutionStrategy getExecutionStrategy() { + return executionStrategy; + } + + public void setExecutionStrategy(ExecutionStrategy executionStrategy) { + this.executionStrategy = executionStrategy; + } } diff --git a/src/main/java/graphql/execution/ExecutionContextBuilder.java b/src/main/java/graphql/execution/ExecutionContextBuilder.java index 1e44102948..cde4018cc8 100644 --- a/src/main/java/graphql/execution/ExecutionContextBuilder.java +++ b/src/main/java/graphql/execution/ExecutionContextBuilder.java @@ -18,7 +18,7 @@ public ExecutionContextBuilder(ValuesResolver valuesResolver) { this.valuesResolver = valuesResolver; } - public ExecutionContext build(GraphQLSchema graphQLSchema, Object root, Document document, String operationName, Map args) { + public ExecutionContext build(GraphQLSchema graphQLSchema, ExecutionStrategy executionStrategy, Object root, Document document, String operationName, Map args) { Map fragmentsByName = new LinkedHashMap<>(); Map operationsByName = new LinkedHashMap<>(); @@ -48,6 +48,7 @@ public ExecutionContext build(GraphQLSchema graphQLSchema, Object root, Document ExecutionContext executionContext = new ExecutionContext(); executionContext.setGraphQLSchema(graphQLSchema); + executionContext.setExecutionStrategy(executionStrategy); executionContext.setOperationDefinition(operation); executionContext.setRoot(root); executionContext.setFragmentsByName(fragmentsByName); diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java new file mode 100644 index 0000000000..f1d291d09d --- /dev/null +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -0,0 +1,142 @@ +package graphql.execution; + +import graphql.ExceptionWhileDataFetching; +import graphql.ExecutionResult; +import graphql.GraphQLException; +import graphql.language.Field; +import graphql.schema.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static graphql.introspection.Introspection.SchemaMetaFieldDef; +import static graphql.introspection.Introspection.TypeMetaFieldDef; +import static graphql.introspection.Introspection.TypeNameMetaFieldDef; + +public abstract class ExecutionStrategy { + private static final Logger log = LoggerFactory.getLogger(ExecutionStrategy.class); + + protected ValuesResolver valuesResolver = new ValuesResolver(); + protected FieldCollector fieldCollector = new FieldCollector(); + + abstract ExecutionResult execute(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map> fields); + + protected Object resolveField(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, List fields) { + GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, fields.get(0)); + if (fieldDef == null) return null; + + Map argumentValues = valuesResolver.getArgumentValues(fieldDef.getArguments(), fields.get(0).getArguments(), executionContext.getVariables()); + DataFetchingEnvironment environment = new DataFetchingEnvironment( + source, + argumentValues, + executionContext.getRoot(), + fields, + fieldDef.getType(), + parentType, + executionContext.getGraphQLSchema() + ); + + Object resolvedValue = null; + try { + resolvedValue = fieldDef.getDataFetcher().get(environment); + } catch (Exception e) { + log.info("Exception while fetching data", e); + executionContext.addError(new ExceptionWhileDataFetching(e)); + } + + return completeValue(executionContext, fieldDef.getType(), fields, resolvedValue); + } + + protected Object completeValue(ExecutionContext executionContext, GraphQLType fieldType, List fields, Object result) { + if (fieldType instanceof GraphQLNonNull) { + GraphQLNonNull graphQLNonNull = (GraphQLNonNull) fieldType; + Object completed = completeValue(executionContext, graphQLNonNull.getWrappedType(), fields, result); + if (completed == null) throw new GraphQLException("Cannot return null for non-nullable type: " + fields); + return completed; + + } else if (result == null) { + return null; + } else if (fieldType instanceof GraphQLList) { + return completeValueForList(executionContext, (GraphQLList) fieldType, fields, (List) result); + } else if (fieldType instanceof GraphQLScalarType) { + return completeValueForScalar((GraphQLScalarType) fieldType, result); + } else if (fieldType instanceof GraphQLEnumType) { + return completeValueForEnum((GraphQLEnumType) fieldType, result); + } + + + GraphQLObjectType resolvedType; + if (fieldType instanceof GraphQLInterfaceType) { + resolvedType = resolveType((GraphQLInterfaceType) fieldType, result); + } else if (fieldType instanceof GraphQLUnionType) { + resolvedType = resolveType((GraphQLUnionType) fieldType, result); + } else { + resolvedType = (GraphQLObjectType) fieldType; + } + + Map> subFields = new LinkedHashMap<>(); + List visitedFragments = new ArrayList<>(); + for (Field field : fields) { + if (field.getSelectionSet() == null) continue; + fieldCollector.collectFields(executionContext, resolvedType, field.getSelectionSet(), visitedFragments, subFields); + } + + // Calling this from the executionContext so that you can shift from the simple execution strategy for mutations + // back to the desired strategy. + + return executionContext.getExecutionStrategy().execute(executionContext, resolvedType, result, subFields).getData(); + } + + protected GraphQLObjectType resolveType(GraphQLInterfaceType graphQLInterfaceType, Object value) { + GraphQLObjectType result = graphQLInterfaceType.getTypeResolver().getType(value); + if (result == null) throw new GraphQLException("could not determine type"); + return result; + } + + protected GraphQLObjectType resolveType(GraphQLUnionType graphQLUnionType, Object value) { + GraphQLObjectType result = graphQLUnionType.getTypeResolver().getType(value); + if (result == null) throw new GraphQLException("could not determine type"); + return result; + } + + + protected Object completeValueForEnum(GraphQLEnumType enumType, Object result) { + return enumType.getCoercing().coerce(result); + } + + protected Object completeValueForScalar(GraphQLScalarType scalarType, Object result) { + return scalarType.getCoercing().coerce(result); + } + + protected Object completeValueForList(ExecutionContext executionContext, GraphQLList fieldType, List fields, List result) { + List completedResults = new ArrayList<>(); + for (Object item : result) { + completedResults.add(completeValue(executionContext, fieldType.getWrappedType(), fields, item)); + } + return completedResults; + } + + protected GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLObjectType parentType, Field field) { + if (schema.getQueryType() == parentType) { + if (field.getName().equals(SchemaMetaFieldDef.getName())) { + return SchemaMetaFieldDef; + } + if (field.getName().equals(TypeMetaFieldDef.getName())) { + return TypeMetaFieldDef; + } + } + if (field.getName().equals(TypeNameMetaFieldDef.getName())) { + return TypeNameMetaFieldDef; + } + + GraphQLFieldDefinition fieldDefinition = parentType.getFieldDefinition(field.getName()); + if (fieldDefinition == null) throw new GraphQLException("unknown field " + field.getName()); + return fieldDefinition; + } + + +} diff --git a/src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java b/src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java new file mode 100644 index 0000000000..e2d57dbfa2 --- /dev/null +++ b/src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java @@ -0,0 +1,52 @@ +package graphql.execution; + +import graphql.ExecutionResult; +import graphql.ExecutionResultImpl; +import graphql.GraphQLException; +import graphql.language.Field; +import graphql.schema.GraphQLObjectType; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +public class ExecutorServiceExecutionStrategy extends ExecutionStrategy { + + ExecutorService executorService; + + public ExecutorServiceExecutionStrategy(ExecutorService executorService) { + this.executorService = executorService; + } + + @Override + ExecutionResult execute(final ExecutionContext executionContext, final GraphQLObjectType parentType, final Object source, final Map> fields) { + if (executorService == null) return new SimpleExecutionStrategy().execute(executionContext, parentType, source, fields); + + Map> futures = new LinkedHashMap<>(); + for (String fieldName : fields.keySet()) { + final List fieldList = fields.get(fieldName); + Callable resolveField = new Callable() { + @Override + public Object call() throws Exception { + return resolveField(executionContext, parentType, source, fieldList); + + } + }; + futures.put(fieldName, executorService.submit(resolveField)); + } + try { + Map results = new LinkedHashMap<>(); + for (String fieldName : futures.keySet()) { + results.put(fieldName, futures.get(fieldName).get()); + } + return new ExecutionResultImpl(results, executionContext.getErrors()); + } catch (InterruptedException | ExecutionException e) { + throw new GraphQLException(e); + } + + } +} diff --git a/src/main/java/graphql/execution/SimpleExecutionStrategy.java b/src/main/java/graphql/execution/SimpleExecutionStrategy.java new file mode 100644 index 0000000000..8af1d15dd8 --- /dev/null +++ b/src/main/java/graphql/execution/SimpleExecutionStrategy.java @@ -0,0 +1,23 @@ +package graphql.execution; + +import graphql.ExecutionResult; +import graphql.ExecutionResultImpl; +import graphql.language.Field; +import graphql.schema.GraphQLObjectType; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class SimpleExecutionStrategy extends ExecutionStrategy { + @Override + public ExecutionResult execute(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map> fields) { + Map results = new LinkedHashMap<>(); + for (String fieldName : fields.keySet()) { + List fieldList = fields.get(fieldName); + Object resolvedResult = resolveField(executionContext, parentType, source, fieldList); + results.put(fieldName, resolvedResult); + } + return new ExecutionResultImpl(results, executionContext.getErrors()); + } +} From 38c6daa0c3bc5e0ba83f630209e1b3cee0bffd83 Mon Sep 17 00:00:00 2001 From: Todd Berman Date: Mon, 7 Sep 2015 15:32:59 -0700 Subject: [PATCH 2/2] add additional fields helper to interface builder, as well as marking execute public --- src/main/java/graphql/execution/ExecutionStrategy.java | 2 +- .../graphql/execution/ExecutorServiceExecutionStrategy.java | 2 +- src/main/java/graphql/schema/GraphQLInterfaceType.java | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index f1d291d09d..4f0e70c1fe 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -23,7 +23,7 @@ public abstract class ExecutionStrategy { protected ValuesResolver valuesResolver = new ValuesResolver(); protected FieldCollector fieldCollector = new FieldCollector(); - abstract ExecutionResult execute(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map> fields); + public abstract ExecutionResult execute(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map> fields); protected Object resolveField(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, List fields) { GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, fields.get(0)); diff --git a/src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java b/src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java index e2d57dbfa2..e163d77cf8 100644 --- a/src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutorServiceExecutionStrategy.java @@ -23,7 +23,7 @@ public ExecutorServiceExecutionStrategy(ExecutorService executorService) { } @Override - ExecutionResult execute(final ExecutionContext executionContext, final GraphQLObjectType parentType, final Object source, final Map> fields) { + public ExecutionResult execute(final ExecutionContext executionContext, final GraphQLObjectType parentType, final Object source, final Map> fields) { if (executorService == null) return new SimpleExecutionStrategy().execute(executionContext, parentType, source, fields); Map> futures = new LinkedHashMap<>(); diff --git a/src/main/java/graphql/schema/GraphQLInterfaceType.java b/src/main/java/graphql/schema/GraphQLInterfaceType.java index 7be928dd20..07c5bd276c 100644 --- a/src/main/java/graphql/schema/GraphQLInterfaceType.java +++ b/src/main/java/graphql/schema/GraphQLInterfaceType.java @@ -89,6 +89,12 @@ public Builder field(GraphQLFieldDefinition fieldDefinition) { return this; } + public Builder fields(List fieldDefinitions) { + assertNotNull(fieldDefinitions, "fieldDefinitions can't be null"); + fields.addAll(fieldDefinitions); + return this; + } + public Builder typeResolver(TypeResolver typeResolver) { this.typeResolver = typeResolver; return this;