Introduce argument resolver for OffsetScrollPosition.
Closes #2856 Original pull request: #2861
This commit is contained in:
committed by
Mark Paluch
parent
eba49c66ce
commit
391607bd14
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2016-2023 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
|
||||
*
|
||||
* https://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 org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* Argument resolver to extract a {@link OffsetScrollPosition} object from a {@link NativeWebRequest} for a particular
|
||||
* {@link MethodParameter}. A {@link OffsetScrollPositionArgumentResolver} can either resolve {@link OffsetScrollPosition} itself or wrap another
|
||||
* {@link OffsetScrollPositionArgumentResolver} to post-process {@link OffsetScrollPosition}.
|
||||
*
|
||||
* @since 3.2
|
||||
* @author Yanming Zhou
|
||||
* @see HandlerMethodArgumentResolver
|
||||
*/
|
||||
public interface OffsetScrollPositionArgumentResolver extends HandlerMethodArgumentResolver {
|
||||
|
||||
/**
|
||||
* Resolves a {@link OffsetScrollPosition} method parameter into an argument value from a given request.
|
||||
*
|
||||
* @param parameter the method parameter to resolve. This parameter must have previously been passed to
|
||||
* {@link #supportsParameter} which must have returned {@code true}.
|
||||
* @param mavContainer the ModelAndViewContainer for the current request
|
||||
* @param webRequest the current request
|
||||
* @param binderFactory a factory for creating {@link WebDataBinder} instances
|
||||
* @return the resolved argument value
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
OffsetScrollPosition resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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
|
||||
*
|
||||
* https://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 org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to automatically create {@link OffsetScrollPosition} instances from request parameters.
|
||||
*
|
||||
* @since 3.2
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public class OffsetScrollPositionHandlerMethodArgumentResolver extends OffsetScrollPositionHandlerMethodArgumentResolverSupport
|
||||
implements OffsetScrollPositionArgumentResolver {
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return OffsetScrollPosition.class.equals(parameter.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OffsetScrollPosition resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
|
||||
|
||||
String[] offsetParameter = webRequest.getParameterValues(getOffsetParameter(parameter));
|
||||
return parseParameterIntoOffsetScrollPosition(offsetParameter != null ? Arrays.asList(offsetParameter) : null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2017-2023 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
|
||||
*
|
||||
* https://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 org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Base class providing methods for handler method argument resolvers to create {@link OffsetScrollPosition} instances from request
|
||||
* parameters.
|
||||
*
|
||||
* @since 3.2
|
||||
* @author Yanming Zhou
|
||||
* @see OffsetScrollPositionHandlerMethodArgumentResolver
|
||||
* @see ReactiveOffsetScrollPositionHandlerMethodArgumentResolver
|
||||
*/
|
||||
public abstract class OffsetScrollPositionHandlerMethodArgumentResolverSupport {
|
||||
|
||||
private static final String DEFAULT_PARAMETER = "offset";
|
||||
|
||||
private static final String DEFAULT_QUALIFIER_DELIMITER = "_";
|
||||
|
||||
private String offsetParameter = DEFAULT_PARAMETER;
|
||||
|
||||
private String qualifierDelimiter = DEFAULT_QUALIFIER_DELIMITER;
|
||||
|
||||
/**
|
||||
* Configure the request parameter to lookup offset information from. Defaults to {@code offset}.
|
||||
*
|
||||
* @param offsetParameter must not be {@literal null} or empty.
|
||||
*/
|
||||
public void setOffsetParameter(String offsetParameter) {
|
||||
|
||||
Assert.hasText(offsetParameter, "offsetParameter must not be null nor empty");
|
||||
this.offsetParameter = offsetParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the delimiter used to separate the qualifier from the offset parameter. Defaults to {@code _}, so a
|
||||
* qualified offset property would look like {@code qualifier_offset}.
|
||||
*
|
||||
* @param qualifierDelimiter the qualifier delimiter to be used or {@literal null} to reset to the default.
|
||||
*/
|
||||
public void setQualifierDelimiter(@Nullable String qualifierDelimiter) {
|
||||
this.qualifierDelimiter = qualifierDelimiter == null ? DEFAULT_QUALIFIER_DELIMITER : qualifierDelimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset parameter to be looked up from the request. Potentially applies qualifiers to it.
|
||||
*
|
||||
* @param parameter can be {@literal null}.
|
||||
* @return the offset parameter
|
||||
*/
|
||||
protected String getOffsetParameter(@Nullable MethodParameter parameter) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String value = SpringDataAnnotationUtils.getQualifier(parameter);
|
||||
|
||||
if (StringUtils.hasLength(value)) {
|
||||
builder.append(value);
|
||||
builder.append(qualifierDelimiter);
|
||||
}
|
||||
|
||||
return builder.append(offsetParameter).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given source into a {@link OffsetScrollPosition} instance.
|
||||
*
|
||||
* @param source could be {@literal null} or empty.
|
||||
* @return parsed OffsetScrollPosition
|
||||
*/
|
||||
OffsetScrollPosition parseParameterIntoOffsetScrollPosition(@Nullable List<String> source) {
|
||||
// No parameter or Single empty parameter, e.g "offset="
|
||||
if (source == null || source.size() == 1 && !StringUtils.hasText(source.get(0))) {
|
||||
return ScrollPosition.offset();
|
||||
}
|
||||
try {
|
||||
long offset = Long.parseLong(source.get(0));
|
||||
return ScrollPosition.offset(offset);
|
||||
} catch (NumberFormatException ex) {
|
||||
return ScrollPosition.offset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2017-2023 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
|
||||
*
|
||||
* https://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 org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Reactive {@link HandlerMethodArgumentResolver} to create {@link OffsetScrollPosition} instances from query string parameters.
|
||||
*
|
||||
* @since 3.2
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public class ReactiveOffsetScrollPositionHandlerMethodArgumentResolver extends OffsetScrollPositionHandlerMethodArgumentResolverSupport
|
||||
implements SyncHandlerMethodArgumentResolver {
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return OffsetScrollPosition.class.equals(parameter.getParameterType());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public OffsetScrollPosition resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
List<String> offsetParameter = exchange.getRequest().getQueryParams().get(getOffsetParameter(parameter));
|
||||
|
||||
return parseParameterIntoOffsetScrollPosition(offsetParameter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2017-2023 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
|
||||
*
|
||||
* https://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.config;
|
||||
|
||||
import org.springframework.data.web.OffsetScrollPositionHandlerMethodArgumentResolver;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to customize the
|
||||
* {@link OffsetScrollPositionHandlerMethodArgumentResolver} configuration.
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public interface OffsetScrollPositionHandlerMethodArgumentResolverCustomizer {
|
||||
|
||||
/**
|
||||
* Customize the given {@link OffsetScrollPositionHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param offsetResolver the {@link OffsetScrollPositionHandlerMethodArgumentResolver} to customize, will never be {@literal null}.
|
||||
*/
|
||||
void customize(OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import org.springframework.data.geo.format.DistanceFormatter;
|
||||
import org.springframework.data.geo.format.PointFormatter;
|
||||
import org.springframework.data.repository.support.DomainClassConverter;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.web.OffsetScrollPositionHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.ProjectingJackson2HttpMessageConverter;
|
||||
import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver;
|
||||
@@ -46,7 +47,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Configuration class to register {@link PageableHandlerMethodArgumentResolver},
|
||||
* {@link SortHandlerMethodArgumentResolver} and {@link DomainClassConverter}.
|
||||
* {@link SortHandlerMethodArgumentResolver}, {@link OffsetScrollPositionHandlerMethodArgumentResolver}
|
||||
* and {@link DomainClassConverter}.
|
||||
*
|
||||
* @since 1.6
|
||||
* @author Oliver Gierke
|
||||
@@ -54,6 +56,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Greg Turnquist
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLoaderAware {
|
||||
@@ -66,6 +69,7 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLo
|
||||
private final Lazy<PageableHandlerMethodArgumentResolver> pageableResolver;
|
||||
private final Lazy<PageableHandlerMethodArgumentResolverCustomizer> pageableResolverCustomizer;
|
||||
private final Lazy<SortHandlerMethodArgumentResolverCustomizer> sortResolverCustomizer;
|
||||
private final Lazy<OffsetScrollPositionHandlerMethodArgumentResolverCustomizer> offsetResolverCustomizer;
|
||||
|
||||
public SpringDataWebConfiguration(ApplicationContext context,
|
||||
@Qualifier("mvcConversionService") ObjectFactory<ConversionService> conversionService) {
|
||||
@@ -83,6 +87,8 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLo
|
||||
() -> context.getBeanProvider(PageableHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
|
||||
this.sortResolverCustomizer = Lazy.of( //
|
||||
() -> context.getBeanProvider(SortHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
|
||||
this.offsetResolverCustomizer = Lazy.of( //
|
||||
() -> context.getBeanProvider(OffsetScrollPositionHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,6 +113,14 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLo
|
||||
return sortResolver;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver() {
|
||||
|
||||
OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver = new OffsetScrollPositionHandlerMethodArgumentResolver();
|
||||
customizeOffsetResolver(offsetResolver);
|
||||
return offsetResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
|
||||
@@ -165,6 +179,10 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLo
|
||||
sortResolverCustomizer.getOptional().ifPresent(c -> c.customize(sortResolver));
|
||||
}
|
||||
|
||||
protected void customizeOffsetResolver(OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver) {
|
||||
offsetResolverCustomizer.getOptional().ifPresent(c -> c.customize(offsetResolver));
|
||||
}
|
||||
|
||||
private void forwardBeanClassLoader(BeanClassLoaderAware target) {
|
||||
|
||||
if (beanClassLoader != null) {
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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
|
||||
*
|
||||
* https://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 jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link OffsetScrollPositionHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @since 3.2
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class OffsetScrollPositionHandlerMethodArgumentResolverUnitTests {
|
||||
|
||||
static MethodParameter PARAMETER;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
PARAMETER = getParameterOfMethod("supportedMethod");
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsSortParameter() {
|
||||
|
||||
var resolver = new OffsetScrollPositionHandlerMethodArgumentResolver();
|
||||
|
||||
assertThat(resolver.supportsParameter(PARAMETER)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fallbackToDefaultOffset() {
|
||||
|
||||
var parameter = TestUtils.getParameterOfMethod(Controller.class, "unsupportedMethod", String.class);
|
||||
var position = resolveOffset(new MockHttpServletRequest(), parameter);
|
||||
assertThat(position).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoversOffsetFromRequest() {
|
||||
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, null), PARAMETER, reference);
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoversOffsetFromRequestWithMultipleParams() {
|
||||
|
||||
var request = new MockHttpServletRequest();
|
||||
request.addParameter("offset", "5");
|
||||
request.addParameter("offset", "6");
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoversQualifiedOffsetFromRequest() {
|
||||
|
||||
var parameter = getParameterOfMethod("qualifiedOffset");
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, "qual"), parameter, reference);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsDefaultForOffsetParamSetToNothing() {
|
||||
|
||||
var request = new MockHttpServletRequest();
|
||||
request.addParameter("offset", (String) null);
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsDefaultForEmptyOffsetParam() {
|
||||
|
||||
var request = new MockHttpServletRequest();
|
||||
request.addParameter("offset", "");
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsDefaultForOffsetParamIsInvalidProperty() {
|
||||
|
||||
var request = new MockHttpServletRequest();
|
||||
request.addParameter("offset", "invalid_number");
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyQualifierIsUsedInParameterLookup() {
|
||||
|
||||
var parameter = getParameterOfMethod("emptyQualifier");
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, ""), parameter, reference);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mergedQualifierIsUsedInParameterLookup() {
|
||||
|
||||
var parameter = getParameterOfMethod("mergedQualifier");
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, "merged"), parameter, reference);
|
||||
}
|
||||
|
||||
private static OffsetScrollPosition resolveOffset(HttpServletRequest request, MethodParameter parameter) {
|
||||
|
||||
var resolver = new OffsetScrollPositionHandlerMethodArgumentResolver();
|
||||
return resolver.resolveArgument(parameter, null, new ServletWebRequest(request), null);
|
||||
}
|
||||
|
||||
private static void assertSupportedAndResolvedTo(NativeWebRequest request, MethodParameter parameter, OffsetScrollPosition position) {
|
||||
|
||||
var resolver = new OffsetScrollPositionHandlerMethodArgumentResolver();
|
||||
assertThat(resolver.supportsParameter(parameter)).isTrue();
|
||||
|
||||
try {
|
||||
assertThat(resolver.resolveArgument(parameter, null, request, null)).isEqualTo(position);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static MethodParameter getParameterOfMethod(String name) {
|
||||
return TestUtils.getParameterOfMethod(Controller.class, name, OffsetScrollPosition.class);
|
||||
}
|
||||
|
||||
private static NativeWebRequest getRequestWithOffset(@Nullable OffsetScrollPosition position, @Nullable String qualifier) {
|
||||
|
||||
var request = new MockHttpServletRequest();
|
||||
|
||||
if (position == null) {
|
||||
return new ServletWebRequest(request);
|
||||
}
|
||||
|
||||
String parameterName = StringUtils.hasLength(qualifier) ? qualifier + "_offset" : "offset";
|
||||
request.addParameter(parameterName, String.valueOf(position.getOffset()));
|
||||
|
||||
return new ServletWebRequest(request);
|
||||
}
|
||||
|
||||
interface Controller {
|
||||
|
||||
void supportedMethod(OffsetScrollPosition offset);
|
||||
|
||||
void unsupportedMethod(String string);
|
||||
|
||||
void qualifiedOffset(@Qualifier("qual") OffsetScrollPosition offset);
|
||||
|
||||
void emptyQualifier(@Qualifier OffsetScrollPosition offset);
|
||||
|
||||
void mergedQualifier(@TestQualifier OffsetScrollPosition offset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2017-2023 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
|
||||
*
|
||||
* https://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 org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.OffsetScrollPosition;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ReactiveOffsetScrollPositionHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @since 3.2
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class ReactiveOffsetScrollPositionHandlerMethodArgumentResolverUnitTests {
|
||||
|
||||
static MethodParameter PARAMETER;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
PARAMETER = getParameterOfMethod("supportedMethod");
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsSortParameter() {
|
||||
|
||||
var resolver = new ReactiveOffsetScrollPositionHandlerMethodArgumentResolver();
|
||||
|
||||
assertThat(resolver.supportsParameter(PARAMETER)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void fallbackToDefaultOffset() {
|
||||
|
||||
var parameter = TestUtils.getParameterOfMethod(Controller.class, "unsupportedMethod", String.class);
|
||||
var request = MockServerHttpRequest.get("/foo").build();
|
||||
var position = resolveOffset(request, parameter);
|
||||
assertThat(position).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoversOffsetFromRequest() {
|
||||
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, null), PARAMETER, reference);
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoversOffsetFromRequestWithMultipleParams() {
|
||||
|
||||
var request = MockServerHttpRequest.get("/foo?offset=5&offset=6").build();
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void discoversQualifiedOffsetFromRequest() {
|
||||
|
||||
var parameter = getParameterOfMethod("qualifiedOffset");
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, "qual"), parameter, reference);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsDefaultForOffsetParamSetToNothing() {
|
||||
|
||||
var request = MockServerHttpRequest.get("/foo").build();
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsDefaultForEmptyOffsetParam() {
|
||||
|
||||
var request = MockServerHttpRequest.get("/foo?offset=").build();
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsDefaultForOffsetParamIsInvalidProperty() {
|
||||
|
||||
var request = MockServerHttpRequest.get("/foo?offset=invalid_number").build();
|
||||
|
||||
assertThat(resolveOffset(request, PARAMETER)).isEqualTo(ScrollPosition.offset());
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyQualifierIsUsedInParameterLookup() {
|
||||
|
||||
var parameter = getParameterOfMethod("emptyQualifier");
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, ""), parameter, reference);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mergedQualifierIsUsedInParameterLookup() {
|
||||
|
||||
var parameter = getParameterOfMethod("mergedQualifier");
|
||||
var reference = ScrollPosition.offset(5);
|
||||
|
||||
assertSupportedAndResolvedTo(getRequestWithOffset(reference, "merged"), parameter, reference);
|
||||
}
|
||||
|
||||
private static OffsetScrollPosition resolveOffset(MockServerHttpRequest request, MethodParameter parameter) {
|
||||
|
||||
var resolver = new ReactiveOffsetScrollPositionHandlerMethodArgumentResolver();
|
||||
return resolver.resolveArgumentValue(parameter, null, MockServerWebExchange.from(request));
|
||||
}
|
||||
|
||||
private static void assertSupportedAndResolvedTo(MockServerHttpRequest request, MethodParameter parameter, OffsetScrollPosition position) {
|
||||
|
||||
var resolver = new ReactiveOffsetScrollPositionHandlerMethodArgumentResolver();
|
||||
assertThat(resolver.supportsParameter(parameter)).isTrue();
|
||||
|
||||
try {
|
||||
var resolved = resolveOffset(request, parameter);
|
||||
assertThat(resolved).isEqualTo(position);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static MethodParameter getParameterOfMethod(String name) {
|
||||
return TestUtils.getParameterOfMethod(Controller.class, name, OffsetScrollPosition.class);
|
||||
}
|
||||
|
||||
private static MockServerHttpRequest getRequestWithOffset(@Nullable OffsetScrollPosition position, @Nullable String qualifier) {
|
||||
|
||||
if (position == null) {
|
||||
return TestUtils.getWebfluxRequest();
|
||||
}
|
||||
|
||||
String parameterName = StringUtils.hasLength(qualifier) ? qualifier + "_offset" : "offset";
|
||||
return MockServerHttpRequest.get(String.format("foo?%s=%s", parameterName, String.valueOf(position.getOffset()))).build();
|
||||
}
|
||||
|
||||
interface Controller {
|
||||
|
||||
void supportedMethod(OffsetScrollPosition offset);
|
||||
|
||||
void unsupportedMethod(String string);
|
||||
|
||||
void qualifiedOffset(@Qualifier("qual") OffsetScrollPosition offset);
|
||||
|
||||
void emptyQualifier(@Qualifier OffsetScrollPosition offset);
|
||||
|
||||
void mergedQualifier(@TestQualifier OffsetScrollPosition offset);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.querydsl.EntityPathResolver;
|
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver;
|
||||
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
|
||||
import org.springframework.data.web.OffsetScrollPositionHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
|
||||
import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver;
|
||||
@@ -52,6 +53,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* @author Oliver Gierke
|
||||
* @author Jens Schauder
|
||||
* @author Vedran Pavic
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
class EnableSpringDataWebSupportIntegrationTests {
|
||||
|
||||
@@ -88,6 +90,17 @@ class EnableSpringDataWebSupportIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableSpringDataWebSupport
|
||||
static class OffsetResolverCustomizerConfig extends SampleConfig {
|
||||
|
||||
@Bean
|
||||
OffsetScrollPositionHandlerMethodArgumentResolverCustomizer testOffsetResolverCustomizer() {
|
||||
return offsetResolver -> offsetResolver.setOffsetParameter("foo");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableSpringDataWebSupport
|
||||
static class CustomEntityPathResolver {
|
||||
@@ -217,6 +230,17 @@ class EnableSpringDataWebSupportIntegrationTests {
|
||||
assertThat((String) ReflectionTestUtils.getField(resolver, "sortParameter")).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void picksUpOffsetResolverCustomizer() {
|
||||
|
||||
ApplicationContext context = WebTestUtils.createApplicationContext(OffsetResolverCustomizerConfig.class);
|
||||
var names = Arrays.asList(context.getBeanDefinitionNames());
|
||||
var resolver = context.getBean("offsetResolver", OffsetScrollPositionHandlerMethodArgumentResolver.class);
|
||||
|
||||
assertThat(names).contains("testOffsetResolverCustomizer");
|
||||
assertThat((String) ReflectionTestUtils.getField(resolver, "offsetParameter")).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1237
|
||||
void configuresProxyingHandlerMethodArgumentResolver() {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user