Browse Source

Merge 8134da8a6c into f4201529bc

pull/3153/merge
Petar Heyken 4 days ago committed by GitHub
parent
commit
fede0cbd5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      src/main/java/org/springframework/data/web/SortDefault.java
  2. 74
      src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolverSupport.java
  3. 60
      src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java

10
src/main/java/org/springframework/data/web/SortDefault.java

@ -25,6 +25,7 @@ import java.lang.annotation.Target; @@ -25,6 +25,7 @@ import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.NullHandling;
/**
* Annotation to define the default {@link Sort} options to be used when injecting a {@link Sort} instance into a
@ -33,6 +34,7 @@ import org.springframework.data.domain.Sort.Direction; @@ -33,6 +34,7 @@ import org.springframework.data.domain.Sort.Direction;
* @since 1.6
* @author Oliver Gierke
* @author Mark Palich
* @author Petar Heyken
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@ -71,6 +73,14 @@ public @interface SortDefault { @@ -71,6 +73,14 @@ public @interface SortDefault {
*/
boolean caseSensitive() default true;
/**
* Specifies which null handling to apply. Defaults to {@link NullHandling#NATIVE}.
*
* @return
* @since 3.4
*/
NullHandling nullHandling() default NullHandling.NATIVE;
/**
* Wrapper annotation to allow declaring multiple {@link SortDefault} annotations on a method parameter.
*

74
src/main/java/org/springframework/data/web/SortHandlerMethodArgumentResolverSupport.java

@ -29,6 +29,7 @@ import org.springframework.core.annotation.MergedAnnotations; @@ -29,6 +29,7 @@ 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.NullHandling;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.web.SortDefault.SortDefaults;
import org.springframework.util.Assert;
@ -41,6 +42,7 @@ import org.springframework.util.StringUtils; @@ -41,6 +42,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Vedran Pavic
* @author Johannes Englmeier
* @author Petar Heyken
* @see SortHandlerMethodArgumentResolver
* @see ReactiveSortHandlerMethodArgumentResolver
* @since 2.2
@ -165,7 +167,14 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -165,7 +167,14 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
List<Order> orders = new ArrayList<>(fields.length);
for (String field : fields) {
Order order = new Order(sortDefault.getEnum("direction", Sort.Direction.class), field);
Order order = new Order(sortDefault.getEnum("direction", Direction.class), field);
order = switch (sortDefault.getEnum("nullHandling", NullHandling.class)) {
case NATIVE -> order.nullsNative();
case NULLS_FIRST -> order.nullsFirst();
case NULLS_LAST -> order.nullsLast();
};
orders.add(sortDefault.getBoolean("caseSensitive") ? order : order.ignoreCase());
}
@ -214,6 +223,7 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -214,6 +223,7 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
}
SortOrderParser.parse(part, delimiter) //
.parseNullHandling() //
.parseIgnoreCase() //
.parseDirection() //
.forEachOrder(allOrders::add);
@ -360,22 +370,28 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -360,22 +370,28 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
static class SortOrderParser {
private static final String IGNORECASE = "ignorecase";
private static final String NULLSNATIVE = "nullsnative";
private static final String NULLSFIRST = "nullsfirst";
private static final String NULLSLAST = "nullslast";
private final String[] elements;
private final int lastIndex;
private final Optional<Direction> direction;
private final Optional<Boolean> ignoreCase;
private final Optional<NullHandling> nullHandling;
private SortOrderParser(String[] elements) {
this(elements, elements.length, Optional.empty(), Optional.empty());
this(elements, elements.length, Optional.empty(), Optional.empty(), Optional.empty());
}
private SortOrderParser(String[] elements, int lastIndex, Optional<Direction> direction,
Optional<Boolean> ignoreCase) {
Optional<Boolean> ignoreCase, Optional<NullHandling> nullHandling) {
this.elements = elements;
this.lastIndex = Math.max(0, lastIndex);
this.direction = direction;
this.ignoreCase = ignoreCase;
this.nullHandling = nullHandling;
}
/**
@ -394,6 +410,21 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -394,6 +410,21 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
return new SortOrderParser(elements);
}
/**
* Parse the {@link NullHandling} portion of the sort specification.
*
* @return a new parsing state object.
*/
public SortOrderParser parseNullHandling() {
Optional<NullHandling> nullHandling = lastIndex > 0 ?
fromOptionalNullHandlingString(elements[lastIndex - 1]) :
Optional.empty();
return new SortOrderParser(elements, lastIndex - (nullHandling.isPresent() ? 1 : 0), direction, ignoreCase,
nullHandling);
}
/**
* Parse the {@code ignoreCase} portion of the sort specification.
*
@ -401,9 +432,12 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -401,9 +432,12 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
*/
public SortOrderParser parseIgnoreCase() {
Optional<Boolean> ignoreCase = lastIndex > 0 ? fromOptionalString(elements[lastIndex - 1]) : Optional.empty();
Optional<Boolean> ignoreCase = lastIndex > 0 ?
fromOptionalIgnoreCaseString(elements[lastIndex - 1]) :
Optional.empty();
return new SortOrderParser(elements, lastIndex - (ignoreCase.isPresent() ? 1 : 0), direction, ignoreCase);
return new SortOrderParser(elements, lastIndex - (ignoreCase.isPresent() ? 1 : 0), direction, ignoreCase,
nullHandling);
}
/**
@ -416,7 +450,8 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -416,7 +450,8 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
Optional<Direction> direction = lastIndex > 0 ? Direction.fromOptionalString(elements[lastIndex - 1])
: Optional.empty();
return new SortOrderParser(elements, lastIndex - (direction.isPresent() ? 1 : 0), direction, ignoreCase);
return new SortOrderParser(elements, lastIndex - (direction.isPresent() ? 1 : 0), direction, ignoreCase,
nullHandling);
}
/**
@ -431,7 +466,24 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -431,7 +466,24 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
}
}
private Optional<Boolean> fromOptionalString(String value) {
private Optional<NullHandling> fromOptionalNullHandlingString(String value) {
if (NULLSNATIVE.equalsIgnoreCase(value)) {
return Optional.of(NullHandling.NATIVE);
}
if (NULLSFIRST.equalsIgnoreCase(value)) {
return Optional.of(NullHandling.NULLS_FIRST);
}
if (NULLSLAST.equalsIgnoreCase(value)) {
return Optional.of(NullHandling.NULLS_LAST);
}
return Optional.empty();
}
private Optional<Boolean> fromOptionalIgnoreCaseString(String value) {
return IGNORECASE.equalsIgnoreCase(value) ? Optional.of(true) : Optional.empty();
}
@ -443,6 +495,14 @@ public abstract class SortHandlerMethodArgumentResolverSupport { @@ -443,6 +495,14 @@ public abstract class SortHandlerMethodArgumentResolverSupport {
Order order = direction.map(it -> new Order(it, property)).orElseGet(() -> Order.by(property));
if (nullHandling.isPresent()) {
order = switch (nullHandling.get()) {
case NATIVE -> order.nullsNative();
case NULLS_FIRST -> order.nullsFirst();
case NULLS_LAST -> order.nullsLast();
};
}
if (ignoreCase.isPresent()) {
return Optional.of(order.ignoreCase());
}

60
src/test/java/org/springframework/data/web/SortHandlerMethodArgumentResolverUnitTests.java

@ -48,6 +48,7 @@ import org.springframework.web.util.UriUtils; @@ -48,6 +48,7 @@ import org.springframework.web.util.UriUtils;
* @author Nick Williams
* @author Mark Paluch
* @author Vedran Pavic
* @author Petar Heyken
*/
class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests {
@ -211,6 +212,16 @@ class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests { @@ -211,6 +212,16 @@ class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests {
.isEqualTo(Sort.by(new Order(DESC, "firstname").ignoreCase(), new Order(DESC, "lastname").ignoreCase()));
}
@Test // GH-3152
void returnsDefaultNullHandling() throws Exception {
final var request = new MockHttpServletRequest();
request.addParameter("sort", "");
assertThat(resolveSort(request, getParameterOfMethod("simpleDefaultWithDirectionAndNullHandling"))).isEqualTo(
Sort.by(new Order(DESC, "firstname").nullsLast(), new Order(DESC, "lastname").nullsLast()));
}
@Test // DATACMNS-379
void parsesCommaParameterForSort() throws Exception {
@ -273,6 +284,52 @@ class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests { @@ -273,6 +284,52 @@ class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests {
assertSupportedAndResolvedTo(new ServletWebRequest(request), parameter, Sort.by("foo").descending());
}
@Test // GH-3152
void sortParamHandlesMultiplePropertiesWithSortOrderAndIgnoreCaseAndNullsLast() throws Exception {
final var request = new MockHttpServletRequest();
request.addParameter("sort", "property1,property2,DESC,IgnoreCase,NullsLast");
assertThat(resolveSort(request, PARAMETER)).isEqualTo(Sort.by(new Order(DESC, "property1").ignoreCase().nullsLast(),
new Order(DESC, "property2").ignoreCase().nullsLast()));
}
@Test // GH-3152
void sortParamHandlesSinglePropertyWithIgnoreCaseAndNullsLast() throws Exception {
final var request = new MockHttpServletRequest();
request.addParameter("sort", "property,IgnoreCase,NullsLast");
assertThat(resolveSort(request, PARAMETER)).isEqualTo(Sort.by(new Order(ASC, "property").ignoreCase().nullsLast()));
}
@Test // GH-3152
void sortParamHandlesSinglePropertyWithNullsFirst() throws Exception {
final var request = new MockHttpServletRequest();
request.addParameter("sort", "property,nullsfirst");
assertThat(resolveSort(request, PARAMETER)).isEqualTo(Sort.by(new Order(ASC, "property").nullsFirst()));
}
@Test // GH-3152
void sortParamHandlesSinglePropertyWithSortOrderAndWithNullsFirst() throws Exception {
final var request = new MockHttpServletRequest();
request.addParameter("sort", "property,DESC,nullsfirst");
assertThat(resolveSort(request, PARAMETER)).isEqualTo(Sort.by(new Order(DESC, "property").nullsFirst()));
}
@Test // GH-3152
void sortParamHandlesSinglePropertyWithSortOrderAndWithNullsNative() throws Exception {
final var request = new MockHttpServletRequest();
request.addParameter("sort", "property,DESC,nullsnative");
assertThat(resolveSort(request, PARAMETER)).isEqualTo(Sort.by(new Order(DESC, "property").nullsNative()));
}
private static Sort resolveSort(HttpServletRequest request, MethodParameter parameter) throws Exception {
var resolver = new SortHandlerMethodArgumentResolver();
@ -335,6 +392,9 @@ class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests { @@ -335,6 +392,9 @@ class SortHandlerMethodArgumentResolverUnitTests extends SortDefaultUnitTests {
void simpleDefaultWithDirectionCaseInsensitive(
@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC, caseSensitive = false) Sort sort);
void simpleDefaultWithDirectionAndNullHandling(
@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC, nullHandling = Sort.NullHandling.NULLS_LAST) Sort sort);
void containeredDefault(@SortDefaults(@SortDefault({ "foo", "bar" })) Sort sort);
void repeatable(@SortDefault({ "one", "two" }) @SortDefault({ "three" }) Sort sort);

Loading…
Cancel
Save