001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2024 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) 2007-2024 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 java.util.LinkedHashMap; 041import java.util.List; 042import java.util.Map; 043 044import com.unboundid.asn1.ASN1Element; 045import com.unboundid.asn1.ASN1Integer; 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.asn1.ASN1Sequence; 048import com.unboundid.ldap.sdk.Control; 049import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.ResultCode; 052import com.unboundid.util.Base64; 053import com.unboundid.util.Debug; 054import com.unboundid.util.NotMutable; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.Nullable; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.Validator; 061import com.unboundid.util.json.JSONField; 062import com.unboundid.util.json.JSONNumber; 063import com.unboundid.util.json.JSONObject; 064import com.unboundid.util.json.JSONString; 065import com.unboundid.util.json.JSONValue; 066 067import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 068 069 070 071/** 072 * This class provides an implementation of the LDAP virtual list view (VLV) 073 * request control as defined in draft-ietf-ldapext-ldapv3-vlv. This control 074 * may be used to retrieve arbitrary "pages" of entries from the complete set of 075 * search results. It is similar to the {@link SimplePagedResultsControl}, with 076 * the exception that the simple paged results control requires scrolling 077 * through the results in sequential order, while the VLV control allows 078 * starting and resuming at any arbitrary point in the result set. The starting 079 * point may be specified using either a positional offset, or based on the 080 * first entry with a value that is greater than or equal to a specified value. 081 * <BR><BR> 082 * When the start of the result set is to be specified using an offset, then the 083 * virtual list view request control should include the following elements: 084 * <UL> 085 * <LI>{@code targetOffset} -- The position in the result set of the entry to 086 * target for the next page of results to return. Note that the offset is 087 * one-based (so the first entry has offset 1, the second entry has offset 088 * 2, etc.).</LI> 089 * <LI>{@code beforeCount} -- The number of entries before the entry specified 090 * as the target offset that should be retrieved.</LI> 091 * <LI>{@code afterCount} -- The number of entries after the entry specified 092 * as the target offset that should be retrieved.</LI> 093 * <LI>{@code contentCount} -- The estimated total number of entries that 094 * are in the total result set. This should be zero for the first request 095 * in a VLV search sequence, but should be the value returned by the 096 * server in the corresponding response control for subsequent searches as 097 * part of the VLV sequence.</LI> 098 * <LI>{@code contextID} -- This is an optional cookie that may be used to 099 * help the server resume processing on a VLV search. It should be absent 100 * from the initial request, but for subsequent requests should be the 101 * value returned in the previous VLV response control.</LI> 102 * </UL> 103 * When the start of the result set is to be specified using a search string, 104 * then the virtual list view request control should include the following 105 * elements: 106 * <UL> 107 * <LI>{@code assertionValue} -- The value that specifies the start of the 108 * page of results to retrieve. The target entry will be the first entry 109 * in which the value for the primary sort attribute is greater than or 110 * equal to this assertion value.</LI> 111 * <LI>{@code beforeCount} -- The number of entries before the entry specified 112 * by the assertion value that should be retrieved.</LI> 113 * <LI>{@code afterCount} -- The number of entries after the entry specified 114 * by the assertion value that should be retrieved.</LI> 115 * <LI>{@code contextID} -- This is an optional cookie that may be used to 116 * help the server resume processing on a VLV search. It should be absent 117 * from the initial request, but for subsequent requests should be the 118 * value returned in the previous VLV response control.</LI> 119 * </UL> 120 * Note that the virtual list view request control may only be included in a 121 * search request if that search request also includes the 122 * {@link ServerSideSortRequestControl}. This is necessary to ensure that a 123 * consistent order is used for the resulting entries. 124 * <BR><BR> 125 * If the search is successful, then the search result done response may include 126 * a {@link VirtualListViewResponseControl} to provide information about the 127 * state of the virtual list view processing. 128 * <BR><BR> 129 * <H2>Example</H2> 130 * The following example demonstrates the use of the virtual list view request 131 * control to iterate through all users, retrieving up to 10 entries at a time: 132 * <PRE> 133 * // Perform a search to retrieve all users in the server, but only retrieving 134 * // ten at a time. Ensure that the users are sorted in ascending order by 135 * // last name, then first name. 136 * int numSearches = 0; 137 * int totalEntriesReturned = 0; 138 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com", 139 * SearchScope.SUB, Filter.createEqualityFilter("objectClass", "person")); 140 * int vlvOffset = 1; 141 * int vlvContentCount = 0; 142 * ASN1OctetString vlvContextID = null; 143 * while (true) 144 * { 145 * // Note that the VLV control always requires the server-side sort 146 * // control. 147 * searchRequest.setControls( 148 * new ServerSideSortRequestControl(new SortKey("sn"), 149 * new SortKey("givenName")), 150 * new VirtualListViewRequestControl(vlvOffset, 0, 9, vlvContentCount, 151 * vlvContextID)); 152 * SearchResult searchResult = connection.search(searchRequest); 153 * numSearches++; 154 * totalEntriesReturned += searchResult.getEntryCount(); 155 * for (SearchResultEntry e : searchResult.getSearchEntries()) 156 * { 157 * // Do something with each entry... 158 * } 159 * 160 * LDAPTestUtils.assertHasControl(searchResult, 161 * VirtualListViewResponseControl.VIRTUAL_LIST_VIEW_RESPONSE_OID); 162 * VirtualListViewResponseControl vlvResponseControl = 163 * VirtualListViewResponseControl.get(searchResult); 164 * vlvContentCount = vlvResponseControl.getContentCount(); 165 * vlvOffset += 10; 166 * vlvContextID = vlvResponseControl.getContextID(); 167 * if (vlvOffset > vlvContentCount) 168 * { 169 * break; 170 * } 171 * } 172 * </PRE> 173 */ 174@NotMutable() 175@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 176public final class VirtualListViewRequestControl 177 extends Control 178{ 179 /** 180 * The OID (2.16.840.1.113730.3.4.9) for the virtual list view request 181 * control. 182 */ 183 @NotNull public static final String VIRTUAL_LIST_VIEW_REQUEST_OID = 184 "2.16.840.1.113730.3.4.9"; 185 186 187 188 /** 189 * The BER type that will be used for the target element when the target is 190 * specified by offset. 191 */ 192 private static final byte TARGET_TYPE_OFFSET = (byte) 0xA0; 193 194 195 196 /** 197 * The BER type that will be used for the target element when the target is 198 * specified by an assertion value. 199 */ 200 private static final byte TARGET_TYPE_GREATER_OR_EQUAL = (byte) 0x81; 201 202 203 204 /** 205 * The name of the field used to hold the after count in the JSON 206 * representation of this control. 207 */ 208 @NotNull private static final String JSON_FIELD_AFTER_COUNT = "after-count"; 209 210 211 212 /** 213 * The name of the field used to hold the assertion value in the JSON 214 * representation of this control. 215 */ 216 @NotNull private static final String JSON_FIELD_ASSERTION_VALUE = 217 "assertion-value"; 218 219 220 221 /** 222 * The name of the field used to hold the before count in the JSON 223 * representation of this control. 224 */ 225 @NotNull private static final String JSON_FIELD_BEFORE_COUNT = "before-count"; 226 227 228 229 /** 230 * The name of the field used to hold the content count in the JSON 231 * representation of this control. 232 */ 233 @NotNull private static final String JSON_FIELD_CONTENT_COUNT = 234 "content-count"; 235 236 237 238 /** 239 * The name of the field used to hold the context ID in the JSON 240 * representation of this control. 241 */ 242 @NotNull private static final String JSON_FIELD_CONTEXT_ID = "context-id"; 243 244 245 246 /** 247 * The name of the field used to hold the target offset in the JSON 248 * representation of this control. 249 */ 250 @NotNull private static final String JSON_FIELD_TARGET_OFFSET = 251 "target-offset"; 252 253 254 255 /** 256 * The serial version UID for this serializable class. 257 */ 258 private static final long serialVersionUID = 4348423177859960815L; 259 260 261 262 // The assertion value that will be used to identify the start of the 263 // requested page of results for a greater-or-equal target type. 264 @Nullable private final ASN1OctetString assertionValue; 265 266 // The context ID that may be used to help the server continue in the same 267 // result set for subsequent searches. 268 @Nullable private final ASN1OctetString contextID; 269 270 // The maximum number of entries to return after the target entry. 271 private final int afterCount; 272 273 // The maximum number of entries to return before the target entry. 274 private final int beforeCount; 275 276 // The estimated number of entries in the complete result set. 277 private final int contentCount; 278 279 // The position of the entry at the start of the requested page of results for 280 // an offset-based target type. 281 private final int targetOffset; 282 283 284 285 /** 286 * Creates a new virtual list view request control that will identify the 287 * beginning of the result set by a target offset. It will be marked 288 * critical. 289 * 290 * @param targetOffset The position of the entry that should be used as the 291 * start of the result set. 292 * @param beforeCount The maximum number of entries that should be returned 293 * before the entry with the specified target offset. 294 * @param afterCount The maximum number of entries that should be returned 295 * after the entry with the specified target offset. 296 * @param contentCount The estimated number of entries in the result set. 297 * For the first request in a series of searches with 298 * the VLV control, it should be zero. For subsequent 299 * searches in the VLV sequence, it should be the 300 * content count included in the response control from 301 * the previous search. 302 * @param contextID The context ID that may be used to help the server 303 * continue in the same result set for subsequent 304 * searches. For the first request in a series of 305 * searches with the VLV control, it should be 306 * {@code null}. For subsequent searches in the VLV 307 * sequence, it should be the (possibly {@code null}) 308 * context ID included in the response control from the 309 * previous search. 310 */ 311 public VirtualListViewRequestControl(final int targetOffset, 312 final int beforeCount, final int afterCount, 313 final int contentCount, 314 @Nullable final ASN1OctetString contextID) 315 { 316 this(targetOffset, beforeCount, afterCount, contentCount, contextID, true); 317 } 318 319 320 321 /** 322 * Creates a new virtual list view request control that will identify the 323 * beginning of the result set by an assertion value. It will be marked 324 * critical. 325 * 326 * @param assertionValue The assertion value that will be used to identify 327 * the start of the result set. The target entry will 328 * be the first entry with a value for the primary 329 * sort attribute that is greater than or equal to 330 * this assertion value. It must not be {@code null}. 331 * @param beforeCount The maximum number of entries that should be 332 * returned before the first entry with a value 333 * greater than or equal to the provided assertion 334 * value. 335 * @param afterCount The maximum number of entries that should be 336 * returned after the first entry with a value 337 * greater than or equal to the provided assertion 338 * value. 339 * @param contextID The context ID that may be used to help the server 340 * continue in the same result set for subsequent 341 * searches. For the first request in a series of 342 * searches with the VLV control, it should be 343 * {@code null}. For subsequent searches in the VLV 344 * sequence, it should be the (possibly {@code null}) 345 * context ID included in the response control from 346 * the previous search. 347 */ 348 public VirtualListViewRequestControl(@NotNull final String assertionValue, 349 final int beforeCount, final int afterCount, 350 @Nullable final ASN1OctetString contextID) 351 { 352 this(new ASN1OctetString(assertionValue), beforeCount, afterCount, 353 contextID, true); 354 } 355 356 357 358 /** 359 * Creates a new virtual list view request control that will identify the 360 * beginning of the result set by an assertion value. It will be marked 361 * critical. 362 * 363 * @param assertionValue The assertion value that will be used to identify 364 * the start of the result set. The target entry will 365 * be the first entry with a value for the primary 366 * sort attribute that is greater than or equal to 367 * this assertion value. It must not be {@code null}. 368 * @param beforeCount The maximum number of entries that should be 369 * returned before the first entry with a value 370 * greater than or equal to the provided assertion 371 * value. 372 * @param afterCount The maximum number of entries that should be 373 * returned after the first entry with a value 374 * greater than or equal to the provided assertion 375 * value. 376 * @param contextID The context ID that may be used to help the server 377 * continue in the same result set for subsequent 378 * searches. For the first request in a series of 379 * searches with the VLV control, it should be 380 * {@code null}. For subsequent searches in the VLV 381 * sequence, it should be the (possibly {@code null}) 382 * context ID included in the response control from 383 * the previous search. 384 */ 385 public VirtualListViewRequestControl(@NotNull final byte[] assertionValue, 386 final int beforeCount, final int afterCount, 387 @Nullable final ASN1OctetString contextID) 388 { 389 this(new ASN1OctetString(assertionValue), beforeCount, afterCount, 390 contextID, true); 391 } 392 393 394 395 /** 396 * Creates a new virtual list view request control that will identify the 397 * beginning of the result set by an assertion value. It will be marked 398 * critical. 399 * 400 * @param assertionValue The assertion value that will be used to identify 401 * the start of the result set. The target entry will 402 * be the first entry with a value for the primary 403 * sort attribute that is greater than or equal to 404 * this assertion value. It must not be {@code null}. 405 * @param beforeCount The maximum number of entries that should be 406 * returned before the first entry with a value 407 * greater than or equal to the provided assertion 408 * value. 409 * @param afterCount The maximum number of entries that should be 410 * returned after the first entry with a value 411 * greater than or equal to the provided assertion 412 * value. 413 * @param contextID The context ID that may be used to help the server 414 * continue in the same result set for subsequent 415 * searches. For the first request in a series of 416 * searches with the VLV control, it should be 417 * {@code null}. For subsequent searches in the VLV 418 * sequence, it should be the (possibly {@code null}) 419 * context ID included in the response control from 420 * the previous search. 421 */ 422 public VirtualListViewRequestControl( 423 @NotNull final ASN1OctetString assertionValue, 424 final int beforeCount, final int afterCount, 425 @Nullable final ASN1OctetString contextID) 426 { 427 this(assertionValue, beforeCount, afterCount, contextID, true); 428 } 429 430 431 432 /** 433 * Creates a new virtual list view request control that will identify the 434 * beginning of the result set by a target offset. 435 * 436 * @param targetOffset The position of the entry that should be used as the 437 * start of the result set. 438 * @param beforeCount The maximum number of entries that should be returned 439 * before the entry with the specified target offset. 440 * @param afterCount The maximum number of entries that should be returned 441 * after the entry with the specified target offset. 442 * @param contentCount The estimated number of entries in the result set. 443 * For the first request in a series of searches with 444 * the VLV control, it should be zero. For subsequent 445 * searches in the VLV sequence, it should be the 446 * content count included in the response control from 447 * the previous search. 448 * @param contextID The context ID that may be used to help the server 449 * continue in the same result set for subsequent 450 * searches. For the first request in a series of 451 * searches with the VLV control, it should be 452 * {@code null}. For subsequent searches in the VLV 453 * sequence, it should be the (possibly {@code null}) 454 * context ID included in the response control from the 455 * previous search. 456 * @param isCritical Indicates whether this control should be marked 457 * critical. 458 */ 459 public VirtualListViewRequestControl(final int targetOffset, 460 final int beforeCount, final int afterCount, 461 final int contentCount, 462 @Nullable final ASN1OctetString contextID, 463 final boolean isCritical) 464 { 465 super(VIRTUAL_LIST_VIEW_REQUEST_OID, isCritical, 466 encodeValue(targetOffset, beforeCount, afterCount, contentCount, 467 contextID)); 468 469 this.targetOffset = targetOffset; 470 this.beforeCount = beforeCount; 471 this.afterCount = afterCount; 472 this.contentCount = contentCount; 473 this.contextID = contextID; 474 475 assertionValue = null; 476 } 477 478 479 480 /** 481 * Creates a new virtual list view request control that will identify the 482 * beginning of the result set by an assertion value. It will be marked 483 * critical. 484 * 485 * @param assertionValue The assertion value that will be used to identify 486 * the start of the result set. The target entry will 487 * be the first entry with a value for the primary 488 * sort attribute that is greater than or equal to 489 * this assertion value. It must not be {@code null}. 490 * @param beforeCount The maximum number of entries that should be 491 * returned before the first entry with a value 492 * greater than or equal to the provided assertion 493 * value. 494 * @param afterCount The maximum number of entries that should be 495 * returned after the first entry with a value 496 * greater than or equal to the provided assertion 497 * value. 498 * @param contextID The context ID that may be used to help the server 499 * continue in the same result set for subsequent 500 * searches. For the first request in a series of 501 * searches with the VLV control, it should be 502 * {@code null}. For subsequent searches in the VLV 503 * sequence, it should be the (possibly {@code null}) 504 * context ID included in the response control from 505 * the previous search. 506 * @param isCritical Indicates whether this control should be marked 507 * critical. 508 */ 509 public VirtualListViewRequestControl(@NotNull final String assertionValue, 510 final int beforeCount, final int afterCount, 511 @Nullable final ASN1OctetString contextID, 512 final boolean isCritical) 513 { 514 this(new ASN1OctetString(assertionValue), beforeCount, afterCount, 515 contextID, isCritical); 516 } 517 518 519 520 /** 521 * Creates a new virtual list view request control that will identify the 522 * beginning of the result set by an assertion value. It will be marked 523 * critical. 524 * 525 * @param assertionValue The assertion value that will be used to identify 526 * the start of the result set. The target entry will 527 * be the first entry with a value for the primary 528 * sort attribute that is greater than or equal to 529 * this assertion value. It must not be {@code null}. 530 * @param beforeCount The maximum number of entries that should be 531 * returned before the first entry with a value 532 * greater than or equal to the provided assertion 533 * value. 534 * @param afterCount The maximum number of entries that should be 535 * returned after the first entry with a value 536 * greater than or equal to the provided assertion 537 * value. 538 * @param contextID The context ID that may be used to help the server 539 * continue in the same result set for subsequent 540 * searches. For the first request in a series of 541 * searches with the VLV control, it should be 542 * {@code null}. For subsequent searches in the VLV 543 * sequence, it should be the (possibly {@code null}) 544 * context ID included in the response control from 545 * the previous search. 546 * @param isCritical Indicates whether this control should be marked 547 * critical. 548 */ 549 public VirtualListViewRequestControl(@NotNull final byte[] assertionValue, 550 final int beforeCount, final int afterCount, 551 @Nullable final ASN1OctetString contextID, 552 final boolean isCritical) 553 { 554 this(new ASN1OctetString(assertionValue), beforeCount, afterCount, 555 contextID, isCritical); 556 } 557 558 559 560 /** 561 * Creates a new virtual list view request control that will identify the 562 * beginning of the result set by an assertion value. It will be marked 563 * critical. 564 * 565 * @param assertionValue The assertion value that will be used to identify 566 * the start of the result set. The target entry will 567 * be the first entry with a value for the primary 568 * sort attribute that is greater than or equal to 569 * this assertion value. It must not be {@code null}. 570 * @param beforeCount The maximum number of entries that should be 571 * returned before the first entry with a value 572 * greater than or equal to the provided assertion 573 * value. 574 * @param afterCount The maximum number of entries that should be 575 * returned after the first entry with a value 576 * greater than or equal to the provided assertion 577 * value. 578 * @param contextID The context ID that may be used to help the server 579 * continue in the same result set for subsequent 580 * searches. For the first request in a series of 581 * searches with the VLV control, it should be 582 * {@code null}. For subsequent searches in the VLV 583 * sequence, it should be the (possibly {@code null}) 584 * context ID included in the response control from 585 * the previous search. 586 * @param isCritical Indicates whether this control should be marked 587 * critical. 588 */ 589 public VirtualListViewRequestControl( 590 @NotNull final ASN1OctetString assertionValue, 591 final int beforeCount, final int afterCount, 592 @Nullable final ASN1OctetString contextID, 593 final boolean isCritical) 594 { 595 super(VIRTUAL_LIST_VIEW_REQUEST_OID, isCritical, 596 encodeValue(assertionValue, beforeCount, afterCount, contextID)); 597 598 this.assertionValue = assertionValue; 599 this.beforeCount = beforeCount; 600 this.afterCount = afterCount; 601 this.contextID = contextID; 602 603 targetOffset = -1; 604 contentCount = -1; 605 } 606 607 608 609 /** 610 * Creates a new virtual list view request control which is decoded from the 611 * provided generic control. 612 * 613 * @param control The generic control to be decoded as a virtual list view 614 * request control. 615 * 616 * @throws LDAPException If the provided control cannot be decoded as a 617 * virtual list view request control. 618 */ 619 public VirtualListViewRequestControl(@NotNull final Control control) 620 throws LDAPException 621 { 622 super(control); 623 624 final ASN1OctetString value = control.getValue(); 625 if (value == null) 626 { 627 throw new LDAPException(ResultCode.DECODING_ERROR, 628 ERR_VLV_REQUEST_NO_VALUE.get()); 629 } 630 631 try 632 { 633 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 634 final ASN1Element[] elements = 635 ASN1Sequence.decodeAsSequence(valueElement).elements(); 636 637 beforeCount = ASN1Integer.decodeAsInteger(elements[0]).intValue(); 638 afterCount = ASN1Integer.decodeAsInteger(elements[1]).intValue(); 639 640 switch (elements[2].getType()) 641 { 642 case TARGET_TYPE_OFFSET: 643 assertionValue = null; 644 final ASN1Element[] offsetElements = 645 ASN1Sequence.decodeAsSequence(elements[2]).elements(); 646 targetOffset = 647 ASN1Integer.decodeAsInteger(offsetElements[0]).intValue(); 648 contentCount = 649 ASN1Integer.decodeAsInteger(offsetElements[1]).intValue(); 650 break; 651 652 case TARGET_TYPE_GREATER_OR_EQUAL: 653 assertionValue = ASN1OctetString.decodeAsOctetString(elements[2]); 654 targetOffset = -1; 655 contentCount = -1; 656 break; 657 658 default: 659 throw new LDAPException(ResultCode.DECODING_ERROR, 660 ERR_VLV_REQUEST_INVALID_ELEMENT_TYPE.get( 661 StaticUtils.toHex(elements[2].getType()))); 662 } 663 664 if (elements.length == 4) 665 { 666 contextID = ASN1OctetString.decodeAsOctetString(elements[3]); 667 } 668 else 669 { 670 contextID = null; 671 } 672 } 673 catch (final LDAPException le) 674 { 675 Debug.debugException(le); 676 throw le; 677 } 678 catch (final Exception e) 679 { 680 Debug.debugException(e); 681 throw new LDAPException(ResultCode.DECODING_ERROR, 682 ERR_VLV_REQUEST_CANNOT_DECODE.get(e), e); 683 } 684 } 685 686 687 688 /** 689 * Encodes the provided information into an octet string that can be used as 690 * the value for this control. 691 * 692 * @param targetOffset The position of the entry that should be used as the 693 * start of the result set. 694 * @param beforeCount The maximum number of entries that should be returned 695 * before the entry with the specified target offset. 696 * @param afterCount The maximum number of entries that should be returned 697 * after the entry with the specified target offset. 698 * @param contentCount The estimated number of entries in the result set. 699 * For the first request in a series of searches with 700 * the VLV control, it should be zero. For subsequent 701 * searches in the VLV sequence, it should be the 702 * content count included in the response control from 703 * the previous search. 704 * @param contextID The context ID that may be used to help the server 705 * continue in the same result set for subsequent 706 * searches. For the first request in a series of 707 * searches with the VLV control, it should be 708 * {@code null}. For subsequent searches in the VLV 709 * sequence, it should be the (possibly {@code null}) 710 * context ID included in the response control from the 711 * previous search. 712 * 713 * @return An ASN.1 octet string that can be used as the value for this 714 * control. 715 */ 716 @NotNull() 717 private static ASN1OctetString encodeValue(final int targetOffset, 718 final int beforeCount, final int afterCount, 719 final int contentCount, 720 @Nullable final ASN1OctetString contextID) 721 { 722 final ASN1Element[] targetElements = 723 { 724 new ASN1Integer(targetOffset), 725 new ASN1Integer(contentCount) 726 }; 727 728 final ASN1Element[] vlvElements; 729 if (contextID == null) 730 { 731 vlvElements = new ASN1Element[] 732 { 733 new ASN1Integer(beforeCount), 734 new ASN1Integer(afterCount), 735 new ASN1Sequence(TARGET_TYPE_OFFSET, targetElements) 736 }; 737 } 738 else 739 { 740 vlvElements = new ASN1Element[] 741 { 742 new ASN1Integer(beforeCount), 743 new ASN1Integer(afterCount), 744 new ASN1Sequence(TARGET_TYPE_OFFSET, targetElements), 745 contextID 746 }; 747 } 748 749 return new ASN1OctetString(new ASN1Sequence(vlvElements).encode()); 750 } 751 752 753 754 /** 755 * Encodes the provided information into an octet string that can be used as 756 * the value for this control. 757 * 758 * @param assertionValue The assertion value that will be used to identify 759 * the start of the result set. The target entry will 760 * be the first entry with a value for the primary 761 * sort attribute that is greater than or equal to 762 * this assertion value. 763 * @param beforeCount The maximum number of entries that should be 764 * returned before the first entry with a value 765 * greater than or equal to the provided assertion 766 * value. 767 * @param afterCount The maximum number of entries that should be 768 * returned after the first entry with a value 769 * greater than or equal to the provided assertion 770 * value. 771 * @param contextID The context ID that may be used to help the server 772 * continue in the same result set for subsequent 773 * searches. For the first request in a series of 774 * searches with the VLV control, it should be 775 * {@code null}. For subsequent searches in the VLV 776 * sequence, it should be the (possibly {@code null}) 777 * context ID included in the response control from 778 * the previous search. 779 * 780 * @return An ASN.1 octet string that can be used as the value for this 781 * control. 782 */ 783 @NotNull() 784 private static ASN1OctetString encodeValue( 785 @NotNull final ASN1OctetString assertionValue, 786 final int beforeCount, 787 final int afterCount, 788 @Nullable final ASN1OctetString contextID) 789 { 790 Validator.ensureNotNull(assertionValue); 791 792 final ASN1Element[] vlvElements; 793 if (contextID == null) 794 { 795 vlvElements = new ASN1Element[] 796 { 797 new ASN1Integer(beforeCount), 798 new ASN1Integer(afterCount), 799 new ASN1OctetString(TARGET_TYPE_GREATER_OR_EQUAL, 800 assertionValue.getValue()) 801 }; 802 } 803 else 804 { 805 vlvElements = new ASN1Element[] 806 { 807 new ASN1Integer(beforeCount), 808 new ASN1Integer(afterCount), 809 new ASN1OctetString(TARGET_TYPE_GREATER_OR_EQUAL, 810 assertionValue.getValue()), 811 contextID 812 }; 813 } 814 815 return new ASN1OctetString(new ASN1Sequence(vlvElements).encode()); 816 } 817 818 819 820 /** 821 * Retrieves the target offset position for this virtual list view request 822 * control, if applicable. 823 * 824 * @return The target offset position for this virtual list view request 825 * control, or -1 if the target is specified by an assertion value. 826 */ 827 public int getTargetOffset() 828 { 829 return targetOffset; 830 } 831 832 833 834 /** 835 * Retrieves the string representation of the assertion value for this virtual 836 * list view request control, if applicable. 837 * 838 * @return The string representation of the assertion value for this virtual 839 * list view request control, or {@code null} if the target is 840 * specified by offset. 841 */ 842 @Nullable() 843 public String getAssertionValueString() 844 { 845 if (assertionValue == null) 846 { 847 return null; 848 } 849 else 850 { 851 return assertionValue.stringValue(); 852 } 853 } 854 855 856 857 /** 858 * Retrieves the byte array representation of the assertion value for this 859 * virtual list view request control, if applicable. 860 * 861 * @return The byte array representation of the assertion value for this 862 * virtual list view request control, or {@code null} if the target 863 * is specified by offset. 864 */ 865 @Nullable() 866 public byte[] getAssertionValueBytes() 867 { 868 if (assertionValue == null) 869 { 870 return null; 871 } 872 else 873 { 874 return assertionValue.getValue(); 875 } 876 } 877 878 879 880 /** 881 * Retrieves the assertion value for this virtual list view request control, 882 * if applicable. 883 * 884 * @return The assertion value for this virtual list view request control, or 885 * {@code null} if the target is specified by offset. 886 */ 887 @Nullable() 888 public ASN1OctetString getAssertionValue() 889 { 890 return assertionValue; 891 } 892 893 894 895 /** 896 * Retrieves the number of entries that should be retrieved before the target 897 * entry. 898 * 899 * @return The number of entries that should be retrieved before the target 900 * entry. 901 */ 902 public int getBeforeCount() 903 { 904 return beforeCount; 905 } 906 907 908 909 /** 910 * Retrieves the number of entries that should be retrieved after the target 911 * entry. 912 * 913 * @return The number of entries that should be retrieved after the target 914 * entry. 915 */ 916 public int getAfterCount() 917 { 918 return afterCount; 919 } 920 921 922 923 /** 924 * Retrieves the estimated number of entries in the result set, if applicable. 925 * 926 * @return The estimated number of entries in the result set, zero if it 927 * is not known (for the first search in a sequence where the 928 * target is specified by offset), or -1 if the target is specified 929 * by an assertion value. 930 */ 931 public int getContentCount() 932 { 933 return contentCount; 934 } 935 936 937 938 /** 939 * Retrieves the context ID for this virtual list view request control, if 940 * available. 941 * 942 * @return The context ID for this virtual list view request control, or 943 * {@code null} if there is none. 944 */ 945 @Nullable() 946 public ASN1OctetString getContextID() 947 { 948 return contextID; 949 } 950 951 952 953 /** 954 * {@inheritDoc} 955 */ 956 @Override() 957 @NotNull() 958 public String getControlName() 959 { 960 return INFO_CONTROL_NAME_VLV_REQUEST.get(); 961 } 962 963 964 965 /** 966 * Retrieves a representation of this virtual list view request control as a 967 * JSON object. The JSON object uses the following fields: 968 * <UL> 969 * <LI> 970 * {@code oid} -- A mandatory string field whose value is the object 971 * identifier for this control. For the virtual list view request 972 * control, the OID is "2.16.840.1.113730.3.4.9". 973 * </LI> 974 * <LI> 975 * {@code control-name} -- An optional string field whose value is a 976 * human-readable name for this control. This field is only intended for 977 * descriptive purposes, and when decoding a control, the {@code oid} 978 * field should be used to identify the type of control. 979 * </LI> 980 * <LI> 981 * {@code criticality} -- A mandatory Boolean field used to indicate 982 * whether this control is considered critical. 983 * </LI> 984 * <LI> 985 * {@code value-base64} -- An optional string field whose value is a 986 * base64-encoded representation of the raw value for this virtual list 987 * view request control. Exactly one of the {@code value-base64} and 988 * {@code value-json} fields must be present. 989 * </LI> 990 * <LI> 991 * {@code value-json} -- An optional JSON object field whose value is a 992 * user-friendly representation of the value for this virtual list view 993 * request control. Exactly one of the {@code value-base64} and 994 * {@code value-json} fields must be present, and if the 995 * {@code value-json} field is used, then it will use the following 996 * fields: 997 * <UL> 998 * <LI> 999 * {@code target-offset} -- An optional integer field whose value is 1000 * the offset of the target entry within the result set, with the 1001 * first entry in the result set having an offset value of one. 1002 * Exactly one of the {@code target-offset} and 1003 * {@code assertion-value} fields must be provided. 1004 * </LI> 1005 * <LI> 1006 * {@code assertion-value} -- An optional string field that indicates 1007 * that the target entry should be the first one in the result set in 1008 * which the value of the primary sort attribute is greater than or 1009 * equal to the provided assertion value. Exactly one of the 1010 * {@code target-offset} and {@code assertion-value} fields must be 1011 * provided. 1012 * </LI> 1013 * <LI> 1014 * {@code before-count} -- A mandatory integer field whose value is 1015 * the maximum number of entries before the target entry that should 1016 * be included in the page of results to return. 1017 * </LI> 1018 * <LI> 1019 * {@code after-count} -- A mandatory integer field whose value is 1020 * the maximum number of entries after the target entry that should 1021 * be included in the page of results to return. 1022 * </LI> 1023 * <LI> 1024 * {@code content-count} -- An optional integer field that represents 1025 * the estimated number of entries in the entire result set. This 1026 * field may only be present when the {@code target-offset} field is 1027 * also provided, and its value may be absent or zero when retrieving 1028 * the first page of results, and it should be the 1029 * {@code content-count} value returned in the previous virtual list 1030 * view response control for all subsequent pages. 1031 * </LI> 1032 * <LI> 1033 * {@code context-id} -- An optional string field that represents an 1034 * opaque cookie that may be used to help the server continue a series 1035 * of searches using the virtual list view request control. For the 1036 * first search in a series, this should be absent. For all 1037 * subsequent requests in the series, it should be the 1038 * {@code context-id} value (if any) included in the response control 1039 * from the previous page of the series. The context ID value used in 1040 * the JSON representation of the control will be a base64-encoded 1041 * representation of the raw cookie value that would be used in the 1042 * LDAP representation of the control, and it must be treated as an 1043 * opaque blob by the client. 1044 * </LI> 1045 * </UL> 1046 * </LI> 1047 * </UL> 1048 * 1049 * @return A JSON object that contains a representation of this control. 1050 */ 1051 @Override() 1052 @NotNull() 1053 public JSONObject toJSONControl() 1054 { 1055 final Map<String,JSONValue> valueFields = new LinkedHashMap<>(); 1056 1057 if (assertionValue == null) 1058 { 1059 valueFields.put(JSON_FIELD_TARGET_OFFSET, new JSONNumber(targetOffset)); 1060 } 1061 else 1062 { 1063 valueFields.put(JSON_FIELD_ASSERTION_VALUE, 1064 new JSONString(assertionValue.stringValue())); 1065 } 1066 1067 valueFields.put(JSON_FIELD_BEFORE_COUNT, new JSONNumber(beforeCount)); 1068 valueFields.put(JSON_FIELD_AFTER_COUNT, new JSONNumber(afterCount)); 1069 1070 if (assertionValue == null) 1071 { 1072 valueFields.put(JSON_FIELD_CONTENT_COUNT, new JSONNumber(contentCount)); 1073 } 1074 1075 if (contextID != null) 1076 { 1077 valueFields.put(JSON_FIELD_CONTEXT_ID, 1078 new JSONString(Base64.encode(contextID.getValue()))); 1079 } 1080 1081 return new JSONObject( 1082 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 1083 VIRTUAL_LIST_VIEW_REQUEST_OID), 1084 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 1085 INFO_CONTROL_NAME_VLV_REQUEST.get()), 1086 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 1087 isCritical()), 1088 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 1089 new JSONObject(valueFields))); 1090 } 1091 1092 1093 1094 /** 1095 * Attempts to decode the provided object as a JSON representation of a 1096 * virtual list view request control. 1097 * 1098 * @param controlObject The JSON object to be decoded. It must not be 1099 * {@code null}. 1100 * @param strict Indicates whether to use strict mode when decoding 1101 * the provided JSON object. If this is {@code true}, 1102 * then this method will throw an exception if the 1103 * provided JSON object contains any unrecognized 1104 * fields. If this is {@code false}, then unrecognized 1105 * fields will be ignored. 1106 * 1107 * @return The virtual list view request control that was decoded from the 1108 * provided JSON object. 1109 * 1110 * @throws LDAPException If the provided JSON object cannot be parsed as a 1111 * valid virtual list view request control. 1112 */ 1113 @NotNull() 1114 public static VirtualListViewRequestControl decodeJSONControl( 1115 @NotNull final JSONObject controlObject, 1116 final boolean strict) 1117 throws LDAPException 1118 { 1119 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 1120 controlObject, strict, true, true); 1121 1122 final ASN1OctetString rawValue = jsonControl.getRawValue(); 1123 if (rawValue != null) 1124 { 1125 return new VirtualListViewRequestControl(new Control( 1126 jsonControl.getOID(), jsonControl.getCriticality(), rawValue)); 1127 } 1128 1129 1130 final JSONObject valueObject = jsonControl.getValueObject(); 1131 1132 final Integer targetOffset = 1133 valueObject.getFieldAsInteger(JSON_FIELD_TARGET_OFFSET); 1134 final String assertionValue = 1135 valueObject.getFieldAsString(JSON_FIELD_ASSERTION_VALUE); 1136 1137 if ((targetOffset == null) && (assertionValue == null)) 1138 { 1139 throw new LDAPException(ResultCode.DECODING_ERROR, 1140 ERR_VLV_REQUEST_JSON_NEITHER_OFFSET_NOR_VALUE.get( 1141 controlObject.toSingleLineString(), JSON_FIELD_TARGET_OFFSET, 1142 JSON_FIELD_ASSERTION_VALUE)); 1143 } 1144 1145 if ((targetOffset != null) && (assertionValue != null)) 1146 { 1147 throw new LDAPException(ResultCode.DECODING_ERROR, 1148 ERR_VLV_REQUEST_JSON_BOTH_OFFSET_AND_VALUE.get( 1149 controlObject.toSingleLineString(), JSON_FIELD_TARGET_OFFSET, 1150 JSON_FIELD_ASSERTION_VALUE)); 1151 } 1152 1153 1154 final Integer beforeCount = 1155 valueObject.getFieldAsInteger(JSON_FIELD_BEFORE_COUNT); 1156 if (beforeCount == null) 1157 { 1158 throw new LDAPException(ResultCode.DECODING_ERROR, 1159 ERR_VLV_REQUEST_JSON_MISSING_FIELD.get( 1160 controlObject.toSingleLineString(), JSON_FIELD_BEFORE_COUNT)); 1161 } 1162 1163 1164 final Integer afterCount = 1165 valueObject.getFieldAsInteger(JSON_FIELD_AFTER_COUNT); 1166 if (afterCount == null) 1167 { 1168 throw new LDAPException(ResultCode.DECODING_ERROR, 1169 ERR_VLV_REQUEST_JSON_MISSING_FIELD.get( 1170 controlObject.toSingleLineString(), JSON_FIELD_AFTER_COUNT)); 1171 } 1172 1173 1174 Integer contentCount = 1175 valueObject.getFieldAsInteger(JSON_FIELD_CONTENT_COUNT); 1176 if (contentCount == null) 1177 { 1178 contentCount = 0; 1179 } 1180 else if (assertionValue != null) 1181 { 1182 throw new LDAPException(ResultCode.DECODING_ERROR, 1183 ERR_VLV_REQUEST_JSON_CONTENT_COUNT_WITH_ASSERTION_VALUE.get( 1184 controlObject.toSingleLineString(), JSON_FIELD_CONTENT_COUNT, 1185 JSON_FIELD_ASSERTION_VALUE)); 1186 } 1187 1188 1189 final ASN1OctetString contextID; 1190 final String contextIDBase64 = 1191 valueObject.getFieldAsString(JSON_FIELD_CONTEXT_ID); 1192 if (contextIDBase64 == null) 1193 { 1194 contextID = null; 1195 } 1196 else 1197 { 1198 try 1199 { 1200 contextID = new ASN1OctetString(Base64.decode(contextIDBase64)); 1201 } 1202 catch (final Exception e) 1203 { 1204 Debug.debugException(e); 1205 throw new LDAPException(ResultCode.DECODING_ERROR, 1206 ERR_VLV_REQUEST_JSON_CONTEXT_ID_NOT_BASE64.get( 1207 controlObject.toSingleLineString(), 1208 JSON_FIELD_CONTEXT_ID), 1209 e); 1210 } 1211 } 1212 1213 1214 if (strict) 1215 { 1216 final List<String> unrecognizedFields = 1217 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1218 valueObject, JSON_FIELD_TARGET_OFFSET, 1219 JSON_FIELD_ASSERTION_VALUE, JSON_FIELD_BEFORE_COUNT, 1220 JSON_FIELD_AFTER_COUNT, JSON_FIELD_CONTENT_COUNT, 1221 JSON_FIELD_CONTEXT_ID); 1222 if (! unrecognizedFields.isEmpty()) 1223 { 1224 throw new LDAPException(ResultCode.DECODING_ERROR, 1225 ERR_VLV_REQUEST_JSON_UNRECOGNIZED_FIELD.get( 1226 controlObject.toSingleLineString(), 1227 unrecognizedFields.get(0))); 1228 } 1229 } 1230 1231 1232 if (assertionValue == null) 1233 { 1234 return new VirtualListViewRequestControl(targetOffset, beforeCount, 1235 afterCount, contentCount, contextID, jsonControl.getCriticality()); 1236 } 1237 else 1238 { 1239 return new VirtualListViewRequestControl(assertionValue, beforeCount, 1240 afterCount, contextID, jsonControl.getCriticality()); 1241 } 1242 } 1243 1244 1245 1246 /** 1247 * {@inheritDoc} 1248 */ 1249 @Override() 1250 public void toString(@NotNull final StringBuilder buffer) 1251 { 1252 buffer.append("VirtualListViewRequestControl(beforeCount="); 1253 buffer.append(beforeCount); 1254 buffer.append(", afterCount="); 1255 buffer.append(afterCount); 1256 1257 if (assertionValue == null) 1258 { 1259 buffer.append(", targetOffset="); 1260 buffer.append(targetOffset); 1261 buffer.append(", contentCount="); 1262 buffer.append(contentCount); 1263 } 1264 else 1265 { 1266 buffer.append(", assertionValue='"); 1267 buffer.append(assertionValue.stringValue()); 1268 buffer.append('\''); 1269 } 1270 1271 buffer.append(", isCritical="); 1272 buffer.append(isCritical()); 1273 buffer.append(')'); 1274 } 1275}