Skip to content
Closed
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
11 changes: 6 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import java.text.SimpleDateFormat

plugins {
id "com.jfrog.bintray" version "1.2"
}
Expand All @@ -9,10 +7,11 @@ apply plugin: 'maven'
apply plugin: 'maven-publish'
apply plugin: 'antlr'

sourceCompatibility = 1.6
targetCompatibility = 1.6
sourceCompatibility = 1.8
targetCompatibility = 1.8
def releaseVersion = System.properties.RELEASE_VERSION
version = releaseVersion ? releaseVersion : new SimpleDateFormat('yyyy-MM-dd\'T\'HH-mm-ss').format(new Date())
version = "2.3.0-SNAPSHOT"
//version = releaseVersion ? releaseVersion : new SimpleDateFormat('yyyy-MM-dd\'T\'HH-mm-ss').format(new Date())
group = 'com.graphql-java'


Expand All @@ -29,12 +28,14 @@ jar {
dependencies {
compile 'org.antlr:antlr4-runtime:4.5.1'
compile 'org.slf4j:slf4j-api:1.7.12'
compile 'com.spotify:completable-futures:0.3.0'
antlr "org.antlr:antlr4:4.5.1"
testCompile group: 'junit', name: 'junit', version: '4.11'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
testCompile 'org.codehaus.groovy:groovy-all:2.4.4'
testCompile 'cglib:cglib-nodep:3.1'
testCompile 'org.objenesis:objenesis:2.1'
testCompile 'org.slf4j:slf4j-log4j12:1.7.21'
}

compileJava.source file("build/generated-src"), sourceSets.main.java
Expand Down
17 changes: 12 additions & 5 deletions src/main/java/graphql/GraphQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,32 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionStage;

import static graphql.Assert.assertNotNull;

public class GraphQL {


private final GraphQLSchema graphQLSchema;
private final ExecutionStrategy executionStrategy;
private final ExecutionStrategy queryStrategy;
private final ExecutionStrategy mutationStrategy;

private static final Logger log = LoggerFactory.getLogger(GraphQL.class);

public GraphQL(GraphQLSchema graphQLSchema) {
this(graphQLSchema, null);
this(graphQLSchema, null, null);
}


public GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy executionStrategy) {
public GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy) {
this(graphQLSchema, queryStrategy, null);
}

public GraphQL(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy, ExecutionStrategy mutationStrategy) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GraphQL spec says that mutations WILL ALWAYS be serial. So why allow people to wire in anything BUT a serial implementation?

I guess it makes it more configureable than today but you have no way of knowing that they are following spec or not.

Maybe its a case of "dont shoot yourself" by wiring in a asynch mutation execution strategy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My serial implementation is async. The current serial implementation is not async. Both are serial though.

this.graphQLSchema = graphQLSchema;
this.executionStrategy = executionStrategy;
this.queryStrategy = queryStrategy;
this.mutationStrategy = mutationStrategy;
}

public ExecutionResult execute(String requestString) {
Expand Down Expand Up @@ -74,7 +81,7 @@ public ExecutionResult execute(String requestString, String operationName, Objec
if (validationErrors.size() > 0) {
return new ExecutionResultImpl(validationErrors);
}
Execution execution = new Execution(executionStrategy);
Execution execution = new Execution(queryStrategy, mutationStrategy);
return execution.execute(graphQLSchema, context, document, operationName, arguments);
}

Expand Down
55 changes: 55 additions & 0 deletions src/main/java/graphql/GraphQLAsync.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package graphql;

import graphql.execution.ExecutionStrategy;
import graphql.schema.GraphQLSchema;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CompletionStage;

import static java.util.concurrent.CompletableFuture.completedFuture;

public class GraphQLAsync extends GraphQL {

public GraphQLAsync(GraphQLSchema graphQLSchema) {
super(graphQLSchema);
}

public GraphQLAsync(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy) {
super(graphQLSchema, queryStrategy);
}

public GraphQLAsync(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy, ExecutionStrategy mutationStrategy) {
super(graphQLSchema, queryStrategy, mutationStrategy);
}

public CompletionStage<ExecutionResult> executeAsync(String requestString) {
return executeAsync(requestString, null);

}

public CompletionStage<ExecutionResult> executeAsync(String requestString, Object context) {
return executeAsync(requestString, context, Collections.emptyMap());

}

public CompletionStage<ExecutionResult> executeAsync(String requestString, String operationName, Object context) {
return executeAsync(requestString, operationName, context, Collections.emptyMap());

}

public CompletionStage<ExecutionResult> executeAsync(String requestString, Object context, Map<String, Object> arguments) {
return executeAsync(requestString, null, context, arguments);

}

@SuppressWarnings("unchecked")
public CompletionStage<ExecutionResult> executeAsync(String requestString, String operationName, Object context, Map<String, Object> arguments) {
ExecutionResult result = execute(requestString, operationName, context, arguments);
Object data1 = result.getData();
if (data1 instanceof CompletionStage) {
return ((CompletionStage<Map<String, Object>>) data1).thenApply(data -> new ExecutionResultImpl(data, result.getErrors()));
}
return completedFuture(result);
}
}
18 changes: 8 additions & 10 deletions src/main/java/graphql/execution/Execution.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,17 @@
public class Execution {

private FieldCollector fieldCollector = new FieldCollector();
private ExecutionStrategy strategy;
private ExecutionStrategy queryStrategy;
private ExecutionStrategy mutationStrategy;

public Execution(ExecutionStrategy strategy) {
this.strategy = strategy;

if (this.strategy == null) {
this.strategy = new SimpleExecutionStrategy();
}
public Execution(ExecutionStrategy queryStrategy, ExecutionStrategy mutationStrategy) {
this.queryStrategy = queryStrategy != null ? queryStrategy : new SimpleExecutionStrategy();
this.mutationStrategy = mutationStrategy != null ? mutationStrategy : new SimpleExecutionStrategy();
}

public ExecutionResult execute(GraphQLSchema graphQLSchema, Object root, Document document, String operationName, Map<String, Object> args) {
ExecutionContextBuilder executionContextBuilder = new ExecutionContextBuilder(new ValuesResolver());
ExecutionContext executionContext = executionContextBuilder.build(graphQLSchema, strategy, root, document, operationName, args);
ExecutionContext executionContext = executionContextBuilder.build(graphQLSchema, queryStrategy, mutationStrategy, root, document, operationName, args);
return executeOperation(executionContext, root, executionContext.getOperationDefinition());
}

Expand All @@ -55,9 +53,9 @@ private ExecutionResult executeOperation(
fieldCollector.collectFields(executionContext, operationRootType, operationDefinition.getSelectionSet(), new ArrayList<String>(), fields);

if (operationDefinition.getOperation() == OperationDefinition.Operation.MUTATION) {
return new SimpleExecutionStrategy().execute(executionContext, operationRootType, root, fields);
return mutationStrategy.execute(executionContext, operationRootType, root, fields);
} else {
return strategy.execute(executionContext, operationRootType, root, fields);
return queryStrategy.execute(executionContext, operationRootType, root, fields);
}
}
}
26 changes: 16 additions & 10 deletions src/main/java/graphql/execution/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@
import graphql.language.OperationDefinition;
import graphql.schema.GraphQLSchema;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

public class ExecutionContext {

private GraphQLSchema graphQLSchema;
private ExecutionStrategy executionStrategy;
private ExecutionStrategy queryStrategy;
private ExecutionStrategy mutationStrategy;
private Map<String, FragmentDefinition> fragmentsByName = new LinkedHashMap<String, FragmentDefinition>();
private OperationDefinition operationDefinition;
private Map<String, Object> variables = new LinkedHashMap<String, Object>();
private Object root;
private List<GraphQLError> errors = new ArrayList<GraphQLError>();
private List<GraphQLError> errors = Collections.synchronizedList(new ArrayList<GraphQLError>());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CopyOnWriteArrayList is pretty efficient list

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure Collections.synchronizedList() is good here anyway. Thanks, I'll check out CopyOnWriteArrayList


public GraphQLSchema getGraphQLSchema() {
return graphQLSchema;
Expand Down Expand Up @@ -73,11 +71,19 @@ public List<GraphQLError> getErrors() {
return errors;
}

public ExecutionStrategy getExecutionStrategy() {
return executionStrategy;
public ExecutionStrategy getQueryStrategy() {
return queryStrategy;
}

public void setExecutionStrategy(ExecutionStrategy executionStrategy) {
this.executionStrategy = executionStrategy;
public void setQueryStrategy(ExecutionStrategy queryStrategy) {
this.queryStrategy = queryStrategy;
}

public ExecutionStrategy getMutationStrategy() {
return mutationStrategy;
}

public void setMutationStrategy(ExecutionStrategy mutationStrategy) {
this.mutationStrategy = mutationStrategy;
}
}
5 changes: 3 additions & 2 deletions src/main/java/graphql/execution/ExecutionContextBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public ExecutionContextBuilder(ValuesResolver valuesResolver) {
this.valuesResolver = valuesResolver;
}

public ExecutionContext build(GraphQLSchema graphQLSchema, ExecutionStrategy executionStrategy, Object root, Document document, String operationName, Map<String, Object> args) {
public ExecutionContext build(GraphQLSchema graphQLSchema, ExecutionStrategy queryStrategy, ExecutionStrategy mutationStrategy, Object root, Document document, String operationName, Map<String, Object> args) {
Map<String, FragmentDefinition> fragmentsByName = new LinkedHashMap<String, FragmentDefinition>();
Map<String, OperationDefinition> operationsByName = new LinkedHashMap<String, OperationDefinition>();

Expand Down Expand Up @@ -48,7 +48,8 @@ public ExecutionContext build(GraphQLSchema graphQLSchema, ExecutionStrategy exe

ExecutionContext executionContext = new ExecutionContext();
executionContext.setGraphQLSchema(graphQLSchema);
executionContext.setExecutionStrategy(executionStrategy);
executionContext.setQueryStrategy(queryStrategy);
executionContext.setMutationStrategy(mutationStrategy);
executionContext.setOperationDefinition(operation);
executionContext.setRoot(root);
executionContext.setFragmentsByName(fragmentsByName);
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ protected ExecutionResult completeValue(ExecutionContext executionContext, Graph
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.
// Calling this from the executionContext to ensure we shift back from mutation strategy to the query strategy.

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

private ExecutionResult completeValueForList(ExecutionContext executionContext, GraphQLList fieldType, List<Field> fields, Object result) {
Expand Down
131 changes: 131 additions & 0 deletions src/main/java/graphql/execution/async/AsyncExecutionStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package graphql.execution.async;

import graphql.ExceptionWhileDataFetching;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionStrategy;
import graphql.language.Field;
import graphql.schema.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Supplier;

import static com.spotify.futures.CompletableFutures.successfulAsList;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you really want Spotify as a dependency. I thought graphql-java was trying to be as lightweight as possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's just in there while I work this out. That artifact is tiny (50 LOC?) and can be copy/pasted into this project license-permitting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look!

import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;


public final class AsyncExecutionStrategy extends ExecutionStrategy {

public static AsyncExecutionStrategy serial() {
return new AsyncExecutionStrategy(true);
}

public static AsyncExecutionStrategy parallel() {
return new AsyncExecutionStrategy(false);
}

private static final Logger log = LoggerFactory.getLogger(AsyncExecutionStrategy.class);

private final boolean serial;

private AsyncExecutionStrategy(boolean serial) {
this.serial = serial;
}

@Override
public ExecutionResult execute(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map<String, List<Field>> fields) {
Map<String, Supplier<CompletionStage<ExecutionResult>>> fieldResolvers = fields.entrySet()
.stream()
.collect(toMap(Map.Entry::getKey, entry -> () -> resolveFieldAsync(executionContext, parentType, source, entry.getValue())));

AsyncFieldsCoordinator coordinator = new AsyncFieldsCoordinator(fieldResolvers);

CompletionStage<Map<String, Object>> data = serial ? coordinator.executeSerially() : coordinator.executeParallelly();
return new ExecutionResultImpl(data, executionContext.getErrors());
}

private CompletionStage<ExecutionResult> resolveFieldAsync(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, List<Field> fieldList) {
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, fieldList.get(0));
GraphQLOutputType fieldType = fieldDef.getType();

DataFetchingEnvironment env = new DataFetchingEnvironment(
source,
valuesResolver.getArgumentValues(fieldDef.getArguments(), fieldList.get(0).getArguments(), executionContext.getVariables()),
executionContext.getRoot(),
fieldList,
fieldDef.getType(),
parentType,
executionContext.getGraphQLSchema()
);

try {
Object obj1 = fieldDef.getDataFetcher().get(env);
if (obj1 instanceof CompletionStage) {
return ((CompletionStage<?>) obj1)
.exceptionally(e -> {
logExceptionWhileFetching(e, fieldList.get(0));
executionContext.addError(new ExceptionWhileDataFetching(e));
return null;
})
.thenCompose(obj2 -> completeValueAsync(executionContext, fieldType, fieldList, obj2));
} else {
return completeValueAsync(executionContext, fieldType, fieldList, obj1);
}
} catch (Exception e) {
logExceptionWhileFetching(e, fieldList.get(0));
executionContext.addError(new ExceptionWhileDataFetching(e));
return completedFuture(new ExecutionResultImpl(null, null));
}
}

@SuppressWarnings("unchecked")
private CompletionStage<ExecutionResult> completeValueAsync(ExecutionContext executionContext, GraphQLType fieldType, List<Field> fieldList, Object result) {
if (fieldType instanceof GraphQLList) {
List<Object> collect = ((List<Object>) result);

List<CompletionStage<ExecutionResult>> collect1 = collect.stream()
.map(object -> {
CompletionStage<Object> stage = object instanceof CompletionStage ? (CompletionStage<Object>) object : completedFuture(object);
return stage
.thenCompose(result5 -> completeValueAsync(executionContext, ((GraphQLList) fieldType).getWrappedType(), fieldList, result5));
})
.collect(toList());

return successfulAsList(collect1, t -> null).thenApply(results -> {
List<Object> list = new ArrayList<>();
List<GraphQLError> errors = new ArrayList<>();
for (ExecutionResult executionResult : results) {
list.add(executionResult.getData());
errors.addAll(executionResult.getErrors());
}
return new ExecutionResultImpl(list, errors);
});

} else {
ExecutionResult completed = completeValue(executionContext, fieldType, fieldList, result);
// Happens when the data fetcher returns null for nullable field
if (completed == null) {
return completedFuture(new ExecutionResultImpl(null, null));
}
if (!(completed.getData() instanceof CompletionStage)) {
return completedFuture(completed);
}
return ((CompletionStage<?>) completed.getData())
.thenApply(data -> new ExecutionResultImpl(data, completed.getErrors()));
}
}

private void logExceptionWhileFetching(Throwable e, Field field) {
log.debug("Exception while fetching data for field {}", field.getName(), e);
}

}
Loading