Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/main/java/graphql/execution/Execution.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import java.util.List;
import java.util.Map;

import static graphql.execution.ExecutionParameters.newParameters;
import static graphql.execution.TypeInfo.newTypeInfo;

import static graphql.language.OperationDefinition.Operation.MUTATION;
import static graphql.language.OperationDefinition.Operation.QUERY;

Expand Down Expand Up @@ -78,11 +81,29 @@ private ExecutionResult executeOperation(
Map<String, List<Field>> fields = new LinkedHashMap<>();
fieldCollector.collectFields(executionContext, operationRootType, operationDefinition.getSelectionSet(), new ArrayList<>(), fields);


ExecutionParameters parameters = newParameters()
.typeInfo(newTypeInfo().type(operationRootType))
.source(root)
.fields(fields)
.build();

ExecutionResult result;
if (operation == MUTATION) {
result = mutationStrategy.execute(executionContext, operationRootType, root, fields);
} else {
result = queryStrategy.execute(executionContext, operationRootType, root, fields);
try {
if (operation == OperationDefinition.Operation.MUTATION) {
result = mutationStrategy.execute(executionContext, parameters);
} else {
result = queryStrategy.execute(executionContext, parameters);
}
} catch (NonNullableFieldWasNullException e) {
// this means it was non null types all the way from an offending non null type
// up to the root object type and there was a a null value some where.
//
// The spec says we should return null for the data in this case
//
// http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability
//
result = new ExecutionResultImpl(null, executionContext.getErrors());
}

dataFetchCtx.onEnd(result);
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/graphql/execution/ExecutionParameters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package graphql.execution;

import graphql.language.Field;

import java.util.List;
import java.util.Map;

import static graphql.Assert.assertNotNull;

/**
* The parameters that are passed to execution strategies
*/
public class ExecutionParameters {
private final TypeInfo typeInfo;
private final Object source;
private final Map<String, List<Field>> fields;

private ExecutionParameters(TypeInfo typeInfo, Object source, Map<String, List<Field>> fields) {
this.typeInfo = assertNotNull(typeInfo, "");
this.fields = assertNotNull(fields, "");
this.source = source;
}

public TypeInfo typeInfo() {
return typeInfo;
}

public Object source() {
return source;
}

public Map<String, List<Field>> fields() {
return fields;
}

public static Builder newParameters() {
return new Builder();
}

@Override
public String toString() {
return String.format("ExecutionParameters { typeInfo=%s, source=%s, fields=%s }",
typeInfo, source, fields);
}

public static class Builder {
TypeInfo typeInfo;
Object source;
Map<String, List<Field>> fields;

public Builder typeInfo(TypeInfo type) {
this.typeInfo = type;
return this;
}

public Builder typeInfo(TypeInfo.Builder type) {
this.typeInfo = type.build();
return this;
}

public Builder fields(Map<String, List<Field>> fields) {
this.fields = fields;
return this;
}

public Builder source(Object source) {
this.source = source;
return this;
}

public ExecutionParameters build() {
return new ExecutionParameters(typeInfo, source, fields);
}
}
}
74 changes: 50 additions & 24 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
Expand All @@ -30,29 +29,32 @@
import java.util.List;
import java.util.Map;

import static graphql.execution.TypeInfo.newTypeInfo;
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 final ValuesResolver valuesResolver = new ValuesResolver();
protected final FieldCollector fieldCollector = new FieldCollector();

public abstract ExecutionResult execute(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map<String, List<Field>> fields);
public abstract ExecutionResult execute(ExecutionContext executionContext, ExecutionParameters parameters) throws NonNullableFieldWasNullException;

protected ExecutionResult resolveField(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, List<Field> fields) {
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, fields.get(0));
protected ExecutionResult resolveField(ExecutionContext executionContext, ExecutionParameters parameters, List<Field> fields) {
GraphQLObjectType type = parameters.typeInfo().castType(GraphQLObjectType.class);
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), type, fields.get(0));

Map<String, Object> argumentValues = valuesResolver.getArgumentValues(fieldDef.getArguments(), fields.get(0).getArguments(), executionContext.getVariables());
DataFetchingEnvironment environment = new DataFetchingEnvironmentImpl(
source,
parameters.source(),
argumentValues,
executionContext.getRoot(),
fields,
fieldDef.getType(),
parentType,
type,
executionContext.getGraphQLSchema()
);

Expand All @@ -73,25 +75,38 @@ protected ExecutionResult resolveField(ExecutionContext executionContext, GraphQ
fetchCtx.onEnd(e);
}

ExecutionResult result = completeValue(executionContext, fieldDef.getType(), fields, resolvedValue);
TypeInfo fieldType = newTypeInfo()
.type(fieldDef.getType())
.parentInfo(parameters.typeInfo())
.build();


ExecutionParameters newParameters = ExecutionParameters.newParameters()
.typeInfo(fieldType)
.fields(parameters.fields())
.source(resolvedValue).build();

ExecutionResult result = completeValue(executionContext, newParameters, fields);

fieldCtx.onEnd(result);
return result;
}

protected ExecutionResult completeValue(ExecutionContext executionContext, GraphQLType fieldType, List<Field> fields, Object result) {
if (fieldType instanceof GraphQLNonNull) {
GraphQLNonNull graphQLNonNull = (GraphQLNonNull) fieldType;
ExecutionResult completed = completeValue(executionContext, graphQLNonNull.getWrappedType(), fields, result);
if (completed == null) {
throw new GraphQLException("Cannot return null for non-nullable type: " + fields);
}
return completed;
protected ExecutionResult completeValue(ExecutionContext executionContext, ExecutionParameters parameters, List<Field> fields) {
TypeInfo typeInfo = parameters.typeInfo();
Object result = parameters.source();
GraphQLType fieldType = parameters.typeInfo().type();

} else if (result == null) {
if (result == null) {
if (typeInfo.typeIsNonNull()) {
// see http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability
NonNullableFieldWasNullException nonNullException = new NonNullableFieldWasNullException(typeInfo);
executionContext.addError(nonNullException);
throw nonNullException;
}
return null;
} else if (fieldType instanceof GraphQLList) {
return completeValueForList(executionContext, (GraphQLList) fieldType, fields, result);
return completeValueForList(executionContext, parameters, fields, toIterable(result));
} else if (fieldType instanceof GraphQLScalarType) {
return completeValueForScalar((GraphQLScalarType) fieldType, result);
} else if (fieldType instanceof GraphQLEnumType) {
Expand All @@ -115,18 +130,22 @@ protected ExecutionResult completeValue(ExecutionContext executionContext, Graph
fieldCollector.collectFields(executionContext, resolvedType, field.getSelectionSet(), visitedFragments, subFields);
}

ExecutionParameters newParameters = ExecutionParameters.newParameters()
.typeInfo(typeInfo.asType(resolvedType))
.fields(subFields)
.source(result).build();

// Calling this from the executionContext to ensure we shift back from mutation strategy to the query strategy.

return executionContext.getQueryStrategy().execute(executionContext, resolvedType, result, subFields);
return executionContext.getQueryStrategy().execute(executionContext, newParameters);
}

private ExecutionResult completeValueForList(ExecutionContext executionContext, GraphQLList fieldType, List<Field> fields, Object result) {
private Iterable<Object> toIterable(Object result) {
if (result.getClass().isArray()) {
result = Arrays.asList((Object[]) result);
}

//noinspection unchecked
return completeValueForList(executionContext, fieldType, fields, (Iterable<Object>) result);
return (Iterable<Object>) result;
}

protected GraphQLObjectType resolveType(GraphQLInterfaceType graphQLInterfaceType, Object value) {
Expand All @@ -145,7 +164,6 @@ protected GraphQLObjectType resolveType(GraphQLUnionType graphQLUnionType, Objec
return result;
}


protected ExecutionResult completeValueForEnum(GraphQLEnumType enumType, Object result) {
return new ExecutionResultImpl(enumType.getCoercing().serialize(result), null);
}
Expand All @@ -159,10 +177,18 @@ protected ExecutionResult completeValueForScalar(GraphQLScalarType scalarType, O
return new ExecutionResultImpl(serialized, null);
}

protected ExecutionResult completeValueForList(ExecutionContext executionContext, GraphQLList fieldType, List<Field> fields, Iterable<Object> result) {
protected ExecutionResult completeValueForList(ExecutionContext executionContext, ExecutionParameters parameters, List<Field> fields, Iterable<Object> result) {
List<Object> completedResults = new ArrayList<>();
TypeInfo typeInfo = parameters.typeInfo();
GraphQLList fieldType = typeInfo.castType(GraphQLList.class);
for (Object item : result) {
ExecutionResult completedValue = completeValue(executionContext, fieldType.getWrappedType(), fields, item);

ExecutionParameters newParameters = ExecutionParameters.newParameters()
.typeInfo(typeInfo.asType(fieldType.getWrappedType()))
.fields(parameters.fields())
.source(item).build();

ExecutionResult completedValue = completeValue(executionContext, newParameters, fields);
completedResults.add(completedValue != null ? completedValue.getData() : null);
}
return new ExecutionResultImpl(completedResults, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/**
* <p>ExecutorServiceExecutionStrategy uses an {@link ExecutorService} to parallelize the resolve.</p>
*
* Due to the nature of {@link #execute(ExecutionContext, GraphQLObjectType, Object, Map)} implementation, {@link ExecutorService}
* Due to the nature of {@link #execute(ExecutionContext, ExecutionParameters)} implementation, {@link ExecutorService}
* MUST have the following 2 characteristics:
* <ul>
* <li>1. The underlying {@link java.util.concurrent.ThreadPoolExecutor} MUST have a reasonable {@code maximumPoolSize}
Expand All @@ -37,17 +37,18 @@ public ExecutorServiceExecutionStrategy(ExecutorService executorService) {
}

@Override
public ExecutionResult execute(final ExecutionContext executionContext, final GraphQLObjectType parentType, final Object source, final Map<String, List<Field>> fields) {
public ExecutionResult execute(final ExecutionContext executionContext, final ExecutionParameters parameters) {
if (executorService == null)
return new SimpleExecutionStrategy().execute(executionContext, parentType, source, fields);
return new SimpleExecutionStrategy().execute(executionContext,parameters);

Map<String, List<Field>> fields = parameters.fields();
Map<String, Future<ExecutionResult>> futures = new LinkedHashMap<>();
for (String fieldName : fields.keySet()) {
final List<Field> fieldList = fields.get(fieldName);
Callable<ExecutionResult> resolveField = new Callable<ExecutionResult>() {
@Override
public ExecutionResult call() throws Exception {
return resolveField(executionContext, parentType, source, fieldList);
return resolveField(executionContext, parameters, fieldList);

}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package graphql.execution;

import graphql.ErrorType;
import graphql.GraphQLError;
import graphql.language.SourceLocation;

import java.util.List;

/**
* See (http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability), but if a non nullable field
* actually resolves to a null value and the parent type is nullable then the parent must in fact become null
* so we use exceptions to indicate this special case
*/
public class NonNullableFieldWasNullException extends RuntimeException implements GraphQLError {

private final TypeInfo typeInfo;


public NonNullableFieldWasNullException(TypeInfo typeInfo) {
super(buildMsg(typeInfo));
this.typeInfo = typeInfo;
}

private static String buildMsg(TypeInfo typeInfo) {
if (typeInfo.hasParentType()) {
return String.format("Cannot return null for non-nullable type: '%s' within parent '%s'", typeInfo.type().getName(), typeInfo.parentTypeInfo().type().getName());
}
return String.format("Cannot return null for non-nullable type: '%s' ", typeInfo.type().getName());
}

public TypeInfo getTypeInfo() {
return typeInfo;
}

@Override
public List<SourceLocation> getLocations() {
return null;
}

@Override
public ErrorType getErrorType() {
return null;
}
}
27 changes: 23 additions & 4 deletions src/main/java/graphql/execution/SimpleExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,40 @@
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<String, List<Field>> fields) {
public ExecutionResult execute(ExecutionContext executionContext, ExecutionParameters parameters) throws NonNullableFieldWasNullException {
Map<String, List<Field>> fields = parameters.fields();
Map<String, Object> results = new LinkedHashMap<>();
for (String fieldName : fields.keySet()) {
List<Field> fieldList = fields.get(fieldName);
ExecutionResult resolvedResult = resolveField(executionContext, parentType, source, fieldList);
try {
ExecutionResult resolvedResult = resolveField(executionContext, parameters, fieldList);

results.put(fieldName, resolvedResult != null ? resolvedResult.getData() : null);
results.put(fieldName, resolvedResult != null ? resolvedResult.getData() : null);
} catch (NonNullableFieldWasNullException e) {
/*
* See (http://facebook.github.io/graphql/#sec-Errors-and-Non-Nullability),
*
* If a non nullable child field type actually resolves to a null value and the parent type is nullable
* then the parent must in fact become null
* so we use exceptions to indicate this special case. However if the parent is in fact a non nullable type
* itself then we need to bubble that upwards again until we get to the root in which case the result
* is meant to be null.
*/

TypeInfo typeInfo = e.getTypeInfo();
if (typeInfo.hasParentType() && typeInfo.parentTypeInfo().typeIsNonNull()) {
throw e;
}
results = null;
break;
}
}
return new ExecutionResultImpl(results, executionContext.getErrors());
}
Expand Down
Loading