From 4d99d2ab314cf59d92ed3aa6d42834c0176dfe06 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 26 Nov 2021 19:18:46 +0000 Subject: [PATCH] Refactoring in query package Increase sharing between QBE and Querydsl: - shared GraphQLTypeVisitor for auto-registration - utility methods for repository info --- ...aphQlWebFluxQuerydslAutoConfiguration.java | 6 +- ...raphQlWebMvcQuerydslAutoConfiguration.java | 6 +- .../query/AutoRegistrationTypeVisitor.java | 107 +++++++++ .../data/query/QueryByExampleDataFetcher.java | 169 ++------------ .../data/query/QuerydslDataFetcher.java | 206 +++--------------- .../graphql/data/query/RepositoryUtils.java | 76 +++++++ .../data/query/QuerydslDataFetcherTests.java | 2 +- 7 files changed, 235 insertions(+), 337 deletions(-) create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/data/query/AutoRegistrationTypeVisitor.java create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebFluxQuerydslAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebFluxQuerydslAutoConfiguration.java index fa050e08..34e1bf79 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebFluxQuerydslAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebFluxQuerydslAutoConfiguration.java @@ -45,7 +45,7 @@ import org.springframework.graphql.execution.GraphQlSource; * * @author Rossen Stoyanchev * @since 1.0.0 - * @see QuerydslDataFetcher#registrationTypeVisitor(List, List) + * @see QuerydslDataFetcher#autoRegistrationTypeVisitor(List, List) */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) @@ -63,8 +63,8 @@ public class GraphQlWebFluxQuerydslAutoConfiguration { executorsProvider.stream().collect(Collectors.toList()); if (!executors.isEmpty()) { - GraphQLTypeVisitor visitor = QuerydslDataFetcher.registrationTypeVisitor(Collections.emptyList(), executors); - builder.typeVisitors(Collections.singletonList(visitor)); + builder.typeVisitors(Collections.singletonList( + QuerydslDataFetcher.autoRegistrationTypeVisitor(Collections.emptyList(), executors))); } }; } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebMvcQuerydslAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebMvcQuerydslAutoConfiguration.java index 9adc2740..44e2394d 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebMvcQuerydslAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/data/GraphQlWebMvcQuerydslAutoConfiguration.java @@ -45,7 +45,7 @@ import org.springframework.graphql.execution.GraphQlSource; * * @author Rossen Stoyanchev * @since 1.0.0 - * @see QuerydslDataFetcher#registrationTypeVisitor(List, List) + * @see QuerydslDataFetcher#autoRegistrationTypeVisitor(List, List) */ @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @@ -67,8 +67,8 @@ public class GraphQlWebMvcQuerydslAutoConfiguration { reactiveExecutorsProvider.stream().collect(Collectors.toList()); if (!executors.isEmpty()) { - GraphQLTypeVisitor visitor = QuerydslDataFetcher.registrationTypeVisitor(executors, reactiveExecutors); - builder.typeVisitors(Collections.singletonList(visitor)); + builder.typeVisitors(Collections.singletonList( + QuerydslDataFetcher.autoRegistrationTypeVisitor(executors, reactiveExecutors))); } }; } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/AutoRegistrationTypeVisitor.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/AutoRegistrationTypeVisitor.java new file mode 100644 index 00000000..e4b7740e --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/AutoRegistrationTypeVisitor.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.data.query; + +import java.util.Map; +import java.util.function.Function; + +import graphql.schema.DataFetcher; +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.GraphQLTypeVisitorStub; +import graphql.schema.PropertyDataFetcher; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +import org.springframework.lang.Nullable; + +/** + * Given a map of GraphQL type names and DataFetcher factories, find queries + * with a matching return type and register DataFetcher's for them, unless they + * already have registrations. + * + * @author Rossen Stoyanchev + * @since 1.0.0 + */ +class AutoRegistrationTypeVisitor extends GraphQLTypeVisitorStub { + + private final Map>> dataFetcherFactories; + + + /** + * Create an instance of the visitor. + * @param dataFetcherFactories map with GraphQL type names as keys and + * functions as values to create a DataFetcher for single or many values + */ + public AutoRegistrationTypeVisitor(Map>> dataFetcherFactories) { + this.dataFetcherFactories = dataFetcherFactories; + } + + + @Override + public TraversalControl visitGraphQLFieldDefinition( + GraphQLFieldDefinition fieldDefinition, TraverserContext context) { + + if (this.dataFetcherFactories.isEmpty()) { + return TraversalControl.QUIT; + } + + GraphQLType fieldType = fieldDefinition.getType(); + GraphQLFieldsContainer parent = (GraphQLFieldsContainer) context.getParentNode(); + if (!parent.getName().equals("Query")) { + return TraversalControl.ABORT; + } + + DataFetcher dataFetcher = (fieldType instanceof GraphQLList ? + getDataFetcher(((GraphQLList) fieldType).getWrappedType(), false) : + getDataFetcher(fieldType, true)); + + if (dataFetcher != null) { + GraphQLCodeRegistry.Builder registry = context.getVarFromParents(GraphQLCodeRegistry.Builder.class); + if (!hasDataFetcher(registry, parent, fieldDefinition)) { + registry.dataFetcher(parent, fieldDefinition, dataFetcher); + } + } + + return TraversalControl.CONTINUE; + } + + @Nullable + private DataFetcher getDataFetcher(GraphQLType type, boolean single) { + if (type instanceof GraphQLNamedOutputType) { + String typeName = ((GraphQLNamedOutputType) type).getName(); + Function> factory = this.dataFetcherFactories.get(typeName); + if (factory != null) { + return factory.apply(single); + } + } + return null; + } + + private boolean hasDataFetcher( + GraphQLCodeRegistry.Builder registry, GraphQLFieldsContainer parent, + GraphQLFieldDefinition fieldDefinition) { + + DataFetcher fetcher = registry.getDataFetcher(parent, fieldDefinition); + return (fetcher != null && !(fetcher instanceof PropertyDataFetcher)); + } + +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java index 6c005e31..77b64a15 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QueryByExampleDataFetcher.java @@ -16,7 +16,6 @@ package org.springframework.graphql.data.query; -import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -27,40 +26,20 @@ import java.util.function.Function; 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; import reactor.core.publisher.Mono; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.MergedAnnotations; import org.springframework.data.domain.Example; import org.springframework.data.domain.Sort; -import org.springframework.data.repository.NoRepositoryBean; -import org.springframework.data.repository.Repository; -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.QueryByExampleExecutor; import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; -import org.springframework.graphql.data.GraphQlRepository; import org.springframework.graphql.data.GraphQlArgumentInitializer; -import org.springframework.lang.Nullable; +import org.springframework.graphql.data.GraphQlRepository; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Main class to create a {@link DataFetcher} from a Query By Example repository. @@ -153,7 +132,7 @@ public abstract class QueryByExampleDataFetcher { * @return a new builder */ public static Builder builder(QueryByExampleExecutor executor) { - return new Builder<>(executor, getDomainType(executor)); + return new Builder<>(executor, RepositoryUtils.getDomainType(executor)); } /** @@ -165,7 +144,7 @@ public abstract class QueryByExampleDataFetcher { * @return a new builder */ public static ReactiveBuilder builder(ReactiveQueryByExampleExecutor executor) { - return new ReactiveBuilder<>(executor, getDomainType(executor)); + return new ReactiveBuilder<>(executor, RepositoryUtils.getDomainType(executor)); } /** @@ -183,32 +162,23 @@ public abstract class QueryByExampleDataFetcher { List> executors, List> reactiveExecutors) { - return new RegistrationTypeVisitor(executors, reactiveExecutors); - } + Map>> factories = new HashMap<>(); - @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) { - Assert.isInstanceOf(Repository.class, executor); - - Type[] genericInterfaces = executor.getClass().getGenericInterfaces(); - for (Type genericInterface : genericInterfaces) { - Class rawClass = ResolvableType.forType(genericInterface).getRawClass(); - if (rawClass == null || MergedAnnotations.from(rawClass).isPresent(NoRepositoryBean.class)) { - continue; - } - if (Repository.class.isAssignableFrom(rawClass)) { - return rawClass; + for (QueryByExampleExecutor executor : executors) { + String typeName = RepositoryUtils.getGraphQlTypeName(executor); + if (typeName != null) { + factories.put(typeName, single -> single ? builder(executor).single() : builder(executor).many()); } } - throw new IllegalArgumentException( - String.format("Cannot resolve repository interface from %s", executor)); + for (ReactiveQueryByExampleExecutor executor : reactiveExecutors) { + String typeName = RepositoryUtils.getGraphQlTypeName(executor); + if (typeName != null) { + factories.put(typeName, single -> single ? builder(executor).single() : builder(executor).many()); + } + } + + return new AutoRegistrationTypeVisitor(factories); } @@ -530,111 +500,4 @@ public abstract class QueryByExampleDataFetcher { } - - /** - * GraphQLTypeVisitor that auto-registers Query By Example Spring Data repositories. - */ - private static class RegistrationTypeVisitor extends GraphQLTypeVisitorStub { - - private final Map>> executorMap; - - RegistrationTypeVisitor( - List> executors, - List> reactiveExecutors) { - - this.executorMap = initExecutorMap(executors, reactiveExecutors); - } - - private Map>> initExecutorMap( - List> executors, - List> reactiveExecutors) { - - Map>> map = new HashMap<>(); - - for (QueryByExampleExecutor executor : executors) { - String typeName = getTypeName(executor); - if (typeName != null) { - map.put(typeName, (single) -> single ? - builder(executor).single() : - builder(executor).many()); - } - } - - for (ReactiveQueryByExampleExecutor reactiveExecutor : reactiveExecutors) { - String typeName = getTypeName(reactiveExecutor); - if (typeName != null) { - map.put(typeName, (single) -> single ? - builder(reactiveExecutor).single() : - builder(reactiveExecutor).many()); - } - } - - return map; - } - - @Nullable - private String getTypeName(Object repository) { - GraphQlRepository annotation = - AnnotatedElementUtils.findMergedAnnotation(repository.getClass(), GraphQlRepository.class); - - if (annotation == null) { - return null; - } - if (StringUtils.hasText(annotation.typeName())) { - return annotation.typeName(); - } - Class repositoryInterface = getRepositoryInterface(repository); - RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); - return metadata.getDomainType().getSimpleName(); - } - - @Override - public TraversalControl visitGraphQLFieldDefinition( - GraphQLFieldDefinition fieldDefinition, TraverserContext context) { - - if (this.executorMap.isEmpty()) { - return TraversalControl.QUIT; - } - - GraphQLType fieldType = fieldDefinition.getType(); - GraphQLFieldsContainer parent = (GraphQLFieldsContainer) context.getParentNode(); - if (!parent.getName().equals("Query")) { - return TraversalControl.ABORT; - } - - DataFetcher dataFetcher = (fieldType instanceof GraphQLList ? - getDataFetcher(((GraphQLList) fieldType).getWrappedType(), false) : - getDataFetcher(fieldType, true)); - - if (dataFetcher != null) { - GraphQLCodeRegistry.Builder registry = context.getVarFromParents(GraphQLCodeRegistry.Builder.class); - if (!hasDataFetcher(registry, parent, fieldDefinition)) { - registry.dataFetcher(parent, fieldDefinition, dataFetcher); - } - } - - return TraversalControl.CONTINUE; - } - - @Nullable - private DataFetcher getDataFetcher(GraphQLType type, boolean single) { - if (type instanceof GraphQLNamedOutputType) { - String typeName = ((GraphQLNamedOutputType) type).getName(); - Function> factory = this.executorMap.get(typeName); - if (factory != null) { - return factory.apply(single); - } - } - return null; - } - - private boolean hasDataFetcher( - GraphQLCodeRegistry.Builder registry, GraphQLFieldsContainer parent, - GraphQLFieldDefinition fieldDefinition) { - - DataFetcher fetcher = registry.getDataFetcher(parent, fieldDefinition); - return (fetcher != null && !(fetcher instanceof PropertyDataFetcher)); - } - } - } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java index ddaa6909..90db8aa8 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/QuerydslDataFetcher.java @@ -16,7 +16,6 @@ package org.springframework.graphql.data.query; -import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -29,24 +28,10 @@ import com.querydsl.core.types.Predicate; 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; import reactor.core.publisher.Mono; -import org.springframework.core.ResolvableType; -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.domain.Sort; import org.springframework.data.querydsl.QuerydslPredicateExecutor; @@ -55,20 +40,14 @@ import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer; import org.springframework.data.querydsl.binding.QuerydslBindings; import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder; -import org.springframework.data.repository.NoRepositoryBean; -import org.springframework.data.repository.Repository; -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.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.graphql.data.GraphQlRepository; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; /** * Main class to create a {@link DataFetcher} from a Querydsl repository. @@ -98,7 +77,7 @@ import org.springframework.util.StringUtils; * options on GraphQL Query argument to Querydsl Predicate binding customizations, * result projections, and sorting. * - *

{@code QuerydslDataFetcher} {@link #registrationTypeVisitor(List, List) exposes} + *

{@code QuerydslDataFetcher} {@link #autoRegistrationTypeVisitor(List, List) exposes} * a {@link GraphQLTypeVisitor} that can auto-register repositories annotated with * {@link GraphQlRepository @GraphQlRepository}. * @@ -180,7 +159,7 @@ public abstract class QuerydslDataFetcher { * @return a new builder */ public static Builder builder(QuerydslPredicateExecutor executor) { - return new Builder<>(executor, getDomainType(executor)); + return new Builder<>(executor, RepositoryUtils.getDomainType(executor)); } /** @@ -191,7 +170,7 @@ public abstract class QuerydslDataFetcher { * @return a new builder */ public static ReactiveBuilder builder(ReactiveQuerydslPredicateExecutor executor) { - return new ReactiveBuilder<>(executor, getDomainType(executor)); + return new ReactiveBuilder<>(executor, RepositoryUtils.getDomainType(executor)); } /** @@ -210,37 +189,37 @@ public abstract class QuerydslDataFetcher { * @param reactiveExecutors reactive repositories to consider for registration * @return the created visitor */ - public static GraphQLTypeVisitor registrationTypeVisitor( + @SuppressWarnings({"unchecked", "rawtypes"}) + public static GraphQLTypeVisitor autoRegistrationTypeVisitor( List> executors, List> reactiveExecutors) { - return new RegistrationTypeVisitor(executors, reactiveExecutors); - } + Map>> factories = new HashMap<>(); - - @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) { - Assert.isInstanceOf(Repository.class, executor); - - Type[] genericInterfaces = executor.getClass().getGenericInterfaces(); - for (Type genericInterface : genericInterfaces) { - Class rawClass = ResolvableType.forType(genericInterface).getRawClass(); - if (rawClass == null || MergedAnnotations.from(rawClass).isPresent(NoRepositoryBean.class)) { - continue; - } - if (Repository.class.isAssignableFrom(rawClass)) { - return rawClass; + for (QuerydslPredicateExecutor executor : executors) { + String typeName = RepositoryUtils.getGraphQlTypeName(executor); + if (typeName != null) { + Builder builder = QuerydslDataFetcher.builder(executor).customizer(customizer(executor)); + factories.put(typeName, single -> single ? builder.single() : builder.many()); } } - throw new IllegalArgumentException( - String.format("Cannot resolve repository interface from %s", executor)); + for (ReactiveQuerydslPredicateExecutor executor : reactiveExecutors) { + String typeName = RepositoryUtils.getGraphQlTypeName(executor); + if (typeName != null) { + ReactiveBuilder builder = QuerydslDataFetcher.builder(executor).customizer(customizer(executor)); + factories.put(typeName, single -> single ? builder.single() : builder.many()); + } + } + + return new AutoRegistrationTypeVisitor(factories); + } + + @SuppressWarnings("rawtypes") + private static QuerydslBinderCustomizer customizer(Object executor) { + return (executor instanceof QuerydslBinderCustomizer ? + (QuerydslBinderCustomizer>) executor : + NO_OP_BINDER_CUSTOMIZER); } @@ -312,7 +291,7 @@ public abstract class QuerydslDataFetcher { * *

If a Querydsl repository implements {@link QuerydslBinderCustomizer} * itself, this is automatically detected and applied during - * {@link #registrationTypeVisitor(List, List) auto-registration}. + * {@link #autoRegistrationTypeVisitor(List, List) auto-registration}. * For manual registration, you will need to use this method to apply it. * * @param customizer to customize the GraphQL query to Querydsl @@ -415,7 +394,7 @@ public abstract class QuerydslDataFetcher { * *

If a Querydsl repository implements {@link QuerydslBinderCustomizer} * itself, this is automatically detected and applied during - * {@link #registrationTypeVisitor(List, List) auto-registration}. + * {@link #autoRegistrationTypeVisitor(List, List) auto-registration}. * For manual registration, you will need to use this method to apply it. * * @param customizer to customize the GraphQL query to Querydsl @@ -627,131 +606,4 @@ public abstract class QuerydslDataFetcher { } - /** - * GraphQLTypeVisitor that auto-registers Querydsl Spring Data repositories. - */ - private static class RegistrationTypeVisitor extends GraphQLTypeVisitorStub { - - private final Map>> executorMap; - - RegistrationTypeVisitor( - List> executors, - List> reactiveExecutors) { - - this.executorMap = initExecutorMap(executors, reactiveExecutors); - } - - - private Map>> initExecutorMap( - List> executors, - List> reactiveExecutors) { - - Map>> map = new HashMap<>(); - - for (QuerydslPredicateExecutor executor : executors) { - String typeName = getTypeName(executor); - if (typeName != null) { - map.put(typeName, (single) -> single ? - builder(executor).single() : - builder(executor).many()); - } - } - - for (ReactiveQuerydslPredicateExecutor reactiveExecutor : reactiveExecutors) { - String typeName = getTypeName(reactiveExecutor); - if (typeName != null) { - map.put(typeName, (single) -> single ? - reactiveBuilder(reactiveExecutor).single() : - reactiveBuilder(reactiveExecutor).many()); - } - } - - return map; - } - - @Nullable - private String getTypeName(Object repository) { - GraphQlRepository annotation = - AnnotatedElementUtils.findMergedAnnotation(repository.getClass(), GraphQlRepository.class); - - if (annotation == null) { - return null; - } - if (StringUtils.hasText(annotation.typeName())) { - return annotation.typeName(); - } - Class repositoryInterface = getRepositoryInterface(repository); - RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); - return metadata.getDomainType().getSimpleName(); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - private Builder builder(QuerydslPredicateExecutor executor) { - return QuerydslDataFetcher.builder(executor) - .customizer((QuerydslBinderCustomizer) detectCustomizer(executor)); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - 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 - public TraversalControl visitGraphQLFieldDefinition( - GraphQLFieldDefinition fieldDefinition, TraverserContext context) { - - if (this.executorMap.isEmpty()) { - return TraversalControl.QUIT; - } - - GraphQLType fieldType = fieldDefinition.getType(); - GraphQLFieldsContainer parent = (GraphQLFieldsContainer) context.getParentNode(); - if (!parent.getName().equals("Query")) { - return TraversalControl.ABORT; - } - - DataFetcher dataFetcher = (fieldType instanceof GraphQLList ? - getDataFetcher(((GraphQLList) fieldType).getWrappedType(), false) : - getDataFetcher(fieldType, true)); - - if (dataFetcher != null) { - GraphQLCodeRegistry.Builder registry = context.getVarFromParents(GraphQLCodeRegistry.Builder.class); - if (!hasDataFetcher(registry, parent, fieldDefinition)) { - registry.dataFetcher(parent, fieldDefinition, dataFetcher); - } - } - - return TraversalControl.CONTINUE; - } - - @Nullable - private DataFetcher getDataFetcher(GraphQLType type, boolean single) { - if (type instanceof GraphQLNamedOutputType) { - String typeName = ((GraphQLNamedOutputType) type).getName(); - Function> factory = this.executorMap.get(typeName); - if (factory != null) { - return factory.apply(single); - } - } - return null; - } - - private boolean hasDataFetcher( - GraphQLCodeRegistry.Builder registry, GraphQLFieldsContainer parent, - GraphQLFieldDefinition fieldDefinition) { - - DataFetcher fetcher = registry.getDataFetcher(parent, fieldDefinition); - return (fetcher != null && !(fetcher instanceof PropertyDataFetcher)); - } - - } - } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java b/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java new file mode 100644 index 00000000..aafcfb27 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/query/RepositoryUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.data.query; + +import java.lang.reflect.Type; + +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.graphql.data.GraphQlRepository; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Utility methods to get information for Spring Data repositories. + * + * @author Rossen Stoyanchev + * @since 1.0.0 + */ +class RepositoryUtils { + + @SuppressWarnings("unchecked") + public static Class getDomainType(Object executor) { + return (Class) getRepositoryMetadata(executor).getDomainType(); + } + + public static RepositoryMetadata getRepositoryMetadata(Object executor) { + Assert.isInstanceOf(Repository.class, executor); + + Type[] genericInterfaces = executor.getClass().getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + Class rawClass = ResolvableType.forType(genericInterface).getRawClass(); + if (rawClass == null || MergedAnnotations.from(rawClass).isPresent(NoRepositoryBean.class)) { + continue; + } + if (Repository.class.isAssignableFrom(rawClass)) { + return new DefaultRepositoryMetadata(rawClass); + } + } + + throw new IllegalArgumentException( + String.format("Cannot resolve repository interface from %s", executor)); + } + + @Nullable + public static String getGraphQlTypeName(Object repository) { + GraphQlRepository annotation = + AnnotatedElementUtils.findMergedAnnotation(repository.getClass(), GraphQlRepository.class); + + if (annotation == null) { + return null; + } + + return (StringUtils.hasText(annotation.typeName()) ? + annotation.typeName() : RepositoryUtils.getDomainType(repository).getSimpleName()); + } + +} diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java index 04d95788..3ea3ebc8 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/query/QuerydslDataFetcherTests.java @@ -265,7 +265,7 @@ class QuerydslDataFetcherTests { @Nullable QuerydslPredicateExecutor executor, @Nullable ReactiveQuerydslPredicateExecutor reactiveExecutor) { - GraphQLTypeVisitor visitor = QuerydslDataFetcher.registrationTypeVisitor( + GraphQLTypeVisitor visitor = QuerydslDataFetcher.autoRegistrationTypeVisitor( (executor != null ? Collections.singletonList(executor) : Collections.emptyList()), (reactiveExecutor != null ? Collections.singletonList(reactiveExecutor) : Collections.emptyList()));