1 // Copyright 2008 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.api
.datastore
;
4 import static com
.google
.appengine
.api
.datastore
.FetchOptions
.Builder
.withStartCursor
;
5 import static com
.google
.common
.base
.Preconditions
.checkNotNull
;
6 import static com
.google
.common
.io
.BaseEncoding
.base64Url
;
8 import com
.google
.apphosting
.datastore
.DatastoreV3Pb
.Query
;
9 import com
.google
.common
.io
.ByteStreams
;
10 import com
.google
.common
.util
.Base64
;
11 import com
.google
.protobuf
.ByteString
;
13 import java
.io
.IOException
;
14 import java
.io
.Serializable
;
17 * A cursor that represents a position in a query.
19 * To resume a {@link Query} at the position defined by a {@link Cursor}, the
20 * {@link Cursor} must be present in the {@link FetchOptions} passed to a {@link
21 * PreparedQuery} identical to the one it was created from.
23 * Cursors can be retrieved from {@code PreparedQuery.asQueryResult*} functions.
24 * A typical use case would be:
28 * Cursor originalCursor = preparedQuery.asQueryResultList(withLimit(20)).getCursor();
29 * String encodedCursor = original.toWebSafeString();
33 * The encoded cursor can then be passed safely in a get or post arg of a web
34 * request and on another request the next batch of results can be retrieved with:
38 * Cursor decodedCursor = Cursor.fromWebSafeString(encodedCursor);
39 * List<Entity> nextBatch = preparedQuery.asQueryResultList(withLimit(20).cursor(decoded));
44 public final class Cursor
implements Serializable
{
45 static final long serialVersionUID
= 3515556366838971499L;
46 private ByteString cursorBytes
;
49 cursorBytes
= ByteString
.EMPTY
;
52 Cursor(Cursor previousCursor
) {
53 this(previousCursor
.cursorBytes
);
56 Cursor(ByteString cursorBytes
) {
57 checkNotNull(cursorBytes
);
58 this.cursorBytes
= cursorBytes
;
61 private void writeObject(java
.io
.ObjectOutputStream out
) throws IOException
{
62 out
.write(cursorBytes
.toByteArray());
65 private void readObject(java
.io
.ObjectInputStream in
) throws IOException
{
66 cursorBytes
= ByteString
.copyFrom(ByteStreams
.toByteArray(in
));
69 Cursor
advance(final int n
, PreparedQuery query
) {
73 return query
.asQueryResultIterator(withStartCursor(this).offset(n
).limit(0)).getCursor();
75 throw new IllegalArgumentException("Unable to offset cursor by " + n
+ " results.");
79 * @deprecated It is no longer necessary to call {@link #reverse()} on
82 * A cursor returned by a query may also be used in the query returned by
83 * {@link com.google.appengine.api.datastore.Query#reverse()}.
85 * @return a cursor identical to {@code this}
88 public Cursor
reverse() {
93 * Encodes the current cursor as a web safe string that can later be decoded
94 * by {@link #fromWebSafeString(String)}
96 public String
toWebSafeString() {
97 return base64Url().omitPadding().encode(cursorBytes
.toByteArray());
101 * Decodes the given encoded cursor
103 * @param encodedCursor
104 * @return the decoded cursor
105 * @throws IllegalArgumentException if the provided string is not a valid encoded cursor
107 public static Cursor
fromWebSafeString(String encodedCursor
) {
108 checkNotNull(encodedCursor
, "encodedCursor must not be null");
112 ByteString
.copyFrom(Base64
.decodeWebSafe(encodedCursor
)));
113 } catch (IllegalArgumentException e
) {
114 throw new IllegalArgumentException("Unable to decode provided cursor.", e
);
118 ByteString
toByteString() {
119 return ByteString
.copyFrom(cursorBytes
.toByteArray());
123 public boolean equals(Object obj
) {
128 if (obj
.getClass() != this.getClass()) {
132 return cursorBytes
.equals(((Cursor
) obj
).cursorBytes
);
136 public int hashCode() {
137 return cursorBytes
.hashCode();
141 public String
toString() {
142 return cursorBytes
.toString();