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 574c41b2..e08c8e96 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 @@ -26,7 +26,19 @@ import java.util.function.Function; import com.querydsl.core.types.EntityPath; import com.querydsl.core.types.Predicate; -import graphql.schema.*; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.DataFetchingFieldSelectionSet; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLFieldsContainer; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNamedOutputType; +import graphql.schema.GraphQLSchemaElement; +import graphql.schema.GraphQLType; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.GraphQLTypeVisitorStub; +import graphql.schema.PropertyDataFetcher; import graphql.util.TraversalControl; import graphql.util.TraverserContext; import reactor.core.publisher.Flux; @@ -49,7 +61,6 @@ import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; -import org.springframework.data.repository.query.QueryByExampleExecutor; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.graphql.data.GraphQlRepository; @@ -102,6 +113,9 @@ public abstract class QuerydslDataFetcher { private static final QuerydslPredicateBuilder BUILDER = new QuerydslPredicateBuilder( DefaultConversionService.getSharedInstance(), SimpleEntityPathResolver.INSTANCE); + @SuppressWarnings("rawtypes") + private static final QuerydslBinderCustomizer NO_OP_BINDER_CUSTOMIZER = (bindings, root) -> {}; + private final TypeInformation domainType; @@ -242,7 +256,7 @@ public abstract class QuerydslDataFetcher { ClassTypeInformation.from((Class) domainType), domainType, Sort.unsorted(), - (bindings, root) -> {}); + NO_OP_BINDER_CUSTOMIZER); } Builder(QuerydslPredicateExecutor executor, ClassTypeInformation domainType, @@ -256,7 +270,7 @@ public abstract class QuerydslDataFetcher { } /** - * Project results returned from the {@link QueryByExampleExecutor} + * Project results returned from the {@link QuerydslPredicateExecutor} * into the target {@code projectionType}. Projection types can be * either interfaces with property getters to expose or regular classes * outside the entity type hierarchy for DTO projections. @@ -335,7 +349,7 @@ public abstract class QuerydslDataFetcher { ClassTypeInformation.from((Class) domainType), domainType, Sort.unsorted(), - (bindings, root) -> {}); + NO_OP_BINDER_CUSTOMIZER); } ReactiveBuilder(ReactiveQuerydslPredicateExecutor executor, @@ -352,7 +366,7 @@ public abstract class QuerydslDataFetcher { } /** - * Project results returned from the {@link ReactiveQueryByExampleExecutor} + * Project results returned from the {@link ReactiveQuerydslPredicateExecutor} * into the target {@code projectionType}. Projection types can be * either interfaces with property getters to expose or regular classes * outside the entity type hierarchy for DTO projections. @@ -610,20 +624,18 @@ public abstract class QuerydslDataFetcher { for (QuerydslPredicateExecutor executor : executors) { String typeName = getTypeName(executor); if (typeName != null) { - QuerydslBinderCustomizer> customizer = detectCustomizer(executor); map.put(typeName, (single) -> single ? - builder(executor, customizer).single() : - builder(executor, customizer).many()); + builder(executor).single() : + builder(executor).many()); } } for (ReactiveQuerydslPredicateExecutor reactiveExecutor : reactiveExecutors) { String typeName = getTypeName(reactiveExecutor); if (typeName != null) { - QuerydslBinderCustomizer> customizer = detectCustomizer(reactiveExecutor); map.put(typeName, (single) -> single ? - reactiveBuilder(reactiveExecutor, customizer).single() : - reactiveBuilder(reactiveExecutor, customizer).many()); + reactiveBuilder(reactiveExecutor).single() : + reactiveBuilder(reactiveExecutor).many()); } } @@ -647,15 +659,22 @@ public abstract class QuerydslDataFetcher { } @SuppressWarnings({"unchecked", "rawtypes"}) - private Builder builder(QuerydslPredicateExecutor executor, - QuerydslBinderCustomizer> customizer) { - return QuerydslDataFetcher.builder(executor).customizer((QuerydslBinderCustomizer)customizer); + private Builder builder(QuerydslPredicateExecutor executor) { + return QuerydslDataFetcher.builder(executor) + .customizer((QuerydslBinderCustomizer) detectCustomizer(executor)); } @SuppressWarnings({"unchecked", "rawtypes"}) - private ReactiveBuilder reactiveBuilder(ReactiveQuerydslPredicateExecutor reactiveExecutor, - QuerydslBinderCustomizer> customizer) { - return QuerydslDataFetcher.builder(reactiveExecutor).customizer((QuerydslBinderCustomizer)customizer); + private ReactiveBuilder reactiveBuilder(ReactiveQuerydslPredicateExecutor reactiveExecutor) { + return QuerydslDataFetcher.builder(reactiveExecutor) + .customizer((QuerydslBinderCustomizer) detectCustomizer(reactiveExecutor)); + } + + @SuppressWarnings("unchecked") + private QuerydslBinderCustomizer> detectCustomizer(Object executor) { + return (executor instanceof QuerydslBinderCustomizer ? + (QuerydslBinderCustomizer>) executor : + NO_OP_BINDER_CUSTOMIZER); } @Override @@ -706,12 +725,6 @@ public abstract class QuerydslDataFetcher { return (fetcher != null && !(fetcher instanceof PropertyDataFetcher)); } - private QuerydslBinderCustomizer> detectCustomizer(Object executor) { - if(executor instanceof QuerydslBinderCustomizer) { - return (QuerydslBinderCustomizer>) executor; - } - return ((bindings, root) -> {}); - } } } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java index 33409e13..f9adc35a 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java @@ -134,6 +134,26 @@ class QuerydslDataFetcherTests { tester.accept(graphQlSetup(mockRepository)); } + @Test + void shouldApplyCustomizerViaBuilder() { + MockRepository mockRepository = mock(MockRepository.class); + + DataFetcher> fetcher = QuerydslDataFetcher.builder(mockRepository) + .customizer((QuerydslBinderCustomizer) (bindings, book) -> + bindings.bind(book.name).firstOptional((path, value) -> value.map(path::startsWith))) + .many(); + + graphQlSetup("books", fetcher).toWebGraphQlHandler() + .handleRequest(input("{ books(name: \"H\", author: \"Doug\") {name}}")) + .block(); + + ArgumentCaptor predicateCaptor = ArgumentCaptor.forClass(Predicate.class); + verify(mockRepository).findBy(predicateCaptor.capture(), any()); + + Predicate predicate = predicateCaptor.getValue(); + assertThat(predicate).isEqualTo(QBook.book.name.startsWith("H").and(QBook.book.author.eq("Doug"))); + } + @Test void shouldFavorExplicitWiring() { MockRepository mockRepository = mock(MockRepository.class); @@ -186,26 +206,6 @@ class QuerydslDataFetcherTests { assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy"); } - @Test - void shouldConstructPredicateProperly() { - MockRepository mockRepository = mock(MockRepository.class); - - DataFetcher> fetcher = QuerydslDataFetcher.builder(mockRepository) - .customizer((QuerydslBinderCustomizer) (bindings, book) -> - bindings.bind(book.name).firstOptional((path, value) -> value.map(path::startsWith))) - .many(); - - WebGraphQlHandler handler = graphQlSetup("books", fetcher).toWebGraphQlHandler(); - - handler.handleRequest(input("{ books(name: \"H\", author: \"Doug\") {name}}")).block(); - - ArgumentCaptor predicateCaptor = ArgumentCaptor.forClass(Predicate.class); - verify(mockRepository).findBy(predicateCaptor.capture(), any()); - - Predicate predicate = predicateCaptor.getValue(); - assertThat(predicate).isEqualTo(QBook.book.name.startsWith("H").and(QBook.book.author.eq("Doug"))); - } - @Test void shouldReactivelyFetchSingleItems() { ReactiveMockRepository mockRepository = mock(ReactiveMockRepository.class); @@ -279,9 +279,9 @@ class QuerydslDataFetcherTests { @GraphQlRepository interface MockRepository extends CrudRepository, QuerydslPredicateExecutor { - } + @GraphQlRepository interface MockWithCustomizerRepository extends CrudRepository, QuerydslPredicateExecutor, QuerydslBinderCustomizer { @@ -290,12 +290,12 @@ class QuerydslDataFetcherTests { default void customize(QuerydslBindings bindings, QBook book){ bindings.bind(book.name).firstOptional((path, value) -> value.map(path::startsWith)); } + } @GraphQlRepository interface ReactiveMockRepository extends Repository, ReactiveQuerydslPredicateExecutor { - }