-
Notifications
You must be signed in to change notification settings - Fork 77
Support cursor-based pagination (RFC 9865) #280
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
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -60,9 +60,11 @@ | |
| import java.io.InputStream; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Set; | ||
|
|
||
| import static com.unboundid.scim2.common.utils.ApiConstants.QUERY_PARAMETER_FILTER; | ||
| import static com.unboundid.scim2.common.utils.ApiConstants.QUERY_PARAMETER_PAGE_CURSOR; | ||
| import static com.unboundid.scim2.common.utils.ApiConstants.QUERY_PARAMETER_PAGE_SIZE; | ||
| import static com.unboundid.scim2.common.utils.ApiConstants.QUERY_PARAMETER_PAGE_START_INDEX; | ||
| import static com.unboundid.scim2.common.utils.ApiConstants.QUERY_PARAMETER_SORT_BY; | ||
|
|
@@ -90,6 +92,9 @@ public class SearchRequestBuilder | |
| @Nullable | ||
| private Integer startIndex; | ||
|
|
||
| @Nullable | ||
| private String cursor; | ||
|
|
||
| @Nullable | ||
| private Integer count; | ||
|
|
||
|
|
@@ -122,6 +127,8 @@ public SearchRequestBuilder filter(@Nullable final String filter) | |
| * | ||
| * @param filter the filter object used to request a subset of resources. | ||
| * @return This builder. | ||
| * | ||
| * @since 4.0.0 | ||
| */ | ||
| @NotNull | ||
| public SearchRequestBuilder filter(@Nullable final Filter filter) | ||
|
|
@@ -148,7 +155,11 @@ public SearchRequestBuilder sort(@Nullable final String sortBy, | |
| } | ||
|
|
||
| /** | ||
| * Request pagination of resources. | ||
| * Request pagination of resources with index-based pagination. | ||
| * <br><br> | ||
| * | ||
| * This type of pagination divides the result set into numeric page numbers. | ||
| * For example, to fetch the first page, use a value of {@code 1}. | ||
| * | ||
| * @param startIndex the 1-based index of the first query result. | ||
| * @param count the desired maximum number of query results per page. | ||
|
|
@@ -163,6 +174,63 @@ public SearchRequestBuilder page(final int startIndex, | |
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Request pagination of resources with cursor-based pagination. For more | ||
| * information on cursor-based pagination, see {@link ListResponse}. | ||
| * <br><br> | ||
| * | ||
| * For a cursor value of "VZUTiy", this will be translated to a request like: | ||
| * <pre> | ||
| * GET /Users?cursor=VZUTiy&count=10 | ||
| * </pre> | ||
| * | ||
| * To obtain the first page of results (i.e., when a cursor value is not | ||
| * known), use the {@link #firstPageCursorWithCount} method, or set | ||
| * {@code cursor} to an empty string. | ||
| * | ||
| * @param cursor The cursor that identifies a page. To request the first page | ||
| * of results, this may be an empty string. This value may not | ||
| * be {@code null}. | ||
| * @param count The desired maximum number of query results per page. | ||
| * @return This builder. | ||
| * | ||
| * @since 5.0.0 | ||
| */ | ||
| @NotNull | ||
| public SearchRequestBuilder pageWithCursor(@NotNull final String cursor, | ||
| final int count) | ||
| { | ||
| // For consistency with the page() method, this value cannot be null. | ||
| this.cursor = Objects.requireNonNull(cursor); | ||
| this.count = count; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Similar to {@link #pageWithCursor}, but requests the first page of | ||
| * resources with cursor-based pagination. The SCIM standard defines this as | ||
| * a request like: | ||
| * <pre> | ||
| * GET /Users?cursor&count=10 | ||
| * </pre> | ||
| * | ||
| * However, due to the way JAX-RS handles query parameters, this will be | ||
| * sent as a key-value pair with an empty value: | ||
| * <pre> | ||
| * GET /Users?cursor=&count=10 | ||
| * </pre> | ||
| * | ||
| * @param count The desired maximum number of query results per page. | ||
| * @return This builder. | ||
| * | ||
| * @since 5.0.0 | ||
| */ | ||
| @NotNull | ||
| public SearchRequestBuilder firstPageCursorWithCount(final int count) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I included "WithCount" because a call like |
||
| { | ||
| return pageWithCursor("", count); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
|
|
@@ -186,6 +254,27 @@ WebTarget buildTarget() | |
| target = target.queryParam(QUERY_PARAMETER_PAGE_START_INDEX, startIndex); | ||
| target = target.queryParam(QUERY_PARAMETER_PAGE_SIZE, count); | ||
| } | ||
| // Check the count again since it is possible to use index-based pagination, | ||
| // cursor-based pagination, or both. | ||
| if (cursor != null && count != null) | ||
| { | ||
| if (!cursor.isEmpty()) | ||
| { | ||
| // A specific page is being requested with a cursor. Provide a query | ||
| // like "?cursor=value". | ||
| target = target.queryParam(QUERY_PARAMETER_PAGE_CURSOR, cursor); | ||
| } | ||
| else | ||
| { | ||
| // The first page is being requested with cursor-based pagination. | ||
| // Ideally, this should just be "?cursor" as the standard describes. | ||
| // Unfortunately, JAX-RS does not appear to support query parameters | ||
| // without a value, so we provide "?cursor=" instead. | ||
| target = target.queryParam(QUERY_PARAMETER_PAGE_CURSOR, ""); | ||
| } | ||
| target = target.queryParam(QUERY_PARAMETER_PAGE_SIZE, count); | ||
| } | ||
|
|
||
| return target; | ||
| } | ||
|
|
||
|
|
@@ -202,7 +291,7 @@ public <T> ListResponse<T> invoke(@NotNull final Class<T> cls) | |
| throws ScimException | ||
| { | ||
| ListResponseBuilder<T> listResponseBuilder = new ListResponseBuilder<>(); | ||
| invoke(false, listResponseBuilder, cls); | ||
| invoke(listResponseBuilder, cls); | ||
| return listResponseBuilder.build(); | ||
| } | ||
|
|
||
|
|
@@ -236,7 +325,7 @@ public <T extends ScimResource> ListResponse<T> invokePost( | |
| throws ScimException | ||
| { | ||
| ListResponseBuilder<T> listResponseBuilder = new ListResponseBuilder<>(); | ||
| invoke(true, listResponseBuilder, cls); | ||
| invokePost(listResponseBuilder, cls); | ||
| return listResponseBuilder.build(); | ||
| } | ||
|
|
||
|
|
@@ -303,6 +392,14 @@ private <T> void invoke(final boolean post, | |
| case "startindex": | ||
| resultHandler.startIndex(parser.getIntValue()); | ||
| break; | ||
| case "previouscursor": | ||
| // The "previousCursor" value as defined by RFC 9865. | ||
| resultHandler.previousCursor(parser.getValueAsString()); | ||
| break; | ||
| case "nextcursor": | ||
| // The "nextCursor" value as defined by RFC 9865. | ||
| resultHandler.nextCursor(parser.getValueAsString()); | ||
| break; | ||
| case "itemsperpage": | ||
| resultHandler.itemsPerPage(parser.getIntValue()); | ||
| break; | ||
|
|
@@ -359,8 +456,8 @@ private Response sendPostSearch() | |
| } | ||
| } | ||
|
|
||
| SearchRequest searchRequest = new SearchRequest(attributeSet, | ||
| excludedAttributeSet, filter, sortBy, sortOrder, startIndex, count); | ||
| var searchRequest = new SearchRequest(attributeSet, excludedAttributeSet, | ||
| filter, sortBy, sortOrder, startIndex, cursor, count); | ||
|
|
||
| Invocation.Builder builder = target(). | ||
| path(ApiConstants.SEARCH_WITH_POST_PATH_EXTENSION). | ||
|
|
||
This file contains hidden or 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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of the update to this interface, this technically requires a major version bump if any client code is implementing this handler. Fortunately, the next release is already going to be 5.0.0.