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 }