Skip to content

Commit 5c343b5

Browse files
UbuntuUbuntu
Ubuntu
authored and
Ubuntu
committed
Add QuerydslPredicateArgumentResolver for WebFlux
1 parent af287fe commit 5c343b5

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package org.springframework.data.webflux.querydsl;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.List;
5+
import java.util.Map.Entry;
6+
import java.util.Optional;
7+
8+
import com.querydsl.core.BooleanBuilder;
9+
import com.querydsl.core.types.Predicate;
10+
11+
import org.springframework.core.MethodParameter;
12+
import org.springframework.core.ResolvableType;
13+
import org.springframework.core.convert.ConversionService;
14+
import org.springframework.core.convert.support.DefaultConversionService;
15+
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
16+
import org.springframework.data.querydsl.binding.QuerydslBindings;
17+
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
18+
import org.springframework.data.querydsl.binding.QuerydslPredicate;
19+
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
20+
import org.springframework.data.util.CastUtils;
21+
import org.springframework.data.util.ClassTypeInformation;
22+
import org.springframework.data.util.TypeInformation;
23+
import org.springframework.util.LinkedMultiValueMap;
24+
import org.springframework.util.MultiValueMap;
25+
import org.springframework.web.reactive.BindingContext;
26+
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
27+
import org.springframework.web.server.ServerWebExchange;
28+
29+
import reactor.core.publisher.Mono;
30+
31+
/**
32+
* {@link HandlerMethodArgumentResolver} to allow injection of
33+
* {@link com.querydsl.core.types.Predicate} into Spring MVC controller methods.
34+
*
35+
* @author Christoph Strobl
36+
* @author Oliver Gierke
37+
* @since 1.11
38+
*/
39+
public class QuerydslPredicateArgumentResolver implements HandlerMethodArgumentResolver {
40+
41+
private static final ResolvableType PREDICATE = ResolvableType.forClass(Predicate.class);
42+
private static final ResolvableType OPTIONAL_OF_PREDICATE = ResolvableType.forClassWithGenerics(Optional.class,
43+
PREDICATE);
44+
45+
private final QuerydslBindingsFactory bindingsFactory;
46+
private final QuerydslPredicateBuilder predicateBuilder;
47+
48+
/**
49+
* Creates a new {@link QuerydslPredicateArgumentResolver} using the given
50+
* {@link ConversionService}.
51+
*
52+
* @param factory
53+
* @param conversionService defaults to {@link DefaultConversionService} if
54+
* {@literal null}.
55+
*/
56+
public QuerydslPredicateArgumentResolver(QuerydslBindingsFactory factory,
57+
Optional<ConversionService> conversionService) {
58+
59+
this.bindingsFactory = factory;
60+
this.predicateBuilder = new QuerydslPredicateBuilder(conversionService.orElseGet(DefaultConversionService::new),
61+
factory.getEntityPathResolver());
62+
}
63+
64+
/*
65+
* (non-Javadoc)
66+
*
67+
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#
68+
* supportsParameter(org.springframework.core.MethodParameter)
69+
*/
70+
@Override
71+
public boolean supportsParameter(MethodParameter parameter) {
72+
73+
ResolvableType type = ResolvableType.forMethodParameter(parameter);
74+
75+
if (PREDICATE.isAssignableFrom(type) || OPTIONAL_OF_PREDICATE.isAssignableFrom(type)) {
76+
return true;
77+
}
78+
79+
if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) {
80+
throw new IllegalArgumentException(
81+
String.format("Parameter at position %s must be of type Predicate but was %s.",
82+
parameter.getParameterIndex(), parameter.getParameterType()));
83+
}
84+
85+
return false;
86+
}
87+
88+
/*
89+
* (non-Javadoc)
90+
*
91+
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#
92+
* resolveArgument(org.springframework.core.MethodParameter,
93+
* org.springframework.web.method.support.ModelAndViewContainer,
94+
* org.springframework.web.context.request.NativeWebRequest,
95+
* org.springframework.web.bind.support.WebDataBinderFactory)
96+
*/
97+
@Override
98+
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
99+
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
100+
101+
for (Entry<String, List<String>> entry : exchange.getRequest().getQueryParams().entrySet()) {
102+
parameters.put(entry.getKey(), entry.getValue());
103+
}
104+
105+
Optional<QuerydslPredicate> annotation = Optional
106+
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class));
107+
TypeInformation<?> domainType = extractTypeInfo(parameter).getRequiredActualType();
108+
109+
Optional<Class<? extends QuerydslBinderCustomizer<?>>> bindingsAnnotation = annotation //
110+
.map(QuerydslPredicate::bindings) //
111+
.map(CastUtils::cast);
112+
113+
QuerydslBindings bindings = bindingsAnnotation //
114+
.map(it -> bindingsFactory.createBindingsFor(domainType, it)) //
115+
.orElseGet(() -> bindingsFactory.createBindingsFor(domainType));
116+
117+
Predicate result = predicateBuilder.getPredicate(domainType, parameters, bindings);
118+
119+
if (!parameter.isOptional() && result == null) {
120+
return Mono.just(new BooleanBuilder());
121+
}
122+
123+
return OPTIONAL_OF_PREDICATE.isAssignableFrom(ResolvableType.forMethodParameter(parameter)) //
124+
? Mono.justOrEmpty(Optional.ofNullable(result)) //
125+
: Mono.justOrEmpty(result);
126+
}
127+
128+
/**
129+
* Obtains the domain type information from the given method parameter. Will
130+
* favor an explicitly registered on through {@link QuerydslPredicate#root()}
131+
* but use the actual type of the method's return type as fallback.
132+
*
133+
* @param parameter must not be {@literal null}.
134+
* @return
135+
*/
136+
static TypeInformation<?> extractTypeInfo(MethodParameter parameter) {
137+
138+
Optional<QuerydslPredicate> annotation = Optional
139+
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class));
140+
141+
return annotation.filter(it -> !Object.class.equals(it.root()))//
142+
.<TypeInformation<?>>map(it -> ClassTypeInformation.from(it.root()))//
143+
.orElseGet(() -> detectDomainType(parameter));
144+
}
145+
146+
private static TypeInformation<?> detectDomainType(MethodParameter parameter) {
147+
148+
Method method = parameter.getMethod();
149+
150+
if (method == null) {
151+
throw new IllegalArgumentException("Method parameter is not backed by a method!");
152+
}
153+
154+
return detectDomainType(ClassTypeInformation.fromReturnTypeOf(method));
155+
}
156+
157+
private static TypeInformation<?> detectDomainType(TypeInformation<?> source) {
158+
159+
if (source.getTypeArguments().isEmpty()) {
160+
return source;
161+
}
162+
163+
TypeInformation<?> actualType = source.getActualType();
164+
165+
if (actualType == null) {
166+
throw new IllegalArgumentException(String.format("Could not determine domain type from %s!", source));
167+
}
168+
169+
if (source != actualType) {
170+
return detectDomainType(actualType);
171+
}
172+
173+
if (source instanceof Iterable) {
174+
return source;
175+
}
176+
177+
return detectDomainType(source.getRequiredComponentType());
178+
}
179+
180+
}

0 commit comments

Comments
 (0)