1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.search
;
5 import com
.google
.appengine
.api
.search
.SearchServicePb
.SearchParams
;
6 import com
.google
.appengine
.api
.search
.checkers
.CursorChecker
;
7 import com
.google
.appengine
.api
.search
.checkers
.Preconditions
;
9 import java
.io
.Serializable
;
12 * Represents a cursor on the set of results found for executing a {@link Query}
13 * during a search on the {@link Index}.
15 * For example, the following code shows how to use a cursor to get the
16 * next page of results
20 * Cursor cursor = Cursor.newBuilder().build();
21 * Query query = Query.newBuilder().setOptions(
22 * QueryOptions.newBuilder().setCursor(cursor).build("some query"));
24 * // Get the first page of results
25 * Results<ScoredDocument> results = index.search(query);
30 * // Get the next set of results from the returned cursor
31 * query = Query.newBuilder().setOptions(
32 * QueryOptions.newBuilder().setCursor(
33 * results.getCursor()).build("some query"));
35 * results = index.search(query);
38 * Alternatively, you can get a cursor to continue from each of the returned
42 * Cursor.newBuilder().setPerResult(true).build();
43 * Query query = Query.newBuilder().setOptions(
44 * QueryOptions.newBuilder().setCursor(cursor).build("some query"));
46 * // Get the first page of results
47 * Results<ScoredDocument> results = index.search(query);
50 * for (ScoredDocument result : results) {
51 * // choose a cursor from one of the results
52 * cursor = result.getCursor();
55 * // Get the next set of results from the result's cursor
56 * query = Query.newBuilder().setOptions(
57 * QueryOptions.newBuilder().setCursor(cursor).build("some query"));
59 * results = index.search(query);
63 public final class Cursor
implements Serializable
{
64 private static final long serialVersionUID
= 5983232584697220044L;
67 * A builder which constructs Cursor objects.
69 public static final class Builder
{ private String webSafeString
; private boolean perResult
;
75 * Constructs a {@link Cursor} builder with the given request.
77 * @param request the search request to populate the builder
79 private Builder(Cursor request
) {
80 setFromWebSafeString(request
.toWebSafeString());
84 * Sets whether or not to return a Cursor on each individual result
85 * in {@link Results} or a single cursor with all
88 * @param perResult if True, then return a Cursor with each
89 * {@link ScoredDocument} result, otherwise return a single Cursor
90 * with {@link Results}
91 * @return this Builder
93 public Builder
setPerResult(boolean perResult
) {
94 this.perResult
= perResult
;
99 * Construct the final message.
101 * @param webSafeString use a cursor returned from a
102 * previous set of search results as a starting point to retrieve
103 * the next set of results. This can get you better performance, and
104 * also improves the consistency of pagination through index updates
105 * @return the Cursor built from the parameters entered on this
107 * @throws IllegalArgumentException if the cursor string is invalid
109 public Cursor
build(String webSafeString
) {
110 setFromWebSafeString(webSafeString
);
111 return new Cursor(this);
115 * @param webSafeString a string from some other
116 * {@link Cursor#toWebSafeString}
118 private void setFromWebSafeString(String webSafeString
) {
119 CursorChecker
.checkCursor(webSafeString
);
120 int colon
= webSafeString
.indexOf(":");
121 Preconditions
.checkArgument(colon
> 0 && colon
< webSafeString
.length(),
122 "Invalid format for cursor string");
123 String booleanString
= webSafeString
.substring(0, colon
);
124 Preconditions
.checkArgument("true".equals(booleanString
) || "false".equals(booleanString
),
125 "Invalid format of webSafeString");
126 this.perResult
= Boolean
.parseBoolean(booleanString
);
127 this.webSafeString
= webSafeString
.substring(colon
+ 1);
131 * Construct the final message.
133 * @return the Cursor built from the parameters entered on this
136 public Cursor
build() {
137 return new Cursor(this);
141 private final String webSafeString
; private final boolean perResult
;
144 * Creates a search request from the builder.
146 * @param builder the search request builder to populate with
148 private Cursor(Builder builder
) {
149 webSafeString
= builder
.webSafeString
;
150 perResult
= builder
.perResult
;
155 * A web safe string representing a cursor returned from a previous set of
156 * search results to use as a starting point to retrieve the next
157 * set of results. Can be null.
159 * @return a web safe string representation of the cursor
161 public String
toWebSafeString() {
162 if (webSafeString
== null) {
165 return perResult
+ ":" + webSafeString
;
169 * @return whether the cursor is for all Results or one cursor per result
171 public boolean isPerResult() {
176 * Creates and returns a {@link Cursor} builder. Set the search request
177 * parameters and use the {@link Builder#build()} method to create a concrete
178 * instance of Cursor.
180 * @return a {@link Builder} which can construct a search request
182 public static Builder
newBuilder() {
183 return new Builder();
187 * Creates a builder from the given request.
189 * @param request the search request for the builder to use
190 * to build another request
191 * @return this builder
193 public static Builder
newBuilder(Cursor request
) {
194 return new Builder(request
);
198 * Checks the search specification is valid, specifically, has
199 * a non-null number of documents to return specification, a valid
200 * cursor if present, valid sort specification list, a valid
201 * collection of field names for sorting.
203 * @return this checked Cursor
204 * @throws IllegalArgumentException if some part of the specification is
207 private Cursor
checkValid() {
208 CursorChecker
.checkCursor(webSafeString
);
213 * Copies the contents of this {@link Cursor} object into a
214 * {@link SearchParams} protocol buffer builder.
216 * @return a search params protocol buffer builder initialized with
217 * the values from this request
218 * @throws IllegalArgumentException if the cursor type is
221 SearchParams
.Builder
copyToProtocolBuffer(SearchParams
.Builder builder
) {
222 if (webSafeString
!= null) {
223 builder
.setCursor(webSafeString
);
226 builder
.setCursorType(SearchParams
.CursorType
.PER_RESULT
);
228 builder
.setCursorType(SearchParams
.CursorType
.SINGLE
);
234 public String
toString() {
235 return String
.format(
237 Util
.fieldToString("webSafeString", toWebSafeString(), true));