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