Refine @PageableDefault and @SortDefault annotations.

We now use MergedAnnotations to evaluate annotation attributes and to use aliasing across attributes. Also, SortDefault is not repeatable.

Closes #2657
This commit is contained in:
Mark Paluch
2022-07-27 09:45:28 +02:00
parent 3a6b04dfe1
commit 7e1712a5de
10 changed files with 71 additions and 68 deletions

View File

@@ -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
*/

View File

@@ -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<PageableDefault> 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<PageableDefault> 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 {
}
}
}

View File

@@ -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 {};
/**

View File

@@ -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<MergedAnnotation<SortDefault>> 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<SortDefault> 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> 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<Order> 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));

View File

@@ -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> T getSpecificPropertyOrDefaultFromValue(Annotation annotation, String property) {
Object propertyDefaultValue = AnnotationUtils.getDefaultValue(annotation, property);