diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcher.java b/spring-graphql/src/main/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcher.java
index 48b85f76..446aacc4 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcher.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcher.java
@@ -59,13 +59,15 @@ import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
- * Entry point to create {@link DataFetcher} using repositories through Querydsl.
- * Exposes builders accepting {@link QuerydslPredicateExecutor} or
- * {@link ReactiveQuerydslPredicateExecutor} that support customization of bindings
- * and interface- and DTO projections. Instances can be created through a
- * {@link #builder(QuerydslPredicateExecutor) builder} to query for
- * {@link Builder#single()} or {@link Builder#many()} objects.
- *
Example:
+ * Main class to create a {@link DataFetcher} from a Querydsl repository.
+ * To create an instance, use one of the following:
+ *
+ * - {@link #builder(QuerydslPredicateExecutor)}
+ *
- {@link #builder(ReactiveQuerydslPredicateExecutor)}
+ *
+ *
+ * For example:
+ *
*
* interface BookRepository extends
* Repository<Book, String>, QuerydslPredicateExecutor<Book>{}
@@ -73,25 +75,16 @@ import org.springframework.util.StringUtils;
* TypeRuntimeWiring wiring = … ;
* BookRepository repository = … ;
*
- * wiring.dataFetcher("books", QuerydslDataFetcher.builder(repository).many())
- * .dataFetcher("book", QuerydslDataFetcher.builder(repository).single());
+ * DataFetcher<?> forMany =
+ * wiring.dataFetcher("books", QuerydslDataFetcher.builder(repository).many());
+ *
+ * DataFetcher<?> forSingle =
+ * wiring.dataFetcher("book", QuerydslDataFetcher.builder(repository).single());
*
*
- *
- * {@link DataFetcher} returning reactive types such as {@link Mono} and {@link Flux}
- * can be constructed from a {@link ReactiveQuerydslPredicateExecutor} using
- * {@link #builder(ReactiveQuerydslPredicateExecutor) builder}.
- *
For example:
- *
- * interface BookRepository extends
- * Repository<Book, String>, ReactiveQuerydslPredicateExecutor<Book>{}
- *
- * TypeRuntimeWiring wiring = …;
- * BookRepository repository = …;
- *
- * wiring.dataFetcher("books", QuerydslDataFetcher.builder(repository).many())
- * .dataFetcher("book", QuerydslDataFetcher.builder(repository).single());
- *
+ * See methods on {@link Builder} and {@link ReactiveBuilder} for further
+ * options on GraphQL Query argument to Querydsl Predicate bindings, result
+ * projections, and sorting.
*
* @param returned result type
* @author Mark Paluch
@@ -108,15 +101,55 @@ public abstract class QuerydslDataFetcher {
private static final QuerydslPredicateBuilder BUILDER = new QuerydslPredicateBuilder(
DefaultConversionService.getSharedInstance(), SimpleEntityPathResolver.INSTANCE);
+
private final TypeInformation domainType;
private final QuerydslBinderCustomizer> customizer;
+
QuerydslDataFetcher(TypeInformation domainType, QuerydslBinderCustomizer> customizer) {
- this.customizer = customizer;
this.domainType = domainType;
+ this.customizer = customizer;
}
+
+ /**
+ * Prepare a {@link Predicate} from GraphQL query arguments, also applying
+ * any {@link QuerydslBinderCustomizer} that may have been configured.
+ * @param environment contextual info for the GraphQL query
+ * @return the resulting predicate
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ protected Predicate buildPredicate(DataFetchingEnvironment environment) {
+ MultiValueMap parameters = new LinkedMultiValueMap<>();
+ QuerydslBindings bindings = new QuerydslBindings();
+
+ EntityPath> path = SimpleEntityPathResolver.INSTANCE.createPath(this.domainType.getType());
+ this.customizer.customize(bindings, path);
+
+ for (Map.Entry entry : environment.getArguments().entrySet()) {
+ parameters.put(entry.getKey(), Collections.singletonList(entry.getValue()));
+ }
+
+ return BUILDER.getPredicate(this.domainType, (MultiValueMap) parameters, bindings);
+ }
+
+ protected boolean requiresProjection(Class> resultType) {
+ return !resultType.equals(this.domainType.getType());
+ }
+
+ protected Collection buildPropertyPaths(DataFetchingFieldSelectionSet selection, Class> resultType){
+
+ // Compute selection only for non-projections
+ if (this.domainType.getType().equals(resultType) ||
+ this.domainType.getType().isAssignableFrom(resultType) ||
+ this.domainType.isSubTypeOf(resultType)) {
+ return PropertySelection.create(this.domainType, selection).toList();
+ }
+ return Collections.emptyList();
+ }
+
+
/**
* Create a new {@link Builder} accepting {@link QuerydslPredicateExecutor}
* to build a {@link DataFetcher}.
@@ -124,17 +157,8 @@ public abstract class QuerydslDataFetcher {
* @param result type
* @return a new builder
*/
- @SuppressWarnings("unchecked")
public static Builder builder(QuerydslPredicateExecutor executor) {
- Class> repositoryInterface = getRepositoryInterface(executor);
- DefaultRepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
- Class domainType = (Class) metadata.getDomainType();
-
- return new Builder<>(executor,
- ClassTypeInformation.from(domainType),
- domainType,
- Sort.unsorted(),
- (bindings, root) -> {});
+ return new Builder<>(executor, getDomainType(executor));
}
/**
@@ -144,17 +168,8 @@ public abstract class QuerydslDataFetcher {
* @param result type
* @return a new builder
*/
- @SuppressWarnings("unchecked")
public static ReactiveBuilder builder(ReactiveQuerydslPredicateExecutor executor) {
- Class> repositoryInterface = getRepositoryInterface(executor);
- DefaultRepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
- Class domainType = (Class) metadata.getDomainType();
-
- return new ReactiveBuilder<>(executor,
- ClassTypeInformation.from(domainType),
- domainType,
- Sort.unsorted(),
- (bindings, root) -> {});
+ return new ReactiveBuilder<>(executor, getDomainType(executor));
}
/**
@@ -174,33 +189,12 @@ public abstract class QuerydslDataFetcher {
return new RegistrationTypeVisitor(executors, reactiveExecutors);
}
- protected boolean requiresProjection(Class> resultType) {
- return !resultType.equals(this.domainType.getType());
- }
- protected Collection buildPropertyPaths(DataFetchingFieldSelectionSet selection,
- Class> resultType){
- // Compute selection only for non-projections
- if(resultType.equals(this.domainType.getType())
- || this.domainType.getType().isAssignableFrom(resultType)
- || this.domainType.isSubTypeOf(resultType)) {
- return PropertySelection.create(this.domainType, selection).toList();
- }
- return Collections.emptyList();
- }
- @SuppressWarnings({"unchecked", "rawtypes"})
- protected Predicate buildPredicate(DataFetchingEnvironment environment) {
- MultiValueMap parameters = new LinkedMultiValueMap<>();
- QuerydslBindings bindings = new QuerydslBindings();
-
- EntityPath> path = SimpleEntityPathResolver.INSTANCE.createPath(this.domainType.getType());
- this.customizer.customize(bindings, path);
-
- for (Map.Entry entry : environment.getArguments().entrySet()) {
- parameters.put(entry.getKey(), Collections.singletonList(entry.getValue()));
- }
-
- return BUILDER.getPredicate(this.domainType, (MultiValueMap) parameters, bindings);
+ @SuppressWarnings("unchecked")
+ private static Class getDomainType(Object executor) {
+ Class> repositoryInterface = getRepositoryInterface(executor);
+ DefaultRepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
+ return (Class) metadata.getDomainType();
}
private static Class> getRepositoryInterface(Object executor) {
@@ -221,6 +215,7 @@ public abstract class QuerydslDataFetcher {
String.format("Cannot resolve repository interface from %s", executor));
}
+
/**
* Builder for a Querydsl-based {@link DataFetcher}. Note that builder
* instances are immutable and return a new instance of the builder
@@ -240,10 +235,17 @@ public abstract class QuerydslDataFetcher {
private final QuerydslBinderCustomizer extends EntityPath> customizer;
+ @SuppressWarnings("unchecked")
+ Builder(QuerydslPredicateExecutor executor, Class domainType) {
+ this(executor,
+ ClassTypeInformation.from((Class) domainType),
+ domainType,
+ Sort.unsorted(),
+ (bindings, root) -> {});
+ }
+
Builder(QuerydslPredicateExecutor executor, ClassTypeInformation domainType,
- Class resultType,
- Sort sort,
- QuerydslBinderCustomizer extends EntityPath> customizer) {
+ Class resultType, Sort sort, QuerydslBinderCustomizer extends EntityPath> customizer) {
this.executor = executor;
this.domainType = domainType;
@@ -264,8 +266,7 @@ public abstract class QuerydslDataFetcher {
*/
public Builder projectAs(Class projectionType) {
Assert.notNull(projectionType, "Projection type must not be null");
- return new Builder<>(
- this.executor, this.domainType, projectionType, this.sort, this.customizer);
+ return new Builder<>(this.executor, this.domainType, projectionType, this.sort, this.customizer);
}
/**
@@ -276,21 +277,18 @@ public abstract class QuerydslDataFetcher {
*/
public Builder sortBy(Sort sort) {
Assert.notNull(sort, "Sort must not be null");
- return new Builder<>(
- this.executor, this.domainType, this.resultType, sort, customizer);
+ return new Builder<>(this.executor, this.domainType, this.resultType, sort, customizer);
}
/**
* Apply a {@link QuerydslBinderCustomizer}.
- * @param customizer the customizer to customize bindings for the
- * actual query
+ * @param customizer to customize the GraphQL query to Querydsl Predicate binding
* @return a new {@link Builder} instance with all previously configured
* options and {@code QuerydslBinderCustomizer} applied
*/
public Builder customizer(QuerydslBinderCustomizer extends EntityPath> customizer) {
Assert.notNull(customizer, "QuerydslBinderCustomizer must not be null");
- return new Builder<>(
- this.executor, this.domainType, this.resultType, this.sort, customizer);
+ return new Builder<>(this.executor, this.domainType, this.resultType, this.sort, customizer);
}
/**
@@ -313,6 +311,7 @@ public abstract class QuerydslDataFetcher {
}
+
/**
* Builder for a reactive Querydsl-based {@link DataFetcher}. Note that builder
* instances are immutable and return a new instance of the builder when
@@ -332,6 +331,15 @@ public abstract class QuerydslDataFetcher {
private final QuerydslBinderCustomizer extends EntityPath> customizer;
+ @SuppressWarnings("unchecked")
+ ReactiveBuilder(ReactiveQuerydslPredicateExecutor executor, Class domainType) {
+ this(executor,
+ ClassTypeInformation.from((Class) domainType),
+ domainType,
+ Sort.unsorted(),
+ (bindings, root) -> {});
+ }
+
ReactiveBuilder(ReactiveQuerydslPredicateExecutor executor,
TypeInformation domainType,
Class resultType,
@@ -357,8 +365,7 @@ public abstract class QuerydslDataFetcher {
*/
public ReactiveBuilder projectAs(Class projectionType) {
Assert.notNull(projectionType, "Projection type must not be null");
- return new ReactiveBuilder<>(
- this.executor, this.domainType, projectionType, this.sort, this.customizer);
+ return new ReactiveBuilder<>(this.executor, this.domainType, projectionType, this.sort, this.customizer);
}
/**
@@ -369,21 +376,18 @@ public abstract class QuerydslDataFetcher {
*/
public ReactiveBuilder sortBy(Sort sort) {
Assert.notNull(sort, "Sort must not be null");
- return new ReactiveBuilder<>(
- this.executor, this.domainType, this.resultType, sort, customizer);
+ return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType, sort, customizer);
}
/**
* Apply a {@link QuerydslBinderCustomizer}.
- * @param customizer the customizer to customize bindings for the
- * actual query
+ * @param customizer to customize the GraphQL query to Querydsl Predicate binding
* @return a new {@link Builder} instance with all previously configured
* options and {@code QuerydslBinderCustomizer} applied
*/
public ReactiveBuilder customizer(QuerydslBinderCustomizer extends EntityPath> customizer) {
Assert.notNull(customizer, "QuerydslBinderCustomizer must not be null");
- return new ReactiveBuilder<>(
- this.executor, this.domainType, this.resultType, this.sort, customizer);
+ return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType, this.sort, customizer);
}
/**
@@ -406,6 +410,7 @@ public abstract class QuerydslDataFetcher {
}
+
private static class SingleEntityFetcher extends QuerydslDataFetcher implements DataFetcher {
private final QuerydslPredicateExecutor executor;
@@ -429,18 +434,20 @@ public abstract class QuerydslDataFetcher {
@Override
@SuppressWarnings({"ConstantConditions", "unchecked"})
- public R get(DataFetchingEnvironment environment) {
- return this.executor.findBy(buildPredicate(environment), q -> {
- FetchableFluentQuery queryToUse = (FetchableFluentQuery) q;
+ public R get(DataFetchingEnvironment env) {
+ return this.executor.findBy(buildPredicate(env), query -> {
+ FetchableFluentQuery queryToUse = (FetchableFluentQuery) query;
if (this.sort.isSorted()){
queryToUse = queryToUse.sortBy(this.sort);
}
- if (requiresProjection(this.resultType)){
- queryToUse = queryToUse.as(this.resultType);
- } else {
- queryToUse = queryToUse.project(buildPropertyPaths(environment.getSelectionSet(), this.resultType));
+ Class resultType = this.resultType;
+ if (requiresProjection(resultType)){
+ queryToUse = queryToUse.as(resultType);
+ }
+ else {
+ queryToUse = queryToUse.project(buildPropertyPaths(env.getSelectionSet(), resultType));
}
return queryToUse.first();
@@ -449,6 +456,7 @@ public abstract class QuerydslDataFetcher {
}
+
private static class ManyEntityFetcher extends QuerydslDataFetcher implements DataFetcher> {
private final QuerydslPredicateExecutor executor;
@@ -471,9 +479,9 @@ public abstract class QuerydslDataFetcher {
@Override
@SuppressWarnings("unchecked")
- public Iterable get(DataFetchingEnvironment environment) {
- return this.executor.findBy(buildPredicate(environment), q -> {
- FetchableFluentQuery queryToUse = (FetchableFluentQuery) q;
+ public Iterable get(DataFetchingEnvironment env) {
+ return this.executor.findBy(buildPredicate(env), query -> {
+ FetchableFluentQuery queryToUse = (FetchableFluentQuery) query;
if (this.sort.isSorted()){
queryToUse = queryToUse.sortBy(this.sort);
@@ -481,8 +489,9 @@ public abstract class QuerydslDataFetcher {
if (requiresProjection(this.resultType)){
queryToUse = queryToUse.as(this.resultType);
- } else {
- queryToUse = queryToUse.project(buildPropertyPaths(environment.getSelectionSet(), this.resultType));
+ }
+ else {
+ queryToUse = queryToUse.project(buildPropertyPaths(env.getSelectionSet(), this.resultType));
}
return queryToUse.all();
@@ -491,6 +500,7 @@ public abstract class QuerydslDataFetcher {
}
+
private static class ReactiveSingleEntityFetcher extends QuerydslDataFetcher implements DataFetcher> {
private final ReactiveQuerydslPredicateExecutor executor;
@@ -514,9 +524,9 @@ public abstract class QuerydslDataFetcher {
@Override
@SuppressWarnings("unchecked")
- public Mono get(DataFetchingEnvironment environment) {
- return this.executor.findBy(buildPredicate(environment), q -> {
- FluentQuery.ReactiveFluentQuery queryToUse = (FluentQuery.ReactiveFluentQuery) q;
+ public Mono get(DataFetchingEnvironment env) {
+ return this.executor.findBy(buildPredicate(env), query -> {
+ FluentQuery.ReactiveFluentQuery queryToUse = (FluentQuery.ReactiveFluentQuery) query;
if (this.sort.isSorted()){
queryToUse = queryToUse.sortBy(this.sort);
@@ -524,8 +534,9 @@ public abstract class QuerydslDataFetcher {
if (requiresProjection(this.resultType)){
queryToUse = queryToUse.as(this.resultType);
- } else {
- queryToUse = queryToUse.project(buildPropertyPaths(environment.getSelectionSet(), this.resultType));
+ }
+ else {
+ queryToUse = queryToUse.project(buildPropertyPaths(env.getSelectionSet(), this.resultType));
}
return queryToUse.first();
@@ -534,6 +545,7 @@ public abstract class QuerydslDataFetcher {
}
+
private static class ReactiveManyEntityFetcher extends QuerydslDataFetcher implements DataFetcher> {
private final ReactiveQuerydslPredicateExecutor executor;
@@ -557,9 +569,9 @@ public abstract class QuerydslDataFetcher {
@Override
@SuppressWarnings("unchecked")
- public Flux get(DataFetchingEnvironment environment) {
- return this.executor.findBy(buildPredicate(environment), q -> {
- FluentQuery.ReactiveFluentQuery queryToUse = (FluentQuery.ReactiveFluentQuery) q;
+ public Flux get(DataFetchingEnvironment env) {
+ return this.executor.findBy(buildPredicate(env), query -> {
+ FluentQuery.ReactiveFluentQuery queryToUse = (FluentQuery.ReactiveFluentQuery) query;
if (this.sort.isSorted()){
queryToUse = queryToUse.sortBy(this.sort);
@@ -567,8 +579,9 @@ public abstract class QuerydslDataFetcher {
if (requiresProjection(this.resultType)){
queryToUse = queryToUse.as(this.resultType);
- } else {
- queryToUse = queryToUse.project(buildPropertyPaths(environment.getSelectionSet(), this.resultType));
+ }
+ else {
+ queryToUse = queryToUse.project(buildPropertyPaths(env.getSelectionSet(), this.resultType));
}
return queryToUse.all();