001/* 002 * Copyright 2014-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2014-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2015-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.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.LDAPException; 055import com.unboundid.ldap.sdk.ResultCode; 056import com.unboundid.ldap.sdk.SearchResult; 057import com.unboundid.util.Debug; 058import com.unboundid.util.NotMutable; 059import com.unboundid.util.StaticUtils; 060import com.unboundid.util.ThreadSafety; 061import com.unboundid.util.ThreadSafetyLevel; 062import com.unboundid.util.Validator; 063 064import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 065 066 067 068/** 069 * This class provides a response control that may be used to provide 070 * information about the number of entries that match a given set of search 071 * criteria. The control will be included in the search result done message 072 * for any successful search operation in which the request contained a matching 073 * entry count request control. 074 * <BR> 075 * <BLOCKQUOTE> 076 * <B>NOTE:</B> This class, and other classes within the 077 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 078 * supported for use against Ping Identity, UnboundID, and 079 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 080 * for proprietary functionality or for external specifications that are not 081 * considered stable or mature enough to be guaranteed to work in an 082 * interoperable way with other types of LDAP servers. 083 * </BLOCKQUOTE> 084 * <BR> 085 * The matching entry count response control has an OID of 086 * "1.3.6.1.4.1.30221.2.5.37", a criticality of false, and a value with the 087 * following encoding: 088 * <PRE> 089 * MatchingEntryCountResponse ::= SEQUENCE { 090 * entryCount CHOICE { 091 * examinedCount [0] INTEGER, 092 * unexaminedCount [1] INTEGER, 093 * upperBound [2] INTEGER, 094 * unknown [3] NULL, 095 * ... } 096 * debugInfo [0] SEQUENCE OF OCTET STRING OPTIONAL, 097 * searchIndexed [1] BOOLEAN DEFAULT TRUE, 098 * ... } 099 * </PRE> 100 * 101 * @see MatchingEntryCountRequestControl 102 */ 103@NotMutable() 104@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 105public final class MatchingEntryCountResponseControl 106 extends Control 107 implements DecodeableControl 108{ 109 /** 110 * The OID (1.3.6.1.4.1.30221.2.5.37) for the matching entry count response 111 * control. 112 */ 113 public static final String MATCHING_ENTRY_COUNT_RESPONSE_OID = 114 "1.3.6.1.4.1.30221.2.5.37"; 115 116 117 118 /** 119 * The BER type for the element used to hold the list of debug messages. 120 */ 121 private static final byte TYPE_DEBUG_INFO = (byte) 0xA0; 122 123 124 125 /** 126 * The BER type for the element used to indicate whether the search criteria 127 * is at least partially indexed. 128 */ 129 private static final byte TYPE_SEARCH_INDEXED = (byte) 0x81; 130 131 132 133 /** 134 * The serial version UID for this serializable class. 135 */ 136 private static final long serialVersionUID = -5488025806310455564L; 137 138 139 140 // Indicates whether the search criteria is considered at least partially 141 // indexed by the server. 142 private final boolean searchIndexed; 143 144 // The count value for this matching entry count response control. 145 private final int countValue; 146 147 // A list of messages providing debug information about the processing 148 // performed by the server. 149 private final List<String> debugInfo; 150 151 // The count type for this matching entry count response control. 152 private final MatchingEntryCountType countType; 153 154 155 156 /** 157 * Creates a new empty control instance that is intended to be used only for 158 * decoding controls via the {@code DecodeableControl} interface. 159 */ 160 MatchingEntryCountResponseControl() 161 { 162 searchIndexed = false; 163 countType = null; 164 countValue = -1; 165 debugInfo = null; 166 } 167 168 169 170 /** 171 * Creates a new matching entry count response control with the provided 172 * information. 173 * 174 * @param countType The matching entry count type. It must not be 175 * {@code null}. 176 * @param countValue The matching entry count value. It must be greater 177 * than or equal to zero for a count type of either 178 * {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}. 179 * It must be greater than zero for a count type of 180 * {@code UPPER_BOUND}. It must be -1 for a count type 181 * of {@code UNKNOWN}. 182 * @param searchIndexed Indicates whether the search criteria is considered 183 * at least partially indexed and could be processed 184 * more efficiently than examining all entries with a 185 * full database scan. 186 * @param debugInfo An optional list of messages providing debug 187 * information about the processing performed by the 188 * server. It may be {@code null} or empty if no debug 189 * messages should be included. 190 */ 191 private MatchingEntryCountResponseControl( 192 final MatchingEntryCountType countType, final int countValue, 193 final boolean searchIndexed, final Collection<String> debugInfo) 194 { 195 super(MATCHING_ENTRY_COUNT_RESPONSE_OID, false, 196 encodeValue(countType, countValue, searchIndexed, debugInfo)); 197 198 this.countType = countType; 199 this.countValue = countValue; 200 this.searchIndexed = searchIndexed; 201 202 if (debugInfo == null) 203 { 204 this.debugInfo = Collections.emptyList(); 205 } 206 else 207 { 208 this.debugInfo = 209 Collections.unmodifiableList(new ArrayList<>(debugInfo)); 210 } 211 } 212 213 214 215 /** 216 * Creates a new matching entry count response control decoded from the given 217 * generic control contents. 218 * 219 * @param oid The OID for the control. 220 * @param isCritical Indicates whether this control should be marked 221 * critical. 222 * @param value The encoded value for the control. 223 * 224 * @throws LDAPException If a problem occurs while attempting to decode the 225 * generic control as a matching entry count response 226 * control. 227 */ 228 public MatchingEntryCountResponseControl(final String oid, 229 final boolean isCritical, 230 final ASN1OctetString value) 231 throws LDAPException 232 { 233 super(oid, isCritical, value); 234 235 if (value == null) 236 { 237 throw new LDAPException(ResultCode.DECODING_ERROR, 238 ERR_MATCHING_ENTRY_COUNT_RESPONSE_MISSING_VALUE.get()); 239 } 240 241 try 242 { 243 final ASN1Element[] elements = 244 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 245 countType = MatchingEntryCountType.valueOf(elements[0].getType()); 246 if (countType == null) 247 { 248 throw new LDAPException(ResultCode.DECODING_ERROR, 249 ERR_MATCHING_ENTRY_COUNT_RESPONSE_INVALID_COUNT_TYPE.get( 250 StaticUtils.toHex(elements[0].getType()))); 251 } 252 253 switch (countType) 254 { 255 case EXAMINED_COUNT: 256 case UNEXAMINED_COUNT: 257 countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); 258 if (countValue < 0) 259 { 260 throw new LDAPException(ResultCode.DECODING_ERROR, 261 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NEGATIVE_EXACT_COUNT.get()); 262 } 263 break; 264 265 case UPPER_BOUND: 266 countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue(); 267 if (countValue <= 0) 268 { 269 throw new LDAPException(ResultCode.DECODING_ERROR, 270 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NON_POSITIVE_UPPER_BOUND. 271 get()); 272 } 273 break; 274 275 case UNKNOWN: 276 default: 277 countValue = -1; 278 break; 279 } 280 281 boolean isIndexed = (countType != MatchingEntryCountType.UNKNOWN); 282 List<String> debugMessages = Collections.emptyList(); 283 for (int i=1; i < elements.length; i++) 284 { 285 switch (elements[i].getType()) 286 { 287 case TYPE_DEBUG_INFO: 288 final ASN1Element[] debugElements = 289 ASN1Sequence.decodeAsSequence(elements[i]).elements(); 290 debugMessages = new ArrayList<>(debugElements.length); 291 for (final ASN1Element e : debugElements) 292 { 293 debugMessages.add( 294 ASN1OctetString.decodeAsOctetString(e).stringValue()); 295 } 296 break; 297 298 case TYPE_SEARCH_INDEXED: 299 isIndexed = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 300 break; 301 302 default: 303 throw new LDAPException(ResultCode.DECODING_ERROR, 304 ERR_MATCHING_ENTRY_COUNT_RESPONSE_UNKNOWN_ELEMENT_TYPE.get( 305 StaticUtils.toHex(elements[i].getType()))); 306 } 307 } 308 309 searchIndexed = isIndexed; 310 debugInfo = Collections.unmodifiableList(debugMessages); 311 } 312 catch (final LDAPException le) 313 { 314 Debug.debugException(le); 315 throw le; 316 } 317 catch (final Exception e) 318 { 319 Debug.debugException(e); 320 throw new LDAPException(ResultCode.DECODING_ERROR, 321 ERR_GET_BACKEND_SET_ID_RESPONSE_CANNOT_DECODE.get( 322 StaticUtils.getExceptionMessage(e)), 323 e); 324 } 325 } 326 327 328 329 /** 330 * Creates a new matching entry count response control for the case in which 331 * the exact number of matching entries is known. 332 * 333 * @param count The exact number of entries matching the associated 334 * search criteria. It must be greater than or equal to 335 * zero. 336 * @param examined Indicates whether the server examined the entries to 337 * exclude those entries that would not be returned to the 338 * client in a normal search with the same criteria. 339 * @param debugInfo An optional list of messages providing debug information 340 * about the processing performed by the server. It may be 341 * {@code null} or empty if no debug messages should be 342 * included. 343 * 344 * @return The matching entry count response control that was created. 345 */ 346 public static MatchingEntryCountResponseControl createExactCountResponse( 347 final int count, final boolean examined, 348 final Collection<String> debugInfo) 349 { 350 return createExactCountResponse(count, examined, true, debugInfo); 351 } 352 353 354 355 /** 356 * Creates a new matching entry count response control for the case in which 357 * the exact number of matching entries is known. 358 * 359 * @param count The exact number of entries matching the associated 360 * search criteria. It must be greater than or equal 361 * to zero. 362 * @param examined Indicates whether the server examined the entries to 363 * exclude those entries that would not be returned to 364 * the client in a normal search with the same 365 * criteria. 366 * @param searchIndexed Indicates whether the search criteria is considered 367 * at least partially indexed and could be processed 368 * more efficiently than examining all entries with a 369 * full database scan. 370 * @param debugInfo An optional list of messages providing debug 371 * information about the processing performed by the 372 * server. It may be {@code null} or empty if no debug 373 * messages should be included. 374 * 375 * @return The matching entry count response control that was created. 376 */ 377 public static MatchingEntryCountResponseControl createExactCountResponse( 378 final int count, final boolean examined, 379 final boolean searchIndexed, 380 final Collection<String> debugInfo) 381 { 382 Validator.ensureTrue(count >= 0); 383 384 final MatchingEntryCountType countType; 385 if (examined) 386 { 387 countType = MatchingEntryCountType.EXAMINED_COUNT; 388 } 389 else 390 { 391 countType = MatchingEntryCountType.UNEXAMINED_COUNT; 392 } 393 394 return new MatchingEntryCountResponseControl(countType, count, 395 searchIndexed, debugInfo); 396 } 397 398 399 400 /** 401 * Creates a new matching entry count response control for the case in which 402 * the exact number of matching entries is not known, but the server was able 403 * to determine an upper bound on the number of matching entries. This upper 404 * bound count may include entries that do not match the search filter, that 405 * are outside the scope of the search, and/or that match the search criteria 406 * but would not have been returned to the client in a normal search with the 407 * same criteria. 408 * 409 * @param upperBound The upper bound on the number of entries that match the 410 * associated search criteria. It must be greater than 411 * zero. 412 * @param debugInfo An optional list of messages providing debug 413 * information about the processing performed by the 414 * server. It may be {@code null} or empty if no debug 415 * messages should be included. 416 * 417 * @return The matching entry count response control that was created. 418 */ 419 public static MatchingEntryCountResponseControl createUpperBoundResponse( 420 final int upperBound, final Collection<String> debugInfo) 421 { 422 return createUpperBoundResponse(upperBound, true, debugInfo); 423 } 424 425 426 427 /** 428 * Creates a new matching entry count response control for the case in which 429 * the exact number of matching entries is not known, but the server was able 430 * to determine an upper bound on the number of matching entries. This upper 431 * bound count may include entries that do not match the search filter, that 432 * are outside the scope of the search, and/or that match the search criteria 433 * but would not have been returned to the client in a normal search with the 434 * same criteria. 435 * 436 * @param upperBound The upper bound on the number of entries that match 437 * the associated search criteria. It must be greater 438 * than zero. 439 * @param searchIndexed Indicates whether the search criteria is considered 440 * at least partially indexed and could be processed 441 * more efficiently than examining all entries with a 442 * full database scan. 443 * @param debugInfo An optional list of messages providing debug 444 * information about the processing performed by the 445 * server. It may be {@code null} or empty if no debug 446 * messages should be included. 447 * 448 * @return The matching entry count response control that was created. 449 */ 450 public static MatchingEntryCountResponseControl createUpperBoundResponse( 451 final int upperBound, final boolean searchIndexed, 452 final Collection<String> debugInfo) 453 { 454 Validator.ensureTrue(upperBound > 0); 455 456 return new MatchingEntryCountResponseControl( 457 MatchingEntryCountType.UPPER_BOUND, upperBound, searchIndexed, 458 debugInfo); 459 } 460 461 462 463 /** 464 * Creates a new matching entry count response control for the case in which 465 * the server was unable to make any meaningful determination about the number 466 * of entries matching the search criteria. 467 * 468 * @param debugInfo An optional list of messages providing debug information 469 * about the processing performed by the server. It may be 470 * {@code null} or empty if no debug messages should be 471 * included. 472 * 473 * @return The matching entry count response control that was created. 474 */ 475 public static MatchingEntryCountResponseControl createUnknownCountResponse( 476 final Collection<String> debugInfo) 477 { 478 return new MatchingEntryCountResponseControl(MatchingEntryCountType.UNKNOWN, 479 -1, false, debugInfo); 480 } 481 482 483 484 /** 485 * Encodes a control value with the provided information. 486 * 487 * @param countType The matching entry count type. It must not be 488 * {@code null}. 489 * @param countValue The matching entry count value. It must be greater 490 * than or equal to zero for a count type of either 491 * {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}. 492 * It must be greater than zero for a count type of 493 * {@code UPPER_BOUND}. It must be -1 for a count type 494 * of {@code UNKNOWN}. 495 * @param searchIndexed Indicates whether the search criteria is considered 496 * at least partially indexed and could be processed 497 * more efficiently than examining all entries with a 498 * full database scan. 499 * @param debugInfo An optional list of messages providing debug 500 * information about the processing performed by the 501 * server. It may be {@code null} or empty if no debug 502 * messages should be included. 503 * 504 * @return The encoded control value. 505 */ 506 private static ASN1OctetString encodeValue( 507 final MatchingEntryCountType countType, 508 final int countValue, 509 final boolean searchIndexed, 510 final Collection<String> debugInfo) 511 { 512 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 513 514 switch (countType) 515 { 516 case EXAMINED_COUNT: 517 case UNEXAMINED_COUNT: 518 case UPPER_BOUND: 519 elements.add(new ASN1Integer(countType.getBERType(), countValue)); 520 break; 521 case UNKNOWN: 522 elements.add(new ASN1Null(countType.getBERType())); 523 break; 524 } 525 526 if (debugInfo != null) 527 { 528 final ArrayList<ASN1Element> debugElements = 529 new ArrayList<>(debugInfo.size()); 530 for (final String s : debugInfo) 531 { 532 debugElements.add(new ASN1OctetString(s)); 533 } 534 535 elements.add(new ASN1Sequence(TYPE_DEBUG_INFO, debugElements)); 536 } 537 538 if (! searchIndexed) 539 { 540 elements.add(new ASN1Boolean(TYPE_SEARCH_INDEXED, searchIndexed)); 541 } 542 543 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 544 } 545 546 547 548 /** 549 * Retrieves the matching entry count type for the response control. 550 * 551 * @return The matching entry count type for the response control. 552 */ 553 public MatchingEntryCountType getCountType() 554 { 555 return countType; 556 } 557 558 559 560 /** 561 * Retrieves the matching entry count value for the response control. For a 562 * count type of {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}, this is 563 * the exact number of matching entries. For a count type of 564 * {@code UPPER_BOUND}, this is the maximum number of entries that may match 565 * the search criteria, but it may also include entries that do not match the 566 * criteria. For a count type of {@code UNKNOWN}, this will always be -1. 567 * 568 * @return The exact count or upper bound of the number of entries in the 569 * server that may match the search criteria, or -1 if the server 570 * could not determine the number of matching entries. 571 */ 572 public int getCountValue() 573 { 574 return countValue; 575 } 576 577 578 579 /** 580 * Indicates whether the server considers the search criteria to be indexed 581 * and therefore it could be processed more efficiently than examining all 582 * entries with a full database scan. 583 * 584 * @return {@code true} if the server considers the search criteria to be 585 * indexed, or {@code false} if not. 586 */ 587 public boolean searchIndexed() 588 { 589 return searchIndexed; 590 } 591 592 593 594 /** 595 * Retrieves a list of messages with debug information about the processing 596 * performed by the server in the course of obtaining the matching entry 597 * count. These messages are intended to be human-readable rather than 598 * machine-parsable. 599 * 600 * @return A list of messages with debug information about the processing 601 * performed by the server in the course of obtaining the matching 602 * entry count, or an empty list if no debug messages were provided. 603 */ 604 public List<String> getDebugInfo() 605 { 606 return debugInfo; 607 } 608 609 610 611 /** 612 * {@inheritDoc} 613 */ 614 @Override() 615 public MatchingEntryCountResponseControl decodeControl(final String oid, 616 final boolean isCritical, 617 final ASN1OctetString value) 618 throws LDAPException 619 { 620 return new MatchingEntryCountResponseControl(oid, isCritical, value); 621 } 622 623 624 625 /** 626 * Extracts a matching entry count response control from the provided search 627 * result. 628 * 629 * @param result The search result from which to retrieve the matching entry 630 * count response control. 631 * 632 * @return The matching entry count response control contained in the 633 * provided result, or {@code null} if the result did not contain a 634 * matching entry count response control. 635 * 636 * @throws LDAPException If a problem is encountered while attempting to 637 * decode the matching entry count response control 638 * contained in the provided result. 639 */ 640 public static MatchingEntryCountResponseControl get(final SearchResult result) 641 throws LDAPException 642 { 643 final Control c = 644 result.getResponseControl(MATCHING_ENTRY_COUNT_RESPONSE_OID); 645 if (c == null) 646 { 647 return null; 648 } 649 650 if (c instanceof MatchingEntryCountResponseControl) 651 { 652 return (MatchingEntryCountResponseControl) c; 653 } 654 else 655 { 656 return new MatchingEntryCountResponseControl(c.getOID(), c.isCritical(), 657 c.getValue()); 658 } 659 } 660 661 662 663 /** 664 * {@inheritDoc} 665 */ 666 @Override() 667 public String getControlName() 668 { 669 return INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get(); 670 } 671 672 673 674 /** 675 * {@inheritDoc} 676 */ 677 @Override() 678 public void toString(final StringBuilder buffer) 679 { 680 buffer.append("MatchingEntryCountResponseControl(countType='"); 681 buffer.append(countType.name()); 682 buffer.append('\''); 683 684 switch (countType) 685 { 686 case EXAMINED_COUNT: 687 case UNEXAMINED_COUNT: 688 buffer.append(", count="); 689 buffer.append(countValue); 690 break; 691 692 case UPPER_BOUND: 693 buffer.append(", upperBound="); 694 buffer.append(countValue); 695 break; 696 } 697 698 buffer.append(", searchIndexed="); 699 buffer.append(searchIndexed); 700 701 if (! debugInfo.isEmpty()) 702 { 703 buffer.append(", debugInfo={"); 704 705 final Iterator<String> iterator = debugInfo.iterator(); 706 while (iterator.hasNext()) 707 { 708 buffer.append('\''); 709 buffer.append(iterator.next()); 710 buffer.append('\''); 711 712 if (iterator.hasNext()) 713 { 714 buffer.append(", "); 715 } 716 } 717 718 buffer.append('}'); 719 } 720 721 buffer.append(')'); 722 } 723}