diff --git a/src/main/java/org/springframework/data/web/PageableDefault.java b/src/main/java/org/springframework/data/web/PageableDefault.java index 37d967600..fffbe783f 100644 --- a/src/main/java/org/springframework/data/web/PageableDefault.java +++ b/src/main/java/org/springframework/data/web/PageableDefault.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.web.SortDefault.SortDefaults; @@ -31,6 +32,7 @@ import org.springframework.data.web.SortDefault.SortDefaults; * * @since 1.6 * @author Oliver Gierke + * @author Mark Paluch */ @Documented @Retention(RetentionPolicy.RUNTIME) @@ -43,22 +45,24 @@ public @interface PageableDefault { * * @return */ + @AliasFor("size") int value() default 10; /** * The default-size the injected {@link org.springframework.data.domain.Pageable} should get if no corresponding - * parameter defined in request (default is 10). + * parameter defined in request (default is {@code 10}). */ + @AliasFor("value") int size() default 10; /** - * The default-pagenumber the injected {@link org.springframework.data.domain.Pageable} should get if no corresponding - * parameter defined in request (default is 0). + * The default page number the injected {@link org.springframework.data.domain.Pageable} should use if no + * corresponding parameter defined in request (default is {@code 0}). */ int page() default 0; /** - * The properties to sort by by default. If unset, no sorting will be applied at all. + * The properties to sort by default. If unset, no sorting will be applied at all. * * @return */ diff --git a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverSupport.java b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverSupport.java index 19ca6612f..6aa8a1450 100644 --- a/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverSupport.java +++ b/src/main/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverSupport.java @@ -22,6 +22,8 @@ import java.util.Optional; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -238,30 +240,34 @@ public abstract class PageableHandlerMethodArgumentResolverSupport { private Pageable getDefaultFromAnnotationOrFallback(MethodParameter methodParameter) { - PageableDefault defaults = methodParameter.getParameterAnnotation(PageableDefault.class); + MergedAnnotation defaults = MergedAnnotations.from(methodParameter.getParameterAnnotations()) + .get(PageableDefault.class); - if (defaults != null) { + if (defaults.isPresent()) { return getDefaultPageRequestFrom(methodParameter, defaults); } return fallbackPageable; } - private static Pageable getDefaultPageRequestFrom(MethodParameter parameter, PageableDefault defaults) { + private static Pageable getDefaultPageRequestFrom(MethodParameter parameter, + MergedAnnotation defaults) { - int defaultPageNumber = defaults.page(); - Integer defaultPageSize = getSpecificPropertyOrDefaultFromValue(defaults, "size"); + int defaultPageNumber = defaults.getInt("page"); + int defaultPageSize = defaults.getInt("size"); if (defaultPageSize < 1) { Method annotatedMethod = parameter.getMethod(); throw new IllegalStateException(String.format(INVALID_DEFAULT_PAGE_SIZE, annotatedMethod)); } - if (defaults.sort().length == 0) { + String[] sort = defaults.getStringArray("sort"); + if (sort.length == 0) { return PageRequest.of(defaultPageNumber, defaultPageSize); } - return PageRequest.of(defaultPageNumber, defaultPageSize, defaults.direction(), defaults.sort()); + return PageRequest.of(defaultPageNumber, defaultPageSize, defaults.getEnum("direction", Sort.Direction.class), + sort); } /** @@ -287,5 +293,4 @@ public abstract class PageableHandlerMethodArgumentResolverSupport { } } - } diff --git a/src/main/java/org/springframework/data/web/SortDefault.java b/src/main/java/org/springframework/data/web/SortDefault.java index dae1d7863..2b5d1bb65 100644 --- a/src/main/java/org/springframework/data/web/SortDefault.java +++ b/src/main/java/org/springframework/data/web/SortDefault.java @@ -17,10 +17,12 @@ package org.springframework.data.web; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -30,10 +32,12 @@ import org.springframework.data.domain.Sort.Direction; * * @since 1.6 * @author Oliver Gierke + * @author Mark Palich */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) +@Repeatable(SortDefault.SortDefaults.class) public @interface SortDefault { /** @@ -41,13 +45,15 @@ public @interface SortDefault { * * @return */ + @AliasFor("sort") String[] value() default {}; /** - * The properties to sort by by default. If unset, no sorting will be applied at all. + * The properties to sort by default. If unset, no sorting will be applied at all. * * @return */ + @AliasFor("value") String[] sort() default {}; /** diff --git a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolverSupport.java b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolverSupport.java index 49061120c..f9210810d 100644 --- a/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolverSupport.java +++ b/src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolverSupport.java @@ -23,6 +23,9 @@ import java.util.Optional; import java.util.function.Consumer; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.RepeatableContainers; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; @@ -121,31 +124,26 @@ public abstract class SortHandlerMethodArgumentResolverSupport { */ protected Sort getDefaultFromAnnotationOrFallback(MethodParameter parameter) { - SortDefaults annotatedDefaults = parameter.getParameterAnnotation(SortDefaults.class); - SortDefault annotatedDefault = parameter.getParameterAnnotation(SortDefault.class); + MergedAnnotations mergedAnnotations = MergedAnnotations.from(parameter, parameter.getParameterAnnotations(), + RepeatableContainers.of(SortDefault.class, SortDefaults.class)); - if (annotatedDefault != null && annotatedDefaults != null) { - throw new IllegalArgumentException( - String.format("Cannot use both @%s and @%s on parameter %s; Move %s into %s to define sorting order", - SORT_DEFAULTS_NAME, SORT_DEFAULT_NAME, parameter.toString(), SORT_DEFAULT_NAME, SORT_DEFAULTS_NAME)); + List> annotations = mergedAnnotations.stream(SortDefault.class).toList(); + + if (annotations.isEmpty()) { + return fallbackSort; } - if (annotatedDefault != null) { - return appendOrCreateSortTo(annotatedDefault, Sort.unsorted()); + if (annotations.size() == 1) { + return appendOrCreateSortTo(annotations.get(0), Sort.unsorted()); } - if (annotatedDefaults != null) { + Sort sort = Sort.unsorted(); - Sort sort = Sort.unsorted(); - - for (SortDefault currentAnnotatedDefault : annotatedDefaults.value()) { - sort = appendOrCreateSortTo(currentAnnotatedDefault, sort); - } - - return sort; + for (MergedAnnotation currentAnnotatedDefault : annotations) { + sort = appendOrCreateSortTo(currentAnnotatedDefault, sort); } - return fallbackSort; + return sort; } /** @@ -156,9 +154,9 @@ public abstract class SortHandlerMethodArgumentResolverSupport { * @param sortOrNull * @return */ - private Sort appendOrCreateSortTo(SortDefault sortDefault, Sort sortOrNull) { + private Sort appendOrCreateSortTo(MergedAnnotation sortDefault, Sort sortOrNull) { - String[] fields = SpringDataAnnotationUtils.getSpecificPropertyOrDefaultFromValue(sortDefault, "sort"); + String[] fields = sortDefault.getStringArray("sort"); if (fields.length == 0) { return Sort.unsorted(); @@ -167,8 +165,8 @@ public abstract class SortHandlerMethodArgumentResolverSupport { List orders = new ArrayList<>(fields.length); for (String field : fields) { - Order order = new Order(sortDefault.direction(), field); - orders.add(sortDefault.caseSensitive() ? order : order.ignoreCase()); + Order order = new Order(sortDefault.getEnum("direction", Sort.Direction.class), field); + orders.add(sortDefault.getBoolean("caseSensitive") ? order : order.ignoreCase()); } return sortOrNull.and(Sort.by(orders)); diff --git a/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java b/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java index e2784abdb..e1021fff2 100644 --- a/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java +++ b/src/main/java/org/springframework/data/web/SpringDataAnnotationUtils.java @@ -91,8 +91,10 @@ abstract class SpringDataAnnotationUtils { * @param annotation must not be {@literal null}. * @param property must not be {@literal null} or empty. * @return + * @deprecated since 3.0 as this method is no longer used within the Framework. */ @SuppressWarnings("unchecked") + @Deprecated public static T getSpecificPropertyOrDefaultFromValue(Annotation annotation, String property) { Object propertyDefaultValue = AnnotationUtils.getDefaultValue(annotation, property); diff --git a/src/test/java/org/springframework/data/web/PageableDefaultUnitTests.java b/src/test/java/org/springframework/data/web/PageableDefaultUnitTests.java index fbdf902b8..d157b78ef 100755 --- a/src/test/java/org/springframework/data/web/PageableDefaultUnitTests.java +++ b/src/test/java/org/springframework/data/web/PageableDefaultUnitTests.java @@ -96,7 +96,7 @@ abstract class PageableDefaultUnitTests { } @Test - void rejectsInvalidQulifiers() throws Exception { + void rejectsInvalidQualifiers() { var parameter = TestUtils.getParameterOfMethod(getControllerClass(), "invalidQualifiers", Pageable.class, Pageable.class); @@ -110,7 +110,7 @@ abstract class PageableDefaultUnitTests { } @Test - void rejectsNoQualifiers() throws Exception { + void rejectsNoQualifiers() { var parameter = TestUtils.getParameterOfMethod(getControllerClass(), "noQualifiers", Pageable.class, Pageable.class); diff --git a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java index 70ef3e08a..5e0dcfbe8 100755 --- a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java @@ -36,6 +36,7 @@ import org.springframework.web.context.request.ServletWebRequest; * @author Oliver Gierke * @author Nick Williams * @author Vedran Pavic + * @author Mark Paluch */ class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefaultUnitTests { @@ -106,13 +107,20 @@ class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefaultUnit @Test // DATACMNS-377 void rejectsInvalidCustomDefaultForPageSize() throws Exception { - var parameter = new MethodParameter(Sample.class.getMethod("invalidDefaultPageSize", Pageable.class), - 0); + var parameter = new MethodParameter(Sample.class.getMethod("invalidDefaultPageSize", Pageable.class), 0); assertThatIllegalStateException().isThrownBy(() -> assertSupportedAndResult(parameter, DEFAULT_PAGE_REQUEST)) // .withMessageContaining("invalidDefaultPageSize"); } + @Test // GH-2657 + void considersValueAlias() throws Exception { + + var parameter = new MethodParameter(Sample.class.getMethod("valuePageSize", Pageable.class), 0); + + assertSupportedAndResult(parameter, PageRequest.of(0, 2)); + } + @Test // DATACMNS-408 void fallsBackToFirstPageIfNegativePageNumberIsGiven() throws Exception { @@ -289,6 +297,8 @@ class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefaultUnit void invalidDefaultPageSize(@PageableDefault(size = 0) Pageable pageable); + void valuePageSize(@PageableDefault(2) Pageable pageable); + void simpleDefault(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER) Pageable pageable); void simpleDefaultWithSort( diff --git a/src/test/java/org/springframework/data/web/ReactiveSortHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/ReactiveSortHandlerMethodArgumentResolverUnitTests.java index a187e2fbe..1f0aa2d9f 100755 --- a/src/test/java/org/springframework/data/web/ReactiveSortHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/ReactiveSortHandlerMethodArgumentResolverUnitTests.java @@ -153,20 +153,9 @@ class ReactiveSortHandlerMethodArgumentResolverUnitTests { assertThat(resolve(request, PARAMETER)).isEqualTo(Sort.by(DESC, "property1")); } - @Test // DATACMNS-1211 - void rejectsDoubleAnnotatedMethod() { - - var parameter = getParameterOfMethod("invalid"); - - var resolver = new ReactiveSortHandlerMethodArgumentResolver(); - assertThat(resolver.supportsParameter(parameter)).isTrue(); - - assertThatIllegalArgumentException() - .isThrownBy(() -> - resolver.resolveArgumentValue(parameter, null, - MockServerWebExchange.from(TestUtils.getWebfluxRequest()))) - .withMessageContaining(SortDefault.class.getSimpleName()) - .withMessageContaining(SortDefaults.class.getSimpleName()).withMessageContaining(parameter.toString()); + @Test // GH-2657 + void considersRepeatableAnnotation() { + assertSupportedAndResolvedTo(getParameterOfMethod("repeatable"), Sort.by("one", "two", "three").ascending()); } @Test // DATACMNS-1211 @@ -261,6 +250,6 @@ class ReactiveSortHandlerMethodArgumentResolverUnitTests { void containeredDefault(@SortDefaults(@SortDefault({ "foo", "bar" })) Sort sort); - void invalid(@SortDefaults(@SortDefault({ "foo", "bar" })) @SortDefault({ "bar", "foo" }) Sort sort); + void repeatable(@SortDefault({ "one", "two" }) @SortDefault({ "three" }) Sort sort); } } diff --git a/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java b/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java index 4bf7db104..ad73a3344 100755 --- a/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java +++ b/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java @@ -25,7 +25,6 @@ import org.springframework.core.MethodParameter; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; -import org.springframework.data.web.SortDefault.SortDefaults; import org.springframework.web.method.support.HandlerMethodArgumentResolver; /** @@ -91,19 +90,9 @@ abstract class SortDefaultUnitTests { assertThat(getResolver().supportsParameter(parameter)).isFalse(); } - @Test - void rejectsDoubleAnnotatedMethod() { - - var parameter = getParameterOfMethod("invalid"); - - HandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver(); - assertThat(resolver.supportsParameter(parameter)).isTrue(); - - assertThatIllegalArgumentException() - .isThrownBy(() -> resolver.resolveArgument(parameter, null, TestUtils.getWebRequest(), null)) // - .withMessageContaining(SortDefault.class.getSimpleName()) // - .withMessageContaining(SortDefaults.class.getSimpleName()) // - .withMessageContaining(parameter.toString()); + @Test // GH-2657 + void considersRepeatableAnnotation() throws Exception { + assertSupportedAndResolvedTo(getParameterOfMethod("repeatable"), Sort.by("one", "two", "three").ascending()); } @Test diff --git a/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java index 6de35ab9d..8d3dca195 100755 --- a/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java @@ -324,7 +324,7 @@ class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests { void containeredDefault(@SortDefaults(@SortDefault({ "foo", "bar" })) Sort sort); - void invalid(@SortDefaults(@SortDefault({ "foo", "bar" })) @SortDefault({ "bar", "foo" }) Sort sort); + void repeatable(@SortDefault({ "one", "two" }) @SortDefault({ "three" }) Sort sort); void emptyQualifier(@Qualifier Sort sort);