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    }