From aaf263dded5542a844904a830358598d20f004a4 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 20 Jan 2021 03:26:17 +0000 Subject: [PATCH] Squashed commit of the following: commit cea8d79598d60f11b14dadf12d7557d90b5cf6a2 Merge: 1e5a890d 1a25c120 Author: Ubuntu Date: Tue Jan 19 22:21:51 2021 +0000 Merge branch 'webflux-1' of https://github.com/matiasah/spring-data-commons into webflux-1 commit 1e5a890dc8f52bd064808c38566e4dec1ebf1c73 Author: Ubuntu Date: Tue Jan 19 22:21:44 2021 +0000 Squashed commit of the following: commit 1a25c120416723cf352372398e51c3327700cd3e Author: Ubuntu Date: Tue Jan 19 22:04:18 2021 +0000 Correcting some comments commit e4c0c0104b95e20dd654c45870cc33843251252f Author: Ubuntu Date: Tue Jan 19 00:08:23 2021 +0000 Add ReactiveQuerydslPredicateArgumentResolver and ReactiveQuerydslWebConfiguration commit eacf835ba59eba2ee74f0f967e35715e3c577a8f Author: Ubuntu Date: Sat Jan 16 00:01:23 2021 +0000 Revert code format commit 1069af30e0ae8f46c3984f4d258a2d11facb97c6 Author: Ubuntu Date: Fri Jan 15 23:53:08 2021 +0000 Adding missing @author annotation commit c778258757343ca39614045a295be0682a8ea32c Author: Ubuntu Date: Fri Jan 15 23:44:29 2021 +0000 Add package-info to WebFlux folders commit 45711bb37de44fd8d24b4f8e268c4d4e8ce923a0 Author: Ubuntu Date: Fri Jan 15 23:41:48 2021 +0000 Add QuerydslWebFluxConfiguration commit 5c343b529f6e70aded5690290a667ca57596b48c Author: Ubuntu Date: Fri Jan 15 23:40:47 2021 +0000 Add QuerydslPredicateArgumentResolver for WebFlux commit 1a25c120416723cf352372398e51c3327700cd3e Author: Ubuntu Date: Tue Jan 19 22:04:18 2021 +0000 Correcting some comments commit e4c0c0104b95e20dd654c45870cc33843251252f Author: Ubuntu Date: Tue Jan 19 00:08:23 2021 +0000 Add ReactiveQuerydslPredicateArgumentResolver and ReactiveQuerydslWebConfiguration commit eacf835ba59eba2ee74f0f967e35715e3c577a8f Author: Ubuntu Date: Sat Jan 16 00:01:23 2021 +0000 Revert code format commit 1069af30e0ae8f46c3984f4d258a2d11facb97c6 Author: Ubuntu Date: Fri Jan 15 23:53:08 2021 +0000 Adding missing @author annotation commit c778258757343ca39614045a295be0682a8ea32c Author: Ubuntu Date: Fri Jan 15 23:44:29 2021 +0000 Add package-info to WebFlux folders commit 45711bb37de44fd8d24b4f8e268c4d4e8ce923a0 Author: Ubuntu Date: Fri Jan 15 23:41:48 2021 +0000 Add QuerydslWebFluxConfiguration commit 5c343b529f6e70aded5690290a667ca57596b48c Author: Ubuntu Date: Fri Jan 15 23:40:47 2021 +0000 Add QuerydslPredicateArgumentResolver for WebFlux --- .../ReactiveQuerydslWebConfiguration.java | 78 ++++++++++ .../QuerydslPredicateArgumentResolver.java | 97 +----------- ...rydslPredicateArgumentResolverSupport.java | 138 ++++++++++++++++++ ...tiveQuerydslPredicateArgumentResolver.java | 94 ++++++++++++ 4 files changed, 314 insertions(+), 93 deletions(-) create mode 100644 src/main/java/org/springframework/data/web/config/ReactiveQuerydslWebConfiguration.java create mode 100644 src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java create mode 100644 src/main/java/org/springframework/data/web/querydsl/ReactiveQuerydslPredicateArgumentResolver.java diff --git a/src/main/java/org/springframework/data/web/config/ReactiveQuerydslWebConfiguration.java b/src/main/java/org/springframework/data/web/config/ReactiveQuerydslWebConfiguration.java new file mode 100644 index 0000000000..44173735dd --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/ReactiveQuerydslWebConfiguration.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015-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.data.web.config; + +import java.util.Optional; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.querydsl.EntityPathResolver; +import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.data.querydsl.binding.QuerydslBindingsFactory; +import org.springframework.data.web.querydsl.ReactiveQuerydslPredicateArgumentResolver; +import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; + +/** + * Querydsl-specific web configuration for Spring Data. Registers a {@link HandlerMethodArgumentResolver} that builds up + * {@link Predicate}s from web requests. + * + * @author Matías Hermosilla + * @since 1.11 + * @soundtrack Anika Nilles - Alter Ego + */ +@Configuration(proxyBeanMethods = false) +public class ReactiveQuerydslWebConfiguration implements WebFluxConfigurer { + + @Autowired + @Qualifier("webFluxConversionService") ObjectFactory conversionService; + @Autowired ObjectProvider resolver; + @Autowired BeanFactory beanFactory; + + /** + * Default {@link ReactiveQuerydslPredicateArgumentResolver} to create Querydsl {@link Predicate} instances for + * Spring WebFlux controller methods. + * + * @return + */ + @Lazy + @Bean + public ReactiveQuerydslPredicateArgumentResolver querydslPredicateArgumentResolver() { + return new ReactiveQuerydslPredicateArgumentResolver( + beanFactory.getBean("querydslBindingsFactory", QuerydslBindingsFactory.class), + Optional.of(conversionService.getObject())); + } + + @Lazy + @Bean + public QuerydslBindingsFactory querydslBindingsFactory() { + return new QuerydslBindingsFactory(resolver.getIfUnique(() -> SimpleEntityPathResolver.INSTANCE)); + } + + @Override + public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { + configurer.addCustomResolver(beanFactory.getBean("querydslPredicateArgumentResolver", + ReactiveQuerydslPredicateArgumentResolver.class)); + } + +} diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java index 40150b0f9f..afae5aaac2 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolver.java @@ -15,7 +15,6 @@ */ package org.springframework.data.web.querydsl; -import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map.Entry; import java.util.Optional; @@ -23,14 +22,11 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer; import org.springframework.data.querydsl.binding.QuerydslBindings; import org.springframework.data.querydsl.binding.QuerydslBindingsFactory; import org.springframework.data.querydsl.binding.QuerydslPredicate; -import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder; import org.springframework.data.util.CastUtils; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.LinkedMultiValueMap; @@ -49,50 +45,15 @@ * * @author Christoph Strobl * @author Oliver Gierke + * @author Matías Hermosilla * @since 1.11 */ -public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentResolver { +public class QuerydslPredicateArgumentResolver extends QuerydslPredicateArgumentResolverSupport + implements HandlerMethodArgumentResolver { - private static final ResolvableType PREDICATE = ResolvableType.forClass(Predicate.class); - private static final ResolvableType OPTIONAL_OF_PREDICATE = ResolvableType.forClassWithGenerics(Optional.class, - PREDICATE); - - private final QuerydslBindingsFactory bindingsFactory; - private final QuerydslPredicateBuilder predicateBuilder; - - /** - * Creates a new {@link QuerydslPredicateArgumentResolver} using the given {@link ConversionService}. - * - * @param factory - * @param conversionService defaults to {@link DefaultConversionService} if {@literal null}. - */ public QuerydslPredicateArgumentResolver(QuerydslBindingsFactory factory, Optional conversionService) { - - this.bindingsFactory = factory; - this.predicateBuilder = new QuerydslPredicateBuilder(conversionService.orElseGet(DefaultConversionService::new), - factory.getEntityPathResolver()); - } - - /* - * (non-Javadoc) - * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) - */ - @Override - public boolean supportsParameter(MethodParameter parameter) { - - ResolvableType type = ResolvableType.forMethodParameter(parameter); - - if (PREDICATE.isAssignableFrom(type) || OPTIONAL_OF_PREDICATE.isAssignableFrom(type)) { - return true; - } - - if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) { - throw new IllegalArgumentException(String.format("Parameter at position %s must be of type Predicate but was %s.", - parameter.getParameterIndex(), parameter.getParameterType())); - } - - return false; + super(factory, conversionService); } /* @@ -133,54 +94,4 @@ public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC : result; } - /** - * Obtains the domain type information from the given method parameter. Will favor an explicitly registered on through - * {@link QuerydslPredicate#root()} but use the actual type of the method's return type as fallback. - * - * @param parameter must not be {@literal null}. - * @return - */ - static TypeInformation extractTypeInfo(MethodParameter parameter) { - - Optional annotation = Optional - .ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); - - return annotation.filter(it -> !Object.class.equals(it.root()))// - .> map(it -> ClassTypeInformation.from(it.root()))// - .orElseGet(() -> detectDomainType(parameter)); - } - - private static TypeInformation detectDomainType(MethodParameter parameter) { - - Method method = parameter.getMethod(); - - if (method == null) { - throw new IllegalArgumentException("Method parameter is not backed by a method!"); - } - - return detectDomainType(ClassTypeInformation.fromReturnTypeOf(method)); - } - - private static TypeInformation detectDomainType(TypeInformation source) { - - if (source.getTypeArguments().isEmpty()) { - return source; - } - - TypeInformation actualType = source.getActualType(); - - if (actualType == null) { - throw new IllegalArgumentException(String.format("Could not determine domain type from %s!", source)); - } - - if (source != actualType) { - return detectDomainType(actualType); - } - - if (source instanceof Iterable) { - return source; - } - - return detectDomainType(source.getRequiredComponentType()); - } } diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java new file mode 100644 index 0000000000..7f05649d31 --- /dev/null +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015-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.data.web.querydsl; + +import java.lang.reflect.Method; +import java.util.Optional; + +import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.querydsl.binding.QuerydslBindingsFactory; +import org.springframework.data.querydsl.binding.QuerydslPredicate; +import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; + +import com.querydsl.core.types.Predicate; + +/** + * {@link HandlerMethodArgumentResolver} to allow injection of {@link com.querydsl.core.types.Predicate} into Spring MVC + * controller methods. + * + * @author Christoph Strobl + * @author Oliver Gierke + * @author Matías Hermosilla + * @since 1.11 + */ +public abstract class QuerydslPredicateArgumentResolverSupport { + + private static final ResolvableType PREDICATE = ResolvableType.forClass(Predicate.class); + protected static final ResolvableType OPTIONAL_OF_PREDICATE = ResolvableType.forClassWithGenerics(Optional.class, + PREDICATE); + + protected final QuerydslBindingsFactory bindingsFactory; + protected final QuerydslPredicateBuilder predicateBuilder; + + /** + * Creates a new {@link QuerydslPredicateArgumentResolver} using the given {@link ConversionService}. + * + * @param factory + * @param conversionService defaults to {@link DefaultConversionService} if {@literal null}. + */ + public QuerydslPredicateArgumentResolverSupport(QuerydslBindingsFactory factory, + Optional conversionService) { + + this.bindingsFactory = factory; + this.predicateBuilder = new QuerydslPredicateBuilder(conversionService.orElseGet(DefaultConversionService::new), + factory.getEntityPathResolver()); + } + + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) + */ + public boolean supportsParameter(MethodParameter parameter) { + + ResolvableType type = ResolvableType.forMethodParameter(parameter); + + if (PREDICATE.isAssignableFrom(type) || OPTIONAL_OF_PREDICATE.isAssignableFrom(type)) { + return true; + } + + if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) { + throw new IllegalArgumentException( + String.format("Parameter at position %s must be of type Predicate but was %s.", + parameter.getParameterIndex(), parameter.getParameterType())); + } + + return false; + } + + /** + * Obtains the domain type information from the given method parameter. Will favor an explicitly registered on + * through {@link QuerydslPredicate#root()} but use the actual type of the method's return type as fallback. + * + * @param parameter must not be {@literal null}. + * @return + */ + protected static TypeInformation extractTypeInfo(MethodParameter parameter) { + + Optional annotation = Optional + .ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); + + return annotation.filter(it -> !Object.class.equals(it.root()))// + .> map(it -> ClassTypeInformation.from(it.root()))// + .orElseGet(() -> detectDomainType(parameter)); + } + + private static TypeInformation detectDomainType(MethodParameter parameter) { + + Method method = parameter.getMethod(); + + if (method == null) { + throw new IllegalArgumentException("Method parameter is not backed by a method!"); + } + + return detectDomainType(ClassTypeInformation.fromReturnTypeOf(method)); + } + + private static TypeInformation detectDomainType(TypeInformation source) { + + if (source.getTypeArguments().isEmpty()) { + return source; + } + + TypeInformation actualType = source.getActualType(); + + if (actualType == null) { + throw new IllegalArgumentException(String.format("Could not determine domain type from %s!", source)); + } + + if (source != actualType) { + return detectDomainType(actualType); + } + + if (source instanceof Iterable) { + return source; + } + + return detectDomainType(source.getRequiredComponentType()); + } + +} diff --git a/src/main/java/org/springframework/data/web/querydsl/ReactiveQuerydslPredicateArgumentResolver.java b/src/main/java/org/springframework/data/web/querydsl/ReactiveQuerydslPredicateArgumentResolver.java new file mode 100644 index 0000000000..fe69e3d211 --- /dev/null +++ b/src/main/java/org/springframework/data/web/querydsl/ReactiveQuerydslPredicateArgumentResolver.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015-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.data.web.querydsl; + +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; + +import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer; +import org.springframework.data.querydsl.binding.QuerydslBindings; +import org.springframework.data.querydsl.binding.QuerydslBindingsFactory; +import org.springframework.data.querydsl.binding.QuerydslPredicate; +import org.springframework.data.util.CastUtils; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.BindingContext; +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; +import org.springframework.web.server.ServerWebExchange; + +import reactor.core.publisher.Mono; + +/** + * {@link HandlerMethodArgumentResolver} to allow injection of {@link com.querydsl.core.types.Predicate} into Spring + * WebFlux controller methods. + * + * @author Matías Hermosilla + * @since 1.11 + */ +public class ReactiveQuerydslPredicateArgumentResolver extends QuerydslPredicateArgumentResolverSupport + implements HandlerMethodArgumentResolver { + + public ReactiveQuerydslPredicateArgumentResolver(QuerydslBindingsFactory factory, + Optional conversionService) { + super(factory, conversionService); + } + + /* + * (non-Javadoc) + * + * @seeorg.springframework.web.reactive.result.method.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.reactive.BindingContext, org.springframework.web.server.ServerWebExchange) + */ + @Override + public Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, + ServerWebExchange exchange) { + MultiValueMap parameters = new LinkedMultiValueMap<>(); + + for (Entry> entry : exchange.getRequest().getQueryParams().entrySet()) { + parameters.put(entry.getKey(), entry.getValue()); + } + + Optional annotation = Optional + .ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); + TypeInformation domainType = extractTypeInfo(parameter).getRequiredActualType(); + + Optional>> bindingsAnnotation = annotation // + .map(QuerydslPredicate::bindings) // + .map(CastUtils::cast); + + QuerydslBindings bindings = bindingsAnnotation // + .map(it -> bindingsFactory.createBindingsFor(domainType, it)) // + .orElseGet(() -> bindingsFactory.createBindingsFor(domainType)); + + Predicate result = predicateBuilder.getPredicate(domainType, parameters, bindings); + + if (!parameter.isOptional() && result == null) { + return Mono.just(new BooleanBuilder()); + } + + return OPTIONAL_OF_PREDICATE.isAssignableFrom(ResolvableType.forMethodParameter(parameter)) // + ? Mono.justOrEmpty(Optional.ofNullable(result)) // + : Mono.justOrEmpty(result); + } + +}