Skip to content

Introduce argument resolver for OffsetScrollPosition #2861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand All @@ -46,14 +47,16 @@

/**
* Configuration class to register {@link PageableHandlerMethodArgumentResolver},
* {@link SortHandlerMethodArgumentResolver} and {@link DomainClassConverter}.
* {@link SortHandlerMethodArgumentResolver}, {@link OffsetScrollPositionHandlerMethodArgumentResolver}
* and {@link DomainClassConverter}.
*
* @since 1.6
* @author Oliver Gierke
* @author Vedran Pavic
* @author Jens Schauder
* @author Mark Paluch
* @author Greg Turnquist
* @author Yanming Zhou
*/
@Configuration(proxyBeanMethods = false)
public class SpringDataWebConfiguration implements WebMvcConfigurer, BeanClassLoaderAware {
Expand All @@ -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) {
Expand All @@ -83,6 +87,8 @@ public SpringDataWebConfiguration(ApplicationContext context,
() -> context.getBeanProvider(PageableHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
this.sortResolverCustomizer = Lazy.of( //
() -> context.getBeanProvider(SortHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
this.offsetResolverCustomizer = Lazy.of( //
() -> context.getBeanProvider(OffsetScrollPositionHandlerMethodArgumentResolverCustomizer.class).getIfAvailable());
}

@Override
Expand All @@ -107,6 +113,14 @@ public SortHandlerMethodArgumentResolver sortResolver() {
return sortResolver;
}

@Bean
public OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver() {

OffsetScrollPositionHandlerMethodArgumentResolver offsetResolver = new OffsetScrollPositionHandlerMethodArgumentResolver();
customizeOffsetResolver(offsetResolver);
return offsetResolver;
}

@Override
public void addFormatters(FormatterRegistry registry) {

Expand Down Expand Up @@ -165,6 +179,10 @@ protected void customizeSortResolver(SortHandlerMethodArgumentResolver sortResol
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) {
Expand Down
Loading