From ba6cf88a7d70bf0676a4fc126e941bca21097d4f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 27 Oct 2021 16:50:51 +0100 Subject: [PATCH] Polishing and minor refactoring Closes gh-168 --- .../data/querydsl/QuerydslDataFetcher.java | 237 +++++++++--------- 1 file changed, 125 insertions(+), 112 deletions(-) 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: + *

+ * + *

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> 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> customizer) { + Class resultType, Sort sort, QuerydslBinderCustomizer> 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> 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> 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> 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();