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;