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