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
064 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
065 *   for proprietary functionality or for external specifications that are not
066 *   considered stable or mature enough to be guaranteed to work in an
067 *   interoperable way with 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<>(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<>(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<>(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<>(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}