Skip to content

Commit 35272b9

Browse files
committed
Add AsyncPage implementation and docs. Add related tests
1 parent 881a078 commit 35272b9

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed

gcloud-java-core/src/main/java/com/google/cloud/AsyncPage.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,34 @@
1818

1919
import java.util.concurrent.Future;
2020

21+
/**
22+
* Interface for asynchronously consuming Google Cloud paginated results.
23+
*
24+
* <p>Use {@code AsyncPage} to iterate through all values (also in next pages):
25+
* <pre> {@code
26+
* AsyncPage<T> page = ...; // get an AsyncPage<T> instance
27+
* Iterator<T> iterator = page.iterateAll();
28+
* while (iterator.hasNext()) {
29+
* T value = iterator.next();
30+
* // do something with value
31+
* }}</pre>
32+
*
33+
* <p>Or handle pagination explicitly:
34+
* <pre> {@code
35+
* AsyncPage<T> page = ...; // get a AsyncPage<T> instance
36+
* while (page != null) {
37+
* for (T value : page.values()) {
38+
* // do something with value
39+
* }
40+
* page = page.nextPage().get();
41+
* }}</pre>
42+
*
43+
* @param <T> the value type that the page holds
44+
*/
2145
public interface AsyncPage<T> extends Page<T> {
46+
47+
/**
48+
* Returns a {@link Future} object for the next page.
49+
*/
2250
Future<AsyncPage<T>> nextPageAsync();
2351
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud;
18+
19+
import com.google.common.base.Throwables;
20+
import com.google.common.util.concurrent.Uninterruptibles;
21+
22+
import java.io.Serializable;
23+
import java.util.concurrent.ExecutionException;
24+
import java.util.concurrent.Future;
25+
26+
/**
27+
* Base implementation for asynchronously consuming Google Cloud paginated results.
28+
*
29+
* @param <T> the value type that the page holds
30+
*/
31+
public class AsyncPageImpl<T> extends PageImpl<T> implements AsyncPage<T> {
32+
33+
private static final long serialVersionUID = -6009473188630364906L;
34+
35+
private final NextPageFetcher<T> asyncPageFetcher;
36+
37+
/**
38+
* Interface for asynchronously fetching the next page of results from the service.
39+
*
40+
* @param <T> the value type that the page holds
41+
*/
42+
public interface NextPageFetcher<T> extends Serializable {
43+
Future<AsyncPage<T>> nextPage();
44+
}
45+
46+
private static class SyncNextPageFetcher<T> implements PageImpl.NextPageFetcher<T> {
47+
48+
private static final long serialVersionUID = -4124568632363525351L;
49+
50+
private NextPageFetcher<T> asyncPageFetcher;
51+
52+
private SyncNextPageFetcher(NextPageFetcher<T> asyncPageFetcher) {
53+
this.asyncPageFetcher = asyncPageFetcher;
54+
}
55+
56+
@Override
57+
public Page<T> nextPage() {
58+
try {
59+
return asyncPageFetcher != null
60+
? Uninterruptibles.getUninterruptibly(asyncPageFetcher.nextPage()) : null;
61+
} catch (ExecutionException ex) {
62+
throw Throwables.propagate(ex.getCause());
63+
}
64+
}
65+
}
66+
67+
/**
68+
* Creates an {@code AsyncPageImpl} object.
69+
*/
70+
public AsyncPageImpl(NextPageFetcher<T> asyncPageFetcher, String cursor, Iterable<T> results) {
71+
super(new SyncNextPageFetcher<T>(asyncPageFetcher), cursor, results);
72+
this.asyncPageFetcher = asyncPageFetcher;
73+
}
74+
75+
@Override
76+
public Future<AsyncPage<T>> nextPageAsync() {
77+
if (nextPageCursor() == null || asyncPageFetcher == null) {
78+
return null;
79+
}
80+
return asyncPageFetcher.nextPage();
81+
}
82+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
import com.google.common.collect.ImmutableList;
22+
import com.google.common.util.concurrent.Futures;
23+
24+
import org.junit.Test;
25+
26+
import java.util.concurrent.ExecutionException;
27+
import java.util.concurrent.Future;
28+
29+
public class AsyncPageImplTest {
30+
31+
private static final ImmutableList<String> VALUES1 = ImmutableList.of("1", "2");
32+
private static final ImmutableList<String> VALUES2 = ImmutableList.of("3", "4");
33+
private static final ImmutableList<String> VALUES3 = ImmutableList.of("5", "6");
34+
private static final ImmutableList<String> ALL_VALUES = ImmutableList.<String>builder()
35+
.addAll(VALUES1)
36+
.addAll(VALUES2)
37+
.addAll(VALUES3)
38+
.build();
39+
private static final ImmutableList<String> SOME_VALUES = ImmutableList.<String>builder()
40+
.addAll(VALUES2)
41+
.addAll(VALUES3)
42+
.build();
43+
44+
@Test
45+
public void testPage() {
46+
final AsyncPageImpl<String> nextResult = new AsyncPageImpl<>(null, "c", VALUES2);
47+
AsyncPageImpl.NextPageFetcher<String> fetcher = new AsyncPageImpl.NextPageFetcher<String>() {
48+
private static final long serialVersionUID = 4703765400378593176L;
49+
50+
@Override
51+
public Future<AsyncPage<String>> nextPage() {
52+
return Futures.<AsyncPage<String>>immediateFuture(nextResult);
53+
}
54+
};
55+
AsyncPageImpl<String> result = new AsyncPageImpl<>(fetcher, "c", VALUES1);
56+
assertEquals(nextResult, result.nextPage());
57+
assertEquals("c", result.nextPageCursor());
58+
assertEquals(VALUES1, result.values());
59+
}
60+
61+
@Test
62+
public void testPageAsync() throws ExecutionException, InterruptedException {
63+
final AsyncPageImpl<String> nextResult = new AsyncPageImpl<>(null, "c", VALUES2);
64+
AsyncPageImpl.NextPageFetcher<String> fetcher = new AsyncPageImpl.NextPageFetcher<String>() {
65+
private static final long serialVersionUID = 4703765400378593176L;
66+
67+
@Override
68+
public Future<AsyncPage<String>> nextPage() {
69+
return Futures.<AsyncPage<String>>immediateFuture(nextResult);
70+
}
71+
};
72+
AsyncPageImpl<String> result = new AsyncPageImpl<>(fetcher, "c", VALUES1);
73+
assertEquals(nextResult, result.nextPageAsync().get());
74+
assertEquals("c", result.nextPageCursor());
75+
assertEquals(VALUES1, result.values());
76+
}
77+
78+
@Test
79+
public void testIterateAll() {
80+
final AsyncPageImpl<String> nextResult2 = new AsyncPageImpl<>(null, "c3", VALUES3);
81+
AsyncPageImpl.NextPageFetcher<String> fetcher2 = new AsyncPageImpl.NextPageFetcher<String>() {
82+
private static final long serialVersionUID = -9203621430631884026L;
83+
84+
@Override
85+
public Future<AsyncPage<String>> nextPage() {
86+
return Futures.<AsyncPage<String>>immediateFuture(nextResult2);
87+
}
88+
};
89+
final AsyncPageImpl<String> nextResult1 = new AsyncPageImpl<>(fetcher2, "c2", VALUES2);
90+
AsyncPageImpl.NextPageFetcher<String> fetcher1 = new AsyncPageImpl.NextPageFetcher<String>() {
91+
private static final long serialVersionUID = -9203621430631884026L;
92+
93+
@Override
94+
public Future<AsyncPage<String>> nextPage() {
95+
return Futures.<AsyncPage<String>>immediateFuture(nextResult1);
96+
}
97+
};
98+
AsyncPageImpl<String> result = new AsyncPageImpl<>(fetcher1, "c1", VALUES1);
99+
assertEquals(ALL_VALUES, ImmutableList.copyOf(result.iterateAll()));
100+
}
101+
102+
@Test
103+
public void testAsyncPageAndIterateAll() throws ExecutionException, InterruptedException {
104+
final AsyncPageImpl<String> nextResult2 = new AsyncPageImpl<>(null, "c3", VALUES3);
105+
AsyncPageImpl.NextPageFetcher<String> fetcher2 = new AsyncPageImpl.NextPageFetcher<String>() {
106+
private static final long serialVersionUID = -9203621430631884026L;
107+
108+
@Override
109+
public Future<AsyncPage<String>> nextPage() {
110+
return Futures.<AsyncPage<String>>immediateFuture(nextResult2);
111+
}
112+
};
113+
final AsyncPageImpl<String> nextResult1 = new AsyncPageImpl<>(fetcher2, "c2", VALUES2);
114+
AsyncPageImpl.NextPageFetcher<String> fetcher1 = new AsyncPageImpl.NextPageFetcher<String>() {
115+
private static final long serialVersionUID = -9203621430631884026L;
116+
117+
@Override
118+
public Future<AsyncPage<String>> nextPage() {
119+
return Futures.<AsyncPage<String>>immediateFuture(nextResult1);
120+
}
121+
};
122+
AsyncPageImpl<String> result = new AsyncPageImpl<>(fetcher1, "c1", VALUES1);
123+
assertEquals(nextResult1, result.nextPageAsync().get());
124+
assertEquals("c1", result.nextPageCursor());
125+
assertEquals(VALUES1, result.values());
126+
assertEquals(SOME_VALUES, ImmutableList.copyOf(result.nextPageAsync().get().iterateAll()));
127+
}
128+
}

gcloud-java-core/src/test/java/com/google/cloud/PageImplTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class PageImplTest {
3535
public void testPage() {
3636
final PageImpl<String> nextResult = new PageImpl<>(null, "c", NEXT_VALUES);
3737
PageImpl.NextPageFetcher<String> fetcher = new PageImpl.NextPageFetcher<String>() {
38+
private static final long serialVersionUID = -1714571149183431798L;
39+
3840
@Override
3941
public PageImpl<String> nextPage() {
4042
return nextResult;

0 commit comments

Comments
 (0)