001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 Ping Identity Corporation 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 @Override() 351 public SimplePagedResultsControl 352 decodeControl(final String oid, final boolean isCritical, 353 final ASN1OctetString value) 354 throws LDAPException 355 { 356 return new SimplePagedResultsControl(oid, isCritical, value); 357 } 358 359 360 361 /** 362 * Extracts a simple paged results response control from the provided result. 363 * 364 * @param result The result from which to retrieve the simple paged results 365 * response control. 366 * 367 * @return The simple paged results response control contained in the 368 * provided result, or {@code null} if the result did not contain a 369 * simple paged results response control. 370 * 371 * @throws LDAPException If a problem is encountered while attempting to 372 * decode the simple paged results response control 373 * contained in the provided result. 374 */ 375 public static SimplePagedResultsControl get(final SearchResult result) 376 throws LDAPException 377 { 378 final Control c = result.getResponseControl(PAGED_RESULTS_OID); 379 if (c == null) 380 { 381 return null; 382 } 383 384 if (c instanceof SimplePagedResultsControl) 385 { 386 return (SimplePagedResultsControl) c; 387 } 388 else 389 { 390 return new SimplePagedResultsControl(c.getOID(), c.isCritical(), 391 c.getValue()); 392 } 393 } 394 395 396 397 /** 398 * Encodes the provided information into an octet string that can be used as 399 * the value for this control. 400 * 401 * @param pageSize The maximum number of entries that the server should 402 * return in the next page of the results. 403 * @param cookie The cookie provided by the server after returning the 404 * previous page of results, or {@code null} if this request 405 * will retrieve the first page of results. 406 * 407 * @return An ASN.1 octet string that can be used as the value for this 408 * control. 409 */ 410 private static ASN1OctetString encodeValue(final int pageSize, 411 final ASN1OctetString cookie) 412 { 413 final ASN1Element[] valueElements; 414 if (cookie == null) 415 { 416 valueElements = new ASN1Element[] 417 { 418 new ASN1Integer(pageSize), 419 new ASN1OctetString() 420 }; 421 } 422 else 423 { 424 valueElements = new ASN1Element[] 425 { 426 new ASN1Integer(pageSize), 427 cookie 428 }; 429 } 430 431 return new ASN1OctetString(new ASN1Sequence(valueElements).encode()); 432 } 433 434 435 436 /** 437 * Retrieves the size for this paged results control. For a request control, 438 * it may be used to specify the number of entries that should be included in 439 * the next page of results. For a response control, it may be used to 440 * specify the estimated number of entries in the complete result set. 441 * 442 * @return The size for this paged results control. 443 */ 444 public int getSize() 445 { 446 return size; 447 } 448 449 450 451 /** 452 * Retrieves the cookie for this control, which may be used in a subsequent 453 * request to resume reading entries from the next page of results. The 454 * value should have a length of zero when used to retrieve the first page of 455 * results for a given search, and also in the response from the server when 456 * there are no more entries to send. It should be non-empty for all other 457 * conditions. 458 * 459 * @return The cookie for this control, or {@code null} if there is none. 460 */ 461 public ASN1OctetString getCookie() 462 { 463 return cookie; 464 } 465 466 467 468 /** 469 * Indicates whether there are more results to return as part of this search. 470 * 471 * @return {@code true} if there are more results to return, or 472 * {@code false} if not. 473 */ 474 public boolean moreResultsToReturn() 475 { 476 return (cookie.getValue().length > 0); 477 } 478 479 480 481 /** 482 * {@inheritDoc} 483 */ 484 @Override() 485 public String getControlName() 486 { 487 return INFO_CONTROL_NAME_PAGED_RESULTS.get(); 488 } 489 490 491 492 /** 493 * {@inheritDoc} 494 */ 495 @Override() 496 public void toString(final StringBuilder buffer) 497 { 498 buffer.append("SimplePagedResultsControl(pageSize="); 499 buffer.append(size); 500 buffer.append(", isCritical="); 501 buffer.append(isCritical()); 502 buffer.append(')'); 503 } 504}