diff --git a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java index e7d5ef7a9..1d804f4b7 100644 --- a/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java +++ b/src/main/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverSupport.java @@ -20,6 +20,8 @@ import java.util.Optional; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.convert.ConversionService; import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer; import org.springframework.data.querydsl.binding.QuerydslBindings; @@ -82,7 +84,9 @@ public abstract class QuerydslPredicateArgumentResolverSupport { return true; } - if (parameter.hasParameterAnnotation(QuerydslPredicate.class)) { + MergedAnnotations annotations = MergedAnnotations.from(parameter.getParameter()); + + if (annotations.isPresent(QuerydslPredicate.class)) { throw new IllegalArgumentException(String.format("Parameter at position %s must be of type Predicate but was %s.", parameter.getParameterIndex(), parameter.getParameterType())); } @@ -93,12 +97,12 @@ public abstract class QuerydslPredicateArgumentResolverSupport { @Nullable Predicate getPredicate(MethodParameter parameter, MultiValueMap queryParameters) { - Optional annotation = Optional - .ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); - TypeInformation domainType = extractTypeInfo(parameter).getRequiredActualType(); + MergedAnnotations annotations = MergedAnnotations.from(parameter.getParameter()); + MergedAnnotation predicateAnnotation = annotations.get(QuerydslPredicate.class); - Optional>> bindingsAnnotation = annotation // - .map(QuerydslPredicate::bindings) // + TypeInformation domainType = extractTypeInfo(parameter, predicateAnnotation).getRequiredActualType(); + + Optional>> bindingsAnnotation = predicateAnnotation.getValue("bindings") // .map(CastUtils::cast); QuerydslBindings bindings = bindingsAnnotation // @@ -115,10 +119,10 @@ public abstract class QuerydslPredicateArgumentResolverSupport { * @param parameter must not be {@literal null}. * @return */ - protected static TypeInformation extractTypeInfo(MethodParameter parameter) { + protected static TypeInformation extractTypeInfo(MethodParameter parameter, + MergedAnnotation predicateAnnotation) { - Optional annotation = Optional - .ofNullable(parameter.getParameterAnnotation(QuerydslPredicate.class)); + Optional annotation = predicateAnnotation.synthesize(MergedAnnotation::isPresent); return annotation.filter(it -> !Object.class.equals(it.root()))// .> map(it -> ClassTypeInformation.from(it.root()))// diff --git a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java index 8912f6827..59773dece 100755 --- a/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/querydsl/QuerydslPredicateArgumentResolverUnitTests.java @@ -18,11 +18,17 @@ package org.springframework.data.web.querydsl; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.MergedAnnotation; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.querydsl.QUser; @@ -149,6 +155,20 @@ class QuerydslPredicateArgumentResolverUnitTests { QUser.user.firstname.eq("egwene".toUpperCase()).and(QUser.user.lastname.toLowerCase().eq("al'vere"))); } + @Test // #2277 + void resolveArgumentShouldHonorMetaAnnotation() throws Exception { + + request.addParameter("firstname", "egwene"); + request.addParameter("lastname", "al'vere"); + + Object predicate = resolver.resolveArgument( + getMethodParameterFor("specificFindWithMetaAnnotation", Predicate.class), null, new ServletWebRequest(request), + null); + + assertThat(predicate).isEqualTo( + QUser.user.firstname.eq("egwene".toUpperCase()).and(QUser.user.lastname.toLowerCase().eq("al'vere"))); + } + @Test // DATACMNS-669 void shouldCreatePredicateForNonStringPropertyCorrectly() throws Exception { @@ -188,7 +208,7 @@ class QuerydslPredicateArgumentResolverUnitTests { void extractTypeInformationShouldUseTypeExtractedFromMethodReturnTypeIfPredicateNotAnnotated() { TypeInformation type = ReflectionTestUtils.invokeMethod(resolver, "extractTypeInfo", - getMethodParameterFor("predicateWithoutAnnotation", Predicate.class)); + getMethodParameterFor("predicateWithoutAnnotation", Predicate.class), MergedAnnotation.missing()); assertThat(type).isEqualTo(ClassTypeInformation.from(User.class)); } @@ -200,9 +220,11 @@ class QuerydslPredicateArgumentResolverUnitTests { TypeInformation USER_TYPE = ClassTypeInformation.from(User.class); TypeInformation MODELA_AND_VIEW_TYPE = ClassTypeInformation.from(ModelAndView.class); - assertThat(extractTypeInfo(getMethodParameterFor("forEntity"))).isEqualTo(USER_TYPE); - assertThat(extractTypeInfo(getMethodParameterFor("forResourceOfUser"))).isEqualTo(USER_TYPE); - assertThat(extractTypeInfo(getMethodParameterFor("forModelAndView"))).isEqualTo(MODELA_AND_VIEW_TYPE); + assertThat(extractTypeInfo(getMethodParameterFor("forEntity"), MergedAnnotation.missing())).isEqualTo(USER_TYPE); + assertThat(extractTypeInfo(getMethodParameterFor("forResourceOfUser"), MergedAnnotation.missing())) + .isEqualTo(USER_TYPE); + assertThat(extractTypeInfo(getMethodParameterFor("forModelAndView"), MergedAnnotation.missing())) + .isEqualTo(MODELA_AND_VIEW_TYPE); } @Test // DATACMNS-1593 @@ -276,6 +298,8 @@ class QuerydslPredicateArgumentResolverUnitTests { User specificFind(@QuerydslPredicate(bindings = SpecificBinding.class) Predicate predicate); + User specificFindWithMetaAnnotation(@MyQuerydslPredicate Predicate predicate); + HttpEntity forEntity(); ModelAndView forModelAndView(); @@ -296,4 +320,23 @@ class QuerydslPredicateArgumentResolverUnitTests { bindings.bind(QUser.user.firstname).first((path, value) -> path.contains(value)); } } + + @Target({ ElementType.PARAMETER, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @QuerydslPredicate + public @interface MyQuerydslPredicate { + + /** + * To customize the way individual properties' values should be bound to the predicate a + * {@link QuerydslBinderCustomizer} can be specified here. We'll try to obtain a Spring bean of this type but fall + * back to a plain instantiation if no bean is found in the current + * {@link org.springframework.beans.factory.BeanFactory}. + * + * @return + */ + @SuppressWarnings("rawtypes") + @AliasFor(annotation = QuerydslPredicate.class, attribute = "bindings") + Class bindings() default SpecificBinding.class; + } + }