001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 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, retrieving up to 10 entries at a
094 * time:
095 * <PRE>
096 * // Perform a search to retrieve all users in the server, but only retrieving
097 * // ten at a time.
098 * int numSearches = 0;
099 * int totalEntriesReturned = 0;
100 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
101 * SearchScope.SUB, Filter.createEqualityFilter("objectClass", "person"));
102 * ASN1OctetString resumeCookie = null;
103 * while (true)
104 * {
105 * searchRequest.setControls(
106 * new SimplePagedResultsControl(10, resumeCookie));
107 * SearchResult searchResult = connection.search(searchRequest);
108 * numSearches++;
109 * totalEntriesReturned += searchResult.getEntryCount();
110 * for (SearchResultEntry e : searchResult.getSearchEntries())
111 * {
112 * // Do something with each entry...
113 * }
114 *
115 * LDAPTestUtils.assertHasControl(searchResult,
116 * SimplePagedResultsControl.PAGED_RESULTS_OID);
117 * SimplePagedResultsControl responseControl =
118 * SimplePagedResultsControl.get(searchResult);
119 * if (responseControl.moreResultsToReturn())
120 * {
121 * // The resume cookie can be included in the simple paged results
122 * // control included in the next search to get the next page of results.
123 * resumeCookie = responseControl.getCookie();
124 * }
125 * else
126 * {
127 * break;
128 * }
129 * }
130 * </PRE>
131 */
132 @NotMutable()
133 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
134 public final class SimplePagedResultsControl
135 extends Control
136 implements DecodeableControl
137 {
138 /**
139 * The OID (1.2.840.113556.1.4.319) for the paged results control.
140 */
141 public static final String PAGED_RESULTS_OID = "1.2.840.113556.1.4.319";
142
143
144
145 /**
146 * The serial version UID for this serializable class.
147 */
148 private static final long serialVersionUID = 2186787148024999291L;
149
150
151
152 // The encoded cookie returned from the server (for a response control) or
153 // that should be included in the next request to the server (for a request
154 // control).
155 private final ASN1OctetString cookie;
156
157 // The maximum requested page size (for a request control), or the estimated
158 // total result set size (for a response control).
159 private final int size;
160
161
162
163 /**
164 * Creates a new empty control instance that is intended to be used only for
165 * decoding controls via the {@code DecodeableControl} interface.
166 */
167 SimplePagedResultsControl()
168 {
169 size = 0;
170 cookie = new ASN1OctetString();
171 }
172
173
174
175 /**
176 * Creates a new paged results control with the specified page size. This
177 * version of the constructor should only be used when creating the first
178 * search as part of the set of paged results. Subsequent searches to
179 * retrieve additional pages should use the response control returned by the
180 * server in their next request, until the response control returned by the
181 * server does not include a cookie.
182 *
183 * @param pageSize The maximum number of entries that the server should
184 * return in the first page.
185 */
186 public SimplePagedResultsControl(final int pageSize)
187 {
188 super(PAGED_RESULTS_OID, false, encodeValue(pageSize, null));
189
190 size = pageSize;
191 cookie = new ASN1OctetString();
192 }
193
194
195
196 /**
197 * Creates a new paged results control with the specified page size. This
198 * version of the constructor should only be used when creating the first
199 * search as part of the set of paged results. Subsequent searches to
200 * retrieve additional pages should use the response control returned by the
201 * server in their next request, until the response control returned by the
202 * server does not include a cookie.
203 *
204 * @param pageSize The maximum number of entries that the server should
205 * return in the first page.
206 * @param isCritical Indicates whether this control should be marked
207 * critical.
208 */
209 public SimplePagedResultsControl(final int pageSize, final boolean isCritical)
210 {
211 super(PAGED_RESULTS_OID, isCritical, encodeValue(pageSize, null));
212
213 size = pageSize;
214 cookie = new ASN1OctetString();
215 }
216
217
218
219 /**
220 * Creates a new paged results control with the specified page size and the
221 * provided cookie. This version of the constructor should be used to
222 * continue iterating through an existing set of results, but potentially
223 * using a different page size.
224 *
225 * @param pageSize The maximum number of entries that the server should
226 * return in the next page of the results.
227 * @param cookie The cookie provided by the server after returning the
228 * previous page of results, or {@code null} if this request
229 * will retrieve the first page of results.
230 */
231 public SimplePagedResultsControl(final int pageSize,
232 final ASN1OctetString cookie)
233 {
234 super(PAGED_RESULTS_OID, false, encodeValue(pageSize, cookie));
235
236 size = pageSize;
237
238 if (cookie == null)
239 {
240 this.cookie = new ASN1OctetString();
241 }
242 else
243 {
244 this.cookie = cookie;
245 }
246 }
247
248
249
250 /**
251 * Creates a new paged results control with the specified page size and the
252 * provided cookie. This version of the constructor should be used to
253 * continue iterating through an existing set of results, but potentially
254 * using a different page size.
255 *
256 * @param pageSize The maximum number of entries that the server should
257 * return in the first page.
258 * @param cookie The cookie provided by the server after returning the
259 * previous page of results, or {@code null} if this
260 * request will retrieve the first page of results.
261 * @param isCritical Indicates whether this control should be marked
262 * critical.
263 */
264 public SimplePagedResultsControl(final int pageSize,
265 final ASN1OctetString cookie,
266 final boolean isCritical)
267 {
268 super(PAGED_RESULTS_OID, isCritical, encodeValue(pageSize, cookie));
269
270 size = pageSize;
271
272 if (cookie == null)
273 {
274 this.cookie = new ASN1OctetString();
275 }
276 else
277 {
278 this.cookie = cookie;
279 }
280 }
281
282
283
284 /**
285 * Creates a new paged results control from the control with the provided set
286 * of information. This should be used to decode the paged results response
287 * control returned by the server with a page of results.
288 *
289 * @param oid The OID for the control.
290 * @param isCritical Indicates whether the control should be marked
291 * critical.
292 * @param value The encoded value for the control. This may be
293 * {@code null} if no value was provided.
294 *
295 * @throws LDAPException If the provided control cannot be decoded as a
296 * simple paged results control.
297 */
298 public SimplePagedResultsControl(final String oid, final boolean isCritical,
299 final ASN1OctetString value)
300 throws LDAPException
301 {
302 super(oid, isCritical, value);
303
304 if (value == null)
305 {
306 throw new LDAPException(ResultCode.DECODING_ERROR,
307 ERR_PAGED_RESULTS_NO_VALUE.get());
308 }
309
310 final ASN1Sequence valueSequence;
311 try
312 {
313 final ASN1Element valueElement = ASN1Element.decode(value.getValue());
314 valueSequence = ASN1Sequence.decodeAsSequence(valueElement);
315 }
316 catch (final ASN1Exception ae)
317 {
318 debugException(ae);
319 throw new LDAPException(ResultCode.DECODING_ERROR,
320 ERR_PAGED_RESULTS_VALUE_NOT_SEQUENCE.get(ae), ae);
321 }
322
323 final ASN1Element[] valueElements = valueSequence.elements();
324 if (valueElements.length != 2)
325 {
326 throw new LDAPException(ResultCode.DECODING_ERROR,
327 ERR_PAGED_RESULTS_INVALID_ELEMENT_COUNT.get(
328 valueElements.length));
329 }
330
331 try
332 {
333 size = ASN1Integer.decodeAsInteger(valueElements[0]).intValue();
334 }
335 catch (final ASN1Exception ae)
336 {
337 debugException(ae);
338 throw new LDAPException(ResultCode.DECODING_ERROR,
339 ERR_PAGED_RESULTS_FIRST_NOT_INTEGER.get(ae), ae);
340 }
341
342 cookie = ASN1OctetString.decodeAsOctetString(valueElements[1]);
343 }
344
345
346
347 /**
348 * {@inheritDoc}
349 */
350 public SimplePagedResultsControl
351 decodeControl(final String oid, final boolean isCritical,
352 final ASN1OctetString value)
353 throws LDAPException
354 {
355 return new SimplePagedResultsControl(oid, isCritical, value);
356 }
357
358
359
360 /**
361 * Extracts a simple paged results response control from the provided result.
362 *
363 * @param result The result from which to retrieve the simple paged results
364 * response control.
365 *
366 * @return The simple paged results response control contained in the
367 * provided result, or {@code null} if the result did not contain a
368 * simple paged results response control.
369 *
370 * @throws LDAPException If a problem is encountered while attempting to
371 * decode the simple paged results response control
372 * contained in the provided result.
373 */
374 public static SimplePagedResultsControl get(final SearchResult result)
375 throws LDAPException
376 {
377 final Control c = result.getResponseControl(PAGED_RESULTS_OID);
378 if (c == null)
379 {
380 return null;
381 }
382
383 if (c instanceof SimplePagedResultsControl)
384 {
385 return (SimplePagedResultsControl) c;
386 }
387 else
388 {
389 return new SimplePagedResultsControl(c.getOID(), c.isCritical(),
390 c.getValue());
391 }
392 }
393
394
395
396 /**
397 * Encodes the provided information into an octet string that can be used as
398 * the value for this control.
399 *
400 * @param pageSize The maximum number of entries that the server should
401 * return in the next page of the results.
402 * @param cookie The cookie provided by the server after returning the
403 * previous page of results, or {@code null} if this request
404 * will retrieve the first page of results.
405 *
406 * @return An ASN.1 octet string that can be used as the value for this
407 * control.
408 */
409 private static ASN1OctetString encodeValue(final int pageSize,
410 final ASN1OctetString cookie)
411 {
412 final ASN1Element[] valueElements;
413 if (cookie == null)
414 {
415 valueElements = new ASN1Element[]
416 {
417 new ASN1Integer(pageSize),
418 new ASN1OctetString()
419 };
420 }
421 else
422 {
423 valueElements = new ASN1Element[]
424 {
425 new ASN1Integer(pageSize),
426 cookie
427 };
428 }
429
430 return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
431 }
432
433
434
435 /**
436 * Retrieves the size for this paged results control. For a request control,
437 * it may be used to specify the number of entries that should be included in
438 * the next page of results. For a response control, it may be used to
439 * specify the estimated number of entries in the complete result set.
440 *
441 * @return The size for this paged results control.
442 */
443 public int getSize()
444 {
445 return size;
446 }
447
448
449
450 /**
451 * Retrieves the cookie for this control, which may be used in a subsequent
452 * request to resume reading entries from the next page of results. The
453 * value should have a length of zero when used to retrieve the first page of
454 * results for a given search, and also in the response from the server when
455 * there are no more entries to send. It should be non-empty for all other
456 * conditions.
457 *
458 * @return The cookie for this control, or {@code null} if there is none.
459 */
460 public ASN1OctetString getCookie()
461 {
462 return cookie;
463 }
464
465
466
467 /**
468 * Indicates whether there are more results to return as part of this search.
469 *
470 * @return {@code true} if there are more results to return, or
471 * {@code false} if not.
472 */
473 public boolean moreResultsToReturn()
474 {
475 return (cookie.getValue().length > 0);
476 }
477
478
479
480 /**
481 * {@inheritDoc}
482 */
483 @Override()
484 public String getControlName()
485 {
486 return INFO_CONTROL_NAME_PAGED_RESULTS.get();
487 }
488
489
490
491 /**
492 * {@inheritDoc}
493 */
494 @Override()
495 public void toString(final StringBuilder buffer)
496 {
497 buffer.append("SimplePagedResultsControl(pageSize=");
498 buffer.append(size);
499 buffer.append(", isCritical=");
500 buffer.append(isCritical());
501 buffer.append(')');
502 }
503 }