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