001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 */
021package com.unboundid.ldap.sdk.controls;
022
023
024
025import com.unboundid.asn1.ASN1Element;
026import com.unboundid.asn1.ASN1Exception;
027import com.unboundid.asn1.ASN1Integer;
028import com.unboundid.asn1.ASN1OctetString;
029import com.unboundid.asn1.ASN1Sequence;
030import com.unboundid.ldap.sdk.Control;
031import com.unboundid.ldap.sdk.DecodeableControl;
032import com.unboundid.ldap.sdk.LDAPException;
033import com.unboundid.ldap.sdk.ResultCode;
034import com.unboundid.ldap.sdk.SearchResult;
035import com.unboundid.util.NotMutable;
036import com.unboundid.util.ThreadSafety;
037import com.unboundid.util.ThreadSafetyLevel;
038
039import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
040import 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)
134public 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}