diff --git a/gcloud-java-core/src/main/java/com/google/cloud/AsyncPage.java b/gcloud-java-core/src/main/java/com/google/cloud/AsyncPage.java
index ea2905795e62..d82d46f19b2d 100644
--- a/gcloud-java-core/src/main/java/com/google/cloud/AsyncPage.java
+++ b/gcloud-java-core/src/main/java/com/google/cloud/AsyncPage.java
@@ -18,6 +18,34 @@
import java.util.concurrent.Future;
+/**
+ * Interface for asynchronously consuming Google Cloud paginated results.
+ *
+ *
Use {@code AsyncPage} to iterate through all values (also in next pages):
+ *
{@code
+ * AsyncPage page = ...; // get an AsyncPage instance
+ * Iterator iterator = page.iterateAll();
+ * while (iterator.hasNext()) {
+ * T value = iterator.next();
+ * // do something with value
+ * }}
+ *
+ * Or handle pagination explicitly:
+ *
{@code
+ * AsyncPage page = ...; // get a AsyncPage instance
+ * while (page != null) {
+ * for (T value : page.values()) {
+ * // do something with value
+ * }
+ * page = page.nextPage().get();
+ * }}
+ *
+ * @param the value type that the page holds
+ */
public interface AsyncPage extends Page {
+
+ /**
+ * Returns a {@link Future} object for the next page.
+ */
Future> nextPageAsync();
}
diff --git a/gcloud-java-core/src/main/java/com/google/cloud/AsyncPageImpl.java b/gcloud-java-core/src/main/java/com/google/cloud/AsyncPageImpl.java
new file mode 100644
index 000000000000..42af7b34a943
--- /dev/null
+++ b/gcloud-java-core/src/main/java/com/google/cloud/AsyncPageImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * 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
+ *
+ * http://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 com.google.cloud;
+
+import com.google.common.base.Throwables;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import java.io.Serializable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * Base implementation for asynchronously consuming Google Cloud paginated results.
+ *
+ * @param the value type that the page holds
+ */
+public class AsyncPageImpl extends PageImpl implements AsyncPage {
+
+ private static final long serialVersionUID = -6009473188630364906L;
+
+ private final NextPageFetcher asyncPageFetcher;
+
+ /**
+ * Interface for asynchronously fetching the next page of results from the service.
+ *
+ * @param the value type that the page holds
+ */
+ public interface NextPageFetcher extends Serializable {
+ Future> nextPage();
+ }
+
+ private static class SyncNextPageFetcher implements PageImpl.NextPageFetcher {
+
+ private static final long serialVersionUID = -4124568632363525351L;
+
+ private NextPageFetcher asyncPageFetcher;
+
+ private SyncNextPageFetcher(NextPageFetcher asyncPageFetcher) {
+ this.asyncPageFetcher = asyncPageFetcher;
+ }
+
+ @Override
+ public Page nextPage() {
+ try {
+ return asyncPageFetcher != null
+ ? Uninterruptibles.getUninterruptibly(asyncPageFetcher.nextPage()) : null;
+ } catch (ExecutionException ex) {
+ throw Throwables.propagate(ex.getCause());
+ }
+ }
+ }
+
+ /**
+ * Creates an {@code AsyncPageImpl} object.
+ */
+ public AsyncPageImpl(NextPageFetcher asyncPageFetcher, String cursor, Iterable results) {
+ super(new SyncNextPageFetcher(asyncPageFetcher), cursor, results);
+ this.asyncPageFetcher = asyncPageFetcher;
+ }
+
+ @Override
+ public Future> nextPageAsync() {
+ if (nextPageCursor() == null || asyncPageFetcher == null) {
+ return null;
+ }
+ return asyncPageFetcher.nextPage();
+ }
+}
diff --git a/gcloud-java-core/src/test/java/com/google/cloud/AsyncPageImplTest.java b/gcloud-java-core/src/test/java/com/google/cloud/AsyncPageImplTest.java
new file mode 100644
index 000000000000..60189a055467
--- /dev/null
+++ b/gcloud-java-core/src/test/java/com/google/cloud/AsyncPageImplTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * 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
+ *
+ * http://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 com.google.cloud;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+
+import org.junit.Test;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+public class AsyncPageImplTest {
+
+ private static final ImmutableList VALUES1 = ImmutableList.of("1", "2");
+ private static final ImmutableList VALUES2 = ImmutableList.of("3", "4");
+ private static final ImmutableList VALUES3 = ImmutableList.of("5", "6");
+ private static final ImmutableList ALL_VALUES = ImmutableList.builder()
+ .addAll(VALUES1)
+ .addAll(VALUES2)
+ .addAll(VALUES3)
+ .build();
+ private static final ImmutableList SOME_VALUES = ImmutableList.builder()
+ .addAll(VALUES2)
+ .addAll(VALUES3)
+ .build();
+
+ @Test
+ public void testPage() {
+ final AsyncPageImpl nextResult = new AsyncPageImpl<>(null, "c", VALUES2);
+ AsyncPageImpl.NextPageFetcher fetcher = new AsyncPageImpl.NextPageFetcher() {
+ private static final long serialVersionUID = 4703765400378593176L;
+
+ @Override
+ public Future> nextPage() {
+ return Futures.>immediateFuture(nextResult);
+ }
+ };
+ AsyncPageImpl result = new AsyncPageImpl<>(fetcher, "c", VALUES1);
+ assertEquals(nextResult, result.nextPage());
+ assertEquals("c", result.nextPageCursor());
+ assertEquals(VALUES1, result.values());
+ }
+
+ @Test
+ public void testPageAsync() throws ExecutionException, InterruptedException {
+ final AsyncPageImpl nextResult = new AsyncPageImpl<>(null, "c", VALUES2);
+ AsyncPageImpl.NextPageFetcher fetcher = new AsyncPageImpl.NextPageFetcher() {
+ private static final long serialVersionUID = 4703765400378593176L;
+
+ @Override
+ public Future> nextPage() {
+ return Futures.>immediateFuture(nextResult);
+ }
+ };
+ AsyncPageImpl result = new AsyncPageImpl<>(fetcher, "c", VALUES1);
+ assertEquals(nextResult, result.nextPageAsync().get());
+ assertEquals("c", result.nextPageCursor());
+ assertEquals(VALUES1, result.values());
+ }
+
+ @Test
+ public void testIterateAll() {
+ final AsyncPageImpl nextResult2 = new AsyncPageImpl<>(null, "c3", VALUES3);
+ AsyncPageImpl.NextPageFetcher fetcher2 = new AsyncPageImpl.NextPageFetcher() {
+ private static final long serialVersionUID = -9203621430631884026L;
+
+ @Override
+ public Future> nextPage() {
+ return Futures.>immediateFuture(nextResult2);
+ }
+ };
+ final AsyncPageImpl nextResult1 = new AsyncPageImpl<>(fetcher2, "c2", VALUES2);
+ AsyncPageImpl.NextPageFetcher fetcher1 = new AsyncPageImpl.NextPageFetcher() {
+ private static final long serialVersionUID = -9203621430631884026L;
+
+ @Override
+ public Future> nextPage() {
+ return Futures.>immediateFuture(nextResult1);
+ }
+ };
+ AsyncPageImpl result = new AsyncPageImpl<>(fetcher1, "c1", VALUES1);
+ assertEquals(ALL_VALUES, ImmutableList.copyOf(result.iterateAll()));
+ }
+
+ @Test
+ public void testAsyncPageAndIterateAll() throws ExecutionException, InterruptedException {
+ final AsyncPageImpl nextResult2 = new AsyncPageImpl<>(null, "c3", VALUES3);
+ AsyncPageImpl.NextPageFetcher fetcher2 = new AsyncPageImpl.NextPageFetcher() {
+ private static final long serialVersionUID = -9203621430631884026L;
+
+ @Override
+ public Future> nextPage() {
+ return Futures.>immediateFuture(nextResult2);
+ }
+ };
+ final AsyncPageImpl nextResult1 = new AsyncPageImpl<>(fetcher2, "c2", VALUES2);
+ AsyncPageImpl.NextPageFetcher fetcher1 = new AsyncPageImpl.NextPageFetcher() {
+ private static final long serialVersionUID = -9203621430631884026L;
+
+ @Override
+ public Future> nextPage() {
+ return Futures.>immediateFuture(nextResult1);
+ }
+ };
+ AsyncPageImpl result = new AsyncPageImpl<>(fetcher1, "c1", VALUES1);
+ assertEquals(nextResult1, result.nextPageAsync().get());
+ assertEquals("c1", result.nextPageCursor());
+ assertEquals(VALUES1, result.values());
+ assertEquals(SOME_VALUES, ImmutableList.copyOf(result.nextPageAsync().get().iterateAll()));
+ }
+}
diff --git a/gcloud-java-core/src/test/java/com/google/cloud/PageImplTest.java b/gcloud-java-core/src/test/java/com/google/cloud/PageImplTest.java
index 07d979ad857c..40db43b61da2 100644
--- a/gcloud-java-core/src/test/java/com/google/cloud/PageImplTest.java
+++ b/gcloud-java-core/src/test/java/com/google/cloud/PageImplTest.java
@@ -35,6 +35,8 @@ public class PageImplTest {
public void testPage() {
final PageImpl nextResult = new PageImpl<>(null, "c", NEXT_VALUES);
PageImpl.NextPageFetcher fetcher = new PageImpl.NextPageFetcher() {
+ private static final long serialVersionUID = -1714571149183431798L;
+
@Override
public PageImpl nextPage() {
return nextResult;