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 027 import com.unboundid.asn1.ASN1Boolean; 028 import com.unboundid.asn1.ASN1Element; 029 import com.unboundid.asn1.ASN1Integer; 030 import com.unboundid.asn1.ASN1OctetString; 031 import com.unboundid.asn1.ASN1Sequence; 032 import com.unboundid.ldap.sdk.Control; 033 import com.unboundid.ldap.sdk.LDAPException; 034 import com.unboundid.ldap.sdk.ResultCode; 035 import com.unboundid.util.Debug; 036 import com.unboundid.util.NotMutable; 037 import com.unboundid.util.StaticUtils; 038 import com.unboundid.util.ThreadSafety; 039 import com.unboundid.util.ThreadSafetyLevel; 040 import com.unboundid.util.Validator; 041 042 import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 043 044 045 046 /** 047 * <BLOCKQUOTE> 048 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID 049 * LDAP SDK for Java. It is not available for use in applications that 050 * include only the Standard Edition of the LDAP SDK, and is not supported for 051 * use in conjunction with non-UnboundID products. 052 * </BLOCKQUOTE> 053 * This class provides a request control which may be included in a search 054 * request to indicate that the server should provide the number of entries that 055 * match the search criteria. The count will be included in the search result 056 * done message, and all search result entries will be suppressed. 057 * <BR><BR> 058 * Whenever possible, the server will use index information to quickly identify 059 * entries matching the criteria of the associated search request. However, if 060 * the count is only determined using index information, then that count may 061 * include entries that would not actually be returned to the client in the 062 * course of processing that search (e.g., because the client doesn't have 063 * permission to access the entry, or because it is a special "operational" 064 * entry like an LDAP subentry, replication conflict entry, or soft-deleted 065 * entry). Indicating that the server should always examine candidate entries 066 * will increase the length of time to obtain the matching entry count, but will 067 * ensure that the count will not include entries that would not otherwise be 068 * returned by that search. 069 * <BR><BR> 070 * Also note that this control is not compatible for use with other controls 071 * that may cause only a subset of entries to be returned, including the simple 072 * paged results control and the virtual list view control. It is also not 073 * compatible for use with other controls that may cause the server to return 074 * more entries than those that match the search criteria, like the LDAP join 075 * control. 076 * <BR><BR> 077 * The OID for a matching entry count request control is 078 * "1.3.6.1.4.1.30221.2.5.36", and it may have a criticality of either 079 * {@code true} or {@code false}. It must include a value with the following 080 * encoding: 081 * <PRE> 082 * MatchingEntryCountRequest ::= SEQUENCE { 083 * maxCandidatesToExamine [0] INTEGER (0 .. MAX) DEFAULT 0, 084 * alwaysExamineCandidates [1] BOOLEAN DEFAULT FALSE, 085 * processSearchIfUnindexed [2] BOOLEAN DEFAULT FALSE, 086 * includeDebugInfo [3] BOOLEAN DEFAULT FALSE, 087 * ... } 088 * </PRE> 089 * 090 * @see MatchingEntryCountResponseControl 091 */ 092 @NotMutable() 093 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 094 public final class MatchingEntryCountRequestControl 095 extends Control 096 { 097 /** 098 * The OID (1.3.6.1.4.1.30221.2.5.36) for the matching entry count request 099 * control. 100 */ 101 public static final String MATCHING_ENTRY_COUNT_REQUEST_OID = 102 "1.3.6.1.4.1.30221.2.5.36"; 103 104 105 106 /** 107 * The BER type for the element that specifies the maximum number of candidate 108 * entries to examine. 109 */ 110 private static final byte TYPE_MAX_CANDIDATES_TO_EXAMINE = (byte) 0x80; 111 112 113 114 /** 115 * The BER type for the element that indicates whether always examine 116 * candidate entries to determine whether they would actually be returned to 117 * the client. 118 */ 119 private static final byte TYPE_ALWAYS_EXAMINE_CANDIDATES = (byte) 0x81; 120 121 122 123 /** 124 * The BER type for the element that indicates whether to process an unindexed 125 * search to determine the number of matching entries. 126 */ 127 private static final byte TYPE_PROCESS_SEARCH_IF_UNINDEXED = (byte) 0x82; 128 129 130 131 /** 132 * The BER type for the element that indicates whether to include debug 133 * information in the response. 134 */ 135 private static final byte TYPE_INCLUDE_DEBUG_INFO = (byte) 0x83; 136 137 138 139 /** 140 * The serial version UID for this serializable class. 141 */ 142 private static final long serialVersionUID = -6077150575658563941L; 143 144 145 146 // Indicates whether the server should internally retrieve and examine 147 // candidate entries to determine whether they would actually be returned to 148 // the client. 149 private final boolean alwaysExamineCandidates; 150 151 // Indicates whether to include debug information in the response control. 152 private final boolean includeDebugInfo; 153 154 // Indicates whether the server should attempt to actually iterate through the 155 // entries in the backend in order to obtain the count if the search criteria 156 // is not indexed. 157 private final boolean processSearchIfUnindexed; 158 159 // The maximum number of candidate entries that should be examined if it is 160 // not possible to obtain an exact count using only information contained in 161 // the server indexes. 162 private final int maxCandidatesToExamine; 163 164 165 166 /** 167 * Creates a new matching entry count request control with the default 168 * settings. The control will be critical, no candidate entries will be 169 * examined, and the search will not be processed if it is unindexed. 170 */ 171 public MatchingEntryCountRequestControl() 172 { 173 this(true, 0, false, false, false); 174 } 175 176 177 178 /** 179 * Creates a new matching entry count request control with the provided 180 * information. 181 * 182 * @param isCritical Indicates whether this control should be 183 * critical. 184 * @param maxCandidatesToExamine The maximum number of candidate entries 185 * that the server should retrieve and 186 * examine to determine whether they 187 * actually match the search criteria. If 188 * the search is partially indexed and the 189 * total number of candidate entries is less 190 * than or equal to this value, then these 191 * candidate entries will be examined to 192 * determine which of them match the search 193 * criteria so that an accurate count can 194 * be determined. If the search is fully 195 * indexed such that the all candidate 196 * entries are known to match the search 197 * criteria, then the server may still 198 * examine each of these entries if the 199 * number of candidates is less than 200 * {@code maxCandidatesToExamine} and 201 * {@code alwaysExamineCandidates} is true 202 * in order to allow the entry count that 203 * is returned to be restricted to only 204 * those entries that would actually be 205 * returned to the client. This will be 206 * ignored for searches that are completely 207 * unindexed. 208 * <BR><BR> 209 * The value for this argument must be 210 * greater than or equal to zero. If it 211 * is zero, then the server will not 212 * examine any entries, so a 213 * partially-indexed search will only be 214 * able to return a count that is an upper 215 * bound, and a fully-indexed search will 216 * only be able to return an unexamined 217 * exact count. If there should be no bound 218 * on the number of entries to retrieve, 219 * then a value of {@code Integer.MAX_VALUE} 220 * may be specified. 221 * @param alwaysExamineCandidates Indicates whether the server should 222 * always examine candidate entries to 223 * determine whether they would actually 224 * be returned to the client in a normal 225 * search. This will only be used for 226 * fully-indexed searches in which the 227 * set of matching entries is known. If the 228 * value is {@code true} and the number of 229 * candidates is smaller than 230 * {@code maxCandidatesToExamine}, then each 231 * matching entry will be internally 232 * retrieved and examined to determine 233 * whether it would be returned to the 234 * client based on the details of the search 235 * request (e.g., whether the requester has 236 * permission to access the entry, whether 237 * it's an LDAP subentry, replication 238 * conflict entry, soft-deleted entry, or 239 * other type of entry that is normally 240 * hidden) so that an exact count can be 241 * returned. If this is {@code false} or 242 * the number of candidates exceeds 243 * {@code maxCandidatesToExamine}, then the 244 * server will only be able to return an 245 * unexamined count which may include 246 * entries that match the search criteria 247 * but that would not normally be returned 248 * to the requester. 249 * @param processSearchIfUnindexed Indicates whether the server should 250 * attempt to determine the number of 251 * matching entries if the search criteria 252 * is completely unindexed. If this is 253 * {@code true} and the requester has the 254 * unindexed-search privilege, then the 255 * server will iterate through all entries 256 * in the scope (which may take a very long 257 * time to complete) in order to to 258 * determine which of them match the search 259 * criteria so that it can return an 260 * accurate count. If this is 261 * {@code false} or the requester does not 262 * have the unindexed-search privilege, then 263 * the server will not spend any time 264 * attempting to determine the number of 265 * matching entries and will instead return 266 * a matching entry count response control 267 * indicating that the entry count is 268 * unknown. 269 * @param includeDebugInfo Indicates whether the server should 270 * include debug information in the response 271 * that may help better understand how it 272 * arrived at the result. If any debug 273 * information is returned, it will be in 274 * the form of human-readable text that is 275 * not intended to be machine-parsable. 276 */ 277 public MatchingEntryCountRequestControl(final boolean isCritical, 278 final int maxCandidatesToExamine, 279 final boolean alwaysExamineCandidates, 280 final boolean processSearchIfUnindexed, 281 final boolean includeDebugInfo) 282 { 283 super(MATCHING_ENTRY_COUNT_REQUEST_OID, isCritical, 284 encodeValue(maxCandidatesToExamine, alwaysExamineCandidates, 285 processSearchIfUnindexed, includeDebugInfo)); 286 287 Validator.ensureTrue(maxCandidatesToExamine >= 0); 288 289 this.maxCandidatesToExamine = maxCandidatesToExamine; 290 this.alwaysExamineCandidates = alwaysExamineCandidates; 291 this.processSearchIfUnindexed = processSearchIfUnindexed; 292 this.includeDebugInfo = includeDebugInfo; 293 } 294 295 296 297 /** 298 * Creates a new matching entry count request control that is decoded from the 299 * provided generic control. 300 * 301 * @param control The control to decode as a matching entry count request 302 * control. 303 * 304 * @throws LDAPException If the provided control cannot be decoded as a 305 * matching entry count request control. 306 */ 307 public MatchingEntryCountRequestControl(final Control control) 308 throws LDAPException 309 { 310 super(control); 311 312 final ASN1OctetString value = control.getValue(); 313 if (value == null) 314 { 315 throw new LDAPException(ResultCode.DECODING_ERROR, 316 ERR_MATCHING_ENTRY_COUNT_REQUEST_MISSING_VALUE.get()); 317 } 318 319 try 320 { 321 boolean alwaysExamine = false; 322 boolean debug = false; 323 boolean processUnindexed = false; 324 int maxCandidates = 0; 325 final ASN1Element[] elements = 326 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 327 for (final ASN1Element e : elements) 328 { 329 switch (e.getType()) 330 { 331 case TYPE_MAX_CANDIDATES_TO_EXAMINE: 332 maxCandidates = ASN1Integer.decodeAsInteger(e).intValue(); 333 if (maxCandidates < 0) 334 { 335 throw new LDAPException(ResultCode.DECODING_ERROR, 336 ERR_MATCHING_ENTRY_COUNT_REQUEST_INVALID_MAX.get()); 337 } 338 break; 339 340 case TYPE_ALWAYS_EXAMINE_CANDIDATES: 341 alwaysExamine = ASN1Boolean.decodeAsBoolean(e).booleanValue(); 342 break; 343 344 case TYPE_PROCESS_SEARCH_IF_UNINDEXED: 345 processUnindexed = ASN1Boolean.decodeAsBoolean(e).booleanValue(); 346 break; 347 348 case TYPE_INCLUDE_DEBUG_INFO: 349 debug = ASN1Boolean.decodeAsBoolean(e).booleanValue(); 350 break; 351 352 default: 353 throw new LDAPException(ResultCode.DECODING_ERROR, 354 ERR_MATCHING_ENTRY_COUNT_REQUEST_INVALID_ELEMENT_TYPE.get( 355 StaticUtils.toHex(e.getType()))); 356 } 357 } 358 359 maxCandidatesToExamine = maxCandidates; 360 alwaysExamineCandidates = alwaysExamine; 361 processSearchIfUnindexed = processUnindexed; 362 includeDebugInfo = debug; 363 } 364 catch (final LDAPException le) 365 { 366 Debug.debugException(le); 367 throw le; 368 } 369 catch (final Exception e) 370 { 371 Debug.debugException(e); 372 throw new LDAPException(ResultCode.DECODING_ERROR, 373 ERR_MATCHING_ENTRY_COUNT_REQUEST_CANNOT_DECODE.get( 374 StaticUtils.getExceptionMessage(e)), 375 e); 376 } 377 } 378 379 380 381 /** 382 * Encodes the provided information into an ASN.1 octet string suitable for 383 * use as the control value. 384 * 385 * @param maxCandidatesToExamine The maximum number of candidate entries 386 * that the server should retrieve and 387 * examine to determine whether they 388 * actually match the search criteria. 389 * @param alwaysExamineCandidates Indicates whether the server should 390 * always examine candidate entries to 391 * determine whether they would actually 392 * be returned to the client in a normal 393 * search with the same criteria. 394 * @param processSearchIfUnindexed Indicates whether the server should 395 * attempt to determine the number of 396 * matching entries if the search criteria 397 * is completely unindexed. 398 * @param includeDebugInfo Indicates whether the server should 399 * include debug information in the response 400 * that may help better understand how it 401 * arrived at the result. 402 * 403 * @return The ASN.1 octet string containing the encoded control value. 404 */ 405 private static ASN1OctetString encodeValue( 406 final int maxCandidatesToExamine, 407 final boolean alwaysExamineCandidates, 408 final boolean processSearchIfUnindexed, 409 final boolean includeDebugInfo) 410 { 411 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4); 412 413 if (maxCandidatesToExamine > 0) 414 { 415 elements.add(new ASN1Integer(TYPE_MAX_CANDIDATES_TO_EXAMINE, 416 maxCandidatesToExamine)); 417 } 418 419 if (alwaysExamineCandidates) 420 { 421 elements.add(new ASN1Boolean(TYPE_ALWAYS_EXAMINE_CANDIDATES, true)); 422 } 423 424 if (processSearchIfUnindexed) 425 { 426 elements.add(new ASN1Boolean(TYPE_PROCESS_SEARCH_IF_UNINDEXED, true)); 427 } 428 429 if (includeDebugInfo) 430 { 431 elements.add(new ASN1Boolean(TYPE_INCLUDE_DEBUG_INFO, true)); 432 } 433 434 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 435 } 436 437 438 439 /** 440 * Retrieves the maximum number of candidate entries that should be examined 441 * in order to determine accurate count of the number of matching entries. 442 * <BR><BR> 443 * For a fully-indexed search, this property will only be used if 444 * {@link #alwaysExamineCandidates} is true. If the number of candidate 445 * entries identified is less than the maximum number of candidates to 446 * examine, then the server will return an {@code EXAMINED_COUNT} result that 447 * indicates the number of entries matching the criteria that would actually 448 * be returned in a normal search with the same criteria. If the number of 449 * candidate entries exceeds the maximum number of candidates to examine, then 450 * the server will return an {@code UNEXAMINED_COUNT} result that indicates 451 * the number of entries matching the search criteria but that may include 452 * entries that would not actually be returned to the client. 453 * <BR><BR> 454 * For a partially-indexed search, if the upper bound on the number of 455 * candidates is less than or equal to the maximum number of candidates to 456 * examine, then the server will internally retrieve and examine each of those 457 * candidates to determine which of them match the search criteria and would 458 * actually be returned to the client, and will then return an 459 * {@code EXAMINED_COUNT} result with that count. If the upper bound on the 460 * number of candidates is greater than the maximum number of candidates to 461 * examine, then the server will return an {@code UPPER_BOUND} result to 462 * indicate that the exact count is not known but an upper bound is available. 463 * 464 * @return The maximum number of candidate entries to examine in order to 465 * determine an accurate count of the number of matching entries. 466 */ 467 public int getMaxCandidatesToExamine() 468 { 469 return maxCandidatesToExamine; 470 } 471 472 473 474 /** 475 * Indicates whether the server should always examine candidate entries in 476 * fully-indexed searches to determine whether they would actually be returned 477 * to the client in a normal search with the same criteria. 478 * 479 * @return {@code true} if the server should attempt to internally retrieve 480 * and examine matching entries to determine whether they would 481 * normally be returned to the client (i.e.., that the client has 482 * permission to access the entry and that it is not a 483 * normally-hidden entry like an LDAP subentry, a replication 484 * conflict entry, or a soft-deleted entry), or {@code false} if the 485 * server should return an unverified count. 486 */ 487 public boolean alwaysExamineCandidates() 488 { 489 return alwaysExamineCandidates; 490 } 491 492 493 494 /** 495 * Indicates whether the server should internally retrieve and examine all 496 * entries within the search scope in order to obtain an exact matching entry 497 * count for an unindexed search. Note that this value will not be considered 498 * for completely-indexed or partially-indexed searches, nor for searches in 499 * which matching entries should be returned. 500 * 501 * @return {@code true} if the server should internally retrieve and examine 502 * all entries within the search scope in order to obtain an exact 503 * matching entry count for an unindexed search, or {@code false} if 504 * not. 505 */ 506 public boolean processSearchIfUnindexed() 507 { 508 return processSearchIfUnindexed; 509 } 510 511 512 513 /** 514 * Indicates whether the server should include debug information in the 515 * response control that provides additional information about how the server 516 * arrived at the result. If debug information is to be provided, it will be 517 * in a human-readable rather than machine-parsable form. 518 * 519 * @return {@code true} if the server should include debug information in 520 * the response control, or {@code false} if not. 521 */ 522 public boolean includeDebugInfo() 523 { 524 return includeDebugInfo; 525 } 526 527 528 529 /** 530 * {@inheritDoc} 531 */ 532 @Override() 533 public String getControlName() 534 { 535 return INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_REQUEST.get(); 536 } 537 538 539 540 /** 541 * {@inheritDoc} 542 */ 543 @Override() 544 public void toString(final StringBuilder buffer) 545 { 546 buffer.append("MatchingEntryCountRequestControl(isCritical="); 547 buffer.append(isCritical()); 548 buffer.append(", maxCandidatesToExamine="); 549 buffer.append(maxCandidatesToExamine); 550 buffer.append(", alwaysExamineCandidates="); 551 buffer.append(alwaysExamineCandidates); 552 buffer.append(", processSearchIfUnindexed="); 553 buffer.append(processSearchIfUnindexed); 554 buffer.append(", includeDebugInfo="); 555 buffer.append(includeDebugInfo); 556 buffer.append(')'); 557 } 558 }