-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #275 from xenit-eu/ACC-1597
Support cursor-based pagination
- Loading branch information
Showing
27 changed files
with
1,527 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
...entgrid/spring/boot/autoconfigure/data/pagination/WebPaginationAutoConfigurationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.contentgrid.spring.boot.autoconfigure.data.pagination; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import com.contentgrid.spring.boot.autoconfigure.data.web.ContentGridSpringDataRestAutoConfiguration; | ||
import com.contentgrid.spring.data.pagination.cursor.CursorCodec; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockito.Mockito; | ||
import org.springframework.boot.autoconfigure.AutoConfigurations; | ||
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; | ||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; | ||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration; | ||
|
||
class WebPaginationAutoConfigurationTest { | ||
|
||
private static final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() | ||
.withConfiguration(AutoConfigurations.of( | ||
ContentGridSpringDataRestAutoConfiguration.class, | ||
WebPaginationAutoConfiguration.class, | ||
RepositoryRestMvcAutoConfiguration.class | ||
)); | ||
|
||
@Test | ||
void pagination_configuration_default_pagination() { | ||
contextRunner.run(context -> { | ||
assertThat(context).hasNotFailed(); | ||
|
||
assertThat(context).hasSingleBean(CursorCodec.class); | ||
assertThat(context.getBean(RepositoryRestConfiguration.class).getPageParamName()).isEqualTo("page"); | ||
}); | ||
|
||
contextRunner | ||
.withPropertyValues("spring.data.rest.page-param-name=my_page") | ||
.run(context -> { | ||
assertThat(context).hasNotFailed(); | ||
|
||
assertThat(context).hasSingleBean(CursorCodec.class); | ||
assertThat(context.getBean(RepositoryRestConfiguration.class).getPageParamName()).isEqualTo( | ||
"my_page"); | ||
}); | ||
} | ||
|
||
@Test | ||
void pagination_configuration_cursor_pagination() { | ||
contextRunner | ||
.withPropertyValues("contentgrid.rest.pagination=page_cursor") | ||
.run(context -> { | ||
assertThat(context).hasNotFailed(); | ||
|
||
assertThat(context).hasSingleBean(CursorCodec.class); | ||
assertThat(context.getBean(RepositoryRestConfiguration.class).getPageParamName()).isEqualTo( | ||
"_cursor"); | ||
}); | ||
|
||
contextRunner | ||
.withPropertyValues("contentgrid.rest.pagination=page_cursor") | ||
.withPropertyValues("spring.data.rest.page-param-name=my_page") | ||
.run(context -> { | ||
assertThat(context).hasNotFailed(); | ||
|
||
assertThat(context).hasSingleBean(CursorCodec.class); | ||
assertThat(context.getBean(RepositoryRestConfiguration.class).getPageParamName()).isEqualTo( | ||
"my_page"); | ||
}); | ||
} | ||
|
||
@Test | ||
void pagination_configuration_custom_codec() { | ||
contextRunner | ||
.withBean(CursorCodec.class, () -> Mockito.mock(CursorCodec.class)) | ||
.withPropertyValues("contentgrid.rest.pagination=page_cursor") | ||
.run(context -> { | ||
assertThat(context).hasNotFailed(); | ||
assertThat(context).hasSingleBean(CursorCodec.class); | ||
assertThat(context.getBean(RepositoryRestConfiguration.class).getPageParamName()).isEqualTo("page"); | ||
}); | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...nation/src/main/java/com/contentgrid/spring/data/pagination/InvalidPageSizeException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.contentgrid.spring.data.pagination; | ||
|
||
public class InvalidPageSizeException extends InvalidPaginationException { | ||
|
||
public InvalidPageSizeException(String parameter, String invalidValue, String message) { | ||
super(parameter, invalidValue, message); | ||
} | ||
|
||
public InvalidPageSizeException(String parameter, String invalidValue, Throwable cause) { | ||
this(parameter, invalidValue, cause.getMessage()); | ||
initCause(cause); | ||
} | ||
|
||
public static InvalidPageSizeException mustBePositive(String parameter, String invalidValue) { | ||
return new InvalidPageSizeException(parameter, invalidValue, "must be positive"); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...tion/src/main/java/com/contentgrid/spring/data/pagination/InvalidPaginationException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.contentgrid.spring.data.pagination; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class InvalidPaginationException extends RuntimeException { | ||
|
||
private final String parameter; | ||
|
||
private final String invalidValue; | ||
|
||
public InvalidPaginationException(String parameter, String invalidValue, String message) { | ||
super("Invalid parameter '%s': %s".formatted(parameter, message)); | ||
this.parameter = parameter; | ||
this.invalidValue = invalidValue; | ||
} | ||
|
||
public InvalidPaginationException(String parameter, String invalidValue, Throwable cause) { | ||
this(parameter, invalidValue, cause.getMessage()); | ||
initCause(cause); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
...rid/spring/data/pagination/cursor/ContentGridSpringDataPaginationCursorConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.contentgrid.spring.data.pagination.cursor; | ||
|
||
import org.springframework.beans.BeansException; | ||
import org.springframework.beans.factory.ObjectProvider; | ||
import org.springframework.beans.factory.config.BeanPostProcessor; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.Lazy; | ||
import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver; | ||
import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver; | ||
import org.springframework.data.web.config.PageableHandlerMethodArgumentResolverCustomizer; | ||
|
||
@Configuration(proxyBeanMethods = false) | ||
public class ContentGridSpringDataPaginationCursorConfiguration { | ||
|
||
@Bean | ||
static BeanPostProcessor replaceHateoasPageableHandlerMethodArgumentResolverBeanPostProcessor( | ||
@Lazy HateoasSortHandlerMethodArgumentResolver sortHandlerMethodArgumentResolver, | ||
@Lazy CursorCodec cursorCodec, | ||
ObjectProvider<PageableHandlerMethodArgumentResolverCustomizer> resolverCustomizers | ||
) { | ||
return new BeanPostProcessor() { | ||
@Override | ||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||
if (bean instanceof HateoasPageableHandlerMethodArgumentResolver) { | ||
return new HateoasPageableCursorHandlerMethodArgumentResolver( | ||
sortHandlerMethodArgumentResolver, | ||
cursorCodec, | ||
resolverCustomizers | ||
); | ||
} | ||
return bean; | ||
} | ||
}; | ||
} | ||
|
||
} |
70 changes: 70 additions & 0 deletions
70
...a-pagination/src/main/java/com/contentgrid/spring/data/pagination/cursor/CursorCodec.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.contentgrid.spring.data.pagination.cursor; | ||
|
||
import java.util.function.UnaryOperator; | ||
import lombok.Builder; | ||
import lombok.NonNull; | ||
import lombok.experimental.StandardException; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.domain.Sort; | ||
import org.springframework.lang.Nullable; | ||
import org.springframework.web.util.UriComponents; | ||
|
||
/** | ||
* Encoder and decoder for pagination query parameters | ||
*/ | ||
public interface CursorCodec { | ||
|
||
/** | ||
* Decodes a cursor to a spring pageable | ||
* | ||
* @param context The cursor to decode | ||
* @param uriComponents The rest of the URI, without cursor, page size or sort parameters | ||
* @return Spring pageable, decoded from the cursor | ||
* @throws CursorDecodeException When a cursor can not be decoded | ||
*/ | ||
Pageable decodeCursor(CursorContext context, UriComponents uriComponents) throws CursorDecodeException; | ||
|
||
/** | ||
* Encodes a spring pageable to a cursor | ||
* | ||
* @param pageable The spring pageable | ||
* @param uriComponents The rest of the URI, without cursor, page size or sort parameters | ||
* @return The cursor that can be used in a request | ||
*/ | ||
CursorContext encodeCursor(Pageable pageable, UriComponents uriComponents); | ||
|
||
/** | ||
* The cursor with its context. | ||
* <p> | ||
* This object represents the pagination information as encoded in a request | ||
* | ||
* @param cursor The cursor (can be null if no cursor is present in the request) | ||
* @param pageSize The size of a page | ||
* @param sort Sorting of the resultset | ||
*/ | ||
@Builder | ||
record CursorContext( | ||
@Nullable | ||
String cursor, | ||
int pageSize, | ||
@NonNull | ||
Sort sort | ||
) { | ||
|
||
public CursorContext mapCursor(@NonNull UnaryOperator<@NonNull String> cursorMapper) { | ||
if (cursor == null) { | ||
return this; | ||
} | ||
return new CursorContext(cursorMapper.apply(cursor), pageSize, sort); | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Thrown when a cursor can not be decoded for any reason | ||
*/ | ||
@StandardException | ||
class CursorDecodeException extends Exception { | ||
|
||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
...pagination/src/main/java/com/contentgrid/spring/data/pagination/cursor/CursorEncoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.contentgrid.spring.data.pagination.cursor; | ||
|
||
import org.springframework.data.domain.Pageable; | ||
|
||
public interface CursorEncoder { | ||
String encodeCursor(Pageable pageable, String referenceUrl); | ||
} |
Oops, something went wrong.