Skip to content

Commit 89c5b38

Browse files
committed
Enable QuerydslPredicate for meta-annotation usage.
We now support composed annotations that are meta-annotated with QuerydslPredicate. Resolves #2277.
1 parent d57fb3e commit 89c5b38

File tree

2 files changed

+60
-13
lines changed

2 files changed

+60
-13
lines changed

src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import org.springframework.core.MethodParameter;
2222
import org.springframework.core.ResolvableType;
23+
import org.springframework.core.annotation.MergedAnnotation;
24+
import org.springframework.core.annotation.MergedAnnotations;
2325
import org.springframework.core.convert.ConversionService;
2426
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
2527
import org.springframework.data.querydsl.binding.QuerydslBindings;
@@ -82,7 +84,9 @@ public boolean supportsParameter(MethodParameter parameter) {
8284
return true;
8385
}
8486

85-
if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) {
87+
MergedAnnotations annotations = MergedAnnotations.from(parameter.getParameter());
88+
89+
if (annotations.isPresent(QuerydslPredicate.class)) {
8690
throw new IllegalArgumentException(String.format("Parameter at position %s must be of type Predicate but was %s.",
8791
parameter.getParameterIndex(), parameter.getParameterType()));
8892
}
@@ -93,12 +97,12 @@ public boolean supportsParameter(MethodParameter parameter) {
9397
@Nullable
9498
Predicate getPredicate(MethodParameter parameter, MultiValueMap<String, String> queryParameters) {
9599

96-
Optional<QuerydslPredicate> annotation = Optional
97-
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class));
98-
TypeInformation<?> domainType = extractTypeInfo(parameter).getRequiredActualType();
100+
MergedAnnotations annotations = MergedAnnotations.from(parameter.getParameter());
101+
MergedAnnotation<QuerydslPredicate> predicateAnnotation = annotations.get(QuerydslPredicate.class);
102+
103+
TypeInformation<?> domainType = extractTypeInfo(parameter, predicateAnnotation).getRequiredActualType();
99104

100-
Optional<Class<? extends QuerydslBinderCustomizer<?>>> bindingsAnnotation = annotation //
101-
.map(QuerydslPredicate::bindings) //
105+
Optional<Class<? extends QuerydslBinderCustomizer<?>>> bindingsAnnotation = predicateAnnotation.getValue("bindings") //
102106
.map(CastUtils::cast);
103107

104108
QuerydslBindings bindings = bindingsAnnotation //
@@ -115,10 +119,10 @@ Predicate getPredicate(MethodParameter parameter, MultiValueMap<String, String>
115119
* @param parameter must not be {@literal null}.
116120
* @return
117121
*/
118-
protected static TypeInformation<?> extractTypeInfo(MethodParameter parameter) {
122+
protected static TypeInformation<?> extractTypeInfo(MethodParameter parameter,
123+
MergedAnnotation<QuerydslPredicate> predicateAnnotation) {
119124

120-
Optional<QuerydslPredicate> annotation = Optional
121-
.ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class));
125+
Optional<QuerydslPredicate> annotation = predicateAnnotation.synthesize(MergedAnnotation::isPresent);
122126

123127
return annotation.filter(it -> !Object.class.equals(it.root()))//
124128
.<TypeInformation<?>> map(it -> ClassTypeInformation.from(it.root()))//

src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver.*;
2020

21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
2125
import java.util.Optional;
2226

2327
import org.junit.jupiter.api.BeforeEach;
2428
import org.junit.jupiter.api.Test;
2529
import org.springframework.core.MethodParameter;
30+
import org.springframework.core.annotation.AliasFor;
31+
import org.springframework.core.annotation.MergedAnnotation;
2632
import org.springframework.data.domain.Page;
2733
import org.springframework.data.domain.Pageable;
2834
import org.springframework.data.querydsl.QUser;
@@ -149,6 +155,20 @@ void resolveArgumentShouldHonorCustomSpecification() throws Exception {
149155
QUser.user.firstname.eq("egwene".toUpperCase()).and(QUser.user.lastname.toLowerCase().eq("al'vere")));
150156
}
151157

158+
@Test // #2277
159+
void resolveArgumentShouldHonorMetaAnnotation() throws Exception {
160+
161+
request.addParameter("firstname", "egwene");
162+
request.addParameter("lastname", "al'vere");
163+
164+
Object predicate = resolver.resolveArgument(
165+
getMethodParameterFor("specificFindWithMetaAnnotation", Predicate.class), null, new ServletWebRequest(request),
166+
null);
167+
168+
assertThat(predicate).isEqualTo(
169+
QUser.user.firstname.eq("egwene".toUpperCase()).and(QUser.user.lastname.toLowerCase().eq("al'vere")));
170+
}
171+
152172
@Test // DATACMNS-669
153173
void shouldCreatePredicateForNonStringPropertyCorrectly() throws Exception {
154174

@@ -188,7 +208,7 @@ void shouldExcludePropertiesCorrectly() throws Exception {
188208
void extractTypeInformationShouldUseTypeExtractedFromMethodReturnTypeIfPredicateNotAnnotated() {
189209

190210
TypeInformation<?> type = ReflectionTestUtils.invokeMethod(resolver, "extractTypeInfo",
191-
getMethodParameterFor("predicateWithoutAnnotation", Predicate.class));
211+
getMethodParameterFor("predicateWithoutAnnotation", Predicate.class), MergedAnnotation.missing());
192212

193213
assertThat(type).isEqualTo(ClassTypeInformation.from(User.class));
194214
}
@@ -200,9 +220,11 @@ void detectsDomainTypesCorrectly() {
200220
TypeInformation USER_TYPE = ClassTypeInformation.from(User.class);
201221
TypeInformation MODELA_AND_VIEW_TYPE = ClassTypeInformation.from(ModelAndView.class);
202222

203-
assertThat(extractTypeInfo(getMethodParameterFor("forEntity"))).isEqualTo(USER_TYPE);
204-
assertThat(extractTypeInfo(getMethodParameterFor("forResourceOfUser"))).isEqualTo(USER_TYPE);
205-
assertThat(extractTypeInfo(getMethodParameterFor("forModelAndView"))).isEqualTo(MODELA_AND_VIEW_TYPE);
223+
assertThat(extractTypeInfo(getMethodParameterFor("forEntity"), MergedAnnotation.missing())).isEqualTo(USER_TYPE);
224+
assertThat(extractTypeInfo(getMethodParameterFor("forResourceOfUser"), MergedAnnotation.missing()))
225+
.isEqualTo(USER_TYPE);
226+
assertThat(extractTypeInfo(getMethodParameterFor("forModelAndView"), MergedAnnotation.missing()))
227+
.isEqualTo(MODELA_AND_VIEW_TYPE);
206228
}
207229

208230
@Test // DATACMNS-1593
@@ -276,6 +298,8 @@ static interface Sample {
276298

277299
User specificFind(@QuerydslPredicate(bindings = SpecificBinding.class) Predicate predicate);
278300

301+
User specificFindWithMetaAnnotation(@MyQuerydslPredicate Predicate predicate);
302+
279303
HttpEntity<User> forEntity();
280304

281305
ModelAndView forModelAndView();
@@ -296,4 +320,23 @@ public void customize(QuerydslBindings bindings, QUser user) {
296320
bindings.bind(QUser.user.firstname).first((path, value) -> path.contains(value));
297321
}
298322
}
323+
324+
@Target({ ElementType.PARAMETER, ElementType.TYPE })
325+
@Retention(RetentionPolicy.RUNTIME)
326+
@QuerydslPredicate
327+
public @interface MyQuerydslPredicate {
328+
329+
/**
330+
* To customize the way individual properties' values should be bound to the predicate a
331+
* {@link QuerydslBinderCustomizer} can be specified here. We'll try to obtain a Spring bean of this type but fall
332+
* back to a plain instantiation if no bean is found in the current
333+
* {@link org.springframework.beans.factory.BeanFactory}.
334+
*
335+
* @return
336+
*/
337+
@SuppressWarnings("rawtypes")
338+
@AliasFor(annotation = QuerydslPredicate.class, attribute = "bindings")
339+
Class<? extends QuerydslBinderCustomizer> bindings() default SpecificBinding.class;
340+
}
341+
299342
}

0 commit comments

Comments
 (0)