001/* 002 * Copyright 2014-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2014-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) 2014-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.unboundidds.controls; 037 038 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.Iterator; 044import java.util.LinkedHashMap; 045import java.util.List; 046import java.util.Map; 047 048import com.unboundid.asn1.ASN1Boolean; 049import com.unboundid.asn1.ASN1Element; 050import com.unboundid.asn1.ASN1Integer; 051import com.unboundid.asn1.ASN1Null; 052import com.unboundid.asn1.ASN1OctetString; 053import com.unboundid.asn1.ASN1Sequence; 054import com.unboundid.ldap.sdk.Control; 055import com.unboundid.ldap.sdk.DecodeableControl; 056import com.unboundid.ldap.sdk.Filter; 057import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 058import com.unboundid.ldap.sdk.LDAPException; 059import com.unboundid.ldap.sdk.ResultCode; 060import com.unboundid.ldap.sdk.SearchResult; 061import com.unboundid.util.Debug; 062import com.unboundid.util.NotMutable; 063import com.unboundid.util.NotNull; 064import com.unboundid.util.Nullable; 065import com.unboundid.util.StaticUtils; 066import com.unboundid.util.ThreadSafety; 067import com.unboundid.util.ThreadSafetyLevel; 068import com.unboundid.util.Validator; 069import com.unboundid.util.json.JSONArray; 070import com.unboundid.util.json.JSONBoolean; 071import com.unboundid.util.json.JSONField; 072import com.unboundid.util.json.JSONNumber; 073import com.unboundid.util.json.JSONObject; 074import com.unboundid.util.json.JSONString; 075import com.unboundid.util.json.JSONValue; 076 077import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 078 079 080 081/** 082 * This class provides a response control that may be used to provide 083 * information about the number of entries that match a given set of search 084 * criteria. The control will be included in the search result done message 085 * for any successful search operation in which the request contained a matching 086 * entry count request control. 087 * <BR> 088 * <BLOCKQUOTE> 089 * <B>NOTE:</B> This class, and other classes within the 090 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 091 * supported for use against Ping Identity, UnboundID, and 092 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 093 * for proprietary functionality or for external specifications that are not 094 * considered stable or mature enough to be guaranteed to work in an 095 * interoperable way with other types of LDAP servers. 096 * </BLOCKQUOTE> 097 * <BR> 098 * The matching entry count response control has an OID of 099 * "1.3.6.1.4.1.30221.2.5.37", a criticality of false, and a value with the 100 * following encoding: 101 * <PRE> 102 * MatchingEntryCountResponse ::= SEQUENCE { 103 * entryCount CHOICE { 104 * examinedCount [0] INTEGER, 105 * unexaminedCount [1] INTEGER, 106 * upperBound [2] INTEGER, 107 * unknown [3] NULL, 108 * ... } 109 * debugInfo [0] SEQUENCE OF OCTET STRING OPTIONAL, 110 * searchIndexed [1] BOOLEAN DEFAULT TRUE, 111 * shortCircuited [2] BOOLEAN OPTIONAL, 112 * fullyIndexed [3] BOOLEAN OPTIONAL, 113 * candidatesAreInScope [4] BOOLEAN OPTIONAL, 114 * remainingFilter [5] Filter OPTIONAL, 115 * ... } 116 * </PRE> 117 * 118 * @see MatchingEntryCountRequestControl 119 */ 120@NotMutable() 121@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 122public final class MatchingEntryCountResponseControl 123 extends Control 124 implements DecodeableControl 125{ 126 /** 127 * The OID (1.3.6.1.4.1.30221.2.5.37) for the matching entry count response 128 * control. 129 */ 130 @NotNull public static final String MATCHING_ENTRY_COUNT_RESPONSE_OID = 131 "1.3.6.1.4.1.30221.2.5.37"; 132 133 134 135 /** 136 * The BER type for the element used to hold the list of debug messages. 137 */ 138 private static final byte TYPE_DEBUG_INFO = (byte) 0xA0; 139 140 141 142 /** 143 * The BER type for the element used to indicate whether the search criteria 144 * is at least partially indexed. 145 */ 146 private static final byte TYPE_SEARCH_INDEXED = (byte) 0x81; 147 148 149 150 /** 151 * The BER type for the element used to indicate whether the server 152 * short-circuited during candidate set processing before evaluating all 153 * elements of the search criteria (the filter and scope). 154 */ 155 private static final byte TYPE_SHORT_CIRCUITED = (byte) 0x82; 156 157 158 159 /** 160 * The BER type for the element used to indicate whether the search criteria 161 * is fully indexed. 162 */ 163 private static final byte TYPE_FULLY_INDEXED = (byte) 0x83; 164 165 166 167 /** 168 * The BER type for the element used to indicate whether all the identified 169 * candidate entries are within the scope of the search. 170 */ 171 private static final byte TYPE_CANDIDATES_ARE_IN_SCOPE = (byte) 0x84; 172 173 174 175 /** 176 * The BER type for the element used to provide the remaining filter for the 177 * search operation, which is the portion of the filter that was determined 178 * to be unindexed, or that was unevaluated if processing short-circuited in 179 * the course of building the candidate set. 180 */ 181 private static final byte TYPE_REMAINING_FILTER = (byte) 0xA5; 182 183 184 185 /** 186 * The name of the field used to hold the candidates are in scope flag in the 187 * JSON representation of this control. 188 */ 189 @NotNull private static final String JSON_FIELD_CANDIDATES_ARE_IN_SCOPE = 190 "candidates-are-in-scope"; 191 192 193 194 /** 195 * The name of the field used to hold the count type in the JSON 196 * representation of this control. 197 */ 198 @NotNull private static final String JSON_FIELD_COUNT_TYPE = "count-type"; 199 200 201 202 /** 203 * The name of the field used to hold the count value in the JSON 204 * representation of this control. 205 */ 206 @NotNull private static final String JSON_FIELD_COUNT_VALUE = "count-value"; 207 208 209 210 /** 211 * The name of the field used to hold the debug info in the JSON 212 * representation of this control. 213 */ 214 @NotNull private static final String JSON_FIELD_DEBUG_INFO = "debug-info"; 215 216 217 218 /** 219 * The name of the field used to hold the fully indexed flag in the JSON 220 * representation of this control. 221 */ 222 @NotNull private static final String JSON_FIELD_FULLY_INDEXED = 223 "fully-indexed"; 224 225 226 227 /** 228 * The name of the field used to hold the remaining filter in the JSON 229 * representation of this control. 230 */ 231 @NotNull private static final String JSON_FIELD_REMAINING_FILTER = 232 "remaining-filter"; 233 234 235 236 /** 237 * The name of the field used to hold the search indexed flag in the JSON 238 * representation of this control. 239 */ 240 @NotNull private static final String JSON_FIELD_SEARCH_INDEXED = 241 "search-indexed"; 242 243 244 245 /** 246 * The name of the field used to hold the short-circuited flag in the JSON 247 * representation of this control. 248 */ 249 @NotNull private static final String JSON_FIELD_SHORT_CIRCUITED = 250 "short-circuited"; 251 252 253 254 /** 255 * The result-type value that will be used for an examined count in the JSON 256 * representation of this control. 257 */ 258 @NotNull private static final String JSON_COUNT_TYPE_EXAMINED_COUNT = 259 "examined-count"; 260 261 262 263 /** 264 * The result-type value that will be used for an unexamined count in the JSON 265 * representation of this control. 266 */ 267 @NotNull private static final String JSON_COUNT_TYPE_UNEXAMINED_COUNT = 268 "unexamined-count"; 269 270 271 272 /** 273 * The result-type value that will be used for an unknown count in the JSON 274 * representation of this control. 275 */ 276 @NotNull private static final String JSON_COUNT_TYPE_UNKNOWN = "unknown"; 277 278 279 280 /** 281 * The result-type value that will be used for an upper-bound count in the 282 * JSON representation of this control. 283 */ 284 @NotNull private static final String JSON_COUNT_TYPE_UPPER_BOUND = 285 "upper-bound"; 286 287 288 289 /** 290 * The serial version UID for this serializable class. 291 */ 292 private static final long serialVersionUID = -7808452580964236458L; 293 294 295 296 // Indicates whether the search criteria is considered at least partially 297 // indexed by the server. 298 private final boolean searchIndexed; 299 300 // Indicates whether all the identified candidate entries are within the scope 301 // of the search. 302 @Nullable private final Boolean candidatesAreInScope; 303 304 // Indicates whether the search criteria is considered fully indexed. 305 @Nullable private final Boolean fullyIndexed; 306 307 // Indicates whether the server short-circuited during candidate set 308 // processing before evaluating all elements of the search criteria (the 309 // filter and scope). 310 @Nullable private final Boolean shortCircuited; 311 312 // The portion of the filter that was either identified as unindexed or that 313 // was not evaluated in the course of building the candidate set. 314 @Nullable private final Filter remainingFilter; 315 316 // The count value for this matching entry count response control. 317 private final int countValue; 318 319 // A list of messages providing debug information about the processing 320 // performed by the server. 321 @NotNull private final List<String> debugInfo; 322 323 // The count type for this matching entry count response control. 324 @NotNull private final MatchingEntryCountType countType; 325 326 327 328 /** 329 * Creates a new empty control instance that is intended to be used only for 330 * decoding controls via the {@code DecodeableControl} interface. 331 */ 332 MatchingEntryCountResponseControl() 333 { 334 searchIndexed = false; 335 candidatesAreInScope = null; 336 fullyIndexed = null; 337 shortCircuited = null; 338 remainingFilter = null; 339 countValue = -1; 340 countType = null; 341 debugInfo = null; 342 } 343 344 345 346 /** 347 * Creates a new matching entry count response control with the provided 348 * information. 349 * 350 * @param countType The matching entry count type. It must not 351 * be {@code null}. 352 * @param countValue The matching entry count value. It must be 353 * greater than or equal to zero for a count 354 * type of either {@code EXAMINED_COUNT} or 355 * {@code UNEXAMINED_COUNT}. It must be greater 356 * than zero for a count type of 357 * {@code UPPER_BOUND}. It must be -1 for a 358 * count type of {@code UNKNOWN}. 359 * @param searchIndexed Indicates whether the search criteria is 360 * considered at least partially indexed and 361 * could be processed more efficiently than 362 * examining all entries with a full database 363 * scan. 364 * @param shortCircuited Indicates whether the server short-circuited 365 * during candidate set processing before 366 * evaluating all elements of the search 367 * criteria (the filter and scope). This may be 368 * {@code null} if it is not available (e.g., 369 * because extended response data was not 370 * requested). 371 * @param fullyIndexed Indicates whether the search is considered 372 * fully indexed. Note that this may be 373 * {@code false} even if the filter is actually 374 * fully indexed if server index processing 375 * short-circuited before evaluating all 376 * components of the filter. To avoid this, 377 * issue the request control with both fast and 378 * slow short-circuit thresholds set to zero. 379 * This may be {@code null} if this is not 380 * available (e.g., because extended response 381 * data was not requested). 382 * @param candidatesAreInScope Indicates whether all the identified 383 * candidate entries are within the scope of 384 * the search. It may be {@code null} if this 385 * is not available (e.g., because extended 386 * response data was not requested). 387 * @param remainingFilter The portion of the filter that was either 388 * identified as unindexed or that was not 389 * evaluated because processing short-circuited 390 * in the course of building the candidate set. 391 * It may be {@code null} if there is no 392 * remaining filter or if this information is 393 * not available (e.g., because extended 394 * response data was not requested). 395 * @param debugInfo An optional list of messages providing debug 396 * information about the processing performed by 397 * the server. It may be {@code null} or empty 398 * if no debug messages should be included. 399 */ 400 private MatchingEntryCountResponseControl( 401 @NotNull final MatchingEntryCountType countType, 402 final int countValue, 403 final boolean searchIndexed, 404 @Nullable final Boolean shortCircuited, 405 @Nullable final Boolean fullyIndexed, 406 @Nullable final Boolean candidatesAreInScope, 407 @Nullable final Filter remainingFilter, 408 @Nullable final Collection<String> debugInfo) 409 { 410 super(MATCHING_ENTRY_COUNT_RESPONSE_OID, false, 411 encodeValue(countType, countValue, searchIndexed, shortCircuited, 412 fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo)); 413 414 this.countType = countType; 415 this.countValue = countValue; 416 this.searchIndexed = searchIndexed; 417 this.shortCircuited = shortCircuited; 418 this.fullyIndexed = fullyIndexed; 419 this.candidatesAreInScope = candidatesAreInScope; 420 this.remainingFilter = remainingFilter; 421 422 if (debugInfo == null) 423 { 424 this.debugInfo = Collections.emptyList(); 425 } 426 else 427 { 428 this.debugInfo = 429 Collections.unmodifiableList(new ArrayList<>(debugInfo)); 430 } 431 } 432 433 434 435 /** 436 * Creates a new matching entry count response control decoded from the given 437 * generic control contents. 438 * 439 * @param oid The OID for the control. 440 * @param isCritical Indicates whether this control should be marked 441 * critical. 442 * @param value The encoded value for the control. 443 * 444 * @throws LDAPException If a problem occurs while attempting to decode the 445 * generic control as a matching entry count response 446 * control. 447 */ 448 public MatchingEntryCountResponseControl(@NotNull final String oid, 449 final boolean isCritical, 450 @Nullable final ASN1OctetString value) 451 throws LDAPException 452 { 453 super(oid, isCritical, value); 454 455 if (value == null) 456 { 457 throw new LDAPException(ResultCode.DECODING_ERROR, 458 ERR_MATCHING_ENTRY_COUNT_RESPONSE_MISSING_VALUE.get()); 459 } 460 461 try 462 { 463 final ASN1Element[] elements = 464 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 465 countType = MatchingEntryCountType.valueOf(elements[0].getType()); 466 if (countType == null) 467 { 468 throw new LDAPException(ResultCode.DECODING_ERROR, 469 ERR_MATCHING_ENTRY_COUNT_RESPONSE_INVALID_COUNT_TYPE.get( 470 StaticUtils.toHex(elements[0].getType()))); 471 } 472 473 switch (countType) 474 { 475 case EXAMINED_COUNT: 476 case UNEXAMINED_COUNT: 477 countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); 478 if (countValue < 0) 479 { 480 throw new LDAPException(ResultCode.DECODING_ERROR, 481 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NEGATIVE_EXACT_COUNT.get()); 482 } 483 break; 484 485 case UPPER_BOUND: 486 countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); 487 if (countValue <= 0) 488 { 489 throw new LDAPException(ResultCode.DECODING_ERROR, 490 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NON_POSITIVE_UPPER_BOUND. 491 get()); 492 } 493 break; 494 495 case UNKNOWN: 496 default: 497 countValue = -1; 498 break; 499 } 500 501 boolean decodedSearchIndexed = 502 (countType != MatchingEntryCountType.UNKNOWN); 503 Boolean decodedFullyIndexed = null; 504 Boolean decodedCandidatesAreInScope = null; 505 Boolean decodedShortCircuited = null; 506 Filter decodedRemainingFilter = null; 507 List<String> debugMessages = Collections.emptyList(); 508 for (int i=1; i < elements.length; i++) 509 { 510 switch (elements[i].getType()) 511 { 512 case TYPE_DEBUG_INFO: 513 final ASN1Element[] debugElements = 514 ASN1Sequence.decodeAsSequence(elements[i]).elements(); 515 debugMessages = new ArrayList<>(debugElements.length); 516 for (final ASN1Element e : debugElements) 517 { 518 debugMessages.add( 519 ASN1OctetString.decodeAsOctetString(e).stringValue()); 520 } 521 break; 522 523 case TYPE_SEARCH_INDEXED: 524 decodedSearchIndexed = 525 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 526 break; 527 528 case TYPE_SHORT_CIRCUITED: 529 decodedShortCircuited = 530 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 531 break; 532 533 case TYPE_FULLY_INDEXED: 534 decodedFullyIndexed = 535 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 536 break; 537 538 case TYPE_CANDIDATES_ARE_IN_SCOPE: 539 decodedCandidatesAreInScope = 540 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 541 break; 542 543 case TYPE_REMAINING_FILTER: 544 final ASN1Element filterElement = 545 ASN1Element.decode(elements[i].getValue()); 546 decodedRemainingFilter = Filter.decode(filterElement); 547 break; 548 } 549 } 550 551 searchIndexed = decodedSearchIndexed; 552 shortCircuited = decodedShortCircuited; 553 fullyIndexed = decodedFullyIndexed; 554 candidatesAreInScope = decodedCandidatesAreInScope; 555 remainingFilter = decodedRemainingFilter; 556 debugInfo = Collections.unmodifiableList(debugMessages); 557 } 558 catch (final LDAPException le) 559 { 560 Debug.debugException(le); 561 throw le; 562 } 563 catch (final Exception e) 564 { 565 Debug.debugException(e); 566 throw new LDAPException(ResultCode.DECODING_ERROR, 567 ERR_GET_BACKEND_SET_ID_RESPONSE_CANNOT_DECODE.get( 568 StaticUtils.getExceptionMessage(e)), 569 e); 570 } 571 } 572 573 574 575 /** 576 * Creates a new matching entry count response control for the case in which 577 * the exact number of matching entries is known. 578 * 579 * @param count The exact number of entries matching the associated 580 * search criteria. It must be greater than or equal to 581 * zero. 582 * @param examined Indicates whether the server examined the entries to 583 * exclude those entries that would not be returned to the 584 * client in a normal search with the same criteria. 585 * @param debugInfo An optional list of messages providing debug information 586 * about the processing performed by the server. It may be 587 * {@code null} or empty if no debug messages should be 588 * included. 589 * 590 * @return The matching entry count response control that was created. 591 */ 592 @NotNull() 593 public static MatchingEntryCountResponseControl createExactCountResponse( 594 final int count, final boolean examined, 595 @Nullable final Collection<String> debugInfo) 596 { 597 return createExactCountResponse(count, examined, true, debugInfo); 598 } 599 600 601 602 /** 603 * Creates a new matching entry count response control for the case in which 604 * the exact number of matching entries is known. 605 * 606 * @param count The exact number of entries matching the associated 607 * search criteria. It must be greater than or equal 608 * to zero. 609 * @param examined Indicates whether the server examined the entries to 610 * exclude those entries that would not be returned to 611 * the client in a normal search with the same 612 * criteria. 613 * @param searchIndexed Indicates whether the search criteria is considered 614 * at least partially indexed and could be processed 615 * more efficiently than examining all entries with a 616 * full database scan. 617 * @param debugInfo An optional list of messages providing debug 618 * information about the processing performed by the 619 * server. It may be {@code null} or empty if no debug 620 * messages should be included. 621 * 622 * @return The matching entry count response control that was created. 623 */ 624 @NotNull() 625 public static MatchingEntryCountResponseControl createExactCountResponse( 626 final int count, final boolean examined, 627 final boolean searchIndexed, 628 @Nullable final Collection<String> debugInfo) 629 { 630 return createExactCountResponse(count, examined, searchIndexed, null, null, 631 null, null, debugInfo); 632 } 633 634 635 636 /** 637 * Creates a new matching entry count response control for the case in which 638 * the exact number of matching entries is known. 639 * 640 * @param count The exact number of entries matching the 641 * associated search criteria. It must be 642 * greater than or equal to zero. 643 * @param examined Indicates whether the server examined the 644 * entries to exclude those entries that would 645 * not be returned to the client in a normal 646 * search with the same criteria. 647 * @param searchIndexed Indicates whether the search criteria is 648 * considered at least partially indexed and 649 * could be processed more efficiently than 650 * examining all entries with a full database 651 * scan. 652 * @param shortCircuited Indicates whether the server short-circuited 653 * during candidate set processing before 654 * evaluating all elements of the search 655 * criteria (the filter and scope). This may be 656 * {@code null} if it is not available (e.g., 657 * because extended response data was not 658 * requested). 659 * @param fullyIndexed Indicates whether the search is considered 660 * fully indexed. Note that this may be 661 * {@code false} even if the filter is actually 662 * fully indexed if server index processing 663 * short-circuited before evaluating all 664 * components of the filter. To avoid this, 665 * issue the request control with both fast and 666 * slow short-circuit thresholds set to zero. 667 * This may be {@code null} if this is not 668 * available (e.g., because extended response 669 * data was not requested). 670 * @param candidatesAreInScope Indicates whether all the identified 671 * candidate entries are within the scope of 672 * the search. It may be {@code null} if this 673 * is not available (e.g., because extended 674 * response data was not requested). 675 * @param remainingFilter The portion of the filter that was either 676 * identified as unindexed or that was not 677 * evaluated because processing short-circuited 678 * in the course of building the candidate set. 679 * It may be {@code null} if there is no 680 * remaining filter or if this information is 681 * not available (e.g., because extended 682 * response data was not requested). 683 * @param debugInfo An optional list of messages providing debug 684 * information about the processing performed by 685 * the server. It may be {@code null} or empty 686 * if no debug messages should be included. 687 * 688 * @return The matching entry count response control that was created. 689 */ 690 @NotNull() 691 public static MatchingEntryCountResponseControl createExactCountResponse( 692 final int count, final boolean examined, 693 final boolean searchIndexed, 694 @Nullable final Boolean shortCircuited, 695 @Nullable final Boolean fullyIndexed, 696 @Nullable final Boolean candidatesAreInScope, 697 @Nullable final Filter remainingFilter, 698 @Nullable final Collection<String> debugInfo) 699 { 700 Validator.ensureTrue(count >= 0); 701 702 final MatchingEntryCountType countType; 703 if (examined) 704 { 705 countType = MatchingEntryCountType.EXAMINED_COUNT; 706 } 707 else 708 { 709 countType = MatchingEntryCountType.UNEXAMINED_COUNT; 710 } 711 712 return new MatchingEntryCountResponseControl(countType, count, 713 searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope, 714 remainingFilter, debugInfo); 715 } 716 717 718 719 /** 720 * Creates a new matching entry count response control for the case in which 721 * the exact number of matching entries is not known, but the server was able 722 * to determine an upper bound on the number of matching entries. This upper 723 * bound count may include entries that do not match the search filter, that 724 * are outside the scope of the search, and/or that match the search criteria 725 * but would not have been returned to the client in a normal search with the 726 * same criteria. 727 * 728 * @param upperBound The upper bound on the number of entries that match the 729 * associated search criteria. It must be greater than 730 * zero. 731 * @param debugInfo An optional list of messages providing debug 732 * information about the processing performed by the 733 * server. It may be {@code null} or empty if no debug 734 * messages should be included. 735 * 736 * @return The matching entry count response control that was created. 737 */ 738 @NotNull() 739 public static MatchingEntryCountResponseControl createUpperBoundResponse( 740 final int upperBound, 741 @Nullable final Collection<String> debugInfo) 742 { 743 return createUpperBoundResponse(upperBound, true, debugInfo); 744 } 745 746 747 748 /** 749 * Creates a new matching entry count response control for the case in which 750 * the exact number of matching entries is not known, but the server was able 751 * to determine an upper bound on the number of matching entries. This upper 752 * bound count may include entries that do not match the search filter, that 753 * are outside the scope of the search, and/or that match the search criteria 754 * but would not have been returned to the client in a normal search with the 755 * same criteria. 756 * 757 * @param upperBound The upper bound on the number of entries that match 758 * the associated search criteria. It must be greater 759 * than zero. 760 * @param searchIndexed Indicates whether the search criteria is considered 761 * at least partially indexed and could be processed 762 * more efficiently than examining all entries with a 763 * full database scan. 764 * @param debugInfo An optional list of messages providing debug 765 * information about the processing performed by the 766 * server. It may be {@code null} or empty if no debug 767 * messages should be included. 768 * 769 * @return The matching entry count response control that was created. 770 */ 771 @NotNull() 772 public static MatchingEntryCountResponseControl createUpperBoundResponse( 773 final int upperBound, final boolean searchIndexed, 774 @Nullable final Collection<String> debugInfo) 775 { 776 return createUpperBoundResponse(upperBound, searchIndexed, null, null, null, 777 null, debugInfo); 778 } 779 780 781 782 /** 783 * Creates a new matching entry count response control for the case in which 784 * the exact number of matching entries is not known, but the server was able 785 * to determine an upper bound on the number of matching entries. This upper 786 * bound count may include entries that do not match the search filter, that 787 * are outside the scope of the search, and/or that match the search criteria 788 * but would not have been returned to the client in a normal search with the 789 * same criteria. 790 * 791 * @param upperBound The upper bound on the number of entries that 792 * match the associated search criteria. It 793 * must be greater than zero. 794 * @param searchIndexed Indicates whether the search criteria is 795 * considered at least partially indexed and 796 * could be processed more efficiently than 797 * examining all entries with a full database 798 * scan. 799 * @param shortCircuited Indicates whether the server short-circuited 800 * during candidate set processing before 801 * evaluating all elements of the search 802 * criteria (the filter and scope). This may be 803 * {@code null} if it is not available (e.g., 804 * because extended response data was not 805 * requested). 806 * @param fullyIndexed Indicates whether the search is considered 807 * fully indexed. Note that this may be 808 * {@code false} even if the filter is actually 809 * fully indexed if server index processing 810 * short-circuited before evaluating all 811 * components of the filter. To avoid this, 812 * issue the request control with both fast and 813 * slow short-circuit thresholds set to zero. 814 * This may be {@code null} if this is not 815 * available (e.g., because extended response 816 * data was not requested). 817 * @param candidatesAreInScope Indicates whether all the identified 818 * candidate entries are within the scope of 819 * the search. It may be {@code null} if this 820 * is not available (e.g., because extended 821 * response data was not requested). 822 * @param remainingFilter The portion of the filter that was either 823 * identified as unindexed or that was not 824 * evaluated because processing short-circuited 825 * in the course of building the candidate set. 826 * It may be {@code null} if there is no 827 * remaining filter or if this information is 828 * not available (e.g., because extended 829 * response data was not requested). 830 * @param debugInfo An optional list of messages providing debug 831 * information about the processing performed by 832 * the server. It may be {@code null} or empty 833 * if no debug messages should be included. 834 * 835 * @return The matching entry count response control that was created. 836 */ 837 @NotNull() 838 public static MatchingEntryCountResponseControl createUpperBoundResponse( 839 final int upperBound, final boolean searchIndexed, 840 @Nullable final Boolean shortCircuited, 841 @Nullable final Boolean fullyIndexed, 842 @Nullable final Boolean candidatesAreInScope, 843 @Nullable final Filter remainingFilter, 844 @Nullable final Collection<String> debugInfo) 845 { 846 Validator.ensureTrue(upperBound > 0); 847 848 return new MatchingEntryCountResponseControl( 849 MatchingEntryCountType.UPPER_BOUND, upperBound, searchIndexed, 850 shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter, 851 debugInfo); 852 } 853 854 855 856 /** 857 * Creates a new matching entry count response control for the case in which 858 * the server was unable to make any meaningful determination about the number 859 * of entries matching the search criteria. 860 * 861 * @param debugInfo An optional list of messages providing debug information 862 * about the processing performed by the server. It may be 863 * {@code null} or empty if no debug messages should be 864 * included. 865 * 866 * @return The matching entry count response control that was created. 867 */ 868 @NotNull() 869 public static MatchingEntryCountResponseControl createUnknownCountResponse( 870 @Nullable final Collection<String> debugInfo) 871 { 872 return new MatchingEntryCountResponseControl(MatchingEntryCountType.UNKNOWN, 873 -1, false, null, null, null, null, debugInfo); 874 } 875 876 877 878 /** 879 * Encodes a control value with the provided information. 880 * 881 * @param countType The matching entry count type. It must not 882 * be {@code null}. 883 * @param countValue The matching entry count value. It must be 884 * greater than or equal to zero for a count 885 * type of either {@code EXAMINED_COUNT} or 886 * {@code UNEXAMINED_COUNT}. It must be greater 887 * than zero for a count type of 888 * {@code UPPER_BOUND}. It must be -1 for a 889 * count type of {@code UNKNOWN}. 890 * @param searchIndexed Indicates whether the search criteria is 891 * considered at least partially indexed and 892 * could be processed more efficiently than 893 * examining all entries with a full database 894 * scan. 895 * @param shortCircuited Indicates whether the server short-circuited 896 * during candidate set processing before 897 * evaluating all elements of the search 898 * criteria (the filter and scope). This may be 899 * {@code null} if it is not available (e.g., 900 * because extended response data was not 901 * requested). 902 * @param fullyIndexed Indicates whether the search is considered 903 * fully indexed. Note that this may be 904 * {@code false} even if the filter is actually 905 * fully indexed if server index processing 906 * short-circuited before evaluating all 907 * components of the filter. To avoid this, 908 * issue the request control with both fast and 909 * slow short-circuit thresholds set to zero. 910 * This may be {@code null} if this is not 911 * available (e.g., because extended response 912 * data was not requested). 913 * @param candidatesAreInScope Indicates whether all the identified 914 * candidate entries are within the scope of 915 * the search. It may be {@code null} if this 916 * is not available (e.g., because extended 917 * response data was not requested). 918 * @param remainingFilter The portion of the filter that was either 919 * identified as unindexed or that was not 920 * evaluated because processing short-circuited 921 * in the course of building the candidate set. 922 * It may be {@code null} if there is no 923 * remaining filter or if this information is 924 * not available (e.g., because extended 925 * response data was not requested). 926 * @param debugInfo An optional list of messages providing debug 927 * information about the processing performed by 928 * the server. It may be {@code null} or empty 929 * if no debug messages should be included. 930 * 931 * @return The encoded control value. 932 */ 933 @NotNull() 934 private static ASN1OctetString encodeValue( 935 @NotNull final MatchingEntryCountType countType, 936 final int countValue, final boolean searchIndexed, 937 @Nullable final Boolean shortCircuited, 938 @Nullable final Boolean fullyIndexed, 939 @Nullable final Boolean candidatesAreInScope, 940 @Nullable final Filter remainingFilter, 941 @Nullable final Collection<String> debugInfo) 942 { 943 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 944 945 switch (countType) 946 { 947 case EXAMINED_COUNT: 948 case UNEXAMINED_COUNT: 949 case UPPER_BOUND: 950 elements.add(new ASN1Integer(countType.getBERType(), countValue)); 951 break; 952 case UNKNOWN: 953 elements.add(new ASN1Null(countType.getBERType())); 954 break; 955 } 956 957 if (debugInfo != null) 958 { 959 final ArrayList<ASN1Element> debugElements = 960 new ArrayList<>(debugInfo.size()); 961 for (final String s : debugInfo) 962 { 963 debugElements.add(new ASN1OctetString(s)); 964 } 965 966 elements.add(new ASN1Sequence(TYPE_DEBUG_INFO, debugElements)); 967 } 968 969 if (! searchIndexed) 970 { 971 elements.add(new ASN1Boolean(TYPE_SEARCH_INDEXED, searchIndexed)); 972 } 973 974 if (shortCircuited != null) 975 { 976 elements.add(new ASN1Boolean(TYPE_SHORT_CIRCUITED, shortCircuited)); 977 } 978 979 if (fullyIndexed != null) 980 { 981 elements.add(new ASN1Boolean(TYPE_FULLY_INDEXED, fullyIndexed)); 982 } 983 984 if (candidatesAreInScope != null) 985 { 986 elements.add(new ASN1Boolean(TYPE_CANDIDATES_ARE_IN_SCOPE, 987 candidatesAreInScope)); 988 } 989 990 if (remainingFilter != null) 991 { 992 elements.add(new ASN1OctetString(TYPE_REMAINING_FILTER, 993 remainingFilter.encode().encode())); 994 } 995 996 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 997 } 998 999 1000 1001 /** 1002 * Retrieves the matching entry count type for the response control. 1003 * 1004 * @return The matching entry count type for the response control. 1005 */ 1006 @NotNull() 1007 public MatchingEntryCountType getCountType() 1008 { 1009 return countType; 1010 } 1011 1012 1013 1014 /** 1015 * Retrieves the matching entry count value for the response control. For a 1016 * count type of {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}, this is 1017 * the exact number of matching entries. For a count type of 1018 * {@code UPPER_BOUND}, this is the maximum number of entries that may match 1019 * the search criteria, but it may also include entries that do not match the 1020 * criteria. For a count type of {@code UNKNOWN}, this will always be -1. 1021 * 1022 * @return The exact count or upper bound of the number of entries in the 1023 * server that may match the search criteria, or -1 if the server 1024 * could not determine the number of matching entries. 1025 */ 1026 public int getCountValue() 1027 { 1028 return countValue; 1029 } 1030 1031 1032 1033 /** 1034 * Indicates whether the server considers the search criteria to be indexed 1035 * and therefore it could be processed more efficiently than examining all 1036 * entries with a full database scan. 1037 * 1038 * @return {@code true} if the server considers the search criteria to be 1039 * indexed, or {@code false} if not. 1040 */ 1041 public boolean searchIndexed() 1042 { 1043 return searchIndexed; 1044 } 1045 1046 1047 1048 /** 1049 * Indicates whether the server short-circuited during candidate set 1050 * processing before evaluating all elements of the search criteria (the 1051 * filter and scope). 1052 * 1053 * @return {@code Boolean.TRUE} if the server did short-circuit during 1054 * candidate set processing before evaluating all elements of the 1055 * search criteria, {@code Boolean.FALSE} if the server evaluated all 1056 * elements of the search criteria, or {@code null} if this 1057 * information is not available (e.g., because extended response data 1058 * was not requested). 1059 */ 1060 @Nullable() 1061 public Boolean getShortCircuited() 1062 { 1063 return shortCircuited; 1064 } 1065 1066 1067 1068 /** 1069 * Indicates whether the server considers the search criteria to be fully 1070 * indexed. Note that if the server short-circuited during candidate set 1071 * processing before evaluating all search criteria (the filter and scope), 1072 * this may be {@code Boolean.FALSE} even if the search is actually completely 1073 * indexed. 1074 * 1075 * @return {@code Boolean.TRUE} if the server considers the search criteria 1076 * to be fully indexed, {@code Boolean.FALSE} if the search criteria 1077 * is not known to be fully indexed, or {@code null} if this 1078 * information is not available (e.g., because extended response data 1079 * was not requested). 1080 */ 1081 @Nullable() 1082 public Boolean getFullyIndexed() 1083 { 1084 return fullyIndexed; 1085 } 1086 1087 1088 1089 /** 1090 * Indicates whether the server can determine that all the identified 1091 * candidates are within the scope of the search. Note that even if the 1092 * server returns {@code Boolean.FALSE}, it does not necessarily mean that 1093 * not all the candidates are within the scope of the search, but just that 1094 * the server is not certain that is the case. 1095 * 1096 * @return {@code Boolean.TRUE} if the server can determine that all the 1097 * identified candidates are within the scope of the search, 1098 * {@code Boolean.FALSE} if the server cannot determine that all the 1099 * identified candidates are within the scope of the search, or 1100 * {@code null} if this information is not available (e.g., because 1101 * extended response data was not requested). 1102 */ 1103 @Nullable() 1104 public Boolean getCandidatesAreInScope() 1105 { 1106 return candidatesAreInScope; 1107 } 1108 1109 1110 1111 /** 1112 * Retrieves the portion of the filter that was either identified as not 1113 * indexed or that was not evaluated during candidate processing (e.g., 1114 * because the server short-circuited processing before examining all filter 1115 * components). 1116 * 1117 * @return The portion of the filter that was either identified as not 1118 * indexed or that was not evaluated during candidate processing, or 1119 * {@code null} if there was no remaining filter or if this 1120 * information is not available (e.g., because extended response data 1121 * was not requested). 1122 */ 1123 @Nullable() 1124 public Filter getRemainingFilter() 1125 { 1126 return remainingFilter; 1127 } 1128 1129 1130 1131 /** 1132 * Retrieves a list of messages with debug information about the processing 1133 * performed by the server in the course of obtaining the matching entry 1134 * count. These messages are intended to be human-readable rather than 1135 * machine-parsable. 1136 * 1137 * @return A list of messages with debug information about the processing 1138 * performed by the server in the course of obtaining the matching 1139 * entry count, or an empty list if no debug messages were provided. 1140 */ 1141 @NotNull() 1142 public List<String> getDebugInfo() 1143 { 1144 return debugInfo; 1145 } 1146 1147 1148 1149 /** 1150 * {@inheritDoc} 1151 */ 1152 @Override() 1153 @NotNull() 1154 public MatchingEntryCountResponseControl decodeControl( 1155 @NotNull final String oid, 1156 final boolean isCritical, 1157 @Nullable final ASN1OctetString value) 1158 throws LDAPException 1159 { 1160 return new MatchingEntryCountResponseControl(oid, isCritical, value); 1161 } 1162 1163 1164 1165 /** 1166 * Extracts a matching entry count response control from the provided search 1167 * result. 1168 * 1169 * @param result The search result from which to retrieve the matching entry 1170 * count response control. 1171 * 1172 * @return The matching entry count response control contained in the 1173 * provided result, or {@code null} if the result did not contain a 1174 * matching entry count response control. 1175 * 1176 * @throws LDAPException If a problem is encountered while attempting to 1177 * decode the matching entry count response control 1178 * contained in the provided result. 1179 */ 1180 @Nullable() 1181 public static MatchingEntryCountResponseControl get( 1182 @NotNull final SearchResult result) 1183 throws LDAPException 1184 { 1185 final Control c = 1186 result.getResponseControl(MATCHING_ENTRY_COUNT_RESPONSE_OID); 1187 if (c == null) 1188 { 1189 return null; 1190 } 1191 1192 if (c instanceof MatchingEntryCountResponseControl) 1193 { 1194 return (MatchingEntryCountResponseControl) c; 1195 } 1196 else 1197 { 1198 return new MatchingEntryCountResponseControl(c.getOID(), c.isCritical(), 1199 c.getValue()); 1200 } 1201 } 1202 1203 1204 1205 /** 1206 * {@inheritDoc} 1207 */ 1208 @Override() 1209 @NotNull() 1210 public String getControlName() 1211 { 1212 return INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get(); 1213 } 1214 1215 1216 1217 /** 1218 * Retrieves a representation of this matching entry count response control as 1219 * a JSON object. The JSON object uses the following fields: 1220 * <UL> 1221 * <LI> 1222 * {@code oid} -- A mandatory string field whose value is the object 1223 * identifier for this control. For the matching entry count response 1224 * control, the OID is "1.3.6.1.4.1.30221.2.5.37". 1225 * </LI> 1226 * <LI> 1227 * {@code control-name} -- An optional string field whose value is a 1228 * human-readable name for this control. This field is only intended for 1229 * descriptive purposes, and when decoding a control, the {@code oid} 1230 * field should be used to identify the type of control. 1231 * </LI> 1232 * <LI> 1233 * {@code criticality} -- A mandatory Boolean field used to indicate 1234 * whether this control is considered critical. 1235 * </LI> 1236 * <LI> 1237 * {@code value-base64} -- An optional string field whose value is a 1238 * base64-encoded representation of the raw value for this matching entry 1239 * count response control. Exactly one of the {@code value-base64} and 1240 * {@code value-json} fields must be present. 1241 * </LI> 1242 * <LI> 1243 * {@code value-json} -- An optional JSON object field whose value is a 1244 * user-friendly representation of the value for this matching entry count 1245 * response control. Exactly one of the {@code value-base64} and 1246 * {@code value-json} fields must be present, and if the 1247 * {@code value-json} field is used, then it will use the following 1248 * fields: 1249 * <UL> 1250 * <LI> 1251 * {@code count-type} -- A string field whose value indicates how 1252 * accurate the count is. The value will be one of 1253 * "{@code examined-count}", "{@code unexamined-count}", 1254 * "{@code upper-bound}", or "{@code unknown}". 1255 * </LI> 1256 * <LI> 1257 * {@code count-value} -- An optional integer field whose value is the 1258 * matching entry count estimate returned by the server. This will 1259 * be absent for a {@code count-type} value of "{@code unknown}", and 1260 * will be present for other {@code count-type} values. 1261 * </LI> 1262 * <LI> 1263 * {@code search-indexed} -- A Boolean field that indicates whether 1264 * the server considers the search to be at least partially indexed. 1265 * </LI> 1266 * <LI> 1267 * {@code fully-indexed} -- An optional Boolean field that indicates 1268 * whether the server considers the search to be fully indexed. 1269 * </LI> 1270 * <LI> 1271 * {@code short-circuited} -- An optional Boolean field that indicates 1272 * whether the server short-circuited at any point in evaluating the 1273 * search criteria. 1274 * </LI> 1275 * <LI> 1276 * {@code candidates-are-in-scope} -- An optional Boolean field that 1277 * indicates whether the server knows that all identified candidate 1278 * entries are within the scope of the search. 1279 * </LI> 1280 * <LI> 1281 * {@code remaining-filter} -- An optional string field whose value is 1282 * the portion of the filter that was not evaluated during the course 1283 * of coming up with the estimate. 1284 * </LI> 1285 * <LI> 1286 * {@code debug-info} -- An optional array field whose values are 1287 * strings with debug information about the processing performed by 1288 * the server in the course of determining the matching entry count 1289 * estimate. 1290 * </LI> 1291 * </UL> 1292 * </LI> 1293 * </UL> 1294 * 1295 * @return A JSON object that contains a representation of this control. 1296 */ 1297 @Override() 1298 @NotNull() 1299 public JSONObject toJSONControl() 1300 { 1301 final Map<String,JSONValue> valueFields = new LinkedHashMap<>(); 1302 1303 switch (countType) 1304 { 1305 case EXAMINED_COUNT: 1306 valueFields.put(JSON_FIELD_COUNT_TYPE, 1307 new JSONString(JSON_COUNT_TYPE_EXAMINED_COUNT)); 1308 valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue)); 1309 break; 1310 case UNEXAMINED_COUNT: 1311 valueFields.put(JSON_FIELD_COUNT_TYPE, 1312 new JSONString(JSON_COUNT_TYPE_UNEXAMINED_COUNT)); 1313 valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue)); 1314 break; 1315 case UPPER_BOUND: 1316 valueFields.put(JSON_FIELD_COUNT_TYPE, 1317 new JSONString(JSON_COUNT_TYPE_UPPER_BOUND)); 1318 valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue)); 1319 break; 1320 case UNKNOWN: 1321 valueFields.put(JSON_FIELD_COUNT_TYPE, 1322 new JSONString(JSON_COUNT_TYPE_UNKNOWN)); 1323 break; 1324 } 1325 1326 valueFields.put(JSON_FIELD_SEARCH_INDEXED, new JSONBoolean(searchIndexed)); 1327 1328 if (fullyIndexed != null) 1329 { 1330 valueFields.put(JSON_FIELD_FULLY_INDEXED, 1331 new JSONBoolean(fullyIndexed)); 1332 } 1333 1334 if (shortCircuited != null) 1335 { 1336 valueFields.put(JSON_FIELD_SHORT_CIRCUITED, 1337 new JSONBoolean(shortCircuited)); 1338 } 1339 1340 if (candidatesAreInScope != null) 1341 { 1342 valueFields.put(JSON_FIELD_CANDIDATES_ARE_IN_SCOPE, 1343 new JSONBoolean(candidatesAreInScope)); 1344 } 1345 1346 if (remainingFilter != null) 1347 { 1348 valueFields.put(JSON_FIELD_REMAINING_FILTER, 1349 new JSONString(remainingFilter.toString())); 1350 } 1351 1352 if ((debugInfo != null) && (! debugInfo.isEmpty())) 1353 { 1354 final List<JSONString> debugInfoValues = 1355 new ArrayList<>(debugInfo.size()); 1356 for (final String s : debugInfo) 1357 { 1358 debugInfoValues.add(new JSONString(s)); 1359 } 1360 1361 valueFields.put(JSON_FIELD_DEBUG_INFO, new JSONArray(debugInfoValues)); 1362 } 1363 1364 1365 return new JSONObject( 1366 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 1367 MATCHING_ENTRY_COUNT_RESPONSE_OID), 1368 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 1369 INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get()), 1370 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 1371 isCritical()), 1372 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 1373 new JSONObject(valueFields))); 1374 } 1375 1376 1377 1378 /** 1379 * Attempts to decode the provided object as a JSON representation of a 1380 * matching entry count response control. 1381 * 1382 * @param controlObject The JSON object to be decoded. It must not be 1383 * {@code null}. 1384 * @param strict Indicates whether to use strict mode when decoding 1385 * the provided JSON object. If this is {@code true}, 1386 * then this method will throw an exception if the 1387 * provided JSON object contains any unrecognized 1388 * fields. If this is {@code false}, then unrecognized 1389 * fields will be ignored. 1390 * 1391 * @return The matching entry count response control that was decoded from 1392 * the provided JSON object. 1393 * 1394 * @throws LDAPException If the provided JSON object cannot be parsed as a 1395 * valid matching entry count response control. 1396 */ 1397 @NotNull() 1398 public static MatchingEntryCountResponseControl decodeJSONControl( 1399 @NotNull final JSONObject controlObject, 1400 final boolean strict) 1401 throws LDAPException 1402 { 1403 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 1404 controlObject, strict, true, true); 1405 1406 final ASN1OctetString rawValue = jsonControl.getRawValue(); 1407 if (rawValue != null) 1408 { 1409 return new MatchingEntryCountResponseControl(jsonControl.getOID(), 1410 jsonControl.getCriticality(), rawValue); 1411 } 1412 1413 1414 final JSONObject valueObject = jsonControl.getValueObject(); 1415 1416 final MatchingEntryCountType countType; 1417 final String countTypeStr = 1418 valueObject.getFieldAsString(JSON_FIELD_COUNT_TYPE); 1419 Integer countValue = valueObject.getFieldAsInteger(JSON_FIELD_COUNT_VALUE); 1420 if (countTypeStr == null) 1421 { 1422 throw new LDAPException(ResultCode.DECODING_ERROR, 1423 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_FIELD.get( 1424 controlObject.toSingleLineString(), 1425 JSON_FIELD_COUNT_TYPE)); 1426 } 1427 1428 switch (countTypeStr) 1429 { 1430 case JSON_COUNT_TYPE_EXAMINED_COUNT: 1431 countType = MatchingEntryCountType.EXAMINED_COUNT; 1432 if (countValue == null) 1433 { 1434 throw new LDAPException(ResultCode.DECODING_ERROR, 1435 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get( 1436 controlObject.toSingleLineString(), 1437 JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, 1438 countTypeStr)); 1439 } 1440 break; 1441 1442 case JSON_COUNT_TYPE_UNEXAMINED_COUNT: 1443 countType = MatchingEntryCountType.UNEXAMINED_COUNT; 1444 if (countValue == null) 1445 { 1446 throw new LDAPException(ResultCode.DECODING_ERROR, 1447 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get( 1448 controlObject.toSingleLineString(), 1449 JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, 1450 countTypeStr)); 1451 } 1452 break; 1453 1454 case JSON_COUNT_TYPE_UPPER_BOUND: 1455 countType = MatchingEntryCountType.UPPER_BOUND; 1456 if (countValue == null) 1457 { 1458 throw new LDAPException(ResultCode.DECODING_ERROR, 1459 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get( 1460 controlObject.toSingleLineString(), 1461 JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, 1462 countTypeStr)); 1463 } 1464 break; 1465 1466 case JSON_COUNT_TYPE_UNKNOWN: 1467 countType = MatchingEntryCountType.UNKNOWN; 1468 if (countValue == null) 1469 { 1470 countValue = -1; 1471 } 1472 else 1473 { 1474 throw new LDAPException(ResultCode.DECODING_ERROR, 1475 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNEXPECTED_COUNT_VALUE. 1476 get(controlObject.toSingleLineString(), 1477 JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE, 1478 countTypeStr)); 1479 } 1480 break; 1481 1482 default: 1483 throw new LDAPException(ResultCode.DECODING_ERROR, 1484 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNKNOWN_COUNT_TYPE.get( 1485 controlObject.toSingleLineString(), 1486 JSON_FIELD_COUNT_TYPE, countTypeStr)); 1487 } 1488 1489 1490 final Boolean searchIndexed = 1491 valueObject.getFieldAsBoolean(JSON_FIELD_SEARCH_INDEXED); 1492 if (searchIndexed == null) 1493 { 1494 throw new LDAPException(ResultCode.DECODING_ERROR, 1495 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_FIELD.get( 1496 controlObject.toSingleLineString(), 1497 JSON_FIELD_SEARCH_INDEXED)); 1498 } 1499 1500 1501 final Boolean fullyIndexed = 1502 valueObject.getFieldAsBoolean(JSON_FIELD_FULLY_INDEXED); 1503 final Boolean shortCircuited = 1504 valueObject.getFieldAsBoolean(JSON_FIELD_SHORT_CIRCUITED); 1505 final Boolean candidatesAreInScope = 1506 valueObject.getFieldAsBoolean(JSON_FIELD_CANDIDATES_ARE_IN_SCOPE); 1507 1508 1509 final Filter remainingFilter; 1510 final String remainingFilterStr = 1511 valueObject.getFieldAsString(JSON_FIELD_REMAINING_FILTER); 1512 if (remainingFilterStr == null) 1513 { 1514 remainingFilter = null; 1515 } 1516 else 1517 { 1518 try 1519 { 1520 remainingFilter = Filter.create(remainingFilterStr); 1521 } 1522 catch (final LDAPException e) 1523 { 1524 Debug.debugException(e); 1525 throw new LDAPException(ResultCode.DECODING_ERROR, 1526 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_INVALID_FILTER.get( 1527 controlObject.toSingleLineString(), 1528 JSON_FIELD_REMAINING_FILTER, remainingFilterStr), 1529 e); 1530 } 1531 } 1532 1533 1534 final List<String> debugInfo; 1535 final List<JSONValue> debugInfoValues = 1536 valueObject.getFieldAsArray(JSON_FIELD_DEBUG_INFO); 1537 if (debugInfoValues == null) 1538 { 1539 debugInfo = null; 1540 } 1541 else 1542 { 1543 debugInfo = new ArrayList<>(debugInfoValues.size()); 1544 for (final JSONValue v : debugInfoValues) 1545 { 1546 if (v instanceof JSONString) 1547 { 1548 debugInfo.add(((JSONString) v).stringValue()); 1549 } 1550 else 1551 { 1552 throw new LDAPException(ResultCode.DECODING_ERROR, 1553 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_DEBUG_INFO_NOT_STRING.get( 1554 controlObject.toSingleLineString(), JSON_FIELD_DEBUG_INFO)); 1555 } 1556 } 1557 } 1558 1559 1560 if (strict) 1561 { 1562 final List<String> unrecognizedFields = 1563 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1564 valueObject, JSON_FIELD_COUNT_TYPE, JSON_FIELD_COUNT_VALUE, 1565 JSON_FIELD_SEARCH_INDEXED, JSON_FIELD_FULLY_INDEXED, 1566 JSON_FIELD_SHORT_CIRCUITED, JSON_FIELD_CANDIDATES_ARE_IN_SCOPE, 1567 JSON_FIELD_REMAINING_FILTER, JSON_FIELD_DEBUG_INFO); 1568 if (! unrecognizedFields.isEmpty()) 1569 { 1570 throw new LDAPException(ResultCode.DECODING_ERROR, 1571 ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNRECOGNIZED_FIELD.get( 1572 controlObject.toSingleLineString(), 1573 unrecognizedFields.get(0))); 1574 } 1575 } 1576 1577 1578 return new MatchingEntryCountResponseControl(countType, countValue, 1579 searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope, 1580 remainingFilter, debugInfo); 1581 } 1582 1583 1584 1585 /** 1586 * {@inheritDoc} 1587 */ 1588 @Override() 1589 public void toString(@NotNull final StringBuilder buffer) 1590 { 1591 buffer.append("MatchingEntryCountResponseControl(countType='"); 1592 buffer.append(countType.name()); 1593 buffer.append('\''); 1594 1595 switch (countType) 1596 { 1597 case EXAMINED_COUNT: 1598 case UNEXAMINED_COUNT: 1599 buffer.append(", count="); 1600 buffer.append(countValue); 1601 break; 1602 1603 case UPPER_BOUND: 1604 buffer.append(", upperBound="); 1605 buffer.append(countValue); 1606 break; 1607 } 1608 1609 buffer.append(", searchIndexed="); 1610 buffer.append(searchIndexed); 1611 1612 if (shortCircuited != null) 1613 { 1614 buffer.append(", shortCircuited="); 1615 buffer.append(shortCircuited); 1616 } 1617 1618 if (fullyIndexed != null) 1619 { 1620 buffer.append(", fullyIndexed="); 1621 buffer.append(fullyIndexed); 1622 } 1623 1624 if (candidatesAreInScope != null) 1625 { 1626 buffer.append(", candidatesAreInScope="); 1627 buffer.append(candidatesAreInScope); 1628 } 1629 1630 if (remainingFilter != null) 1631 { 1632 buffer.append(", remainingFilter='"); 1633 remainingFilter.toString(buffer); 1634 buffer.append('\''); 1635 } 1636 1637 if (! debugInfo.isEmpty()) 1638 { 1639 buffer.append(", debugInfo={"); 1640 1641 final Iterator<String> iterator = debugInfo.iterator(); 1642 while (iterator.hasNext()) 1643 { 1644 buffer.append('\''); 1645 buffer.append(iterator.next()); 1646 buffer.append('\''); 1647 1648 if (iterator.hasNext()) 1649 { 1650 buffer.append(", "); 1651 } 1652 } 1653 1654 buffer.append('}'); 1655 } 1656 1657 buffer.append(')'); 1658 } 1659}