Many operations provided by the client libraries within the Azure Java SDK return more than one result. The Azure Java SDK uses standard return types in these cases to ensure developer experience is maximized. The return types used are PagedIterable
for sync APIs and PagedFlux
for async APIs. The APIs differ slightly on account of their different use cases, but conceptually they offer the same functionality:
-
By default, both
PagedIterable
andPagedFlux
enable the common case to be quickly and easily achieved: iterating over a paginated response deserialized into a given typeT
. In the case ofPagedIterable
, it implements theIterable
interface, and offers API to receive aStream
. In the case ofPagedFlux
, it is aFlux
. -
Both
PagedIterable
andPagedFlux
have methods that will return appropriate types to iterate by page, rather than by individual element. This allows for easy retrieval of response information.
The most common use case is to iterate over each element individually, rather than per page. The code samples below show how the PagedIterable
API allows for users to use the iteration style they prefer to implement this functionality.
Because PagedIterable
implements Iterable
, it is possible to iterate through the elements using code such as that shown below:
PagedIterable<Secret> secrets = client.listSecrets();
for (Secret secret : secrets) {
System.out.println("Secret is: " + secret);
}
client.listSecrets()
.stream()
.forEach(secret -> System.out.println("Secret is: " + secret));
Iterator<Secret> secrets = client.listSecrets().iterator();
while (it.hasNext()) {
System.out.println("Secret is: " + it.next);
}
When working with individual pages is required, for example for when HTTP response information is required, or when continuation tokens are important to retain iteration history, it is possible to iterate per page.
Iterable<PagedResponse<Secret>> secretPages = client.listSecrets().iterableByPage();
for (PagedResponse<Secret> page : secretPages) {
System.out.println("Secret page is: " + page);
}
client.listSecrets()
.streamByPage()
.forEach(page -> System.out.println("Secret page is: " + page));
When using PagedIterable, iterating by page will result in fetching the first page of results and in addition to the first page, an additional page is eagerly retrieved as the underlying publishers for the first page and the next page are concatenated. This is because the iterable is created by stringing together a series of Mono
publishers and when the first page is retrieved, the underlying flux needs to know whether to complete the flux or to wait for more data from the concatenated Mono
. PagedFlux
doesn't require this eager retrieval as the flux is directly handed to the user and nothing happens until user subscribes and depending on the subscription type, the flux will fetch only the necessary pages lazily.
Most Azure services that support pagination will provide a continuation token in the response and clients can directly use this continuation token to request for the next page. However, some services, like Cosmos, generate continuation token on the client-side using complex operations which can be quite expensive. To support such scenarios, Java has a Page
interface that can be implemented by custom paged responses to lazily generate the continuation token when requested. See below example:
public class LazyPagedResponse implements PagedResponse<String> {
private final List<String> items;
private final Supplier<String> lazyContinuationTokenSupplier;
private final int statusCode;
private final HttpHeaders headers;
private final HttpRequest httpRequest;
public LazyPagedResponse(List<String> items, Supplier<String> lazyContinuationTokenSupplier, int statusCode,
HttpHeaders headers, HttpRequest httpRequest) {
this.items = items;
this.lazyContinuationTokenSupplier = lazyContinuationTokenSupplier;
this.statusCode = statusCode;
this.headers = headers;
this.httpRequest = httpRequest;
}
@Override
public List<String> getItems() {
return this.items;
}
@Override
public String getContinuationToken() {
return this.lazyContinuationTokenSupplier.get();
}
@Override
public int getStatusCode() {
return this.statusCode;
}
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
@Override
public HttpRequest getRequest() {
return this.httpRequest;
}
@Override
public void close() throws IOException {
}
}
To test:
@Test
public void testLazyPagedResponse() {
Supplier<String> expensiveContinuationTokenGenerator = () -> {
try {
// expensive operation
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException ex) {
}
System.out.println("Generating new continuation token");
return UUID.randomUUID().toString();
};
LazyPagedResponse response = new LazyPagedResponse(Arrays.asList("foo", "bar"),
expensiveContinuationTokenGenerator, 200, null, null);
System.out.println(response.getItems());
System.out.println(response.getStatusCode());
// Continuation token is not generated until it's required
System.out.println(response.getContinuationToken());
}
TODO