Browse Source
We now provide ReactiveQuerydslPredicateArgumentResolver to resolve Querydsl Predicates when using Spring WebFlux. Related ticket: #2200. Original pull request: #2274.pull/2285/head
4 changed files with 314 additions and 93 deletions
@ -0,0 +1,78 @@
@@ -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> conversionService; |
||||
@Autowired ObjectProvider<EntityPathResolver> 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)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,138 @@
@@ -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> 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<QuerydslPredicate> annotation = Optional |
||||
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); |
||||
|
||||
return annotation.filter(it -> !Object.class.equals(it.root()))//
|
||||
.<TypeInformation<?>> 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()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,94 @@
@@ -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> 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<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, |
||||
ServerWebExchange exchange) { |
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
||||
|
||||
for (Entry<String, List<String>> entry : exchange.getRequest().getQueryParams().entrySet()) { |
||||
parameters.put(entry.getKey(), entry.getValue()); |
||||
} |
||||
|
||||
Optional<QuerydslPredicate> annotation = Optional |
||||
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); |
||||
TypeInformation<?> domainType = extractTypeInfo(parameter).getRequiredActualType(); |
||||
|
||||
Optional<Class<? extends QuerydslBinderCustomizer<?>>> 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); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue