001/* 002 * Copyright 2014-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2014-2022 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-2022 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.List; 045 046import com.unboundid.asn1.ASN1Boolean; 047import com.unboundid.asn1.ASN1Element; 048import com.unboundid.asn1.ASN1Integer; 049import com.unboundid.asn1.ASN1Null; 050import com.unboundid.asn1.ASN1OctetString; 051import com.unboundid.asn1.ASN1Sequence; 052import com.unboundid.ldap.sdk.Control; 053import com.unboundid.ldap.sdk.DecodeableControl; 054import com.unboundid.ldap.sdk.Filter; 055import com.unboundid.ldap.sdk.LDAPException; 056import com.unboundid.ldap.sdk.ResultCode; 057import com.unboundid.ldap.sdk.SearchResult; 058import com.unboundid.util.Debug; 059import com.unboundid.util.NotMutable; 060import com.unboundid.util.NotNull; 061import com.unboundid.util.Nullable; 062import com.unboundid.util.StaticUtils; 063import com.unboundid.util.ThreadSafety; 064import com.unboundid.util.ThreadSafetyLevel; 065import com.unboundid.util.Validator; 066 067import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 068 069 070 071/** 072 * This class provides a response control that may be used to provide 073 * information about the number of entries that match a given set of search 074 * criteria. The control will be included in the search result done message 075 * for any successful search operation in which the request contained a matching 076 * entry count request control. 077 * <BR> 078 * <BLOCKQUOTE> 079 * <B>NOTE:</B> This class, and other classes within the 080 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 081 * supported for use against Ping Identity, UnboundID, and 082 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 083 * for proprietary functionality or for external specifications that are not 084 * considered stable or mature enough to be guaranteed to work in an 085 * interoperable way with other types of LDAP servers. 086 * </BLOCKQUOTE> 087 * <BR> 088 * The matching entry count response control has an OID of 089 * "1.3.6.1.4.1.30221.2.5.37", a criticality of false, and a value with the 090 * following encoding: 091 * <PRE> 092 * MatchingEntryCountResponse ::= SEQUENCE { 093 * entryCount CHOICE { 094 * examinedCount [0] INTEGER, 095 * unexaminedCount [1] INTEGER, 096 * upperBound [2] INTEGER, 097 * unknown [3] NULL, 098 * ... } 099 * debugInfo [0] SEQUENCE OF OCTET STRING OPTIONAL, 100 * searchIndexed [1] BOOLEAN DEFAULT TRUE, 101 * shortCircuited [2] BOOLEAN OPTIONAL, 102 * fullyIndexed [3] BOOLEAN OPTIONAL, 103 * candidatesAreInScope [4] BOOLEAN OPTIONAL, 104 * remainingFilter [5] Filter OPTIONAL, 105 * ... } 106 * </PRE> 107 * 108 * @see MatchingEntryCountRequestControl 109 */ 110@NotMutable() 111@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 112public final class MatchingEntryCountResponseControl 113 extends Control 114 implements DecodeableControl 115{ 116 /** 117 * The OID (1.3.6.1.4.1.30221.2.5.37) for the matching entry count response 118 * control. 119 */ 120 @NotNull public static final String MATCHING_ENTRY_COUNT_RESPONSE_OID = 121 "1.3.6.1.4.1.30221.2.5.37"; 122 123 124 125 /** 126 * The BER type for the element used to hold the list of debug messages. 127 */ 128 private static final byte TYPE_DEBUG_INFO = (byte) 0xA0; 129 130 131 132 /** 133 * The BER type for the element used to indicate whether the search criteria 134 * is at least partially indexed. 135 */ 136 private static final byte TYPE_SEARCH_INDEXED = (byte) 0x81; 137 138 139 140 /** 141 * The BER type for the element used to indicate whether the server 142 * short-circuited during candidate set processing before evaluating all 143 * elements of the search criteria (the filter and scope). 144 */ 145 private static final byte TYPE_SHORT_CIRCUITED = (byte) 0x82; 146 147 148 149 /** 150 * The BER type for the element used to indicate whether the search criteria 151 * is fully indexed. 152 */ 153 private static final byte TYPE_FULLY_INDEXED = (byte) 0x83; 154 155 156 157 /** 158 * The BER type for the element used to indicate whether all the identified 159 * candidate entries are within the scope of the search. 160 */ 161 private static final byte TYPE_CANDIDATES_ARE_IN_SCOPE = (byte) 0x84; 162 163 164 165 /** 166 * The BER type for the element used to provide the remaining filter for the 167 * search operation, which is the portion of the filter that was determined 168 * to be unindexed, or that was unevaluated if processing short-circuited in 169 * the course of building the candidate set. 170 */ 171 private static final byte TYPE_REMAINING_FILTER = (byte) 0xA5; 172 173 174 175 /** 176 * The serial version UID for this serializable class. 177 */ 178 private static final long serialVersionUID = -7808452580964236458L; 179 180 181 182 // Indicates whether the search criteria is considered at least partially 183 // indexed by the server. 184 private final boolean searchIndexed; 185 186 // Indicates whether all the identified candidate entries are within the scope 187 // of the search. 188 @Nullable private final Boolean candidatesAreInScope; 189 190 // Indicates whether the search criteria is considered fully indexed. 191 @Nullable private final Boolean fullyIndexed; 192 193 // Indicates whether the server short-circuited during candidate set 194 // processing before evaluating all elements of the search criteria (the 195 // filter and scope). 196 @Nullable private final Boolean shortCircuited; 197 198 // The portion of the filter that was either identified as unindexed or that 199 // was not evaluated in the course of building the candidate set. 200 @Nullable private final Filter remainingFilter; 201 202 // The count value for this matching entry count response control. 203 private final int countValue; 204 205 // A list of messages providing debug information about the processing 206 // performed by the server. 207 @NotNull private final List<String> debugInfo; 208 209 // The count type for this matching entry count response control. 210 @NotNull private final MatchingEntryCountType countType; 211 212 213 214 /** 215 * Creates a new empty control instance that is intended to be used only for 216 * decoding controls via the {@code DecodeableControl} interface. 217 */ 218 MatchingEntryCountResponseControl() 219 { 220 searchIndexed = false; 221 candidatesAreInScope = null; 222 fullyIndexed = null; 223 shortCircuited = null; 224 remainingFilter = null; 225 countValue = -1; 226 countType = null; 227 debugInfo = null; 228 } 229 230 231 232 /** 233 * Creates a new matching entry count response control with the provided 234 * information. 235 * 236 * @param countType The matching entry count type. It must not 237 * be {@code null}. 238 * @param countValue The matching entry count value. It must be 239 * greater than or equal to zero for a count 240 * type of either {@code EXAMINED_COUNT} or 241 * {@code UNEXAMINED_COUNT}. It must be greater 242 * than zero for a count type of 243 * {@code UPPER_BOUND}. It must be -1 for a 244 * count type of {@code UNKNOWN}. 245 * @param searchIndexed Indicates whether the search criteria is 246 * considered at least partially indexed and 247 * could be processed more efficiently than 248 * examining all entries with a full database 249 * scan. 250 * @param shortCircuited Indicates whether the server short-circuited 251 * during candidate set processing before 252 * evaluating all elements of the search 253 * criteria (the filter and scope). This may be 254 * {@code null} if it is not available (e.g., 255 * because extended response data was not 256 * requested). 257 * @param fullyIndexed Indicates whether the search is considered 258 * fully indexed. Note that this may be 259 * {@code false} even if the filter is actually 260 * fully indexed if server index processing 261 * short-circuited before evaluating all 262 * components of the filter. To avoid this, 263 * issue the request control with both fast and 264 * slow short-circuit thresholds set to zero. 265 * This may be {@code null} if this is not 266 * available (e.g., because extended response 267 * data was not requested). 268 * @param candidatesAreInScope Indicates whether all the identified 269 * candidate entries are within the scope of 270 * the search. It may be {@code null} if this 271 * is not available (e.g., because extended 272 * response data was not requested). 273 * @param remainingFilter The portion of the filter that was either 274 * identified as unindexed or that was not 275 * evaluated because processing short-circuited 276 * in the course of building the candidate set. 277 * It may be {@code null} if there is no 278 * remaining filter or if this information is 279 * not available (e.g., because extended 280 * response data was not requested). 281 * @param debugInfo An optional list of messages providing debug 282 * information about the processing performed by 283 * the server. It may be {@code null} or empty 284 * if no debug messages should be included. 285 */ 286 private MatchingEntryCountResponseControl( 287 @NotNull final MatchingEntryCountType countType, 288 final int countValue, 289 final boolean searchIndexed, 290 @Nullable final Boolean shortCircuited, 291 @Nullable final Boolean fullyIndexed, 292 @Nullable final Boolean candidatesAreInScope, 293 @Nullable final Filter remainingFilter, 294 @Nullable final Collection<String> debugInfo) 295 { 296 super(MATCHING_ENTRY_COUNT_RESPONSE_OID, false, 297 encodeValue(countType, countValue, searchIndexed, shortCircuited, 298 fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo)); 299 300 this.countType = countType; 301 this.countValue = countValue; 302 this.searchIndexed = searchIndexed; 303 this.shortCircuited = shortCircuited; 304 this.fullyIndexed = fullyIndexed; 305 this.candidatesAreInScope = candidatesAreInScope; 306 this.remainingFilter = remainingFilter; 307 308 if (debugInfo == null) 309 { 310 this.debugInfo = Collections.emptyList(); 311 } 312 else 313 { 314 this.debugInfo = 315 Collections.unmodifiableList(new ArrayList<>(debugInfo)); 316 } 317 } 318 319 320 321 /** 322 * Creates a new matching entry count response control decoded from the given 323 * generic control contents. 324 * 325 * @param oid The OID for the control. 326 * @param isCritical Indicates whether this control should be marked 327 * critical. 328 * @param value The encoded value for the control. 329 * 330 * @throws LDAPException If a problem occurs while attempting to decode the 331 * generic control as a matching entry count response 332 * control. 333 */ 334 public MatchingEntryCountResponseControl(@NotNull final String oid, 335 final boolean isCritical, 336 @Nullable final ASN1OctetString value) 337 throws LDAPException 338 { 339 super(oid, isCritical, value); 340 341 if (value == null) 342 { 343 throw new LDAPException(ResultCode.DECODING_ERROR, 344 ERR_MATCHING_ENTRY_COUNT_RESPONSE_MISSING_VALUE.get()); 345 } 346 347 try 348 { 349 final ASN1Element[] elements = 350 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 351 countType = MatchingEntryCountType.valueOf(elements[0].getType()); 352 if (countType == null) 353 { 354 throw new LDAPException(ResultCode.DECODING_ERROR, 355 ERR_MATCHING_ENTRY_COUNT_RESPONSE_INVALID_COUNT_TYPE.get( 356 StaticUtils.toHex(elements[0].getType()))); 357 } 358 359 switch (countType) 360 { 361 case EXAMINED_COUNT: 362 case UNEXAMINED_COUNT: 363 countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); 364 if (countValue < 0) 365 { 366 throw new LDAPException(ResultCode.DECODING_ERROR, 367 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NEGATIVE_EXACT_COUNT.get()); 368 } 369 break; 370 371 case UPPER_BOUND: 372 countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); 373 if (countValue <= 0) 374 { 375 throw new LDAPException(ResultCode.DECODING_ERROR, 376 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NON_POSITIVE_UPPER_BOUND. 377 get()); 378 } 379 break; 380 381 case UNKNOWN: 382 default: 383 countValue = -1; 384 break; 385 } 386 387 boolean decodedSearchIndexed = 388 (countType != MatchingEntryCountType.UNKNOWN); 389 Boolean decodedFullyIndexed = null; 390 Boolean decodedCandidatesAreInScope = null; 391 Boolean decodedShortCircuited = null; 392 Filter decodedRemainingFilter = null; 393 List<String> debugMessages = Collections.emptyList(); 394 for (int i=1; i < elements.length; i++) 395 { 396 switch (elements[i].getType()) 397 { 398 case TYPE_DEBUG_INFO: 399 final ASN1Element[] debugElements = 400 ASN1Sequence.decodeAsSequence(elements[i]).elements(); 401 debugMessages = new ArrayList<>(debugElements.length); 402 for (final ASN1Element e : debugElements) 403 { 404 debugMessages.add( 405 ASN1OctetString.decodeAsOctetString(e).stringValue()); 406 } 407 break; 408 409 case TYPE_SEARCH_INDEXED: 410 decodedSearchIndexed = 411 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 412 break; 413 414 case TYPE_SHORT_CIRCUITED: 415 decodedShortCircuited = 416 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 417 break; 418 419 case TYPE_FULLY_INDEXED: 420 decodedFullyIndexed = 421 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 422 break; 423 424 case TYPE_CANDIDATES_ARE_IN_SCOPE: 425 decodedCandidatesAreInScope = 426 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 427 break; 428 429 case TYPE_REMAINING_FILTER: 430 final ASN1Element filterElement = 431 ASN1Element.decode(elements[i].getValue()); 432 decodedRemainingFilter = Filter.decode(filterElement); 433 break; 434 } 435 } 436 437 searchIndexed = decodedSearchIndexed; 438 shortCircuited = decodedShortCircuited; 439 fullyIndexed = decodedFullyIndexed; 440 candidatesAreInScope = decodedCandidatesAreInScope; 441 remainingFilter = decodedRemainingFilter; 442 debugInfo = Collections.unmodifiableList(debugMessages); 443 } 444 catch (final LDAPException le) 445 { 446 Debug.debugException(le); 447 throw le; 448 } 449 catch (final Exception e) 450 { 451 Debug.debugException(e); 452 throw new LDAPException(ResultCode.DECODING_ERROR, 453 ERR_GET_BACKEND_SET_ID_RESPONSE_CANNOT_DECODE.get( 454 StaticUtils.getExceptionMessage(e)), 455 e); 456 } 457 } 458 459 460 461 /** 462 * Creates a new matching entry count response control for the case in which 463 * the exact number of matching entries is known. 464 * 465 * @param count The exact number of entries matching the associated 466 * search criteria. It must be greater than or equal to 467 * zero. 468 * @param examined Indicates whether the server examined the entries to 469 * exclude those entries that would not be returned to the 470 * client in a normal search with the same criteria. 471 * @param debugInfo An optional list of messages providing debug information 472 * about the processing performed by the server. It may be 473 * {@code null} or empty if no debug messages should be 474 * included. 475 * 476 * @return The matching entry count response control that was created. 477 */ 478 @NotNull() 479 public static MatchingEntryCountResponseControl createExactCountResponse( 480 final int count, final boolean examined, 481 @Nullable final Collection<String> debugInfo) 482 { 483 return createExactCountResponse(count, examined, true, debugInfo); 484 } 485 486 487 488 /** 489 * Creates a new matching entry count response control for the case in which 490 * the exact number of matching entries is known. 491 * 492 * @param count The exact number of entries matching the associated 493 * search criteria. It must be greater than or equal 494 * to zero. 495 * @param examined Indicates whether the server examined the entries to 496 * exclude those entries that would not be returned to 497 * the client in a normal search with the same 498 * criteria. 499 * @param searchIndexed Indicates whether the search criteria is considered 500 * at least partially indexed and could be processed 501 * more efficiently than examining all entries with a 502 * full database scan. 503 * @param debugInfo An optional list of messages providing debug 504 * information about the processing performed by the 505 * server. It may be {@code null} or empty if no debug 506 * messages should be included. 507 * 508 * @return The matching entry count response control that was created. 509 */ 510 @NotNull() 511 public static MatchingEntryCountResponseControl createExactCountResponse( 512 final int count, final boolean examined, 513 final boolean searchIndexed, 514 @Nullable final Collection<String> debugInfo) 515 { 516 return createExactCountResponse(count, examined, searchIndexed, null, null, 517 null, null, debugInfo); 518 } 519 520 521 522 /** 523 * Creates a new matching entry count response control for the case in which 524 * the exact number of matching entries is known. 525 * 526 * @param count The exact number of entries matching the 527 * associated search criteria. It must be 528 * greater than or equal to zero. 529 * @param examined Indicates whether the server examined the 530 * entries to exclude those entries that would 531 * not be returned to the client in a normal 532 * search with the same criteria. 533 * @param searchIndexed Indicates whether the search criteria is 534 * considered at least partially indexed and 535 * could be processed more efficiently than 536 * examining all entries with a full database 537 * scan. 538 * @param shortCircuited Indicates whether the server short-circuited 539 * during candidate set processing before 540 * evaluating all elements of the search 541 * criteria (the filter and scope). This may be 542 * {@code null} if it is not available (e.g., 543 * because extended response data was not 544 * requested). 545 * @param fullyIndexed Indicates whether the search is considered 546 * fully indexed. Note that this may be 547 * {@code false} even if the filter is actually 548 * fully indexed if server index processing 549 * short-circuited before evaluating all 550 * components of the filter. To avoid this, 551 * issue the request control with both fast and 552 * slow short-circuit thresholds set to zero. 553 * This may be {@code null} if this is not 554 * available (e.g., because extended response 555 * data was not requested). 556 * @param candidatesAreInScope Indicates whether all the identified 557 * candidate entries are within the scope of 558 * the search. It may be {@code null} if this 559 * is not available (e.g., because extended 560 * response data was not requested). 561 * @param remainingFilter The portion of the filter that was either 562 * identified as unindexed or that was not 563 * evaluated because processing short-circuited 564 * in the course of building the candidate set. 565 * It may be {@code null} if there is no 566 * remaining filter or if this information is 567 * not available (e.g., because extended 568 * response data was not requested). 569 * @param debugInfo An optional list of messages providing debug 570 * information about the processing performed by 571 * the server. It may be {@code null} or empty 572 * if no debug messages should be included. 573 * 574 * @return The matching entry count response control that was created. 575 */ 576 @NotNull() 577 public static MatchingEntryCountResponseControl createExactCountResponse( 578 final int count, final boolean examined, 579 final boolean searchIndexed, 580 @Nullable final Boolean shortCircuited, 581 @Nullable final Boolean fullyIndexed, 582 @Nullable final Boolean candidatesAreInScope, 583 @Nullable final Filter remainingFilter, 584 @Nullable final Collection<String> debugInfo) 585 { 586 Validator.ensureTrue(count >= 0); 587 588 final MatchingEntryCountType countType; 589 if (examined) 590 { 591 countType = MatchingEntryCountType.EXAMINED_COUNT; 592 } 593 else 594 { 595 countType = MatchingEntryCountType.UNEXAMINED_COUNT; 596 } 597 598 return new MatchingEntryCountResponseControl(countType, count, 599 searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope, 600 remainingFilter, debugInfo); 601 } 602 603 604 605 /** 606 * Creates a new matching entry count response control for the case in which 607 * the exact number of matching entries is not known, but the server was able 608 * to determine an upper bound on the number of matching entries. This upper 609 * bound count may include entries that do not match the search filter, that 610 * are outside the scope of the search, and/or that match the search criteria 611 * but would not have been returned to the client in a normal search with the 612 * same criteria. 613 * 614 * @param upperBound The upper bound on the number of entries that match the 615 * associated search criteria. It must be greater than 616 * zero. 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 createUpperBoundResponse( 626 final int upperBound, 627 @Nullable final Collection<String> debugInfo) 628 { 629 return createUpperBoundResponse(upperBound, true, debugInfo); 630 } 631 632 633 634 /** 635 * Creates a new matching entry count response control for the case in which 636 * the exact number of matching entries is not known, but the server was able 637 * to determine an upper bound on the number of matching entries. This upper 638 * bound count may include entries that do not match the search filter, that 639 * are outside the scope of the search, and/or that match the search criteria 640 * but would not have been returned to the client in a normal search with the 641 * same criteria. 642 * 643 * @param upperBound The upper bound on the number of entries that match 644 * the associated search criteria. It must be greater 645 * than zero. 646 * @param searchIndexed Indicates whether the search criteria is considered 647 * at least partially indexed and could be processed 648 * more efficiently than examining all entries with a 649 * full database scan. 650 * @param debugInfo An optional list of messages providing debug 651 * information about the processing performed by the 652 * server. It may be {@code null} or empty if no debug 653 * messages should be included. 654 * 655 * @return The matching entry count response control that was created. 656 */ 657 @NotNull() 658 public static MatchingEntryCountResponseControl createUpperBoundResponse( 659 final int upperBound, final boolean searchIndexed, 660 @Nullable final Collection<String> debugInfo) 661 { 662 return createUpperBoundResponse(upperBound, searchIndexed, null, null, null, 663 null, debugInfo); 664 } 665 666 667 668 /** 669 * Creates a new matching entry count response control for the case in which 670 * the exact number of matching entries is not known, but the server was able 671 * to determine an upper bound on the number of matching entries. This upper 672 * bound count may include entries that do not match the search filter, that 673 * are outside the scope of the search, and/or that match the search criteria 674 * but would not have been returned to the client in a normal search with the 675 * same criteria. 676 * 677 * @param upperBound The upper bound on the number of entries that 678 * match the associated search criteria. It 679 * must be greater than zero. 680 * @param searchIndexed Indicates whether the search criteria is 681 * considered at least partially indexed and 682 * could be processed more efficiently than 683 * examining all entries with a full database 684 * scan. 685 * @param shortCircuited Indicates whether the server short-circuited 686 * during candidate set processing before 687 * evaluating all elements of the search 688 * criteria (the filter and scope). This may be 689 * {@code null} if it is not available (e.g., 690 * because extended response data was not 691 * requested). 692 * @param fullyIndexed Indicates whether the search is considered 693 * fully indexed. Note that this may be 694 * {@code false} even if the filter is actually 695 * fully indexed if server index processing 696 * short-circuited before evaluating all 697 * components of the filter. To avoid this, 698 * issue the request control with both fast and 699 * slow short-circuit thresholds set to zero. 700 * This may be {@code null} if this is not 701 * available (e.g., because extended response 702 * data was not requested). 703 * @param candidatesAreInScope Indicates whether all the identified 704 * candidate entries are within the scope of 705 * the search. It may be {@code null} if this 706 * is not available (e.g., because extended 707 * response data was not requested). 708 * @param remainingFilter The portion of the filter that was either 709 * identified as unindexed or that was not 710 * evaluated because processing short-circuited 711 * in the course of building the candidate set. 712 * It may be {@code null} if there is no 713 * remaining filter or if this information is 714 * not available (e.g., because extended 715 * response data was not requested). 716 * @param debugInfo An optional list of messages providing debug 717 * information about the processing performed by 718 * the server. It may be {@code null} or empty 719 * if no debug messages should be included. 720 * 721 * @return The matching entry count response control that was created. 722 */ 723 @NotNull() 724 public static MatchingEntryCountResponseControl createUpperBoundResponse( 725 final int upperBound, final boolean searchIndexed, 726 @Nullable final Boolean shortCircuited, 727 @Nullable final Boolean fullyIndexed, 728 @Nullable final Boolean candidatesAreInScope, 729 @Nullable final Filter remainingFilter, 730 @Nullable final Collection<String> debugInfo) 731 { 732 Validator.ensureTrue(upperBound > 0); 733 734 return new MatchingEntryCountResponseControl( 735 MatchingEntryCountType.UPPER_BOUND, upperBound, searchIndexed, 736 shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter, 737 debugInfo); 738 } 739 740 741 742 /** 743 * Creates a new matching entry count response control for the case in which 744 * the server was unable to make any meaningful determination about the number 745 * of entries matching the search criteria. 746 * 747 * @param debugInfo An optional list of messages providing debug information 748 * about the processing performed by the server. It may be 749 * {@code null} or empty if no debug messages should be 750 * included. 751 * 752 * @return The matching entry count response control that was created. 753 */ 754 @NotNull() 755 public static MatchingEntryCountResponseControl createUnknownCountResponse( 756 @Nullable final Collection<String> debugInfo) 757 { 758 return new MatchingEntryCountResponseControl(MatchingEntryCountType.UNKNOWN, 759 -1, false, null, null, null, null, debugInfo); 760 } 761 762 763 764 /** 765 * Encodes a control value with the provided information. 766 * 767 * @param countType The matching entry count type. It must not 768 * be {@code null}. 769 * @param countValue The matching entry count value. It must be 770 * greater than or equal to zero for a count 771 * type of either {@code EXAMINED_COUNT} or 772 * {@code UNEXAMINED_COUNT}. It must be greater 773 * than zero for a count type of 774 * {@code UPPER_BOUND}. It must be -1 for a 775 * count type of {@code UNKNOWN}. 776 * @param searchIndexed Indicates whether the search criteria is 777 * considered at least partially indexed and 778 * could be processed more efficiently than 779 * examining all entries with a full database 780 * scan. 781 * @param shortCircuited Indicates whether the server short-circuited 782 * during candidate set processing before 783 * evaluating all elements of the search 784 * criteria (the filter and scope). This may be 785 * {@code null} if it is not available (e.g., 786 * because extended response data was not 787 * requested). 788 * @param fullyIndexed Indicates whether the search is considered 789 * fully indexed. Note that this may be 790 * {@code false} even if the filter is actually 791 * fully indexed if server index processing 792 * short-circuited before evaluating all 793 * components of the filter. To avoid this, 794 * issue the request control with both fast and 795 * slow short-circuit thresholds set to zero. 796 * This may be {@code null} if this is not 797 * available (e.g., because extended response 798 * data was not requested). 799 * @param candidatesAreInScope Indicates whether all the identified 800 * candidate entries are within the scope of 801 * the search. It may be {@code null} if this 802 * is not available (e.g., because extended 803 * response data was not requested). 804 * @param remainingFilter The portion of the filter that was either 805 * identified as unindexed or that was not 806 * evaluated because processing short-circuited 807 * in the course of building the candidate set. 808 * It may be {@code null} if there is no 809 * remaining filter or if this information is 810 * not available (e.g., because extended 811 * response data was not requested). 812 * @param debugInfo An optional list of messages providing debug 813 * information about the processing performed by 814 * the server. It may be {@code null} or empty 815 * if no debug messages should be included. 816 * 817 * @return The encoded control value. 818 */ 819 @NotNull() 820 private static ASN1OctetString encodeValue( 821 @NotNull final MatchingEntryCountType countType, 822 final int countValue, final boolean searchIndexed, 823 @Nullable final Boolean shortCircuited, 824 @Nullable final Boolean fullyIndexed, 825 @Nullable final Boolean candidatesAreInScope, 826 @Nullable final Filter remainingFilter, 827 @Nullable final Collection<String> debugInfo) 828 { 829 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 830 831 switch (countType) 832 { 833 case EXAMINED_COUNT: 834 case UNEXAMINED_COUNT: 835 case UPPER_BOUND: 836 elements.add(new ASN1Integer(countType.getBERType(), countValue)); 837 break; 838 case UNKNOWN: 839 elements.add(new ASN1Null(countType.getBERType())); 840 break; 841 } 842 843 if (debugInfo != null) 844 { 845 final ArrayList<ASN1Element> debugElements = 846 new ArrayList<>(debugInfo.size()); 847 for (final String s : debugInfo) 848 { 849 debugElements.add(new ASN1OctetString(s)); 850 } 851 852 elements.add(new ASN1Sequence(TYPE_DEBUG_INFO, debugElements)); 853 } 854 855 if (! searchIndexed) 856 { 857 elements.add(new ASN1Boolean(TYPE_SEARCH_INDEXED, searchIndexed)); 858 } 859 860 if (shortCircuited != null) 861 { 862 elements.add(new ASN1Boolean(TYPE_SHORT_CIRCUITED, shortCircuited)); 863 } 864 865 if (fullyIndexed != null) 866 { 867 elements.add(new ASN1Boolean(TYPE_FULLY_INDEXED, fullyIndexed)); 868 } 869 870 if (candidatesAreInScope != null) 871 { 872 elements.add(new ASN1Boolean(TYPE_CANDIDATES_ARE_IN_SCOPE, 873 candidatesAreInScope)); 874 } 875 876 if (remainingFilter != null) 877 { 878 elements.add(new ASN1OctetString(TYPE_REMAINING_FILTER, 879 remainingFilter.encode().encode())); 880 } 881 882 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 883 } 884 885 886 887 /** 888 * Retrieves the matching entry count type for the response control. 889 * 890 * @return The matching entry count type for the response control. 891 */ 892 @NotNull() 893 public MatchingEntryCountType getCountType() 894 { 895 return countType; 896 } 897 898 899 900 /** 901 * Retrieves the matching entry count value for the response control. For a 902 * count type of {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}, this is 903 * the exact number of matching entries. For a count type of 904 * {@code UPPER_BOUND}, this is the maximum number of entries that may match 905 * the search criteria, but it may also include entries that do not match the 906 * criteria. For a count type of {@code UNKNOWN}, this will always be -1. 907 * 908 * @return The exact count or upper bound of the number of entries in the 909 * server that may match the search criteria, or -1 if the server 910 * could not determine the number of matching entries. 911 */ 912 public int getCountValue() 913 { 914 return countValue; 915 } 916 917 918 919 /** 920 * Indicates whether the server considers the search criteria to be indexed 921 * and therefore it could be processed more efficiently than examining all 922 * entries with a full database scan. 923 * 924 * @return {@code true} if the server considers the search criteria to be 925 * indexed, or {@code false} if not. 926 */ 927 public boolean searchIndexed() 928 { 929 return searchIndexed; 930 } 931 932 933 934 /** 935 * Indicates whether the server short-circuited during candidate set 936 * processing before evaluating all elements of the search criteria (the 937 * filter and scope). 938 * 939 * @return {@code Boolean.TRUE} if the server did short-circuit during 940 * candidate set processing before evaluating all elements of the 941 * search criteria, {@code Boolean.FALSE} if the server evaluated all 942 * elements of the search criteria, or {@code null} if this 943 * information is not available (e.g., because extended response data 944 * was not requested). 945 */ 946 @Nullable() 947 public Boolean getShortCircuited() 948 { 949 return shortCircuited; 950 } 951 952 953 954 /** 955 * Indicates whether the server considers the search criteria to be fully 956 * indexed. Note that if the server short-circuited during candidate set 957 * processing before evaluating all search criteria (the filter and scope), 958 * this may be {@code Boolean.FALSE} even if the search is actually completely 959 * indexed. 960 * 961 * @return {@code Boolean.TRUE} if the server considers the search criteria 962 * to be fully indexed, {@code Boolean.FALSE} if the search criteria 963 * is not known to be fully indexed, or {@code null} if this 964 * information is not available (e.g., because extended response data 965 * was not requested). 966 */ 967 @Nullable() 968 public Boolean getFullyIndexed() 969 { 970 return fullyIndexed; 971 } 972 973 974 975 /** 976 * Indicates whether the server can determine that all the identified 977 * candidates are within the scope of the search. Note that even if the 978 * server returns {@code Boolean.FALSE}, it does not necessarily mean that 979 * not all the candidates are within the scope of the search, but just that 980 * the server is not certain that is the case. 981 * 982 * @return {@code Boolean.TRUE} if the server can determine that all the 983 * identified candidates are within the scope of the search, 984 * {@code Boolean.FALSE} if the server cannot determine that all the 985 * identified candidates are within the scope of the search, or 986 * {@code null} if this information is not available (e.g., because 987 * extended response data was not requested). 988 */ 989 @Nullable() 990 public Boolean getCandidatesAreInScope() 991 { 992 return candidatesAreInScope; 993 } 994 995 996 997 /** 998 * Retrieves the portion of the filter that was either identified as not 999 * indexed or that was not evaluated during candidate processing (e.g., 1000 * because the server short-circuited processing before examining all filter 1001 * components). 1002 * 1003 * @return The portion of the filter that was either identified as not 1004 * indexed or that was not evaluated during candidate processing, or 1005 * {@code null} if there was no remaining filter or if this 1006 * information is not available (e.g., because extended response data 1007 * was not requested). 1008 */ 1009 @Nullable() 1010 public Filter getRemainingFilter() 1011 { 1012 return remainingFilter; 1013 } 1014 1015 1016 1017 /** 1018 * Retrieves a list of messages with debug information about the processing 1019 * performed by the server in the course of obtaining the matching entry 1020 * count. These messages are intended to be human-readable rather than 1021 * machine-parsable. 1022 * 1023 * @return A list of messages with debug information about the processing 1024 * performed by the server in the course of obtaining the matching 1025 * entry count, or an empty list if no debug messages were provided. 1026 */ 1027 @NotNull() 1028 public List<String> getDebugInfo() 1029 { 1030 return debugInfo; 1031 } 1032 1033 1034 1035 /** 1036 * {@inheritDoc} 1037 */ 1038 @Override() 1039 @NotNull() 1040 public MatchingEntryCountResponseControl decodeControl( 1041 @NotNull final String oid, 1042 final boolean isCritical, 1043 @Nullable final ASN1OctetString value) 1044 throws LDAPException 1045 { 1046 return new MatchingEntryCountResponseControl(oid, isCritical, value); 1047 } 1048 1049 1050 1051 /** 1052 * Extracts a matching entry count response control from the provided search 1053 * result. 1054 * 1055 * @param result The search result from which to retrieve the matching entry 1056 * count response control. 1057 * 1058 * @return The matching entry count response control contained in the 1059 * provided result, or {@code null} if the result did not contain a 1060 * matching entry count response control. 1061 * 1062 * @throws LDAPException If a problem is encountered while attempting to 1063 * decode the matching entry count response control 1064 * contained in the provided result. 1065 */ 1066 @Nullable() 1067 public static MatchingEntryCountResponseControl get( 1068 @NotNull final SearchResult result) 1069 throws LDAPException 1070 { 1071 final Control c = 1072 result.getResponseControl(MATCHING_ENTRY_COUNT_RESPONSE_OID); 1073 if (c == null) 1074 { 1075 return null; 1076 } 1077 1078 if (c instanceof MatchingEntryCountResponseControl) 1079 { 1080 return (MatchingEntryCountResponseControl) c; 1081 } 1082 else 1083 { 1084 return new MatchingEntryCountResponseControl(c.getOID(), c.isCritical(), 1085 c.getValue()); 1086 } 1087 } 1088 1089 1090 1091 /** 1092 * {@inheritDoc} 1093 */ 1094 @Override() 1095 @NotNull() 1096 public String getControlName() 1097 { 1098 return INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get(); 1099 } 1100 1101 1102 1103 /** 1104 * {@inheritDoc} 1105 */ 1106 @Override() 1107 public void toString(@NotNull final StringBuilder buffer) 1108 { 1109 buffer.append("MatchingEntryCountResponseControl(countType='"); 1110 buffer.append(countType.name()); 1111 buffer.append('\''); 1112 1113 switch (countType) 1114 { 1115 case EXAMINED_COUNT: 1116 case UNEXAMINED_COUNT: 1117 buffer.append(", count="); 1118 buffer.append(countValue); 1119 break; 1120 1121 case UPPER_BOUND: 1122 buffer.append(", upperBound="); 1123 buffer.append(countValue); 1124 break; 1125 } 1126 1127 buffer.append(", searchIndexed="); 1128 buffer.append(searchIndexed); 1129 1130 if (shortCircuited != null) 1131 { 1132 buffer.append(", shortCircuited="); 1133 buffer.append(shortCircuited); 1134 } 1135 1136 if (fullyIndexed != null) 1137 { 1138 buffer.append(", fullyIndexed="); 1139 buffer.append(fullyIndexed); 1140 } 1141 1142 if (candidatesAreInScope != null) 1143 { 1144 buffer.append(", candidatesAreInScope="); 1145 buffer.append(candidatesAreInScope); 1146 } 1147 1148 if (remainingFilter != null) 1149 { 1150 buffer.append(", remainingFilter='"); 1151 remainingFilter.toString(buffer); 1152 buffer.append('\''); 1153 } 1154 1155 if (! debugInfo.isEmpty()) 1156 { 1157 buffer.append(", debugInfo={"); 1158 1159 final Iterator<String> iterator = debugInfo.iterator(); 1160 while (iterator.hasNext()) 1161 { 1162 buffer.append('\''); 1163 buffer.append(iterator.next()); 1164 buffer.append('\''); 1165 1166 if (iterator.hasNext()) 1167 { 1168 buffer.append(", "); 1169 } 1170 } 1171 1172 buffer.append('}'); 1173 } 1174 1175 buffer.append(')'); 1176 } 1177}