1.9.22 release.
[gae.git] / java / src / main / com / google / appengine / api / datastore / Cursor.java
blobf049578751b9a6ab079c2e07ae6429f6ee90bbee
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.base.CharMatcher;
10 import com.google.common.io.ByteStreams;
11 import com.google.protobuf.ByteString;
13 import java.io.IOException;
14 import java.io.Serializable;
16 /**
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.
22 * <p>
23 * Cursors can be retrieved from {@code PreparedQuery.asQueryResult*} functions.
24 * A typical use case would be:
26 * <blockquote>
27 * <pre>
28 * Cursor originalCursor = preparedQuery.asQueryResultList(withLimit(20)).getCursor();
29 * String encodedCursor = original.toWebSafeString();
30 * </pre>
31 * </blockquote>
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:
36 * <blockquote>
37 * <pre>{@code
38 * Cursor decodedCursor = Cursor.fromWebSafeString(encodedCursor);
39 * List<Entity> nextBatch = preparedQuery.asQueryResultList(withLimit(20).cursor(decoded));
40 * }</pre>
41 * </blockquote>
44 public final class Cursor implements Serializable {
45 static final long serialVersionUID = 3515556366838971499L;
46 private ByteString cursorBytes;
48 Cursor() {
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) {
70 if (n == 0) {
71 return this;
72 } else if (n > 0) {
73 return query.asQueryResultIterator(withStartCursor(this).offset(n).limit(0)).getCursor();
75 throw new IllegalArgumentException("Unable to offset cursor by " + n + " results.");
78 /**
79 * @deprecated It is no longer necessary to call {@link #reverse()} on
80 * cursors.
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}
87 @Deprecated
88 public Cursor reverse() {
89 return this;
92 /**
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");
110 try {
111 return new Cursor(
112 ByteString.copyFrom(base64Url().decode(
113 CharMatcher.WHITESPACE.removeFrom(encodedCursor))));
114 } catch (IllegalArgumentException e) {
115 throw new IllegalArgumentException("Unable to decode provided cursor.", e);
119 ByteString toByteString() {
120 return ByteString.copyFrom(cursorBytes.toByteArray());
123 @Override
124 public boolean equals(Object obj) {
125 if (obj == null) {
126 return false;
129 if (obj.getClass() != this.getClass()) {
130 return false;
133 return cursorBytes.equals(((Cursor) obj).cursorBytes);
136 @Override
137 public int hashCode() {
138 return cursorBytes.hashCode();
141 @Override
142 public String toString() {
143 return cursorBytes.toString();