001 /*
002 * Copyright 2007-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk.controls;
022
023
024
025 import com.unboundid.asn1.ASN1Element;
026 import com.unboundid.asn1.ASN1Exception;
027 import com.unboundid.asn1.ASN1Integer;
028 import com.unboundid.asn1.ASN1OctetString;
029 import com.unboundid.asn1.ASN1Sequence;
030 import com.unboundid.ldap.sdk.Control;
031 import com.unboundid.ldap.sdk.DecodeableControl;
032 import com.unboundid.ldap.sdk.LDAPException;
033 import com.unboundid.ldap.sdk.ResultCode;
034 import com.unboundid.ldap.sdk.SearchResult;
035 import com.unboundid.util.NotMutable;
036 import com.unboundid.util.ThreadSafety;
037 import com.unboundid.util.ThreadSafetyLevel;
038
039 import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
040 import static com.unboundid.util.Debug.*;
041
042
043
044 /**
045 * This class provides an implementation of the simple paged results control as
046 * defined in <A HREF="http://www.ietf.org/rfc/rfc2696.txt">RFC 2696</A>. It
047 * allows the client to iterate through a potentially large set of search
048 * results in subsets of a specified number of entries (i.e., "pages").
049 * <BR><BR>
050 * The same control encoding is used for both the request control sent by
051 * clients and the response control returned by the server. It may contain
052 * two elements:
053 * <UL>
054 * <LI>Size -- In a request control, this provides the requested page size,
055 * which is the maximum number of entries that the server should return
056 * in the next iteration of the search. In a response control, it is an
057 * estimate of the total number of entries that match the search
058 * criteria.</LI>
059 * <LI>Cookie -- A token which is used by the server to keep track of its
060 * position in the set of search results. The first request sent by the
061 * client should not include a cookie, and the last response sent by the
062 * server should not include a cookie. For all other intermediate search
063 * requests and responses, the server will include a cookie value in its
064 * response that the client should include in its next request.</LI>
065 * </UL>
066 * When the client wishes to use the paged results control, the first search
067 * request should include a version of the paged results request control that
068 * was created with a requested page size but no cookie. The corresponding
069 * response from the server will include a version of the paged results control
070 * that may include an estimate of the total number of matching entries, and
071 * may also include a cookie. The client should include this cookie in the
072 * next request (with the same set of search criteria) to retrieve the next page
073 * of results. This process should continue until the response control returned
074 * by the server does not include a cookie, which indicates that the end of the
075 * result set has been reached.
076 * <BR><BR>
077 * Note that the simple paged results control is similar to the
078 * {@link VirtualListViewRequestControl} in that both allow the client to
079 * request that only a portion of the result set be returned at any one time.
080 * However, there are significant differences between them, including:
081 * <UL>
082 * <LI>In order to use the virtual list view request control, it is also
083 * necessary to use the {@link ServerSideSortRequestControl} to ensure
084 * that the entries are sorted. This is not a requirement for the
085 * simple paged results control.</LI>
086 * <LI>The simple paged results control may only be used to iterate
087 * sequentially through the set of search results. The virtual list view
088 * control can retrieve pages out of order, can retrieve overlapping
089 * pages, and can re-request pages that it had already retrieved.</LI>
090 * </UL>
091 * <H2>Example</H2>
092 * The following example demonstrates the use of the simple paged results
093 * control. It will iterate through all users in the "Sales" department,
094 * retrieving up to 10 entries at a time:
095 * <PRE>
096 * SearchRequest searchRequest =
097 * new SearchRequest("dc=example,dc=com", SearchScope.SUB,"(ou=Sales)");
098 * ASN1OctetString cookie = null;
099 * do
100 * {
101 * searchRequest.setControls(
102 * new Control[] { new SimplePagedResultsControl(10, cookie) });
103 * SearchResult searchResult = connection.search(searchRequest);
104 *
105 * // Do something with the entries that are returned.
106 *
107 * cookie = null;
108 * SimplePagedResultControl c = SimplePagedResultControl.get(searchResult);
109 * if (c != null)
110 * {
111 * cookie = c.getCookie();
112 * }
113 * } while ((cookie != null) && (cookie.getValueLength() > 0));
114 * </PRE>
115 */
116 @NotMutable()
117 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
118 public final class SimplePagedResultsControl
119 extends Control
120 implements DecodeableControl
121 {
122 /**
123 * The OID (1.2.840.113556.1.4.319) for the paged results control.
124 */
125 public static final String PAGED_RESULTS_OID = "1.2.840.113556.1.4.319";
126
127
128
129 /**
130 * The serial version UID for this serializable class.
131 */
132 private static final long serialVersionUID = 2186787148024999291L;
133
134
135
136 // The encoded cookie returned from the server (for a response control) or
137 // that should be included in the next request to the server (for a request
138 // control).
139 private final ASN1OctetString cookie;
140
141 // The maximum requested page size (for a request control), or the estimated
142 // total result set size (for a response control).
143 private final int size;
144
145
146
147 /**
148 * Creates a new empty control instance that is intended to be used only for
149 * decoding controls via the {@code DecodeableControl} interface.
150 */
151 SimplePagedResultsControl()
152 {
153 size = 0;
154 cookie = new ASN1OctetString();
155 }
156
157
158
159 /**
160 * Creates a new paged results control with the specified page size. This
161 * version of the constructor should only be used when creating the first
162 * search as part of the set of paged results. Subsequent searches to
163 * retrieve additional pages should use the response control returned by the
164 * server in their next request, until the response control returned by the
165 * server does not include a cookie.
166 *
167 * @param pageSize The maximum number of entries that the server should
168 * return in the first page.
169 */
170 public SimplePagedResultsControl(final int pageSize)
171 {
172 super(PAGED_RESULTS_OID, false, encodeValue(pageSize, null));
173
174 size = pageSize;
175 cookie = new ASN1OctetString();
176 }
177
178
179
180 /**
181 * Creates a new paged results control with the specified page size. This
182 * version of the constructor should only be used when creating the first
183 * search as part of the set of paged results. Subsequent searches to
184 * retrieve additional pages should use the response control returned by the
185 * server in their next request, until the response control returned by the
186 * server does not include a cookie.
187 *
188 * @param pageSize The maximum number of entries that the server should
189 * return in the first page.
190 * @param isCritical Indicates whether this control should be marked
191 * critical.
192 */
193 public SimplePagedResultsControl(final int pageSize, final boolean isCritical)
194 {
195 super(PAGED_RESULTS_OID, isCritical, encodeValue(pageSize, null));
196
197 size = pageSize;
198 cookie = new ASN1OctetString();
199 }
200
201
202
203 /**
204 * Creates a new paged results control with the specified page size and the
205 * provided cookie. This version of the constructor should be used to
206 * continue iterating through an existing set of results, but potentially
207 * using a different page size.
208 *
209 * @param pageSize The maximum number of entries that the server should
210 * return in the next page of the results.
211 * @param cookie The cookie provided by the server after returning the
212 * previous page of results, or {@code null} if this request
213 * will retrieve the first page of results.
214 */
215 public SimplePagedResultsControl(final int pageSize,
216 final ASN1OctetString cookie)
217 {
218 super(PAGED_RESULTS_OID, false, encodeValue(pageSize, cookie));
219
220 size = pageSize;
221
222 if (cookie == null)
223 {
224 this.cookie = new ASN1OctetString();
225 }
226 else
227 {
228 this.cookie = cookie;
229 }
230 }
231
232
233
234 /**
235 * Creates a new paged results control with the specified page size and the
236 * provided cookie. This version of the constructor should be used to
237 * continue iterating through an existing set of results, but potentially
238 * using a different page size.
239 *
240 * @param pageSize The maximum number of entries that the server should
241 * return in the first page.
242 * @param cookie The cookie provided by the server after returning the
243 * previous page of results, or {@code null} if this
244 * request will retrieve the first page of results.
245 * @param isCritical Indicates whether this control should be marked
246 * critical.
247 */
248 public SimplePagedResultsControl(final int pageSize,
249 final ASN1OctetString cookie,
250 final boolean isCritical)
251 {
252 super(PAGED_RESULTS_OID, isCritical, encodeValue(pageSize, cookie));
253
254 size = pageSize;
255
256 if (cookie == null)
257 {
258 this.cookie = new ASN1OctetString();
259 }
260 else
261 {
262 this.cookie = cookie;
263 }
264 }
265
266
267
268 /**
269 * Creates a new paged results control from the control with the provided set
270 * of information. This should be used to decode the paged results response
271 * control returned by the server with a page of results.
272 *
273 * @param oid The OID for the control.
274 * @param isCritical Indicates whether the control should be marked
275 * critical.
276 * @param value The encoded value for the control. This may be
277 * {@code null} if no value was provided.
278 *
279 * @throws LDAPException If the provided control cannot be decoded as a
280 * simple paged results control.
281 */
282 public SimplePagedResultsControl(final String oid, final boolean isCritical,
283 final ASN1OctetString value)
284 throws LDAPException
285 {
286 super(oid, isCritical, value);
287
288 if (value == null)
289 {
290 throw new LDAPException(ResultCode.DECODING_ERROR,
291 ERR_PAGED_RESULTS_NO_VALUE.get());
292 }
293
294 final ASN1Sequence valueSequence;
295 try
296 {
297 final ASN1Element valueElement = ASN1Element.decode(value.getValue());
298 valueSequence = ASN1Sequence.decodeAsSequence(valueElement);
299 }
300 catch (final ASN1Exception ae)
301 {
302 debugException(ae);
303 throw new LDAPException(ResultCode.DECODING_ERROR,
304 ERR_PAGED_RESULTS_VALUE_NOT_SEQUENCE.get(ae), ae);
305 }
306
307 final ASN1Element[] valueElements = valueSequence.elements();
308 if (valueElements.length != 2)
309 {
310 throw new LDAPException(ResultCode.DECODING_ERROR,
311 ERR_PAGED_RESULTS_INVALID_ELEMENT_COUNT.get(
312 valueElements.length));
313 }
314
315 try
316 {
317 size = ASN1Integer.decodeAsInteger(valueElements[0]).intValue();
318 }
319 catch (final ASN1Exception ae)
320 {
321 debugException(ae);
322 throw new LDAPException(ResultCode.DECODING_ERROR,
323 ERR_PAGED_RESULTS_FIRST_NOT_INTEGER.get(ae), ae);
324 }
325
326 cookie = ASN1OctetString.decodeAsOctetString(valueElements[1]);
327 }
328
329
330
331 /**
332 * {@inheritDoc}
333 */
334 public SimplePagedResultsControl
335 decodeControl(final String oid, final boolean isCritical,
336 final ASN1OctetString value)
337 throws LDAPException
338 {
339 return new SimplePagedResultsControl(oid, isCritical, value);
340 }
341
342
343
344 /**
345 * Extracts a simple paged results response control from the provided result.
346 *
347 * @param result The result from which to retrieve the simple paged results
348 * response control.
349 *
350 * @return The simple paged results response control contained in the
351 * provided result, or {@code null} if the result did not contain a
352 * simple paged results response control.
353 *
354 * @throws LDAPException If a problem is encountered while attempting to
355 * decode the simple paged results response control
356 * contained in the provided result.
357 */
358 public static SimplePagedResultsControl get(final SearchResult result)
359 throws LDAPException
360 {
361 final Control c = result.getResponseControl(PAGED_RESULTS_OID);
362 if (c == null)
363 {
364 return null;
365 }
366
367 if (c instanceof SimplePagedResultsControl)
368 {
369 return (SimplePagedResultsControl) c;
370 }
371 else
372 {
373 return new SimplePagedResultsControl(c.getOID(), c.isCritical(),
374 c.getValue());
375 }
376 }
377
378
379
380 /**
381 * Encodes the provided information into an octet string that can be used as
382 * the value for this control.
383 *
384 * @param pageSize The maximum number of entries that the server should
385 * return in the next page of the results.
386 * @param cookie The cookie provided by the server after returning the
387 * previous page of results, or {@code null} if this request
388 * will retrieve the first page of results.
389 *
390 * @return An ASN.1 octet string that can be used as the value for this
391 * control.
392 */
393 private static ASN1OctetString encodeValue(final int pageSize,
394 final ASN1OctetString cookie)
395 {
396 final ASN1Element[] valueElements;
397 if (cookie == null)
398 {
399 valueElements = new ASN1Element[]
400 {
401 new ASN1Integer(pageSize),
402 new ASN1OctetString()
403 };
404 }
405 else
406 {
407 valueElements = new ASN1Element[]
408 {
409 new ASN1Integer(pageSize),
410 cookie
411 };
412 }
413
414 return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
415 }
416
417
418
419 /**
420 * Retrieves the size for this paged results control. For a request control,
421 * it may be used to specify the number of entries that should be included in
422 * the next page of results. For a response control, it may be used to
423 * specify the estimated number of entries in the complete result set.
424 *
425 * @return The size for this paged results control.
426 */
427 public int getSize()
428 {
429 return size;
430 }
431
432
433
434 /**
435 * Retrieves the cookie for this control, which may be used in a subsequent
436 * request to resume reading entries from the next page of results. The
437 * value should have a length of zero when used to retrieve the first page of
438 * results for a given search, and also in the response from the server when
439 * there are no more entries to send. It should be non-empty for all other
440 * conditions.
441 *
442 * @return The cookie for this control, or {@code null} if there is none.
443 */
444 public ASN1OctetString getCookie()
445 {
446 return cookie;
447 }
448
449
450
451 /**
452 * Indicates whether there are more results to return as part of this search.
453 *
454 * @return {@code true} if there are more results to return, or
455 * {@code false} if not.
456 */
457 public boolean moreResultsToReturn()
458 {
459 return (cookie.getValue().length > 0);
460 }
461
462
463
464 /**
465 * {@inheritDoc}
466 */
467 @Override()
468 public String getControlName()
469 {
470 return INFO_CONTROL_NAME_PAGED_RESULTS.get();
471 }
472
473
474
475 /**
476 * {@inheritDoc}
477 */
478 @Override()
479 public void toString(final StringBuilder buffer)
480 {
481 buffer.append("SimplePagedResultsControl(pageSize=");
482 buffer.append(size);
483 buffer.append(", isCritical=");
484 buffer.append(isCritical());
485 buffer.append(')');
486 }
487 }