001/* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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}