Rename PaginationRequest and ScrollRequest

...to Subrange and ScrollSubrange

See gh-620
This commit is contained in:
rstoyanchev
2023-03-19 14:41:37 +00:00
parent d017506d43
commit a0af83de24
8 changed files with 112 additions and 117 deletions

View File

@@ -94,8 +94,19 @@ import org.springframework.validation.DataBinder;
* @author Brian Clozel
* @since 1.0.0
*/
public class AnnotatedControllerConfigurer
implements ApplicationContextAware, InitializingBean, RuntimeWiringConfigurer {
public class AnnotatedControllerConfigurer implements ApplicationContextAware, InitializingBean, RuntimeWiringConfigurer {
private static final ClassLoader classLoader = AnnotatedControllerConfigurer.class.getClassLoader();
private final static boolean springDataPresent = ClassUtils.isPresent(
"org.springframework.data.projection.SpelAwareProxyProjectionFactory", classLoader);
private final static boolean springSecurityPresent = ClassUtils.isPresent(
"org.springframework.security.core.context.SecurityContext", classLoader);
private final static boolean beanValidationPresent = ClassUtils.isPresent(
"jakarta.validation.executable.ExecutableValidator", classLoader);
private final static Log logger = LogFactory.getLog(AnnotatedControllerConfigurer.class);
@@ -111,18 +122,6 @@ public class AnnotatedControllerConfigurer
*/
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
private final static boolean springDataPresent = ClassUtils.isPresent(
"org.springframework.data.projection.SpelAwareProxyProjectionFactory",
AnnotatedControllerConfigurer.class.getClassLoader());
private final static boolean springSecurityPresent = ClassUtils.isPresent(
"org.springframework.security.core.context.SecurityContext",
AnnotatedControllerConfigurer.class.getClassLoader());
private final static boolean beanValidationPresent = ClassUtils.isPresent(
"jakarta.validation.executable.ExecutableValidator",
AnnotatedControllerConfigurer.class.getClassLoader());
private final FormattingConversionService conversionService = new DefaultFormattingConversionService();
@@ -165,10 +164,9 @@ public class AnnotatedControllerConfigurer
* results in one of the following:
* <ul>
* <li>If Spring Data is present, and the strategy supports {@code ScrollPosition},
* then {@link ScrollRequestMethodArgumentResolver} is
* configured as a method argument resolver.
* <li>Otherwise {@link PaginationRequestMethodArgumentResolver} is added
* instead.
* then {@link ScrollSubrangeMethodArgumentResolver} is configured as a method
* argument resolver.
* <li>Otherwise {@link SubrangeMethodArgumentResolver} is added.
* </ul>
* @since 1.2
*/
@@ -286,7 +284,7 @@ public class AnnotatedControllerConfigurer
resolvers.addResolver(new DataFetchingEnvironmentMethodArgumentResolver());
resolvers.addResolver(new DataLoaderMethodArgumentResolver());
if (this.cursorStrategy != null) {
resolvers.addResolver(initPaginationResolver(this.cursorStrategy));
resolvers.addResolver(createSubrangeMethodArgumentResolver(this.cursorStrategy));
}
if (this.sortStrategy != null) {
resolvers.addResolver(new SortMethodArgumentResolver(this.sortStrategy));
@@ -309,14 +307,14 @@ public class AnnotatedControllerConfigurer
}
@SuppressWarnings("unchecked")
private HandlerMethodArgumentResolver initPaginationResolver(CursorStrategy<?> cursorStrategy) {
private static HandlerMethodArgumentResolver createSubrangeMethodArgumentResolver(CursorStrategy<?> strategy) {
if (springDataPresent) {
if (cursorStrategy.supports(org.springframework.data.domain.ScrollPosition.class)) {
return new ScrollRequestMethodArgumentResolver(
(CursorStrategy<org.springframework.data.domain.ScrollPosition>) cursorStrategy);
if (strategy.supports(org.springframework.data.domain.ScrollPosition.class)) {
return new ScrollSubrangeMethodArgumentResolver(
(CursorStrategy<org.springframework.data.domain.ScrollPosition>) strategy);
}
}
return new PaginationRequestMethodArgumentResolver<>(cursorStrategy);
return new SubrangeMethodArgumentResolver<>(strategy);
}
protected final ApplicationContext obtainApplicationContext() {

View File

@@ -20,33 +20,32 @@ package org.springframework.graphql.data.method.annotation.support;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.graphql.data.pagination.CursorStrategy;
import org.springframework.graphql.data.query.ScrollRequest;
import org.springframework.graphql.data.query.ScrollSubrange;
import org.springframework.lang.Nullable;
/**
* Subclass of {@link PaginationRequestMethodArgumentResolver} that supports
* {@link ScrollRequest} with cursors converted to {@link ScrollPosition} for
* forward or backward pagination.
* A {@link SubrangeMethodArgumentResolver} that supports {@link ScrollSubrange}
* and {@link ScrollPosition} as cursor.
*
* @author Rossen Stoyanchev
* @since 1.2
*/
public class ScrollRequestMethodArgumentResolver extends PaginationRequestMethodArgumentResolver<ScrollPosition> {
public class ScrollSubrangeMethodArgumentResolver extends SubrangeMethodArgumentResolver<ScrollPosition> {
public ScrollRequestMethodArgumentResolver(CursorStrategy<ScrollPosition> cursorStrategy) {
super(cursorStrategy);
public ScrollSubrangeMethodArgumentResolver(CursorStrategy<ScrollPosition> strategy) {
super(strategy);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(ScrollRequest.class);
return parameter.getParameterType().equals(ScrollSubrange.class);
}
protected ScrollRequest createRequest(@Nullable ScrollPosition position, @Nullable Integer size, boolean forward) {
return new ScrollRequest(position, size, forward);
protected ScrollSubrange createSubrange(@Nullable ScrollPosition pos, @Nullable Integer size, boolean forward) {
return new ScrollSubrange(pos, size, forward);
}
}

View File

@@ -22,23 +22,23 @@ import graphql.schema.DataFetchingEnvironment;
import org.springframework.core.MethodParameter;
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
import org.springframework.graphql.data.pagination.CursorStrategy;
import org.springframework.graphql.data.pagination.PaginationRequest;
import org.springframework.graphql.data.pagination.Subrange;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Resolver for a method argument of type {@link PaginationRequest} initialized
* Resolver for a method argument of type {@link Subrange} initialized
* from "first", "last", "before", and "after" GraphQL arguments.
*
* @author Rossen Stoyanchev
* @since 1.2
*/
public class PaginationRequestMethodArgumentResolver<P> implements HandlerMethodArgumentResolver {
public class SubrangeMethodArgumentResolver<P> implements HandlerMethodArgumentResolver {
private final CursorStrategy<P> cursorStrategy;
public PaginationRequestMethodArgumentResolver(CursorStrategy<P> cursorStrategy) {
public SubrangeMethodArgumentResolver(CursorStrategy<P> cursorStrategy) {
Assert.notNull(cursorStrategy, "CursorStrategy is required");
this.cursorStrategy = cursorStrategy;
}
@@ -46,7 +46,7 @@ public class PaginationRequestMethodArgumentResolver<P> implements HandlerMethod
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.getParameterType().equals(PaginationRequest.class) &&
return (parameter.getParameterType().equals(Subrange.class) &&
this.cursorStrategy.supports(parameter.nested().getNestedParameterType()));
}
@@ -56,14 +56,14 @@ public class PaginationRequestMethodArgumentResolver<P> implements HandlerMethod
Integer count = environment.getArgument(forward ? "first" : "last");
String cursor = environment.getArgument(forward ? "after" : "before");
P position = (cursor != null ? this.cursorStrategy.fromCursor(cursor) : null);
return createRequest(position, count, forward);
return createSubrange(position, count, forward);
}
/**
* Create the {@code PaginationRequest} instance.
* Allows subclasses to create an extension of {@link Subrange}.
*/
protected PaginationRequest<P> createRequest(@Nullable P position, @Nullable Integer size, boolean forward) {
return new PaginationRequest<>(position, size, forward);
protected Subrange<P> createSubrange(@Nullable P pos, @Nullable Integer size, boolean forward) {
return new Subrange<>(pos, size, forward);
}
}

View File

@@ -22,12 +22,13 @@ import java.util.Optional;
import org.springframework.lang.Nullable;
/**
* Container for a pagination request.
* Container for parameters that limit result elements to a subrange including a
* relative position, number of elements, and direction.
*
* @author Rossen Stoyanchev
* @since 1.2
*/
public class PaginationRequest<P> {
public class Subrange<P> {
@Nullable
private final P position;
@@ -39,9 +40,9 @@ public class PaginationRequest<P> {
/**
* Constructor with the position, count, and direction.
* Constructor with the relative position, count, and direction.
*/
public PaginationRequest(@Nullable P position, @Nullable Integer count, boolean forward) {
public Subrange(@Nullable P position, @Nullable Integer count, boolean forward) {
this.position = position;
this.forward = forward;
this.count = count;
@@ -49,16 +50,16 @@ public class PaginationRequest<P> {
/**
* The position of an element relative to which to paginate, decoded from a
* String cursor, e.g. the "before" and "after" arguments from the GraphQL
* Cursor connection spec.
* The position of the result element the subrange is relative to. This is
* decoded from the "before" or "after" input arguments from the GraphQL
* Cursor connection spec via {@link CursorStrategy}.
*/
public Optional<P> position() {
return Optional.ofNullable(this.position);
}
/**
* The number of elements requested, e.g. "first" and "last" N elements
* The number of elements in the subrange based on the "first" and "last"
* arguments from the GraphQL Cursor connection spec.
*/
public Optional<Integer> count() {
@@ -66,12 +67,11 @@ public class PaginationRequest<P> {
}
/**
* Whether forward or backward pagination is requested, e.g. depending on
* whether "fist" or "last" N elements was sent.
* <p><strong>Note:</strong> This value may not reflect the one originally
* sent by the client. For example, for backward pagination, an offset cursor
* may be adjusted down by the number of requested elements, turning into
* forward pagination.
* Whether the subrange is forward or backward from ths position, depending
* on whether the argument sent "fist" or "last".
* <p><strong>Note:</strong> The direction may not always match the original
* value. For backward pagination, for example, an offset cursor could be
* adjusted down by the count of elements, switching backward to forward.
*/
public boolean forward() {
return this.forward;

View File

@@ -23,43 +23,41 @@ import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.KeysetScrollPosition.Direction;
import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.graphql.data.pagination.PaginationRequest;
import org.springframework.graphql.data.pagination.Subrange;
import org.springframework.lang.Nullable;
/**
* Container for pagination request with a {@link ScrollPosition} cursor.
* Container for parameters that limit result elements to a subrange including a
* relative {@link ScrollPosition}, number of elements, and direction.
*
* <p>An {@link OffsetScrollPosition} is always used for forward pagination.
* When backward pagination is requested, the offset is adjusted down by the
* requested count, thus turning it into forward pagination.
* <p> For backward pagination, the offset of an {@link OffsetScrollPosition}
* is adjusted to point to the first item in the range by subtracting the count
* from it. Hence, for {@code OffsetScrollPosition} {@link #forward()} is
* always {@code true}.
*
* @author Rossen Stoyanchev
* @since 1.2
*/
public final class ScrollRequest extends PaginationRequest<ScrollPosition> {
public final class ScrollSubrange extends Subrange<ScrollPosition> {
public ScrollRequest(@Nullable ScrollPosition position, @Nullable Integer count, boolean forward) {
super(initPosition(position, count, forward), count,
(position instanceof OffsetScrollPosition || forward));
public ScrollSubrange(@Nullable ScrollPosition pos, @Nullable Integer count, boolean forward) {
super(initPosition(pos, count, forward), count, (pos instanceof OffsetScrollPosition || forward));
}
@Nullable
private static ScrollPosition initPosition(
@Nullable ScrollPosition position, @Nullable Integer count, boolean forward) {
private static ScrollPosition initPosition(@Nullable ScrollPosition pos, @Nullable Integer count, boolean forward) {
if (!forward) {
if (position instanceof OffsetScrollPosition offsetPosition && count != null) {
if (pos instanceof OffsetScrollPosition offsetPosition && count != null) {
long offset = offsetPosition.getOffset();
return OffsetScrollPosition.of(offset > count ? offset - count : 0);
}
else if (position instanceof KeysetScrollPosition keysetPosition) {
else if (pos instanceof KeysetScrollPosition keysetPosition) {
Map<String, Object> keys = keysetPosition.getKeys();
position = KeysetScrollPosition.of(keys, Direction.Backward);
pos = KeysetScrollPosition.of(keys, Direction.Backward);
}
}
return position;
return pos;
}
}

View File

@@ -33,7 +33,7 @@ import org.springframework.graphql.TestExecutionRequest;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.pagination.ConnectionFieldTypeVisitor;
import org.springframework.graphql.data.query.ScrollPositionCursorStrategy;
import org.springframework.graphql.data.query.ScrollRequest;
import org.springframework.graphql.data.query.ScrollSubrange;
import org.springframework.graphql.data.query.WindowConnectionAdapter;
import org.springframework.graphql.execution.ConnectionTypeGenerator;
import org.springframework.stereotype.Controller;
@@ -120,7 +120,7 @@ public class SchemaMappingPaginationTests {
AnnotatedControllerConfigurer configurer = new AnnotatedControllerConfigurer();
configurer.setApplicationContext(context);
GraphQlSetup setup = GraphQlSetup.schemaContent(this.SCHEMA).runtimeWiring(configurer);
GraphQlSetup setup = GraphQlSetup.schemaContent(SCHEMA).runtimeWiring(configurer);
consumer.accept(configurer, setup);
configurer.afterPropertiesSet();
@@ -134,9 +134,9 @@ public class SchemaMappingPaginationTests {
private static class BookController {
@QueryMapping
public Window<Book> books(ScrollRequest request) {
int offset = (int) ((OffsetScrollPosition) request.position().get()).getOffset();
int count = request.count().get();
public Window<Book> books(ScrollSubrange subrange) {
int offset = (int) ((OffsetScrollPosition) subrange.position().orElse(OffsetScrollPosition.initial())).getOffset();
int count = subrange.count().orElse(5);
List<Book> books = BookSource.books().subList(offset, offset + count);
return Window.from(books, OffsetScrollPosition::of);
}

View File

@@ -27,29 +27,29 @@ import org.springframework.data.domain.Window;
import org.springframework.graphql.Book;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.pagination.CursorStrategy;
import org.springframework.graphql.data.pagination.PaginationRequest;
import org.springframework.graphql.data.pagination.Subrange;
import org.springframework.stereotype.Controller;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link PaginationRequestMethodArgumentResolver}.
* Unit tests for {@link SubrangeMethodArgumentResolver}.
* @author Rossen Stoyanchev
*/
public class PaginationRequestMethodArgumentResolverTests extends ArgumentResolverTestSupport {
public class SubrangeMethodArgumentResolverTests extends ArgumentResolverTestSupport {
private final PaginationRequestMethodArgumentResolver<MyPosition> resolver =
new PaginationRequestMethodArgumentResolver<>(new MyPositionCursorStrategy());
private final SubrangeMethodArgumentResolver<MyPosition> resolver =
new SubrangeMethodArgumentResolver<>(new MyPositionCursorStrategy());
private final MethodParameter param =
methodParam(BookController.class, "getBooks", PaginationRequest.class);
methodParam(BookController.class, "getBooks", Subrange.class);
@Test
void supports() {
assertThat(this.resolver.supportsParameter(this.param)).isTrue();
MethodParameter param = methodParam(BookController.class, "getBooksWithUnknownPosition", PaginationRequest.class);
MethodParameter param = methodParam(BookController.class, "getBooksWithUnknownPosition", Subrange.class);
assertThat(this.resolver.supportsParameter(param)).isFalse();
}
@@ -74,10 +74,10 @@ public class PaginationRequestMethodArgumentResolverTests extends ArgumentResolv
}
private static void testRequest(int count, int index, Object result, boolean forward) {
PaginationRequest<MyPosition> request = (PaginationRequest<MyPosition>) result;
assertThat(request.position().get().index()).isEqualTo(index);
assertThat(request.count().get()).isEqualTo(count);
assertThat(request.forward()).isEqualTo(forward);
Subrange<MyPosition> subrange = (Subrange<MyPosition>) result;
assertThat(subrange.position().get().index()).isEqualTo(index);
assertThat(subrange.count().get()).isEqualTo(count);
assertThat(subrange.forward()).isEqualTo(forward);
}
private static DataFetchingEnvironment environment(Map<String, Object> arguments) {
@@ -90,12 +90,12 @@ public class PaginationRequestMethodArgumentResolverTests extends ArgumentResolv
private static class BookController {
@QueryMapping
public Window<Book> getBooks(PaginationRequest<MyPosition> request) {
public Window<Book> getBooks(Subrange<MyPosition> subrange) {
return null;
}
@QueryMapping
public Window<Book> getBooksWithUnknownPosition(PaginationRequest<UnknownPosition> request) {
public Window<Book> getBooksWithUnknownPosition(Subrange<UnknownPosition> subrange) {
return null;
}

View File

@@ -33,22 +33,22 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Rossen Stoyanchev
*/
public class ScrollRequestTests {
public class ScrollSubrangeTests {
@Test
void offset() {
OffsetScrollPosition position = OffsetScrollPosition.of(30);
int count = 10;
ScrollRequest request = new ScrollRequest(position, count, true);
assertThat(((OffsetScrollPosition) request.position().get())).isEqualTo(position);
assertThat(request.count().get()).isEqualTo(count);
assertThat(request.forward()).isTrue();
ScrollSubrange subrange = new ScrollSubrange(position, count, true);
assertThat(((OffsetScrollPosition) subrange.position().get())).isEqualTo(position);
assertThat(subrange.count().get()).isEqualTo(count);
assertThat(subrange.forward()).isTrue();
request = new ScrollRequest(position, count, false);
assertThat(((OffsetScrollPosition) request.position().get()).getOffset()).isEqualTo(20);
assertThat(request.count().get()).isEqualTo(count);
assertThat(request.forward()).isTrue();
subrange = new ScrollSubrange(position, count, false);
assertThat(((OffsetScrollPosition) subrange.position().get()).getOffset()).isEqualTo(20);
assertThat(subrange.count().get()).isEqualTo(count);
assertThat(subrange.forward()).isTrue();
}
@Test
@@ -61,38 +61,38 @@ public class ScrollRequestTests {
ScrollPosition position = KeysetScrollPosition.of(keys);
int count = 10;
ScrollRequest request = new ScrollRequest(position, count, true);
KeysetScrollPosition actualPosition = (KeysetScrollPosition) request.position().get();
ScrollSubrange subrange = new ScrollSubrange(position, count, true);
KeysetScrollPosition actualPosition = (KeysetScrollPosition) subrange.position().get();
assertThat(actualPosition.getKeys()).isEqualTo(keys);
assertThat(actualPosition.getDirection()).isEqualTo(Direction.Forward);
assertThat(request.count().get()).isEqualTo(count);
assertThat(request.forward()).isTrue();
assertThat(subrange.count().get()).isEqualTo(count);
assertThat(subrange.forward()).isTrue();
request = new ScrollRequest(position, count, false);
actualPosition = (KeysetScrollPosition) request.position().get();
subrange = new ScrollSubrange(position, count, false);
actualPosition = (KeysetScrollPosition) subrange.position().get();
assertThat(actualPosition.getKeys()).isEqualTo(keys);
assertThat(actualPosition.getDirection()).isEqualTo(Direction.Backward);
assertThat(request.count().get()).isEqualTo(count);
assertThat(request.forward()).isFalse();
assertThat(subrange.count().get()).isEqualTo(count);
assertThat(subrange.forward()).isFalse();
}
@Test
void nullInput() {
ScrollRequest request = new ScrollRequest(null, null, true);
ScrollSubrange subrange = new ScrollSubrange(null, null, true);
assertThat(request.position()).isNotPresent();
assertThat(request.count()).isNotPresent();
assertThat(request.forward()).isTrue();
assertThat(subrange.position()).isNotPresent();
assertThat(subrange.count()).isNotPresent();
assertThat(subrange.forward()).isTrue();
}
@Test
void offsetBackwardPaginationNullSize() {
OffsetScrollPosition position = OffsetScrollPosition.of(30);
ScrollRequest request = new ScrollRequest(position, null, false);
ScrollSubrange subrange = new ScrollSubrange(position, null, false);
assertThat(((OffsetScrollPosition) request.position().get())).isEqualTo(position);
assertThat(request.count()).isNotPresent();
assertThat(request.forward()).isTrue();
assertThat(((OffsetScrollPosition) subrange.position().get())).isEqualTo(position);
assertThat(subrange.count()).isNotPresent();
assertThat(subrange.forward()).isTrue();
}
}