Skip to content

Commit 9d94784

Browse files
committed
#278 - generate a unique id for a graphql query
1 parent 902681f commit 9d94784

File tree

9 files changed

+168
-19
lines changed

9 files changed

+168
-19
lines changed

src/main/java/graphql/Assert.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
public class Assert {
66

77

8-
public static void assertNotNull(Object object, String errorMessage) {
9-
if (object != null) return;
8+
public static <T> T assertNotNull(T object, String errorMessage) {
9+
if (object != null) return object;
1010
throw new AssertException(errorMessage);
1111
}
1212

13-
public static void assertNotEmpty(Collection<?> c, String errorMessage) {
13+
public static <T> Collection<T> assertNotEmpty(Collection<T> c, String errorMessage) {
1414
if (c == null || c.isEmpty()) throw new AssertException(errorMessage);
15-
return;
15+
return c;
1616
}
1717

1818
}

src/main/java/graphql/GraphQL.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33

44
import graphql.execution.Execution;
5+
import graphql.execution.ExecutionId;
6+
import graphql.execution.ExecutionIdProvider;
57
import graphql.execution.ExecutionStrategy;
68
import graphql.language.Document;
79
import graphql.language.SourceLocation;
@@ -26,6 +28,18 @@ public class GraphQL {
2628

2729
private final GraphQLSchema graphQLSchema;
2830
private final ExecutionStrategy executionStrategy;
31+
//
32+
// later PR changes will allow api consumers to provide their own id provider
33+
//
34+
// see https://github.com/graphql-java/graphql-java/pull/276 for the builder pattern
35+
// needed to make this sustainable. But for now we will use hard coded approach.
36+
//
37+
private final ExecutionIdProvider idProvider = new ExecutionIdProvider() {
38+
@Override
39+
public ExecutionId generate(String query, String operationName, Object context) {
40+
return ExecutionId.generate();
41+
}
42+
};
2943

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

@@ -66,16 +80,19 @@ public ExecutionResult execute(String requestString, String operationName, Objec
6680
RecognitionException recognitionException = (RecognitionException) e.getCause();
6781
SourceLocation sourceLocation = new SourceLocation(recognitionException.getOffendingToken().getLine(), recognitionException.getOffendingToken().getCharPositionInLine());
6882
InvalidSyntaxError invalidSyntaxError = new InvalidSyntaxError(sourceLocation);
69-
return new ExecutionResultImpl(Arrays.asList(invalidSyntaxError));
83+
return new ExecutionResultImpl(Collections.singletonList(invalidSyntaxError));
7084
}
7185

7286
Validator validator = new Validator();
7387
List<ValidationError> validationErrors = validator.validateDocument(graphQLSchema, document);
7488
if (validationErrors.size() > 0) {
7589
return new ExecutionResultImpl(validationErrors);
7690
}
91+
92+
ExecutionId executionId = idProvider.generate(requestString, operationName, context);
93+
7794
Execution execution = new Execution(executionStrategy);
78-
return execution.execute(graphQLSchema, context, document, operationName, arguments);
95+
return execution.execute(executionId, graphQLSchema, context, document, operationName, arguments);
7996
}
8097

8198

src/main/java/graphql/execution/Execution.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@
1616

1717
public class Execution {
1818

19-
private FieldCollector fieldCollector = new FieldCollector();
20-
private ExecutionStrategy strategy;
19+
private final FieldCollector fieldCollector = new FieldCollector();
20+
private final ExecutionStrategy strategy;
2121

22-
public Execution(ExecutionStrategy strategy) {
23-
this.strategy = strategy;
24-
25-
if (this.strategy == null) {
26-
this.strategy = new SimpleExecutionStrategy();
27-
}
22+
public Execution(ExecutionStrategy executionStrategy) {
23+
this.strategy = executionStrategy == null ? new SimpleExecutionStrategy() : executionStrategy;
2824
}
2925

30-
public ExecutionResult execute(GraphQLSchema graphQLSchema, Object root, Document document, String operationName, Map<String, Object> args) {
26+
public ExecutionResult execute(ExecutionId executionId, GraphQLSchema graphQLSchema, Object root, Document document, String operationName, Map<String, Object> args) {
3127
ExecutionContextBuilder executionContextBuilder = new ExecutionContextBuilder(new ValuesResolver());
32-
ExecutionContext executionContext = executionContextBuilder.build(graphQLSchema, strategy, root, document, operationName, args);
28+
ExecutionContext executionContext = executionContextBuilder
29+
.executionId(executionId)
30+
.build(graphQLSchema, strategy, root, document, operationName, args);
31+
3332
return executeOperation(executionContext, root, executionContext.getOperationDefinition());
3433
}
3534

src/main/java/graphql/execution/ExecutionContext.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,24 @@
66
import graphql.language.OperationDefinition;
77
import graphql.schema.GraphQLSchema;
88

9-
import java.util.LinkedHashMap;
109
import java.util.List;
1110
import java.util.Map;
1211
import java.util.concurrent.CopyOnWriteArrayList;
1312

1413
public class ExecutionContext {
1514

1615
private final GraphQLSchema graphQLSchema;
16+
private final ExecutionId executionId;
1717
private final ExecutionStrategy executionStrategy;
1818
private final Map<String, FragmentDefinition> fragmentsByName;
1919
private final OperationDefinition operationDefinition;
2020
private final Map<String, Object> variables;
2121
private final Object root;
2222
private final List<GraphQLError> errors = new CopyOnWriteArrayList<GraphQLError>();
2323

24-
public ExecutionContext(GraphQLSchema graphQLSchema, ExecutionStrategy executionStrategy, Map<String, FragmentDefinition> fragmentsByName, OperationDefinition operationDefinition, Map<String, Object> variables, Object root) {
24+
public ExecutionContext(GraphQLSchema graphQLSchema, ExecutionId executionId, ExecutionStrategy executionStrategy, Map<String, FragmentDefinition> fragmentsByName, OperationDefinition operationDefinition, Map<String, Object> variables, Object root) {
2525
this.graphQLSchema = graphQLSchema;
26+
this.executionId = executionId;
2627
this.executionStrategy = executionStrategy;
2728
this.fragmentsByName = fragmentsByName;
2829
this.operationDefinition = operationDefinition;
@@ -34,6 +35,10 @@ public GraphQLSchema getGraphQLSchema() {
3435
return graphQLSchema;
3536
}
3637

38+
public ExecutionId getExecutionId() {
39+
return executionId;
40+
}
41+
3742
public Map<String, FragmentDefinition> getFragmentsByName() {
3843
return fragmentsByName;
3944
}

src/main/java/graphql/execution/ExecutionContextBuilder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package graphql.execution;
22

3+
import graphql.Assert;
34
import graphql.GraphQLException;
45
import graphql.language.Definition;
56
import graphql.language.Document;
@@ -10,15 +11,27 @@
1011
import java.util.LinkedHashMap;
1112
import java.util.Map;
1213

14+
import static graphql.Assert.assertNotNull;
15+
1316
public class ExecutionContextBuilder {
1417

1518
private ValuesResolver valuesResolver;
1619

20+
private ExecutionId executionId;
21+
1722
public ExecutionContextBuilder(ValuesResolver valuesResolver) {
1823
this.valuesResolver = valuesResolver;
1924
}
2025

26+
public ExecutionContextBuilder executionId(ExecutionId executionId) {
27+
this.executionId = executionId;
28+
return this;
29+
}
30+
2131
public ExecutionContext build(GraphQLSchema graphQLSchema, ExecutionStrategy executionStrategy, Object root, Document document, String operationName, Map<String, Object> args) {
32+
// preconditions
33+
assertNotNull(executionId,"You must provide a query identifier");
34+
2235
Map<String, FragmentDefinition> fragmentsByName = new LinkedHashMap<String, FragmentDefinition>();
2336
Map<String, OperationDefinition> operationsByName = new LinkedHashMap<String, OperationDefinition>();
2437

@@ -50,6 +63,7 @@ public ExecutionContext build(GraphQLSchema graphQLSchema, ExecutionStrategy exe
5063

5164
return new ExecutionContext(
5265
graphQLSchema,
66+
executionId,
5367
executionStrategy,
5468
fragmentsByName,
5569
operation,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package graphql.execution;
2+
3+
import graphql.Assert;
4+
5+
import java.util.UUID;
6+
7+
/**
8+
* This opaque identifier is used to identify a unique query execution
9+
*/
10+
public class ExecutionId {
11+
12+
/**
13+
* Create an identifier from the given string
14+
* @return a query identifier
15+
*/
16+
public static ExecutionId generate() {
17+
return new ExecutionId(UUID.randomUUID().toString());
18+
}
19+
20+
/**
21+
* Create an identifier from the given string
22+
* @param id the string to wrap
23+
* @return a query identifier
24+
*/
25+
public static ExecutionId generateFrom(String id) {
26+
return new ExecutionId(id);
27+
}
28+
29+
private final String id;
30+
31+
private ExecutionId(String id) {
32+
Assert.assertNotNull(id, "You must provided a non null id");
33+
this.id = id;
34+
}
35+
36+
@Override
37+
public String toString() {
38+
return id;
39+
}
40+
41+
@Override
42+
public boolean equals(Object o) {
43+
if (this == o) return true;
44+
if (o == null || getClass() != o.getClass()) return false;
45+
46+
ExecutionId that = (ExecutionId) o;
47+
48+
return id.equals(that.id);
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return id.hashCode();
54+
}
55+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package graphql.execution;
2+
3+
/**
4+
* A provider of {@link ExecutionId}s
5+
*/
6+
public interface ExecutionIdProvider {
7+
8+
/**
9+
* Allows generation of a unique identifier per query execution
10+
*
11+
* @param query the query to be executed
12+
* @param operationName thr name of the operation
13+
* @param context the context object passed to the query
14+
* @return a non null {@link ExecutionId}
15+
*/
16+
ExecutionId generate(String query, String operationName, Object context);
17+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package graphql.execution
2+
3+
import graphql.ExecutionResult
4+
import graphql.GraphQL
5+
import graphql.StarWarsSchema
6+
import graphql.language.Field
7+
import graphql.schema.GraphQLObjectType
8+
import spock.lang.Specification
9+
10+
class ExecutionIdTest extends Specification {
11+
12+
class CaptureIdStrategy extends SimpleExecutionStrategy {
13+
ExecutionId executionId = null
14+
15+
@Override
16+
ExecutionResult execute(ExecutionContext executionContext, GraphQLObjectType parentType, Object source, Map<String, List<Field>> fields) {
17+
executionId = executionContext.executionId
18+
return super.execute(executionContext, parentType, source, fields)
19+
}
20+
}
21+
22+
def 'Ensures that an execution identifier is present'() {
23+
given:
24+
def query = """
25+
query HeroNameAndFriendsQuery {
26+
hero {
27+
id
28+
}
29+
}
30+
"""
31+
32+
when:
33+
34+
CaptureIdStrategy idStrategy = new CaptureIdStrategy()
35+
36+
new GraphQL(StarWarsSchema.starWarsSchema, idStrategy).execute(query).data
37+
38+
then:
39+
40+
idStrategy.executionId != null
41+
}
42+
}

src/test/groovy/graphql/execution/ExecutionStrategySpec.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class ExecutionStrategySpec extends Specification {
2121
}
2222

2323
def buildContext() {
24-
new ExecutionContext(null,executionStrategy,null,null,null,null)
24+
new ExecutionContext(null, null, executionStrategy, null, null, null, null)
2525
}
2626

2727
def "completes value for a java.util.List"() {

0 commit comments

Comments
 (0)