001/* 002 * Copyright 2007-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.controls; 037 038 039 040import com.unboundid.asn1.ASN1Element; 041import com.unboundid.asn1.ASN1Exception; 042import com.unboundid.asn1.ASN1Integer; 043import com.unboundid.asn1.ASN1OctetString; 044import com.unboundid.asn1.ASN1Sequence; 045import com.unboundid.ldap.sdk.Control; 046import com.unboundid.ldap.sdk.DecodeableControl; 047import com.unboundid.ldap.sdk.LDAPException; 048import com.unboundid.ldap.sdk.ResultCode; 049import com.unboundid.ldap.sdk.SearchResult; 050import com.unboundid.util.Debug; 051import com.unboundid.util.NotMutable; 052import com.unboundid.util.ThreadSafety; 053import com.unboundid.util.ThreadSafetyLevel; 054 055import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 056 057 058 059/** 060 * This class provides an implementation of the simple paged results control as 061 * defined in <A HREF="http://www.ietf.org/rfc/rfc2696.txt">RFC 2696</A>. It 062 * allows the client to iterate through a potentially large set of search 063 * results in subsets of a specified number of entries (i.e., "pages"). 064 * <BR><BR> 065 * The same control encoding is used for both the request control sent by 066 * clients and the response control returned by the server. It may contain 067 * two elements: 068 * <UL> 069 * <LI>Size -- In a request control, this provides the requested page size, 070 * which is the maximum number of entries that the server should return 071 * in the next iteration of the search. In a response control, it is an 072 * estimate of the total number of entries that match the search 073 * criteria.</LI> 074 * <LI>Cookie -- A token which is used by the server to keep track of its 075 * position in the set of search results. The first request sent by the 076 * client should not include a cookie, and the last response sent by the 077 * server should not include a cookie. For all other intermediate search 078 * requests and responses, the server will include a cookie value in its 079 * response that the client should include in its next request.</LI> 080 * </UL> 081 * When the client wishes to use the paged results control, the first search 082 * request should include a version of the paged results request control that 083 * was created with a requested page size but no cookie. The corresponding 084 * response from the server will include a version of the paged results control 085 * that may include an estimate of the total number of matching entries, and 086 * may also include a cookie. The client should include this cookie in the 087 * next request (with the same set of search criteria) to retrieve the next page 088 * of results. This process should continue until the response control returned 089 * by the server does not include a cookie, which indicates that the end of the 090 * result set has been reached. 091 * <BR><BR> 092 * Note that the simple paged results control is similar to the 093 * {@link VirtualListViewRequestControl} in that both allow the client to 094 * request that only a portion of the result set be returned at any one time. 095 * However, there are significant differences between them, including: 096 * <UL> 097 * <LI>In order to use the virtual list view request control, it is also 098 * necessary to use the {@link ServerSideSortRequestControl} to ensure 099 * that the entries are sorted. This is not a requirement for the 100 * simple paged results control.</LI> 101 * <LI>The simple paged results control may only be used to iterate 102 * sequentially through the set of search results. The virtual list view 103 * control can retrieve pages out of order, can retrieve overlapping 104 * pages, and can re-request pages that it had already retrieved.</LI> 105 * </UL> 106 * <H2>Example</H2> 107 * The following example demonstrates the use of the simple paged results 108 * control. It will iterate through all users, retrieving up to 10 entries at a 109 * time: 110 * <PRE> 111 * // Perform a search to retrieve all users in the server, but only retrieving 112 * // ten at a time. 113 * int numSearches = 0; 114 * int totalEntriesReturned = 0; 115 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com", 116 * SearchScope.SUB, Filter.createEqualityFilter("objectClass", "person")); 117 * ASN1OctetString resumeCookie = null; 118 * while (true) 119 * { 120 * searchRequest.setControls( 121 * new SimplePagedResultsControl(10, resumeCookie)); 122 * SearchResult searchResult = connection.search(searchRequest); 123 * numSearches++; 124 * totalEntriesReturned += searchResult.getEntryCount(); 125 * for (SearchResultEntry e : searchResult.getSearchEntries()) 126 * { 127 * // Do something with each entry... 128 * } 129 * 130 * LDAPTestUtils.assertHasControl(searchResult, 131 * SimplePagedResultsControl.PAGED_RESULTS_OID); 132 * SimplePagedResultsControl responseControl = 133 * SimplePagedResultsControl.get(searchResult); 134 * if (responseControl.moreResultsToReturn()) 135 * { 136 * // The resume cookie can be included in the simple paged results 137 * // control included in the next search to get the next page of results. 138 * resumeCookie = responseControl.getCookie(); 139 * } 140 * else 141 * { 142 * break; 143 * } 144 * } 145 * </PRE> 146 */ 147@NotMutable() 148@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 149public final class SimplePagedResultsControl 150 extends Control 151 implements DecodeableControl 152{ 153 /** 154 * The OID (1.2.840.113556.1.4.319) for the paged results control. 155 */ 156 public static final String PAGED_RESULTS_OID = "1.2.840.113556.1.4.319"; 157 158 159 160 /** 161 * The serial version UID for this serializable class. 162 */ 163 private static final long serialVersionUID = 2186787148024999291L; 164 165 166 167 // The encoded cookie returned from the server (for a response control) or 168 // that should be included in the next request to the server (for a request 169 // control). 170 private final ASN1OctetString cookie; 171 172 // The maximum requested page size (for a request control), or the estimated 173 // total result set size (for a response control). 174 private final int size; 175 176 177 178 /** 179 * Creates a new empty control instance that is intended to be used only for 180 * decoding controls via the {@code DecodeableControl} interface. 181 */ 182 SimplePagedResultsControl() 183 { 184 size = 0; 185 cookie = new ASN1OctetString(); 186 } 187 188 189 190 /** 191 * Creates a new paged results control with the specified page size. This 192 * version of the constructor should only be used when creating the first 193 * search as part of the set of paged results. Subsequent searches to 194 * retrieve additional pages should use the response control returned by the 195 * server in their next request, until the response control returned by the 196 * server does not include a cookie. 197 * 198 * @param pageSize The maximum number of entries that the server should 199 * return in the first page. 200 */ 201 public SimplePagedResultsControl(final int pageSize) 202 { 203 super(PAGED_RESULTS_OID, false, encodeValue(pageSize, null)); 204 205 size = pageSize; 206 cookie = new ASN1OctetString(); 207 } 208 209 210 211 /** 212 * Creates a new paged results control with the specified page size. This 213 * version of the constructor should only be used when creating the first 214 * search as part of the set of paged results. Subsequent searches to 215 * retrieve additional pages should use the response control returned by the 216 * server in their next request, until the response control returned by the 217 * server does not include a cookie. 218 * 219 * @param pageSize The maximum number of entries that the server should 220 * return in the first page. 221 * @param isCritical Indicates whether this control should be marked 222 * critical. 223 */ 224 public SimplePagedResultsControl(final int pageSize, final boolean isCritical) 225 { 226 super(PAGED_RESULTS_OID, isCritical, encodeValue(pageSize, null)); 227 228 size = pageSize; 229 cookie = new ASN1OctetString(); 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 next page of the results. 242 * @param cookie The cookie provided by the server after returning the 243 * previous page of results, or {@code null} if this request 244 * will retrieve the first page of results. 245 */ 246 public SimplePagedResultsControl(final int pageSize, 247 final ASN1OctetString cookie) 248 { 249 super(PAGED_RESULTS_OID, false, encodeValue(pageSize, cookie)); 250 251 size = pageSize; 252 253 if (cookie == null) 254 { 255 this.cookie = new ASN1OctetString(); 256 } 257 else 258 { 259 this.cookie = cookie; 260 } 261 } 262 263 264 265 /** 266 * Creates a new paged results control with the specified page size and the 267 * provided cookie. This version of the constructor should be used to 268 * continue iterating through an existing set of results, but potentially 269 * using a different page size. 270 * 271 * @param pageSize The maximum number of entries that the server should 272 * return in the first page. 273 * @param cookie The cookie provided by the server after returning the 274 * previous page of results, or {@code null} if this 275 * request will retrieve the first page of results. 276 * @param isCritical Indicates whether this control should be marked 277 * critical. 278 */ 279 public SimplePagedResultsControl(final int pageSize, 280 final ASN1OctetString cookie, 281 final boolean isCritical) 282 { 283 super(PAGED_RESULTS_OID, isCritical, encodeValue(pageSize, cookie)); 284 285 size = pageSize; 286 287 if (cookie == null) 288 { 289 this.cookie = new ASN1OctetString(); 290 } 291 else 292 { 293 this.cookie = cookie; 294 } 295 } 296 297 298 299 /** 300 * Creates a new paged results control from the control with the provided set 301 * of information. This should be used to decode the paged results response 302 * control returned by the server with a page of results. 303 * 304 * @param oid The OID for the control. 305 * @param isCritical Indicates whether the control should be marked 306 * critical. 307 * @param value The encoded value for the control. This may be 308 * {@code null} if no value was provided. 309 * 310 * @throws LDAPException If the provided control cannot be decoded as a 311 * simple paged results control. 312 */ 313 public SimplePagedResultsControl(final String oid, final boolean isCritical, 314 final ASN1OctetString value) 315 throws LDAPException 316 { 317 super(oid, isCritical, value); 318 319 if (value == null) 320 { 321 throw new LDAPException(ResultCode.DECODING_ERROR, 322 ERR_PAGED_RESULTS_NO_VALUE.get()); 323 } 324 325 final ASN1Sequence valueSequence; 326 try 327 { 328 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 329 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 330 } 331 catch (final ASN1Exception ae) 332 { 333 Debug.debugException(ae); 334 throw new LDAPException(ResultCode.DECODING_ERROR, 335 ERR_PAGED_RESULTS_VALUE_NOT_SEQUENCE.get(ae), ae); 336 } 337 338 final ASN1Element[] valueElements = valueSequence.elements(); 339 if (valueElements.length != 2) 340 { 341 throw new LDAPException(ResultCode.DECODING_ERROR, 342 ERR_PAGED_RESULTS_INVALID_ELEMENT_COUNT.get( 343 valueElements.length)); 344 } 345 346 try 347 { 348 size = ASN1Integer.decodeAsInteger(valueElements[0]).intValue(); 349 } 350 catch (final ASN1Exception ae) 351 { 352 Debug.debugException(ae); 353 throw new LDAPException(ResultCode.DECODING_ERROR, 354 ERR_PAGED_RESULTS_FIRST_NOT_INTEGER.get(ae), ae); 355 } 356 357 cookie = ASN1OctetString.decodeAsOctetString(valueElements[1]); 358 } 359 360 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override() 366 public SimplePagedResultsControl 367 decodeControl(final String oid, final boolean isCritical, 368 final ASN1OctetString value) 369 throws LDAPException 370 { 371 return new SimplePagedResultsControl(oid, isCritical, value); 372 } 373 374 375 376 /** 377 * Extracts a simple paged results response control from the provided result. 378 * 379 * @param result The result from which to retrieve the simple paged results 380 * response control. 381 * 382 * @return The simple paged results response control contained in the 383 * provided result, or {@code null} if the result did not contain a 384 * simple paged results response control. 385 * 386 * @throws LDAPException If a problem is encountered while attempting to 387 * decode the simple paged results response control 388 * contained in the provided result. 389 */ 390 public static SimplePagedResultsControl get(final SearchResult result) 391 throws LDAPException 392 { 393 final Control c = result.getResponseControl(PAGED_RESULTS_OID); 394 if (c == null) 395 { 396 return null; 397 } 398 399 if (c instanceof SimplePagedResultsControl) 400 { 401 return (SimplePagedResultsControl) c; 402 } 403 else 404 { 405 return new SimplePagedResultsControl(c.getOID(), c.isCritical(), 406 c.getValue()); 407 } 408 } 409 410 411 412 /** 413 * Encodes the provided information into an octet string that can be used as 414 * the value for this control. 415 * 416 * @param pageSize The maximum number of entries that the server should 417 * return in the next page of the results. 418 * @param cookie The cookie provided by the server after returning the 419 * previous page of results, or {@code null} if this request 420 * will retrieve the first page of results. 421 * 422 * @return An ASN.1 octet string that can be used as the value for this 423 * control. 424 */ 425 private static ASN1OctetString encodeValue(final int pageSize, 426 final ASN1OctetString cookie) 427 { 428 final ASN1Element[] valueElements; 429 if (cookie == null) 430 { 431 valueElements = new ASN1Element[] 432 { 433 new ASN1Integer(pageSize), 434 new ASN1OctetString() 435 }; 436 } 437 else 438 { 439 valueElements = new ASN1Element[] 440 { 441 new ASN1Integer(pageSize), 442 cookie 443 }; 444 } 445 446 return new ASN1OctetString(new ASN1Sequence(valueElements).encode()); 447 } 448 449 450 451 /** 452 * Retrieves the size for this paged results control. For a request control, 453 * it may be used to specify the number of entries that should be included in 454 * the next page of results. For a response control, it may be used to 455 * specify the estimated number of entries in the complete result set. 456 * 457 * @return The size for this paged results control. 458 */ 459 public int getSize() 460 { 461 return size; 462 } 463 464 465 466 /** 467 * Retrieves the cookie for this control, which may be used in a subsequent 468 * request to resume reading entries from the next page of results. The 469 * value should have a length of zero when used to retrieve the first page of 470 * results for a given search, and also in the response from the server when 471 * there are no more entries to send. It should be non-empty for all other 472 * conditions. 473 * 474 * @return The cookie for this control, or {@code null} if there is none. 475 */ 476 public ASN1OctetString getCookie() 477 { 478 return cookie; 479 } 480 481 482 483 /** 484 * Indicates whether there are more results to return as part of this search. 485 * 486 * @return {@code true} if there are more results to return, or 487 * {@code false} if not. 488 */ 489 public boolean moreResultsToReturn() 490 { 491 return (cookie.getValue().length > 0); 492 } 493 494 495 496 /** 497 * {@inheritDoc} 498 */ 499 @Override() 500 public String getControlName() 501 { 502 return INFO_CONTROL_NAME_PAGED_RESULTS.get(); 503 } 504 505 506 507 /** 508 * {@inheritDoc} 509 */ 510 @Override() 511 public void toString(final StringBuilder buffer) 512 { 513 buffer.append("SimplePagedResultsControl(pageSize="); 514 buffer.append(size); 515 buffer.append(", isCritical="); 516 buffer.append(isCritical()); 517 buffer.append(')'); 518 } 519}