Skip to content

Commit bb2df57

Browse files
authored
Added DataFetcherFactory support (graphql-java#735)
* Added DataFetcherFactory support Adds a level of indirection into the data fetchers which are specified at schema def time but needed "wired" at query execution time * PR feedback on naming and environment
1 parent 92468df commit bb2df57

File tree

9 files changed

+260
-76
lines changed

9 files changed

+260
-76
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package graphql.schema;
2+
3+
import graphql.PublicApi;
4+
5+
/**
6+
* A helper for {@link graphql.schema.DataFetcherFactory}
7+
*/
8+
@PublicApi
9+
public class DataFetcherFactories {
10+
11+
/**
12+
* Creates a {@link graphql.schema.DataFetcherFactory} that always returns the provided {@link graphql.schema.DataFetcher}
13+
*
14+
* @param dataFetcher the data fetcher to always return
15+
* @param <T> the type of the data fetcher
16+
*
17+
* @return a data fetcher factory that always returns the provided data fetcher
18+
*/
19+
public static <T> DataFetcherFactory<T> useDataFetcher(DataFetcher<T> dataFetcher) {
20+
return fieldDefinition -> dataFetcher;
21+
}
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package graphql.schema;
2+
3+
import graphql.PublicSpi;
4+
5+
/**
6+
* A DataFetcherFactory allows a level of indirection in providing {@link graphql.schema.DataFetcher}s for graphql fields.
7+
*
8+
* For example if you are using an IoC container such as Spring or Guice, you can use this indirection to give you
9+
* per request late binding of a data fetcher with its dependencies injected in.
10+
*
11+
* @param <T> the type of DataFetcher
12+
*/
13+
@PublicSpi
14+
public interface DataFetcherFactory<T> {
15+
16+
/**
17+
* Returns a {@link graphql.schema.DataFetcher}
18+
*
19+
* @param environment the environment that needs the data fetcher
20+
*
21+
* @return a data fetcher
22+
*/
23+
DataFetcher<T> get(DataFetcherFactoryEnvironment environment);
24+
25+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package graphql.schema;
2+
3+
import graphql.PublicApi;
4+
5+
/**
6+
* This is passed to a {@link graphql.schema.DataFetcherFactory} when it is invoked to
7+
* get a {@link graphql.schema.DataFetcher}
8+
*/
9+
@PublicApi
10+
public class DataFetcherFactoryEnvironment {
11+
private final GraphQLFieldDefinition fieldDefinition;
12+
13+
DataFetcherFactoryEnvironment(GraphQLFieldDefinition fieldDefinition) {
14+
this.fieldDefinition = fieldDefinition;
15+
}
16+
17+
/**
18+
* @return the field that needs a {@link graphql.schema.DataFetcher}
19+
*/
20+
public GraphQLFieldDefinition getFieldDefinition() {
21+
return fieldDefinition;
22+
}
23+
24+
public static Builder newDataFetchingFactoryEnvironment() {
25+
return new Builder();
26+
}
27+
28+
static class Builder {
29+
GraphQLFieldDefinition fieldDefinition;
30+
31+
public Builder fieldDefinition(GraphQLFieldDefinition fieldDefinition) {
32+
this.fieldDefinition = fieldDefinition;
33+
return this;
34+
}
35+
36+
public DataFetcherFactoryEnvironment build() {
37+
return new DataFetcherFactoryEnvironment(fieldDefinition);
38+
}
39+
}
40+
}

src/main/java/graphql/schema/GraphQLFieldDefinition.java

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import static graphql.Assert.assertNotNull;
1515
import static graphql.Assert.assertValidName;
16+
import static graphql.schema.DataFetcherFactoryEnvironment.newDataFetchingFactoryEnvironment;
1617

1718
/**
1819
* Fields are the ways you get data values in graphql and a field definition represents a field, its type, the arguments it takes
@@ -30,27 +31,28 @@ public class GraphQLFieldDefinition {
3031
private final String name;
3132
private final String description;
3233
private GraphQLOutputType type;
33-
private final DataFetcher dataFetcher;
34+
private final DataFetcherFactory dataFetcherFactory;
3435
private final String deprecationReason;
3536
private final List<GraphQLArgument> arguments;
3637
private final FieldDefinition definition;
3738

3839

40+
@Deprecated
3941
@Internal
40-
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcher dataFetcher, List<GraphQLArgument> arguments, String deprecationReason) {
41-
this(name, description, type, dataFetcher, arguments, deprecationReason, null);
42+
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcher<?> dataFetcher, List<GraphQLArgument> arguments, String deprecationReason) {
43+
this(name, description, type, DataFetcherFactories.useDataFetcher(dataFetcher), arguments, deprecationReason, null);
4244
}
4345

4446
@Internal
45-
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcher dataFetcher, List<GraphQLArgument> arguments, String deprecationReason, FieldDefinition definition) {
47+
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcherFactory dataFetcherFactory, List<GraphQLArgument> arguments, String deprecationReason, FieldDefinition definition) {
4648
assertValidName(name);
47-
assertNotNull(dataFetcher, "dataFetcher can't be null");
49+
assertNotNull(dataFetcherFactory, "you have to provide a DataFetcher (or DataFetcherFactory)");
4850
assertNotNull(type, "type can't be null");
4951
assertNotNull(arguments, "arguments can't be null");
5052
this.name = name;
5153
this.description = description;
5254
this.type = type;
53-
this.dataFetcher = dataFetcher;
55+
this.dataFetcherFactory = dataFetcherFactory;
5456
this.arguments = Collections.unmodifiableList(new ArrayList<>(arguments));
5557
this.deprecationReason = deprecationReason;
5658
this.definition = definition;
@@ -71,7 +73,9 @@ public GraphQLOutputType getType() {
7173
}
7274

7375
public DataFetcher getDataFetcher() {
74-
return dataFetcher;
76+
return dataFetcherFactory.get(newDataFetchingFactoryEnvironment()
77+
.fieldDefinition(this)
78+
.build());
7579
}
7680

7781
public GraphQLArgument getArgument(String name) {
@@ -106,8 +110,8 @@ public String toString() {
106110
return "GraphQLFieldDefinition{" +
107111
"name='" + name + '\'' +
108112
", type=" + type +
109-
", dataFetcher=" + dataFetcher +
110113
", arguments=" + arguments +
114+
", dataFetcherFactory=" + dataFetcherFactory +
111115
", description='" + description + '\'' +
112116
", deprecationReason='" + deprecationReason + '\'' +
113117
", definition=" + definition +
@@ -124,7 +128,7 @@ public static class Builder {
124128
private String name;
125129
private String description;
126130
private GraphQLOutputType type;
127-
private DataFetcher dataFetcher;
131+
private DataFetcherFactory<?> dataFetcherFactory;
128132
private List<GraphQLArgument> arguments = new ArrayList<>();
129133
private String deprecationReason;
130134
private boolean isField;
@@ -163,14 +167,41 @@ public Builder type(GraphQLOutputType type) {
163167
return this;
164168
}
165169

166-
public Builder dataFetcher(DataFetcher dataFetcher) {
170+
/**
171+
* Sets the {@link graphql.schema.DataFetcher} to use with this field.
172+
*
173+
* @param dataFetcher the data fetcher to use
174+
*
175+
* @return this builder
176+
*/
177+
public Builder dataFetcher(DataFetcher<?> dataFetcher) {
167178
assertNotNull(dataFetcher, "dataFetcher must be not null");
168-
this.dataFetcher = dataFetcher;
179+
this.dataFetcherFactory = DataFetcherFactories.useDataFetcher(dataFetcher);
180+
return this;
181+
}
182+
183+
/**
184+
* Sets the {@link graphql.schema.DataFetcherFactory} to use with this field.
185+
*
186+
* @param dataFetcherFactory the factory to use
187+
*
188+
* @return this builder
189+
*/
190+
public Builder dataFetcherFactory(DataFetcherFactory dataFetcherFactory) {
191+
assertNotNull(dataFetcherFactory, "dataFetcherFactory must be not null");
192+
this.dataFetcherFactory = dataFetcherFactory;
169193
return this;
170194
}
171195

196+
/**
197+
* This will cause the data fetcher of this field to always return the supplied value
198+
*
199+
* @param value the value to always return
200+
*
201+
* @return this builder
202+
*/
172203
public Builder staticValue(final Object value) {
173-
this.dataFetcher = environment -> value;
204+
this.dataFetcherFactory = DataFetcherFactories.useDataFetcher(environment -> value);
174205
return this;
175206
}
176207

@@ -232,16 +263,14 @@ public Builder deprecate(String deprecationReason) {
232263
}
233264

234265
public GraphQLFieldDefinition build() {
235-
if (dataFetcher == null) {
266+
if (dataFetcherFactory == null) {
236267
if (isField) {
237-
dataFetcher = new FieldDataFetcher(name);
268+
dataFetcherFactory = DataFetcherFactories.useDataFetcher(new FieldDataFetcher<>(name));
238269
} else {
239-
dataFetcher = new PropertyDataFetcher(name);
270+
dataFetcherFactory = DataFetcherFactories.useDataFetcher(new PropertyDataFetcher<>(name));
240271
}
241272
}
242-
return new GraphQLFieldDefinition(name, description, type, dataFetcher, arguments, deprecationReason, definition);
273+
return new GraphQLFieldDefinition(name, description, type, dataFetcherFactory, arguments, deprecationReason, definition);
243274
}
244-
245-
246275
}
247276
}

src/main/java/graphql/schema/idl/CombinedWiringFactory.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.schema.idl;
22

33
import graphql.schema.DataFetcher;
4+
import graphql.schema.DataFetcherFactory;
45
import graphql.schema.TypeResolver;
56

67
import java.util.ArrayList;
@@ -61,6 +62,26 @@ public TypeResolver getTypeResolver(UnionWiringEnvironment environment) {
6162
return assertShouldNeverHappen();
6263
}
6364

65+
@Override
66+
public boolean providesDataFetcherFactory(FieldWiringEnvironment environment) {
67+
for (WiringFactory factory : factories) {
68+
if (factory.providesDataFetcherFactory(environment)) {
69+
return true;
70+
}
71+
}
72+
return false;
73+
}
74+
75+
@Override
76+
public <T> DataFetcherFactory<T> getDataFetcherFactory(FieldWiringEnvironment environment) {
77+
for (WiringFactory factory : factories) {
78+
if (factory.providesDataFetcherFactory(environment)) {
79+
return factory.getDataFetcherFactory(environment);
80+
}
81+
}
82+
return assertShouldNeverHappen();
83+
}
84+
6485
@Override
6586
public boolean providesDataFetcher(FieldWiringEnvironment environment) {
6687
for (WiringFactory factory : factories) {

src/main/java/graphql/schema/idl/SchemaGenerator.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import graphql.language.UnionTypeDefinition;
2828
import graphql.language.Value;
2929
import graphql.schema.DataFetcher;
30+
import graphql.schema.DataFetcherFactories;
31+
import graphql.schema.DataFetcherFactory;
3032
import graphql.schema.GraphQLArgument;
3133
import graphql.schema.GraphQLEnumType;
3234
import graphql.schema.GraphQLFieldDefinition;
@@ -458,7 +460,7 @@ private GraphQLFieldDefinition buildField(BuildContext buildCtx, TypeDefinition
458460
builder.description(buildDescription(fieldDef));
459461
builder.deprecate(buildDeprecationReason(fieldDef.getDirectives()));
460462

461-
builder.dataFetcher(buildDataFetcher(buildCtx, parentType, fieldDef));
463+
builder.dataFetcherFactory(buildDataFetcherFactory(buildCtx, parentType, fieldDef));
462464

463465
fieldDef.getInputValueDefinitions().forEach(inputValueDefinition ->
464466
builder.argument(buildArgument(buildCtx, inputValueDefinition)));
@@ -469,7 +471,7 @@ private GraphQLFieldDefinition buildField(BuildContext buildCtx, TypeDefinition
469471
return builder.build();
470472
}
471473

472-
private DataFetcher buildDataFetcher(BuildContext buildCtx, TypeDefinition parentType, FieldDefinition fieldDef) {
474+
private DataFetcherFactory buildDataFetcherFactory(BuildContext buildCtx, TypeDefinition parentType, FieldDefinition fieldDef) {
473475
String fieldName = fieldDef.getName();
474476
String parentTypeName = parentType.getName();
475477
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
@@ -478,21 +480,31 @@ private DataFetcher buildDataFetcher(BuildContext buildCtx, TypeDefinition paren
478480

479481
FieldWiringEnvironment wiringEnvironment = new FieldWiringEnvironment(typeRegistry, parentType, fieldDef);
480482

481-
DataFetcher dataFetcher;
482-
if (wiringFactory.providesDataFetcher(wiringEnvironment)) {
483-
dataFetcher = wiringFactory.getDataFetcher(wiringEnvironment);
484-
assertNotNull(dataFetcher, "The WiringFactory indicated it provides a data fetcher but then returned null");
483+
DataFetcherFactory<?> dataFetcherFactory;
484+
if (wiringFactory.providesDataFetcherFactory(wiringEnvironment)) {
485+
dataFetcherFactory = wiringFactory.getDataFetcherFactory(wiringEnvironment);
486+
assertNotNull(dataFetcherFactory, "The WiringFactory indicated it provides a data fetcher factory but then returned null");
485487
} else {
486-
dataFetcher = runtimeWiring.getDataFetcherForType(parentTypeName).get(fieldName);
487-
if (dataFetcher == null) {
488-
dataFetcher = runtimeWiring.getDefaultDataFetcherForType(parentTypeName);
488+
//
489+
// ok they provide a data fetcher directly
490+
DataFetcher<?> dataFetcher;
491+
if (wiringFactory.providesDataFetcher(wiringEnvironment)) {
492+
dataFetcher = wiringFactory.getDataFetcher(wiringEnvironment);
493+
assertNotNull(dataFetcher, "The WiringFactory indicated it provides a data fetcher but then returned null");
494+
} else {
495+
dataFetcher = runtimeWiring.getDataFetcherForType(parentTypeName).get(fieldName);
489496
if (dataFetcher == null) {
490-
dataFetcher = wiringFactory.getDefaultDataFetcher(wiringEnvironment);
491-
assertNotNull(dataFetcher, "The WiringFactory indicated MUST provide a default data fetcher as part of its contract");
497+
dataFetcher = runtimeWiring.getDefaultDataFetcherForType(parentTypeName);
498+
if (dataFetcher == null) {
499+
dataFetcher = wiringFactory.getDefaultDataFetcher(wiringEnvironment);
500+
assertNotNull(dataFetcher, "The WiringFactory indicated MUST provide a default data fetcher as part of its contract");
501+
}
492502
}
493503
}
504+
dataFetcherFactory = DataFetcherFactories.useDataFetcher(dataFetcher);
494505
}
495-
return dataFetcher;
506+
507+
return dataFetcherFactory;
496508
}
497509

498510
private GraphQLInputObjectType buildInputObjectType(BuildContext buildCtx, InputObjectTypeDefinition typeDefinition) {

src/main/java/graphql/schema/idl/WiringFactory.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import graphql.PublicSpi;
44
import graphql.schema.DataFetcher;
5+
import graphql.schema.DataFetcherFactory;
56
import graphql.schema.PropertyDataFetcher;
67
import graphql.schema.TypeResolver;
78

@@ -59,6 +60,29 @@ default TypeResolver getTypeResolver(UnionWiringEnvironment environment) {
5960
return assertShouldNeverHappen();
6061
}
6162

63+
/**
64+
* This is called to ask if this factory can provide a {@link graphql.schema.DataFetcherFactory} for the definition
65+
*
66+
* @param environment the wiring environment
67+
*
68+
* @return true if the factory can give out a data fetcher factory
69+
*/
70+
default boolean providesDataFetcherFactory(FieldWiringEnvironment environment) {
71+
return false;
72+
}
73+
74+
/**
75+
* Returns a {@link graphql.schema.DataFetcherFactory} given the type definition
76+
*
77+
* @param environment the wiring environment
78+
* @param <T> the type of the data fetcher
79+
*
80+
* @return a {@link graphql.schema.DataFetcherFactory}
81+
*/
82+
default <T> DataFetcherFactory<T> getDataFetcherFactory(FieldWiringEnvironment environment) {
83+
return assertShouldNeverHappen();
84+
}
85+
6286
/**
6387
* This is called to ask if this factory can provide a data fetcher for the definition
6488
*
@@ -84,6 +108,7 @@ default DataFetcher getDataFetcher(FieldWiringEnvironment environment) {
84108
/**
85109
* All fields need a data fetcher of some sort and this method is called to provide the data fetcher
86110
* that will be used if no specific one has been provided
111+
*
87112
* @param environment the wiring environment
88113
*
89114
* @return a {@link DataFetcher}

0 commit comments

Comments
 (0)