DATACMNS-1211 - Add ReactiveSortHandlerMethodArgumentResolver.

Add ReactiveSortHandlerMethodArgumentResolver and extract shared code from imperative SortHandlerMethodArgumentResolver into SortHandlerMethodArgumentResolverSupport.

Original pull request: #264.
This commit is contained in:
Mark Paluch
2017-12-03 11:58:24 -08:00
parent d1d5fa085d
commit 48daa4c4b1
11 changed files with 1377 additions and 379 deletions

View File

@@ -0,0 +1,280 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.web;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.web.PageableDefaultUnitTests.*;
import static org.springframework.data.web.PageableHandlerMethodArgumentResolver.*;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.web.SortDefault.SortDefaults;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
/**
* Unit tests for {@link ReactivePageableHandlerMethodArgumentResolver}.
*
* @author Mark Paluch
*/
public class ReactivePageableHandlerMethodArgumentResolverUnitTests {
@Rule public final ExpectedException exception = ExpectedException.none();
MethodParameter supportedMethodParameter;
@Before
public void setUp() throws Exception {
this.supportedMethodParameter = new MethodParameter(Sample.class.getMethod("supportedMethod", Pageable.class), 0);
}
@Test // DATACMNS-1211
public void preventsPageSizeFromExceedingMayValueIfConfigured() {
// Read side
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=0&size=200").build();
assertSupportedAndResult(supportedMethodParameter, PageRequest.of(0, 100), request);
}
@Test(expected = IllegalArgumentException.class) // DATACMNS-1211
public void rejectsEmptyPageParameterName() {
new ReactivePageableHandlerMethodArgumentResolver().setPageParameterName("");
}
@Test(expected = IllegalArgumentException.class) // DATACMNS-1211
public void rejectsNullPageParameterName() {
new ReactivePageableHandlerMethodArgumentResolver().setPageParameterName(null);
}
@Test(expected = IllegalArgumentException.class) // DATACMNS-1211
public void rejectsEmptySizeParameterName() {
new ReactivePageableHandlerMethodArgumentResolver().setSizeParameterName("");
}
@Test(expected = IllegalArgumentException.class) // DATACMNS-1211
public void rejectsNullSizeParameterName() {
new ReactivePageableHandlerMethodArgumentResolver().setSizeParameterName(null);
}
@Test // DATACMNS-1211
public void qualifierIsUsedInParameterLookup() throws Exception {
MethodParameter parameter = new MethodParameter(Sample.class.getMethod("validQualifier", Pageable.class), 0);
MockServerHttpRequest request = MockServerHttpRequest.get("foo?foo_page=2&foo_size=10").build();
assertSupportedAndResult(parameter, PageRequest.of(2, 10), request);
}
@Test // DATACMNS-1211
public void usesDefaultPageSizeIfRequestPageSizeIsLessThanOne() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=0&size=0").build();
assertSupportedAndResult(supportedMethodParameter, DEFAULT_PAGE_REQUEST, request);
}
@Test // DATACMNS-1211
public void rejectsInvalidCustomDefaultForPageSize() throws Exception {
MethodParameter parameter = new MethodParameter(Sample.class.getMethod("invalidDefaultPageSize", Pageable.class),
0);
exception.expect(IllegalStateException.class);
exception.expectMessage("invalidDefaultPageSize");
assertSupportedAndResult(parameter, DEFAULT_PAGE_REQUEST);
}
@Test // DATACMNS-1211
public void fallsBackToFirstPageIfNegativePageNumberIsGiven() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=-1").build();
assertSupportedAndResult(supportedMethodParameter, DEFAULT_PAGE_REQUEST, request);
}
@Test // DATACMNS-1211
public void pageParamIsNotNumeric() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=a").build();
assertSupportedAndResult(supportedMethodParameter, DEFAULT_PAGE_REQUEST, request);
}
@Test // DATACMNS-1211
public void sizeParamIsNotNumeric() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?size=a").build();
assertSupportedAndResult(supportedMethodParameter, DEFAULT_PAGE_REQUEST, request);
}
@Test // DATACMNS-1211
public void returnsNullIfFallbackIsUnpagedAndNoParametersGiven() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setFallbackPageable(Pageable.unpaged());
assertSupportedAndResult(supportedMethodParameter, Pageable.unpaged(), TestUtils.getWebfluxRequest(), resolver);
}
@Test // DATACMNS-1211
public void returnsFallbackIfOnlyPageIsGiven() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setFallbackPageable(Pageable.unpaged());
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=20").build();
assertThat(resolve(resolver, request)).isEqualTo(Pageable.unpaged());
}
@Test // DATACMNS-1211
public void returnsFallbackIfFallbackIsUnpagedAndOnlySizeIsGiven() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setFallbackPageable(Pageable.unpaged());
MockServerHttpRequest request = MockServerHttpRequest.get("foo?size=10").build();
assertThat(resolve(resolver, request)).isEqualTo(Pageable.unpaged());
}
@Test // DATACMNS-1211
public void considersOneIndexedParametersSetting() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setOneIndexedParameters(true);
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=1").build();
assertThat(resolve(resolver, request).getPageNumber()).isEqualTo(0);
}
@Test // DATACMNS-1211
public void usesNullSortIfNoDefaultIsConfiguredAndPageAndSizeAreGiven() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setFallbackPageable(Pageable.unpaged());
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=0&size=10").build();
Pageable result = resolve(resolver, request);
assertThat(result.getPageNumber()).isEqualTo(0);
assertThat(result.getPageSize()).isEqualTo(10);
assertThat(result.getSort().isSorted()).isFalse();
}
@Test // DATACMNS-1211
public void oneIndexedParametersDefaultsIndexOutOfRange() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setOneIndexedParameters(true);
MockServerHttpRequest request = MockServerHttpRequest.get("foo?page=0").build();
assertThat(resolve(resolver, request).getPageNumber()).isEqualTo(0);
}
@Test // DATACMNS-1211
public void returnsCorrectPageSizeForOneIndexParameters() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setOneIndexedParameters(true);
MockServerHttpRequest request = MockServerHttpRequest.get("foo?size=10").build();
assertThat(resolve(resolver, request).getPageSize()).isEqualTo(10);
}
@Test // DATACMNS-1211
public void detectsFallbackPageableIfNullOneIsConfigured() {
ReactivePageableHandlerMethodArgumentResolver resolver = getReactiveResolver();
resolver.setFallbackPageable(Pageable.unpaged());
assertThat(resolver.isFallbackPageable(null)).isFalse();
assertThat(resolver.isFallbackPageable(PageRequest.of(0, 10))).isFalse();
}
private ReactivePageableHandlerMethodArgumentResolver getReactiveResolver() {
ReactivePageableHandlerMethodArgumentResolver resolver = new ReactivePageableHandlerMethodArgumentResolver();
resolver.setMaxPageSize(100);
return resolver;
}
private void assertSupportedAndResult(MethodParameter parameter, Pageable pageable) {
assertSupportedAndResult(parameter, pageable, TestUtils.getWebfluxRequest());
}
private void assertSupportedAndResult(MethodParameter parameter, Pageable pageable, MockServerHttpRequest request) {
assertSupportedAndResult(parameter, pageable, request, getReactiveResolver());
}
private void assertSupportedAndResult(MethodParameter parameter, Pageable pageable, MockServerHttpRequest request,
SyncHandlerMethodArgumentResolver resolver) {
assertThat(resolver.supportsParameter(parameter)).isTrue();
Object value = resolver.resolveArgumentValue(parameter, null, MockServerWebExchange.from(request));
assertThat(value).isEqualTo(pageable);
}
private Pageable resolve(ReactivePageableHandlerMethodArgumentResolver resolver, MockServerHttpRequest request) {
return resolver.resolveArgumentValue(supportedMethodParameter, null, MockServerWebExchange.from(request));
}
interface Sample {
void supportedMethod(Pageable pageable);
void unsupportedMethod(String string);
void invalidDefaultPageSize(@PageableDefault(size = 0) Pageable pageable);
void simpleDefault(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER) Pageable pageable);
void simpleDefaultWithSort(
@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER, sort = { "firstname", "lastname" }) Pageable pageable);
void simpleDefaultWithSortAndDirection(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER,
sort = { "firstname", "lastname" }, direction = Direction.DESC) Pageable pageable);
void simpleDefaultWithExternalSort(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER) //
@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC) Pageable pageable);
void simpleDefaultWithContaineredExternalSort(@PageableDefault(size = PAGE_SIZE, page = PAGE_NUMBER) //
@SortDefaults(@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC)) Pageable pageable);
void invalidQualifiers(@Qualifier("foo") Pageable first, @Qualifier("foo") Pageable second);
void validQualifier(@Qualifier("foo") Pageable pageable);
void noQualifiers(Pageable first, Pageable second);
}
}

View File

@@ -0,0 +1,271 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.web;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.Sort.Direction.*;
import static org.springframework.data.web.SortDefaultUnitTests.*;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Qualifier;
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.lang.Nullable;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.util.StringUtils;
/**
* Unit tests for {@link ReactiveSortHandlerMethodArgumentResolver}.
*
* @author Mark Paluch
*/
public class ReactiveSortHandlerMethodArgumentResolverUnitTests {
static MethodParameter PARAMETER;
@Rule public final ExpectedException exception = ExpectedException.none();
@BeforeClass
public static void setUp() throws Exception {
PARAMETER = new MethodParameter(Controller.class.getMethod("supportedMethod", Sort.class), 0);
}
@Test // DATACMNS-1211
public void supportsSortParameter() {
ReactiveSortHandlerMethodArgumentResolver resolver = new ReactiveSortHandlerMethodArgumentResolver();
assertThat(resolver.supportsParameter(getParameterOfMethod("supportedMethod"))).isTrue();
}
@Test // DATACMNS-1211
public void returnsNullForNoDefault() {
assertSupportedAndResolvedTo(getParameterOfMethod("supportedMethod"), Sort.unsorted());
}
@Test // DATACMNS-1211
public void discoversSimpleDefault() {
assertSupportedAndResolvedTo(getParameterOfMethod("simpleDefault"), Sort.by(SORT_FIELDS).ascending());
}
@Test // DATACMNS-1211
public void discoversSimpleDefaultWithDirection() {
assertSupportedAndResolvedTo(getParameterOfMethod("simpleDefaultWithDirection"), SORT);
}
@Test // DATACMNS-1211
public void fallbackToGivenDefaultSort() {
MethodParameter parameter = TestUtils.getParameterOfMethod(Controller.class, "unsupportedMethod", String.class);
ReactiveSortHandlerMethodArgumentResolver resolver = new ReactiveSortHandlerMethodArgumentResolver();
Sort fallbackSort = Sort.by(Direction.ASC, "ID");
resolver.setFallbackSort(fallbackSort);
assertThat(resolve(resolver, TestUtils.getWebfluxRequest(), parameter)).isEqualTo(fallbackSort);
}
@Test // DATACMNS-1211
public void fallbackToDefaultDefaultSort() {
MethodParameter parameter = TestUtils.getParameterOfMethod(Controller.class, "unsupportedMethod", String.class);
ReactiveSortHandlerMethodArgumentResolver resolver = new ReactiveSortHandlerMethodArgumentResolver();
assertThat(resolve(resolver, TestUtils.getWebfluxRequest(), parameter).isSorted()).isFalse();
}
@Test // DATACMNS-1211
public void discoversSimpleSortFromRequest() {
MethodParameter parameter = getParameterOfMethod("simpleDefault");
Sort reference = Sort.by("bar", "foo");
MockServerHttpRequest request = getRequestWithSort(reference);
assertSupportedAndResolvedTo(request, parameter, reference);
}
@Test // DATACMNS-1211
public void discoversComplexSortFromRequest() {
MethodParameter parameter = getParameterOfMethod("simpleDefault");
Sort reference = Sort.by("bar", "foo").and(Sort.by("fizz", "buzz"));
assertSupportedAndResolvedTo(getRequestWithSort(reference), parameter, reference);
}
@Test // DATACMNS-1211
public void discoversQualifiedSortFromRequest() {
MethodParameter parameter = getParameterOfMethod("qualifiedSort");
Sort reference = Sort.by("bar", "foo");
assertSupportedAndResolvedTo(getRequestWithSort(reference, "qual"), parameter, reference);
}
@Test // DATACMNS-1211
public void requestForMultipleSortPropertiesIsUnmarshalledCorrectly() {
MockServerHttpRequest request = MockServerHttpRequest.get(String.format("foo?sort=%s", SortDefaultUnitTests.SORT_3))
.build();
ReactiveSortHandlerMethodArgumentResolver resolver = new ReactiveSortHandlerMethodArgumentResolver();
Sort result = resolve(resolver, request, PARAMETER);
assertThat(result).isEqualTo(Sort.by(Direction.ASC, "firstname", "lastname"));
}
@Test // DATACMNS-1211
public void parsesEmptySortToNull() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?sort=").build();
assertThat(resolve(request, PARAMETER).isSorted()).isFalse();
}
@Test // DATACMNS-1211
public void sortParamIsInvalidProperty() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?sort=,DESC").build();
assertThat(resolve(request, PARAMETER).isSorted()).isFalse();
}
@Test // DATACMNS-1211
public void sortParamIsInvalidPropertyWhenMultiProperty() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?sort=property1,,DESC").build();
assertThat(resolve(request, PARAMETER)).isEqualTo(Sort.by(DESC, "property1"));
}
@Test // DATACMNS-1211
public void rejectsDoubleAnnotatedMethod() {
MethodParameter parameter = getParameterOfMethod("invalid");
ReactiveSortHandlerMethodArgumentResolver resolver = new ReactiveSortHandlerMethodArgumentResolver();
assertThat(resolver.supportsParameter(parameter)).isTrue();
exception.expect(IllegalArgumentException.class);
exception.expectMessage(SortDefault.class.getSimpleName());
exception.expectMessage(SortDefaults.class.getSimpleName());
exception.expectMessage(parameter.toString());
resolver.resolveArgumentValue(parameter, null,
MockServerWebExchange.from(TestUtils.getWebfluxRequest()));
}
@Test // DATACMNS-1211
public void sortParamIsEmptyWhenMultiParams() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?sort=property,DESC&sort=").build();
assertThat(resolve(request, PARAMETER)).isEqualTo(Sort.by(DESC, "property"));
}
@Test // DATACMNS-1211
public void parsesCommaParameterForSort() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?sort=,").build();
assertThat(resolve(request, PARAMETER).isSorted()).isFalse();
}
@Test // DATACMNS-1211
public void doesNotReturnNullWhenAnnotatedWithSortDefault() {
MockServerHttpRequest request = MockServerHttpRequest.get("foo?sort=").build();
assertThat(resolve(request, getParameterOfMethod("simpleDefault"))).isEqualTo(Sort.by("firstname", "lastname"));
assertThat(resolve(request, getParameterOfMethod("containeredDefault"))).isEqualTo(Sort.by("foo", "bar"));
}
private static Sort resolve(MockServerHttpRequest request, MethodParameter parameter) {
return resolve(new ReactiveSortHandlerMethodArgumentResolver(), request, parameter);
}
private static Sort resolve(ReactiveSortHandlerMethodArgumentResolver resolver, MockServerHttpRequest request,
MethodParameter parameter) {
return resolver.resolveArgumentValue(parameter, null, MockServerWebExchange.from(request));
}
private static void assertSupportedAndResolvedTo(MethodParameter parameter, Sort sort) {
assertSupportedAndResolvedTo(TestUtils.getWebfluxRequest(), parameter, sort);
}
private static void assertSupportedAndResolvedTo(MockServerHttpRequest request, MethodParameter parameter,
Sort sort) {
ReactiveSortHandlerMethodArgumentResolver resolver = new ReactiveSortHandlerMethodArgumentResolver();
assertThat(resolver.supportsParameter(parameter)).isTrue();
Sort resolved = resolve(resolver, request, parameter);
assertThat(resolved).isEqualTo(sort);
}
private static MockServerHttpRequest getRequestWithSort(Sort sort) {
return getRequestWithSort(sort, null);
}
private static MockServerHttpRequest getRequestWithSort(@Nullable Sort sort, @Nullable String qualifier) {
if (sort == null) {
return TestUtils.getWebfluxRequest();
}
StringBuilder queryString = new StringBuilder();
for (Order order : sort) {
String prefix = StringUtils.hasText(qualifier) ? qualifier + "_" : "";
if (queryString.length() != 0) {
queryString.append('&');
}
queryString.append(String.format("%ssort=%s,%s", prefix, order.getProperty(), order.getDirection().name()));
}
return MockServerHttpRequest.get(String.format("foo?%s", queryString.toString())).build();
}
private static MethodParameter getParameterOfMethod(String name) {
return TestUtils.getParameterOfMethod(Controller.class, name, Sort.class);
}
interface Controller {
void supportedMethod(Sort sort);
void unsupportedMethod(String string);
void qualifiedSort(@Qualifier("qual") Sort sort);
void simpleDefault(@SortDefault({ "firstname", "lastname" }) Sort sort);
void simpleDefaultWithDirection(
@SortDefault(sort = { "firstname", "lastname" }, direction = Direction.DESC) Sort sort);
void containeredDefault(@SortDefaults(@SortDefault({ "foo", "bar" })) Sort sort);
void invalid(@SortDefaults(@SortDefault({ "foo", "bar" })) @SortDefault({ "bar", "foo" }) Sort sort);
}
}

View File

@@ -17,6 +17,8 @@ package org.springframework.data.web;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.core.MethodParameter;
@@ -57,7 +59,7 @@ public abstract class SortDefaultUnitTests {
private static void assertSortStringParsedInto(Sort expected, String... source) {
SortHandlerMethodArgumentResolver resolver = new SortHandlerMethodArgumentResolver();
Sort sort = resolver.parseParameterIntoSort(source, ",");
Sort sort = resolver.parseParameterIntoSort(Arrays.asList(source), ",");
assertThat(sort).isEqualTo(expected);
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.web;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
@@ -27,6 +28,7 @@ import org.springframework.web.context.request.ServletWebRequest;
*
* @since 1.6
* @author Oliver Gierke
* @author Mark Paluch
*/
class TestUtils {
@@ -34,6 +36,10 @@ class TestUtils {
return new ServletWebRequest(new MockHttpServletRequest());
}
public static MockServerHttpRequest getWebfluxRequest() {
return MockServerHttpRequest.get("foo").build();
}
public static MethodParameter getParameterOfMethod(Class<?> controller, String name, Class<?>... argumentTypes) {
Method method = getMethod(controller, name, argumentTypes);