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
22 changes: 22 additions & 0 deletions src/main/java/graphql/schema/DataFetcherFactories.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package graphql.schema;

import graphql.PublicApi;

/**
* A helper for {@link graphql.schema.DataFetcherFactory}
*/
@PublicApi
public class DataFetcherFactories {

/**
* Creates a {@link graphql.schema.DataFetcherFactory} that always returns the provided {@link graphql.schema.DataFetcher}
*
* @param dataFetcher the data fetcher to always return
* @param <T> the type of the data fetcher
*
* @return a data fetcher factory that always returns the provided data fetcher
*/
public static <T> DataFetcherFactory<T> useDataFetcher(DataFetcher<T> dataFetcher) {
return fieldDefinition -> dataFetcher;
}
}
25 changes: 25 additions & 0 deletions src/main/java/graphql/schema/DataFetcherFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package graphql.schema;

import graphql.PublicSpi;

/**
* A DataFetcherFactory allows a level of indirection in providing {@link graphql.schema.DataFetcher}s for graphql fields.
*
* For example if you are using an IoC container such as Spring or Guice, you can use this indirection to give you
* per request late binding of a data fetcher with its dependencies injected in.
*
* @param <T> the type of DataFetcher
*/
@PublicSpi
public interface DataFetcherFactory<T> {

/**
* Returns a {@link graphql.schema.DataFetcher}
*
* @param environment the environment that needs the data fetcher
*
* @return a data fetcher
*/
DataFetcher<T> get(DataFetcherFactoryEnvironment environment);

}
40 changes: 40 additions & 0 deletions src/main/java/graphql/schema/DataFetcherFactoryEnvironment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package graphql.schema;

import graphql.PublicApi;

/**
* This is passed to a {@link graphql.schema.DataFetcherFactory} when it is invoked to
* get a {@link graphql.schema.DataFetcher}
*/
@PublicApi
public class DataFetcherFactoryEnvironment {
private final GraphQLFieldDefinition fieldDefinition;

DataFetcherFactoryEnvironment(GraphQLFieldDefinition fieldDefinition) {
this.fieldDefinition = fieldDefinition;
}

/**
* @return the field that needs a {@link graphql.schema.DataFetcher}
*/
public GraphQLFieldDefinition getFieldDefinition() {
return fieldDefinition;
}

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

static class Builder {
GraphQLFieldDefinition fieldDefinition;

public Builder fieldDefinition(GraphQLFieldDefinition fieldDefinition) {
this.fieldDefinition = fieldDefinition;
return this;
}

public DataFetcherFactoryEnvironment build() {
return new DataFetcherFactoryEnvironment(fieldDefinition);
}
}
}
65 changes: 47 additions & 18 deletions src/main/java/graphql/schema/GraphQLFieldDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertValidName;
import static graphql.schema.DataFetcherFactoryEnvironment.newDataFetchingFactoryEnvironment;

/**
* Fields are the ways you get data values in graphql and a field definition represents a field, its type, the arguments it takes
Expand All @@ -30,27 +31,28 @@ public class GraphQLFieldDefinition {
private final String name;
private final String description;
private GraphQLOutputType type;
private final DataFetcher dataFetcher;
private final DataFetcherFactory dataFetcherFactory;
private final String deprecationReason;
private final List<GraphQLArgument> arguments;
private final FieldDefinition definition;


@Deprecated
@Internal
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcher dataFetcher, List<GraphQLArgument> arguments, String deprecationReason) {
this(name, description, type, dataFetcher, arguments, deprecationReason, null);
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcher<?> dataFetcher, List<GraphQLArgument> arguments, String deprecationReason) {
this(name, description, type, DataFetcherFactories.useDataFetcher(dataFetcher), arguments, deprecationReason, null);
}

@Internal
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcher dataFetcher, List<GraphQLArgument> arguments, String deprecationReason, FieldDefinition definition) {
public GraphQLFieldDefinition(String name, String description, GraphQLOutputType type, DataFetcherFactory dataFetcherFactory, List<GraphQLArgument> arguments, String deprecationReason, FieldDefinition definition) {
assertValidName(name);
assertNotNull(dataFetcher, "dataFetcher can't be null");
assertNotNull(dataFetcherFactory, "you have to provide a DataFetcher (or DataFetcherFactory)");
assertNotNull(type, "type can't be null");
assertNotNull(arguments, "arguments can't be null");
this.name = name;
this.description = description;
this.type = type;
this.dataFetcher = dataFetcher;
this.dataFetcherFactory = dataFetcherFactory;
this.arguments = Collections.unmodifiableList(new ArrayList<>(arguments));
this.deprecationReason = deprecationReason;
this.definition = definition;
Expand All @@ -71,7 +73,9 @@ public GraphQLOutputType getType() {
}

public DataFetcher getDataFetcher() {
return dataFetcher;
return dataFetcherFactory.get(newDataFetchingFactoryEnvironment()
.fieldDefinition(this)
.build());
}

public GraphQLArgument getArgument(String name) {
Expand Down Expand Up @@ -106,8 +110,8 @@ public String toString() {
return "GraphQLFieldDefinition{" +
"name='" + name + '\'' +
", type=" + type +
", dataFetcher=" + dataFetcher +
", arguments=" + arguments +
", dataFetcherFactory=" + dataFetcherFactory +
", description='" + description + '\'' +
", deprecationReason='" + deprecationReason + '\'' +
", definition=" + definition +
Expand All @@ -124,7 +128,7 @@ public static class Builder {
private String name;
private String description;
private GraphQLOutputType type;
private DataFetcher dataFetcher;
private DataFetcherFactory<?> dataFetcherFactory;
private List<GraphQLArgument> arguments = new ArrayList<>();
private String deprecationReason;
private boolean isField;
Expand Down Expand Up @@ -163,14 +167,41 @@ public Builder type(GraphQLOutputType type) {
return this;
}

public Builder dataFetcher(DataFetcher dataFetcher) {
/**
* Sets the {@link graphql.schema.DataFetcher} to use with this field.
*
* @param dataFetcher the data fetcher to use
*
* @return this builder
*/
public Builder dataFetcher(DataFetcher<?> dataFetcher) {
assertNotNull(dataFetcher, "dataFetcher must be not null");
this.dataFetcher = dataFetcher;
this.dataFetcherFactory = DataFetcherFactories.useDataFetcher(dataFetcher);
return this;
}

/**
* Sets the {@link graphql.schema.DataFetcherFactory} to use with this field.
*
* @param dataFetcherFactory the factory to use
*
* @return this builder
*/
public Builder dataFetcherFactory(DataFetcherFactory dataFetcherFactory) {
assertNotNull(dataFetcherFactory, "dataFetcherFactory must be not null");
this.dataFetcherFactory = dataFetcherFactory;
return this;
}

/**
* This will cause the data fetcher of this field to always return the supplied value
*
* @param value the value to always return
*
* @return this builder
*/
public Builder staticValue(final Object value) {
this.dataFetcher = environment -> value;
this.dataFetcherFactory = DataFetcherFactories.useDataFetcher(environment -> value);
return this;
}

Expand Down Expand Up @@ -232,16 +263,14 @@ public Builder deprecate(String deprecationReason) {
}

public GraphQLFieldDefinition build() {
if (dataFetcher == null) {
if (dataFetcherFactory == null) {
if (isField) {
dataFetcher = new FieldDataFetcher(name);
dataFetcherFactory = DataFetcherFactories.useDataFetcher(new FieldDataFetcher<>(name));
} else {
dataFetcher = new PropertyDataFetcher(name);
dataFetcherFactory = DataFetcherFactories.useDataFetcher(new PropertyDataFetcher<>(name));
}
}
return new GraphQLFieldDefinition(name, description, type, dataFetcher, arguments, deprecationReason, definition);
return new GraphQLFieldDefinition(name, description, type, dataFetcherFactory, arguments, deprecationReason, definition);
}


}
}
21 changes: 21 additions & 0 deletions src/main/java/graphql/schema/idl/CombinedWiringFactory.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graphql.schema.idl;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetcherFactory;
import graphql.schema.TypeResolver;

import java.util.ArrayList;
Expand Down Expand Up @@ -61,6 +62,26 @@ public TypeResolver getTypeResolver(UnionWiringEnvironment environment) {
return assertShouldNeverHappen();
}

@Override
public boolean providesDataFetcherFactory(FieldWiringEnvironment environment) {
for (WiringFactory factory : factories) {
if (factory.providesDataFetcherFactory(environment)) {
return true;
}
}
return false;
}

@Override
public <T> DataFetcherFactory<T> getDataFetcherFactory(FieldWiringEnvironment environment) {
for (WiringFactory factory : factories) {
if (factory.providesDataFetcherFactory(environment)) {
return factory.getDataFetcherFactory(environment);
}
}
return assertShouldNeverHappen();
}

@Override
public boolean providesDataFetcher(FieldWiringEnvironment environment) {
for (WiringFactory factory : factories) {
Expand Down
36 changes: 24 additions & 12 deletions src/main/java/graphql/schema/idl/SchemaGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import graphql.language.UnionTypeDefinition;
import graphql.language.Value;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetcherFactories;
import graphql.schema.DataFetcherFactory;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
Expand Down Expand Up @@ -458,7 +460,7 @@ private GraphQLFieldDefinition buildField(BuildContext buildCtx, TypeDefinition
builder.description(buildDescription(fieldDef));
builder.deprecate(buildDeprecationReason(fieldDef.getDirectives()));

builder.dataFetcher(buildDataFetcher(buildCtx, parentType, fieldDef));
builder.dataFetcherFactory(buildDataFetcherFactory(buildCtx, parentType, fieldDef));

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

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

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

DataFetcher dataFetcher;
if (wiringFactory.providesDataFetcher(wiringEnvironment)) {
dataFetcher = wiringFactory.getDataFetcher(wiringEnvironment);
assertNotNull(dataFetcher, "The WiringFactory indicated it provides a data fetcher but then returned null");
DataFetcherFactory<?> dataFetcherFactory;
if (wiringFactory.providesDataFetcherFactory(wiringEnvironment)) {
dataFetcherFactory = wiringFactory.getDataFetcherFactory(wiringEnvironment);
assertNotNull(dataFetcherFactory, "The WiringFactory indicated it provides a data fetcher factory but then returned null");
} else {
dataFetcher = runtimeWiring.getDataFetcherForType(parentTypeName).get(fieldName);
if (dataFetcher == null) {
dataFetcher = runtimeWiring.getDefaultDataFetcherForType(parentTypeName);
//
// ok they provide a data fetcher directly
DataFetcher<?> dataFetcher;
if (wiringFactory.providesDataFetcher(wiringEnvironment)) {
dataFetcher = wiringFactory.getDataFetcher(wiringEnvironment);
assertNotNull(dataFetcher, "The WiringFactory indicated it provides a data fetcher but then returned null");
} else {
dataFetcher = runtimeWiring.getDataFetcherForType(parentTypeName).get(fieldName);
if (dataFetcher == null) {
dataFetcher = wiringFactory.getDefaultDataFetcher(wiringEnvironment);
assertNotNull(dataFetcher, "The WiringFactory indicated MUST provide a default data fetcher as part of its contract");
dataFetcher = runtimeWiring.getDefaultDataFetcherForType(parentTypeName);
if (dataFetcher == null) {
dataFetcher = wiringFactory.getDefaultDataFetcher(wiringEnvironment);
assertNotNull(dataFetcher, "The WiringFactory indicated MUST provide a default data fetcher as part of its contract");
}
}
}
dataFetcherFactory = DataFetcherFactories.useDataFetcher(dataFetcher);
}
return dataFetcher;

return dataFetcherFactory;
}

private GraphQLInputObjectType buildInputObjectType(BuildContext buildCtx, InputObjectTypeDefinition typeDefinition) {
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/graphql/schema/idl/WiringFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import graphql.PublicSpi;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetcherFactory;
import graphql.schema.PropertyDataFetcher;
import graphql.schema.TypeResolver;

Expand Down Expand Up @@ -59,6 +60,29 @@ default TypeResolver getTypeResolver(UnionWiringEnvironment environment) {
return assertShouldNeverHappen();
}

/**
* This is called to ask if this factory can provide a {@link graphql.schema.DataFetcherFactory} for the definition
*
* @param environment the wiring environment
*
* @return true if the factory can give out a data fetcher factory
*/
default boolean providesDataFetcherFactory(FieldWiringEnvironment environment) {
return false;
}

/**
* Returns a {@link graphql.schema.DataFetcherFactory} given the type definition
*
* @param environment the wiring environment
* @param <T> the type of the data fetcher
*
* @return a {@link graphql.schema.DataFetcherFactory}
*/
default <T> DataFetcherFactory<T> getDataFetcherFactory(FieldWiringEnvironment environment) {
return assertShouldNeverHappen();
}

/**
* This is called to ask if this factory can provide a data fetcher for the definition
*
Expand All @@ -84,6 +108,7 @@ default DataFetcher getDataFetcher(FieldWiringEnvironment environment) {
/**
* All fields need a data fetcher of some sort and this method is called to provide the data fetcher
* that will be used if no specific one has been provided
*
* @param environment the wiring environment
*
* @return a {@link DataFetcher}
Expand Down
Loading