Skip to content

Commit 1ac7d87

Browse files
committed
Implement argument resolver for OffsetScrollPosition
See spring-projectsGH-2856
1 parent 8aff631 commit 1ac7d87

9 files changed

+679
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2016-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.web;
17+
18+
import org.springframework.core.MethodParameter;
19+
import org.springframework.data.domain.OffsetScrollPosition;
20+
import org.springframework.lang.NonNull;
21+
import org.springframework.lang.Nullable;
22+
import org.springframework.web.bind.WebDataBinder;
23+
import org.springframework.web.bind.support.WebDataBinderFactory;
24+
import org.springframework.web.context.request.NativeWebRequest;
25+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
26+
import org.springframework.web.method.support.ModelAndViewContainer;
27+
28+
/**
29+
* Argument resolver to extract a {@link OffsetScrollPosition} object from a {@link NativeWebRequest} for a particular
30+
* {@link MethodParameter}. A {@link OffsetScrollPositionArgumentResolver} can either resolve {@link OffsetScrollPosition} itself or wrap another
31+
* {@link OffsetScrollPositionArgumentResolver} to post-process {@link OffsetScrollPosition}.
32+
*
33+
* @since 3.2
34+
* @author Yanming Zhou
35+
* @see HandlerMethodArgumentResolver
36+
*/
37+
public interface OffsetScrollPositionArgumentResolver extends HandlerMethodArgumentResolver {
38+
39+
/**
40+
* Resolves a {@link OffsetScrollPosition} method parameter into an argument value from a given request.
41+
*
42+
* @param parameter the method parameter to resolve. This parameter must have previously been passed to
43+
* {@link #supportsParameter} which must have returned {@code true}.
44+
* @param mavContainer the ModelAndViewContainer for the current request
45+
* @param webRequest the current request
46+
* @param binderFactory a factory for creating {@link WebDataBinder} instances
47+
* @return the resolved argument value
48+
*/
49+
@NonNull
50+
@Override
51+
OffsetScrollPosition resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
52+
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory);
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2013-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.web;
17+
18+
import org.springframework.core.MethodParameter;
19+
import org.springframework.data.domain.OffsetScrollPosition;
20+
import org.springframework.lang.Nullable;
21+
import org.springframework.web.bind.support.WebDataBinderFactory;
22+
import org.springframework.web.context.request.NativeWebRequest;
23+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
24+
import org.springframework.web.method.support.ModelAndViewContainer;
25+
26+
import java.util.Arrays;
27+
28+
/**
29+
* {@link HandlerMethodArgumentResolver} to automatically create {@link OffsetScrollPosition} instances from request parameters.
30+
*
31+
* @since 3.2
32+
* @author Yanming Zhou
33+
*/
34+
public class OffsetScrollPositionHandlerMethodArgumentResolver extends OffsetScrollPositionHandlerMethodArgumentResolverSupport
35+
implements OffsetScrollPositionArgumentResolver {
36+
37+
@Override
38+
public boolean supportsParameter(MethodParameter parameter) {
39+
return OffsetScrollPosition.class.equals(parameter.getParameterType());
40+
}
41+
42+
@Override
43+
public OffsetScrollPosition resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
44+
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
45+
46+
String[] offsetParameter = webRequest.getParameterValues(getOffsetParameter(parameter));
47+
return parseParameterIntoOffsetScrollPosition(offsetParameter != null ? Arrays.asList(offsetParameter) : null);
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2017-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.web;
17+
18+
import org.springframework.core.MethodParameter;
19+
import org.springframework.data.domain.OffsetScrollPosition;
20+
import org.springframework.data.domain.ScrollPosition;
21+
import org.springframework.lang.Nullable;
22+
import org.springframework.util.Assert;
23+
import org.springframework.util.StringUtils;
24+
25+
import java.util.List;
26+
import java.util.Objects;
27+
28+
/**
29+
* Base class providing methods for handler method argument resolvers to create {@link OffsetScrollPosition} instances from request
30+
* parameters.
31+
*
32+
* @since 3.2
33+
* @author Yanming Zhou
34+
* @see OffsetScrollPositionHandlerMethodArgumentResolver
35+
* @see ReactiveOffsetScrollPositionHandlerMethodArgumentResolver
36+
*/
37+
public abstract class OffsetScrollPositionHandlerMethodArgumentResolverSupport {
38+
39+
private static final String DEFAULT_PARAMETER = "offset";
40+
41+
private String offsetParameter = DEFAULT_PARAMETER;
42+
43+
/**
44+
* Configure the request parameter to lookup offset information from. Defaults to {@code offset}.
45+
*
46+
* @param offsetParameter must not be {@literal null} or empty.
47+
*/
48+
public void setOffsetParameter(String offsetParameter) {
49+
50+
Assert.hasText(offsetParameter, "offsetParameter must not be null nor empty");
51+
this.offsetParameter = offsetParameter;
52+
}
53+
54+
/**
55+
* Returns the offset parameter to be looked up from the request. Potentially applies qualifiers to it.
56+
*
57+
* @param parameter can be {@literal null}.
58+
* @return the offset parameter
59+
*/
60+
protected String getOffsetParameter(@Nullable MethodParameter parameter) {
61+
62+
String result = SpringDataAnnotationUtils.getQualifier(parameter);
63+
if ("".equals(result)) {
64+
// empty qualifier
65+
result = parameter.getParameterName();
66+
}
67+
return Objects.requireNonNullElse(result, this.offsetParameter);
68+
}
69+
70+
/**
71+
* Parses the given source into a {@link OffsetScrollPosition} instance.
72+
*
73+
* @param source could be {@literal null} or empty.
74+
* @return parsed OffsetScrollPosition
75+
*/
76+
OffsetScrollPosition parseParameterIntoOffsetScrollPosition(@Nullable List<String> source) {
77+
// No parameter or Single empty parameter, e.g "offset="
78+
if (source == null || source.size() == 1 && !StringUtils.hasText(source.get(0))) {
79+
return ScrollPosition.offset();
80+
}
81+
try {
82+
long offset = Long.parseLong(source.get(0));
83+
return ScrollPosition.offset(offset);
84+
} catch (NumberFormatException ex) {
85+
return ScrollPosition.offset();
86+
}
87+
}
88+
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2017-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.web;
17+
18+
import org.springframework.core.MethodParameter;
19+
import org.springframework.data.domain.OffsetScrollPosition;
20+
import org.springframework.lang.NonNull;
21+
import org.springframework.web.reactive.BindingContext;
22+
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
23+
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
24+
import org.springframework.web.server.ServerWebExchange;
25+
26+
import java.util.List;
27+
28+
/**
29+
* Reactive {@link HandlerMethodArgumentResolver} to create {@link OffsetScrollPosition} instances from query string parameters.
30+
*
31+
* @since 3.2
32+
* @author Yanming Zhou
33+
*/
34+
public class ReactiveOffsetScrollPositionHandlerMethodArgumentResolver extends OffsetScrollPositionHandlerMethodArgumentResolverSupport
35+
implements SyncHandlerMethodArgumentResolver {
36+
37+
@Override
38+
public boolean supportsParameter(MethodParameter parameter) {
39+
return OffsetScrollPosition.class.equals(parameter.getParameterType());
40+
}
41+
42+
@NonNull
43+
@Override
44+
public OffsetScrollPosition resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext,
45+
ServerWebExchange exchange) {
46+
47+
List<String> offsetParameter = exchange.getRequest().getQueryParams().get(getOffsetParameter(parameter));
48+
49+
return parseParameterIntoOffsetScrollPosition(offsetParameter);
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2017-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.web.config;
17+
18+
import org.springframework.data.web.OffsetScrollPositionHandlerMethodArgumentResolver;
19+
20+
/**
21+
* Callback interface that can be implemented by beans wishing to customize the
22+
* {@link OffsetScrollPositionHandlerMethodArgumentResolver} configuration.
23+
*
24+
* @since 2.0
25+
* @author Yanming Zhou
26+
*/
27+
public interface OffsetScrollPositionHandlerMethodArgumentResolverCustomizer {
28+
29+
/**
30+
* Customize the given {@link OffsetScrollPositionHandlerMethodArgumentResolver}.
31+
*
32+
* @param offsetResolver the {@link OffsetScrollPositionHandlerMethodArgumentResolver} to customize, will never be {@literal null}.
33+
*/
34+
void customize(OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver);
35+
}

src/main/java/org/springframework/data/web/config/SpringDataWebConfiguration.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.geo.format.PointFormatter;
2929
import org.springframework.data.repository.support.DomainClassConverter;
3030
import org.springframework.data.util.Lazy;
31+
import org.springframework.data.web.OffsetScrollPositionHandlerMethodArgumentResolver;
3132
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
3233
import org.springframework.data.web.ProjectingJackson2HttpMessageConverter;
3334
import org.springframework.data.web.ProxyingHandlerMethodArgumentResolver;
@@ -46,14 +47,16 @@
4647

4748
/**
4849
* Configuration class to register {@link PageableHandlerMethodArgumentResolver},
49-
* {@link SortHandlerMethodArgumentResolver} and {@link DomainClassConverter}.
50+
* {@link SortHandlerMethodArgumentResolver}, {@link OffsetScrollPositionHandlerMethodArgumentResolver}
51+
* and {@link DomainClassConverter}.
5052
*
5153
* @since 1.6
5254
* @author Oliver Gierke
5355
* @author Vedran Pavic
5456
* @author Jens Schauder
5557
* @author Mark Paluch
5658
* @author Greg Turnquist
59+
* @author Yanming Zhou
5760
*/
5861
@Configuration(proxyBeanMethods = false)
5962
public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLoaderAware {
@@ -66,6 +69,7 @@ public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLo
6669
private final Lazy<PageableHandlerMethodArgumentResolver> pageableResolver;
6770
private final Lazy<PageableHandlerMethodArgumentResolverCustomizer> pageableResolverCustomizer;
6871
private final Lazy<SortHandlerMethodArgumentResolverCustomizer> sortResolverCustomizer;
72+
private final Lazy<OffsetScrollPositionHandlerMethodArgumentResolverCustomizer> offsetResolverCustomizer;
6973

7074
public SpringDataWebConfiguration(ApplicationContext context,
7175
@Qualifier("mvcConversionService") ObjectFactory<ConversionService> conversionService) {
@@ -83,6 +87,8 @@ public SpringDataWebConfiguration(ApplicationContext context,
8387
() -> context.getBeanProvider(PageableHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
8488
this.sortResolverCustomizer = Lazy.of( //
8589
() -> context.getBeanProvider(SortHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
90+
this.offsetResolverCustomizer = Lazy.of( //
91+
() -> context.getBeanProvider(OffsetScrollPositionHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
8692
}
8793

8894
@Override
@@ -107,6 +113,14 @@ public SortHandlerMethodArgumentResolver sortResolver() {
107113
return sortResolver;
108114
}
109115

116+
@Bean
117+
public OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver() {
118+
119+
OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver = new OffsetScrollPositionHandlerMethodArgumentResolver();
120+
customizeOffsetResolver(offsetResolver);
121+
return offsetResolver;
122+
}
123+
110124
@Override
111125
public void addFormatters(FormatterRegistry registry) {
112126

@@ -165,6 +179,10 @@ protected void customizeSortResolver(SortHandlerMethodArgumentResolver sortResol
165179
sortResolverCustomizer.getOptional().ifPresent(c -> c.customize(sortResolver));
166180
}
167181

182+
protected void customizeOffsetResolver(OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver) {
183+
offsetResolverCustomizer.getOptional().ifPresent(c -> c.customize(offsetResolver));
184+
}
185+
168186
private void forwardBeanClassLoader(BeanClassLoaderAware target) {
169187

170188
if (beanClassLoader != null) {

0 commit comments

Comments
 (0)