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 }