001/*
002 * Copyright 2014-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2014-2022 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) 2014-2022 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.Filter;
055import com.unboundid.ldap.sdk.LDAPException;
056import com.unboundid.ldap.sdk.ResultCode;
057import com.unboundid.ldap.sdk.SearchResult;
058import com.unboundid.util.Debug;
059import com.unboundid.util.NotMutable;
060import com.unboundid.util.NotNull;
061import com.unboundid.util.Nullable;
062import com.unboundid.util.StaticUtils;
063import com.unboundid.util.ThreadSafety;
064import com.unboundid.util.ThreadSafetyLevel;
065import com.unboundid.util.Validator;
066
067import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
068
069
070
071/**
072 * This class provides a response control that may be used to provide
073 * information about the number of entries that match a given set of search
074 * criteria.  The control will be included in the search result done message
075 * for any successful search operation in which the request contained a matching
076 * entry count request control.
077 * <BR>
078 * <BLOCKQUOTE>
079 *   <B>NOTE:</B>  This class, and other classes within the
080 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
081 *   supported for use against Ping Identity, UnboundID, and
082 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
083 *   for proprietary functionality or for external specifications that are not
084 *   considered stable or mature enough to be guaranteed to work in an
085 *   interoperable way with other types of LDAP servers.
086 * </BLOCKQUOTE>
087 * <BR>
088 * The matching entry count response control has an OID of
089 * "1.3.6.1.4.1.30221.2.5.37", a criticality of false, and a value with the
090 * following encoding:
091 * <PRE>
092 *   MatchingEntryCountResponse ::= SEQUENCE {
093 *        entryCount               CHOICE {
094 *             examinedCount            [0] INTEGER,
095 *             unexaminedCount          [1] INTEGER,
096 *             upperBound               [2] INTEGER,
097 *             unknown                  [3] NULL,
098 *             ... }
099 *        debugInfo                [0] SEQUENCE OF OCTET STRING OPTIONAL,
100 *        searchIndexed            [1] BOOLEAN DEFAULT TRUE,
101 *        shortCircuited           [2] BOOLEAN OPTIONAL,
102 *        fullyIndexed             [3] BOOLEAN OPTIONAL,
103 *        candidatesAreInScope     [4] BOOLEAN OPTIONAL,
104 *        remainingFilter          [5] Filter OPTIONAL,
105 *        ... }
106 * </PRE>
107 *
108 * @see  MatchingEntryCountRequestControl
109 */
110@NotMutable()
111@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
112public final class MatchingEntryCountResponseControl
113       extends Control
114       implements DecodeableControl
115{
116  /**
117   * The OID (1.3.6.1.4.1.30221.2.5.37) for the matching entry count response
118   * control.
119   */
120  @NotNull public static final String MATCHING_ENTRY_COUNT_RESPONSE_OID =
121       "1.3.6.1.4.1.30221.2.5.37";
122
123
124
125  /**
126   * The BER type for the element used to hold the list of debug messages.
127   */
128  private static final byte TYPE_DEBUG_INFO = (byte) 0xA0;
129
130
131
132  /**
133   * The BER type for the element used to indicate whether the search criteria
134   * is at least partially indexed.
135   */
136  private static final byte TYPE_SEARCH_INDEXED = (byte) 0x81;
137
138
139
140  /**
141   * The BER type for the element used to indicate whether the server
142   * short-circuited during candidate set processing before evaluating all
143   * elements of the search criteria (the filter and scope).
144   */
145  private static final byte TYPE_SHORT_CIRCUITED = (byte) 0x82;
146
147
148
149  /**
150   * The BER type for the element used to indicate whether the search criteria
151   * is fully indexed.
152   */
153  private static final byte TYPE_FULLY_INDEXED = (byte) 0x83;
154
155
156
157  /**
158   * The BER type for the element used to indicate whether all the identified
159   * candidate entries are within the scope of the search.
160   */
161  private static final byte TYPE_CANDIDATES_ARE_IN_SCOPE = (byte) 0x84;
162
163
164
165  /**
166   * The BER type for the element used to provide the remaining filter for the
167   * search operation, which is the portion of the filter that was determined
168   * to be unindexed, or that was unevaluated if processing short-circuited in
169   * the course of building the candidate set.
170   */
171  private static final byte TYPE_REMAINING_FILTER = (byte) 0xA5;
172
173
174
175  /**
176   * The serial version UID for this serializable class.
177   */
178  private static final long serialVersionUID = -7808452580964236458L;
179
180
181
182  // Indicates whether the search criteria is considered at least partially
183  // indexed by the server.
184  private final boolean searchIndexed;
185
186  // Indicates whether all the identified candidate entries are within the scope
187  // of the search.
188  @Nullable private final Boolean candidatesAreInScope;
189
190  // Indicates whether the search criteria is considered fully indexed.
191  @Nullable private final Boolean fullyIndexed;
192
193  // Indicates whether the server short-circuited during candidate set
194  // processing before evaluating all elements of the search criteria (the
195  // filter and scope).
196  @Nullable private final Boolean shortCircuited;
197
198  // The portion of the filter that was either identified as unindexed or that
199  // was not evaluated in the course of building the candidate set.
200  @Nullable private final Filter remainingFilter;
201
202  // The count value for this matching entry count response control.
203  private final int countValue;
204
205  // A list of messages providing debug information about the processing
206  // performed by the server.
207  @NotNull private final List<String> debugInfo;
208
209  // The count type for this matching entry count response control.
210  @NotNull private final MatchingEntryCountType countType;
211
212
213
214  /**
215   * Creates a new empty control instance that is intended to be used only for
216   * decoding controls via the {@code DecodeableControl} interface.
217   */
218  MatchingEntryCountResponseControl()
219  {
220    searchIndexed = false;
221    candidatesAreInScope = null;
222    fullyIndexed = null;
223    shortCircuited = null;
224    remainingFilter = null;
225    countValue = -1;
226    countType = null;
227    debugInfo = null;
228  }
229
230
231
232  /**
233   * Creates a new matching entry count response control with the provided
234   * information.
235   *
236   * @param  countType             The matching entry count type.  It must not
237   *                               be {@code null}.
238   * @param  countValue            The matching entry count value.  It must be
239   *                               greater than or equal to zero for a count
240   *                               type of either {@code EXAMINED_COUNT} or
241   *                               {@code UNEXAMINED_COUNT}.  It must be greater
242   *                               than zero for a count type of
243   *                               {@code UPPER_BOUND}.  It must be -1 for a
244   *                               count type of {@code UNKNOWN}.
245   * @param  searchIndexed         Indicates whether the search criteria is
246   *                               considered at least partially indexed and
247   *                               could be processed more efficiently than
248   *                               examining all entries with a full database
249   *                               scan.
250   * @param  shortCircuited        Indicates whether the server short-circuited
251   *                               during candidate set processing before
252   *                               evaluating all elements of the search
253   *                               criteria (the filter and scope).  This may be
254   *                               {@code null} if it is not available (e.g.,
255   *                               because extended response data was not
256   *                               requested).
257   * @param  fullyIndexed          Indicates whether the search is considered
258   *                               fully indexed.  Note that this may be
259   *                               {@code false} even if the filter is actually
260   *                               fully indexed if server index processing
261   *                               short-circuited before evaluating all
262   *                               components of the filter.  To avoid this,
263   *                               issue the request control with both fast and
264   *                               slow short-circuit thresholds set to zero.
265   *                               This may be {@code null} if this is not
266   *                               available (e.g., because extended response
267   *                               data was not requested).
268   * @param  candidatesAreInScope  Indicates whether all the identified
269   *                               candidate entries are within the scope of
270   *                               the search.  It may be {@code null} if this
271   *                               is not available (e.g., because extended
272   *                               response data was not requested).
273   * @param  remainingFilter       The portion of the filter that was either
274   *                               identified as unindexed or that was not
275   *                               evaluated because processing short-circuited
276   *                               in the course of building the candidate set.
277   *                               It may be {@code null} if there is no
278   *                               remaining filter or if this information is
279   *                               not available (e.g., because extended
280   *                               response data was not requested).
281   * @param  debugInfo             An optional list of messages providing debug
282   *                               information about the processing performed by
283   *                               the server.  It may be {@code null} or empty
284   *                               if no debug messages should be included.
285   */
286  private MatchingEntryCountResponseControl(
287               @NotNull final MatchingEntryCountType countType,
288               final int countValue,
289               final boolean searchIndexed,
290               @Nullable final Boolean shortCircuited,
291               @Nullable final Boolean fullyIndexed,
292               @Nullable final Boolean candidatesAreInScope,
293               @Nullable final Filter remainingFilter,
294               @Nullable final Collection<String> debugInfo)
295  {
296    super(MATCHING_ENTRY_COUNT_RESPONSE_OID, false,
297         encodeValue(countType, countValue, searchIndexed, shortCircuited,
298              fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo));
299
300    this.countType = countType;
301    this.countValue = countValue;
302    this.searchIndexed = searchIndexed;
303    this.shortCircuited = shortCircuited;
304    this.fullyIndexed = fullyIndexed;
305    this.candidatesAreInScope = candidatesAreInScope;
306    this.remainingFilter = remainingFilter;
307
308    if (debugInfo == null)
309    {
310      this.debugInfo = Collections.emptyList();
311    }
312    else
313    {
314      this.debugInfo =
315           Collections.unmodifiableList(new ArrayList<>(debugInfo));
316    }
317  }
318
319
320
321  /**
322   * Creates a new matching entry count response control decoded from the given
323   * generic control contents.
324   *
325   * @param  oid         The OID for the control.
326   * @param  isCritical  Indicates whether this control should be marked
327   *                     critical.
328   * @param  value       The encoded value for the control.
329   *
330   * @throws LDAPException  If a problem occurs while attempting to decode the
331   *                        generic control as a matching entry count response
332   *                        control.
333   */
334  public MatchingEntryCountResponseControl(@NotNull final String oid,
335              final boolean isCritical,
336              @Nullable final ASN1OctetString value)
337         throws LDAPException
338  {
339    super(oid, isCritical, value);
340
341    if (value == null)
342    {
343      throw new LDAPException(ResultCode.DECODING_ERROR,
344           ERR_MATCHING_ENTRY_COUNT_RESPONSE_MISSING_VALUE.get());
345    }
346
347    try
348    {
349      final ASN1Element[] elements =
350           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
351      countType = MatchingEntryCountType.valueOf(elements[0].getType());
352      if (countType == null)
353      {
354        throw new LDAPException(ResultCode.DECODING_ERROR,
355             ERR_MATCHING_ENTRY_COUNT_RESPONSE_INVALID_COUNT_TYPE.get(
356                  StaticUtils.toHex(elements[0].getType())));
357      }
358
359      switch (countType)
360      {
361        case EXAMINED_COUNT:
362        case UNEXAMINED_COUNT:
363          countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue();
364          if (countValue < 0)
365          {
366            throw new LDAPException(ResultCode.DECODING_ERROR,
367                 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NEGATIVE_EXACT_COUNT.get());
368          }
369          break;
370
371        case UPPER_BOUND:
372          countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue();
373          if (countValue <= 0)
374          {
375            throw new LDAPException(ResultCode.DECODING_ERROR,
376                 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NON_POSITIVE_UPPER_BOUND.
377                      get());
378          }
379          break;
380
381        case UNKNOWN:
382        default:
383          countValue = -1;
384          break;
385      }
386
387      boolean decodedSearchIndexed =
388           (countType != MatchingEntryCountType.UNKNOWN);
389      Boolean decodedFullyIndexed = null;
390      Boolean decodedCandidatesAreInScope = null;
391      Boolean decodedShortCircuited = null;
392      Filter decodedRemainingFilter = null;
393      List<String> debugMessages = Collections.emptyList();
394      for (int i=1; i < elements.length; i++)
395      {
396        switch (elements[i].getType())
397        {
398          case TYPE_DEBUG_INFO:
399            final ASN1Element[] debugElements =
400                 ASN1Sequence.decodeAsSequence(elements[i]).elements();
401            debugMessages = new ArrayList<>(debugElements.length);
402            for (final ASN1Element e : debugElements)
403            {
404              debugMessages.add(
405                   ASN1OctetString.decodeAsOctetString(e).stringValue());
406            }
407            break;
408
409          case TYPE_SEARCH_INDEXED:
410            decodedSearchIndexed =
411                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
412            break;
413
414          case TYPE_SHORT_CIRCUITED:
415            decodedShortCircuited =
416                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
417            break;
418
419          case TYPE_FULLY_INDEXED:
420            decodedFullyIndexed =
421                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
422            break;
423
424          case TYPE_CANDIDATES_ARE_IN_SCOPE:
425            decodedCandidatesAreInScope =
426                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
427            break;
428
429          case TYPE_REMAINING_FILTER:
430            final ASN1Element filterElement =
431                 ASN1Element.decode(elements[i].getValue());
432            decodedRemainingFilter = Filter.decode(filterElement);
433            break;
434        }
435      }
436
437      searchIndexed = decodedSearchIndexed;
438      shortCircuited = decodedShortCircuited;
439      fullyIndexed = decodedFullyIndexed;
440      candidatesAreInScope = decodedCandidatesAreInScope;
441      remainingFilter = decodedRemainingFilter;
442      debugInfo = Collections.unmodifiableList(debugMessages);
443    }
444    catch (final LDAPException le)
445    {
446      Debug.debugException(le);
447      throw le;
448    }
449    catch (final Exception e)
450    {
451      Debug.debugException(e);
452      throw new LDAPException(ResultCode.DECODING_ERROR,
453           ERR_GET_BACKEND_SET_ID_RESPONSE_CANNOT_DECODE.get(
454                StaticUtils.getExceptionMessage(e)),
455           e);
456    }
457  }
458
459
460
461  /**
462   * Creates a new matching entry count response control for the case in which
463   * the exact number of matching entries is known.
464   *
465   * @param  count      The exact number of entries matching the associated
466   *                    search criteria.  It must be greater than or equal to
467   *                    zero.
468   * @param  examined   Indicates whether the server examined the entries to
469   *                    exclude those entries that would not be returned to the
470   *                    client in a normal search with the same criteria.
471   * @param  debugInfo  An optional list of messages providing debug information
472   *                    about the processing performed by the server.  It may be
473   *                    {@code null} or empty if no debug messages should be
474   *                    included.
475   *
476   * @return  The matching entry count response control that was created.
477   */
478  @NotNull()
479  public static MatchingEntryCountResponseControl createExactCountResponse(
480                     final int count, final boolean examined,
481                     @Nullable final Collection<String> debugInfo)
482  {
483    return createExactCountResponse(count, examined, true, debugInfo);
484  }
485
486
487
488  /**
489   * Creates a new matching entry count response control for the case in which
490   * the exact number of matching entries is known.
491   *
492   * @param  count          The exact number of entries matching the associated
493   *                        search criteria.  It must be greater than or equal
494   *                        to zero.
495   * @param  examined       Indicates whether the server examined the entries to
496   *                        exclude those entries that would not be returned to
497   *                        the client in a normal search with the same
498   *                        criteria.
499   * @param  searchIndexed  Indicates whether the search criteria is considered
500   *                        at least partially indexed and could be processed
501   *                        more efficiently than examining all entries with a
502   *                        full database scan.
503   * @param  debugInfo      An optional list of messages providing debug
504   *                        information about the processing performed by the
505   *                        server.  It may be {@code null} or empty if no debug
506   *                        messages should be included.
507   *
508   * @return  The matching entry count response control that was created.
509   */
510  @NotNull()
511  public static MatchingEntryCountResponseControl createExactCountResponse(
512                     final int count, final boolean examined,
513                     final boolean searchIndexed,
514                     @Nullable final Collection<String> debugInfo)
515  {
516    return createExactCountResponse(count, examined, searchIndexed, null, null,
517         null, null, debugInfo);
518  }
519
520
521
522  /**
523   * Creates a new matching entry count response control for the case in which
524   * the exact number of matching entries is known.
525   *
526   * @param  count                 The exact number of entries matching the
527   *                               associated search criteria.  It must be
528   *                               greater than or equal to zero.
529   * @param  examined              Indicates whether the server examined the
530   *                               entries to exclude those entries that would
531   *                               not be returned to the client in a normal
532   *                               search with the same criteria.
533   * @param  searchIndexed         Indicates whether the search criteria is
534   *                               considered at least partially indexed and
535   *                               could be processed more efficiently than
536   *                               examining all entries with a full database
537   *                               scan.
538   * @param  shortCircuited        Indicates whether the server short-circuited
539   *                               during candidate set processing before
540   *                               evaluating all elements of the search
541   *                               criteria (the filter and scope).  This may be
542   *                               {@code null} if it is not available (e.g.,
543   *                               because extended response data was not
544   *                               requested).
545   * @param  fullyIndexed          Indicates whether the search is considered
546   *                               fully indexed.  Note that this may be
547   *                               {@code false} even if the filter is actually
548   *                               fully indexed if server index processing
549   *                               short-circuited before evaluating all
550   *                               components of the filter.  To avoid this,
551   *                               issue the request control with both fast and
552   *                               slow short-circuit thresholds set to zero.
553   *                               This may be {@code null} if this is not
554   *                               available (e.g., because extended response
555   *                               data was not requested).
556   * @param  candidatesAreInScope  Indicates whether all the identified
557   *                               candidate entries are within the scope of
558   *                               the search.  It may be {@code null} if this
559   *                               is not available (e.g., because extended
560   *                               response data was not requested).
561   * @param  remainingFilter       The portion of the filter that was either
562   *                               identified as unindexed or that was not
563   *                               evaluated because processing short-circuited
564   *                               in the course of building the candidate set.
565   *                               It may be {@code null} if there is no
566   *                               remaining filter or if this information is
567   *                               not available (e.g., because extended
568   *                               response data was not requested).
569   * @param  debugInfo             An optional list of messages providing debug
570   *                               information about the processing performed by
571   *                               the server.  It may be {@code null} or empty
572   *                               if no debug messages should be included.
573   *
574   * @return  The matching entry count response control that was created.
575   */
576  @NotNull()
577  public static MatchingEntryCountResponseControl createExactCountResponse(
578                     final int count, final boolean examined,
579                     final boolean searchIndexed,
580                     @Nullable final Boolean shortCircuited,
581                     @Nullable final Boolean fullyIndexed,
582                     @Nullable final Boolean candidatesAreInScope,
583                     @Nullable final Filter remainingFilter,
584                     @Nullable final Collection<String> debugInfo)
585  {
586    Validator.ensureTrue(count >= 0);
587
588    final MatchingEntryCountType countType;
589    if (examined)
590    {
591      countType = MatchingEntryCountType.EXAMINED_COUNT;
592    }
593    else
594    {
595      countType = MatchingEntryCountType.UNEXAMINED_COUNT;
596    }
597
598    return new MatchingEntryCountResponseControl(countType, count,
599         searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope,
600         remainingFilter, debugInfo);
601  }
602
603
604
605  /**
606   * Creates a new matching entry count response control for the case in which
607   * the exact number of matching entries is not known, but the server was able
608   * to determine an upper bound on the number of matching entries.  This upper
609   * bound count may include entries that do not match the search filter, that
610   * are outside the scope of the search, and/or that match the search criteria
611   * but would not have been returned to the client in a normal search with the
612   * same criteria.
613   *
614   * @param  upperBound  The upper bound on the number of entries that match the
615   *                     associated search criteria.  It must be greater than
616   *                     zero.
617   * @param  debugInfo   An optional list of messages providing debug
618   *                     information about the processing performed by the
619   *                     server.  It may be {@code null} or empty if no debug
620   *                     messages should be included.
621   *
622   * @return  The matching entry count response control that was created.
623   */
624  @NotNull()
625  public static MatchingEntryCountResponseControl createUpperBoundResponse(
626                     final int upperBound,
627                     @Nullable final Collection<String> debugInfo)
628  {
629    return createUpperBoundResponse(upperBound, true, debugInfo);
630  }
631
632
633
634  /**
635   * Creates a new matching entry count response control for the case in which
636   * the exact number of matching entries is not known, but the server was able
637   * to determine an upper bound on the number of matching entries.  This upper
638   * bound count may include entries that do not match the search filter, that
639   * are outside the scope of the search, and/or that match the search criteria
640   * but would not have been returned to the client in a normal search with the
641   * same criteria.
642   *
643   * @param  upperBound     The upper bound on the number of entries that match
644   *                        the associated search criteria.  It must be greater
645   *                        than zero.
646   * @param  searchIndexed  Indicates whether the search criteria is considered
647   *                        at least partially indexed and could be processed
648   *                        more efficiently than examining all entries with a
649   *                        full database scan.
650   * @param  debugInfo      An optional list of messages providing debug
651   *                        information about the processing performed by the
652   *                        server.  It may be {@code null} or empty if no debug
653   *                        messages should be included.
654   *
655   * @return  The matching entry count response control that was created.
656   */
657  @NotNull()
658  public static MatchingEntryCountResponseControl createUpperBoundResponse(
659                     final int upperBound, final boolean searchIndexed,
660                     @Nullable final Collection<String> debugInfo)
661  {
662    return createUpperBoundResponse(upperBound, searchIndexed, null, null, null,
663         null, debugInfo);
664  }
665
666
667
668  /**
669   * Creates a new matching entry count response control for the case in which
670   * the exact number of matching entries is not known, but the server was able
671   * to determine an upper bound on the number of matching entries.  This upper
672   * bound count may include entries that do not match the search filter, that
673   * are outside the scope of the search, and/or that match the search criteria
674   * but would not have been returned to the client in a normal search with the
675   * same criteria.
676   *
677   * @param  upperBound            The upper bound on the number of entries that
678   *                               match the associated search criteria.  It
679   *                               must be greater than zero.
680   * @param  searchIndexed         Indicates whether the search criteria is
681   *                               considered at least partially indexed and
682   *                               could be processed more efficiently than
683   *                               examining all entries with a full database
684   *                               scan.
685   * @param  shortCircuited        Indicates whether the server short-circuited
686   *                               during candidate set processing before
687   *                               evaluating all elements of the search
688   *                               criteria (the filter and scope).  This may be
689   *                               {@code null} if it is not available (e.g.,
690   *                               because extended response data was not
691   *                               requested).
692   * @param  fullyIndexed          Indicates whether the search is considered
693   *                               fully indexed.  Note that this may be
694   *                               {@code false} even if the filter is actually
695   *                               fully indexed if server index processing
696   *                               short-circuited before evaluating all
697   *                               components of the filter.  To avoid this,
698   *                               issue the request control with both fast and
699   *                               slow short-circuit thresholds set to zero.
700   *                               This may be {@code null} if this is not
701   *                               available (e.g., because extended response
702   *                               data was not requested).
703   * @param  candidatesAreInScope  Indicates whether all the identified
704   *                               candidate entries are within the scope of
705   *                               the search.  It may be {@code null} if this
706   *                               is not available (e.g., because extended
707   *                               response data was not requested).
708   * @param  remainingFilter       The portion of the filter that was either
709   *                               identified as unindexed or that was not
710   *                               evaluated because processing short-circuited
711   *                               in the course of building the candidate set.
712   *                               It may be {@code null} if there is no
713   *                               remaining filter or if this information is
714   *                               not available (e.g., because extended
715   *                               response data was not requested).
716   * @param  debugInfo             An optional list of messages providing debug
717   *                               information about the processing performed by
718   *                               the server.  It may be {@code null} or empty
719   *                               if no debug messages should be included.
720   *
721   * @return  The matching entry count response control that was created.
722   */
723  @NotNull()
724  public static MatchingEntryCountResponseControl createUpperBoundResponse(
725                     final int upperBound, final boolean searchIndexed,
726                     @Nullable final Boolean shortCircuited,
727                     @Nullable final Boolean fullyIndexed,
728                     @Nullable final Boolean candidatesAreInScope,
729                     @Nullable final Filter remainingFilter,
730                     @Nullable final Collection<String> debugInfo)
731  {
732    Validator.ensureTrue(upperBound > 0);
733
734    return new MatchingEntryCountResponseControl(
735         MatchingEntryCountType.UPPER_BOUND, upperBound, searchIndexed,
736         shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter,
737         debugInfo);
738  }
739
740
741
742  /**
743   * Creates a new matching entry count response control for the case in which
744   * the server was unable to make any meaningful determination about the number
745   * of entries matching the search criteria.
746   *
747   * @param  debugInfo  An optional list of messages providing debug information
748   *                    about the processing performed by the server.  It may be
749   *                    {@code null} or empty if no debug messages should be
750   *                    included.
751   *
752   * @return  The matching entry count response control that was created.
753   */
754  @NotNull()
755  public static MatchingEntryCountResponseControl createUnknownCountResponse(
756                     @Nullable final Collection<String> debugInfo)
757  {
758    return new MatchingEntryCountResponseControl(MatchingEntryCountType.UNKNOWN,
759         -1, false, null, null, null, null, debugInfo);
760  }
761
762
763
764  /**
765   * Encodes a control value with the provided information.
766   *
767   * @param  countType             The matching entry count type.  It must not
768   *                               be {@code null}.
769   * @param  countValue            The matching entry count value.  It must be
770   *                               greater than or equal to zero for a count
771   *                               type of either {@code EXAMINED_COUNT} or
772   *                               {@code UNEXAMINED_COUNT}.  It must be greater
773   *                               than zero for a count type of
774   *                               {@code UPPER_BOUND}.  It must be -1 for a
775   *                               count type of {@code UNKNOWN}.
776   * @param  searchIndexed         Indicates whether the search criteria is
777   *                               considered at least partially indexed and
778   *                               could be processed more efficiently than
779   *                               examining all entries with a full database
780   *                               scan.
781   * @param  shortCircuited        Indicates whether the server short-circuited
782   *                               during candidate set processing before
783   *                               evaluating all elements of the search
784   *                               criteria (the filter and scope).  This may be
785   *                               {@code null} if it is not available (e.g.,
786   *                               because extended response data was not
787   *                               requested).
788   * @param  fullyIndexed          Indicates whether the search is considered
789   *                               fully indexed.  Note that this may be
790   *                               {@code false} even if the filter is actually
791   *                               fully indexed if server index processing
792   *                               short-circuited before evaluating all
793   *                               components of the filter.  To avoid this,
794   *                               issue the request control with both fast and
795   *                               slow short-circuit thresholds set to zero.
796   *                               This may be {@code null} if this is not
797   *                               available (e.g., because extended response
798   *                               data was not requested).
799   * @param  candidatesAreInScope  Indicates whether all the identified
800   *                               candidate entries are within the scope of
801   *                               the search.  It may be {@code null} if this
802   *                               is not available (e.g., because extended
803   *                               response data was not requested).
804   * @param  remainingFilter       The portion of the filter that was either
805   *                               identified as unindexed or that was not
806   *                               evaluated because processing short-circuited
807   *                               in the course of building the candidate set.
808   *                               It may be {@code null} if there is no
809   *                               remaining filter or if this information is
810   *                               not available (e.g., because extended
811   *                               response data was not requested).
812   * @param  debugInfo             An optional list of messages providing debug
813   *                               information about the processing performed by
814   *                               the server.  It may be {@code null} or empty
815   *                               if no debug messages should be included.
816   *
817   * @return  The encoded control value.
818   */
819  @NotNull()
820  private static ASN1OctetString encodeValue(
821               @NotNull final MatchingEntryCountType countType,
822               final int countValue, final boolean searchIndexed,
823               @Nullable final Boolean shortCircuited,
824               @Nullable final Boolean fullyIndexed,
825               @Nullable final Boolean candidatesAreInScope,
826               @Nullable final Filter remainingFilter,
827               @Nullable final Collection<String> debugInfo)
828  {
829    final ArrayList<ASN1Element> elements = new ArrayList<>(3);
830
831    switch (countType)
832    {
833      case EXAMINED_COUNT:
834      case UNEXAMINED_COUNT:
835      case UPPER_BOUND:
836        elements.add(new ASN1Integer(countType.getBERType(), countValue));
837        break;
838      case UNKNOWN:
839        elements.add(new ASN1Null(countType.getBERType()));
840        break;
841    }
842
843    if (debugInfo != null)
844    {
845      final ArrayList<ASN1Element> debugElements =
846           new ArrayList<>(debugInfo.size());
847      for (final String s : debugInfo)
848      {
849        debugElements.add(new ASN1OctetString(s));
850      }
851
852      elements.add(new ASN1Sequence(TYPE_DEBUG_INFO, debugElements));
853    }
854
855    if (! searchIndexed)
856    {
857      elements.add(new ASN1Boolean(TYPE_SEARCH_INDEXED, searchIndexed));
858    }
859
860    if (shortCircuited != null)
861    {
862      elements.add(new ASN1Boolean(TYPE_SHORT_CIRCUITED, shortCircuited));
863    }
864
865    if (fullyIndexed != null)
866    {
867      elements.add(new ASN1Boolean(TYPE_FULLY_INDEXED, fullyIndexed));
868    }
869
870    if (candidatesAreInScope != null)
871    {
872      elements.add(new ASN1Boolean(TYPE_CANDIDATES_ARE_IN_SCOPE,
873           candidatesAreInScope));
874    }
875
876    if (remainingFilter != null)
877    {
878      elements.add(new ASN1OctetString(TYPE_REMAINING_FILTER,
879           remainingFilter.encode().encode()));
880    }
881
882    return new ASN1OctetString(new ASN1Sequence(elements).encode());
883  }
884
885
886
887  /**
888   * Retrieves the matching entry count type for the response control.
889   *
890   * @return  The matching entry count type for the response control.
891   */
892  @NotNull()
893  public MatchingEntryCountType getCountType()
894  {
895    return countType;
896  }
897
898
899
900  /**
901   * Retrieves the matching entry count value for the response control.  For a
902   * count type of {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}, this is
903   * the exact number of matching entries.  For a count type of
904   * {@code UPPER_BOUND}, this is the maximum number of entries that may match
905   * the search criteria, but it may also include entries that do not match the
906   * criteria.  For a count type of {@code UNKNOWN}, this will always be -1.
907   *
908   * @return  The exact count or upper bound of the number of entries in the
909   *          server that may match the search criteria, or -1 if the server
910   *          could not determine the number of matching entries.
911   */
912  public int getCountValue()
913  {
914    return countValue;
915  }
916
917
918
919  /**
920   * Indicates whether the server considers the search criteria to be indexed
921   * and therefore it could be processed more efficiently than examining all
922   * entries with a full database scan.
923   *
924   * @return  {@code true} if the server considers the search criteria to be
925   *          indexed, or {@code false} if not.
926   */
927  public boolean searchIndexed()
928  {
929    return searchIndexed;
930  }
931
932
933
934  /**
935   * Indicates whether the server short-circuited during candidate set
936   * processing before evaluating all elements of the search criteria (the
937   * filter and scope).
938   *
939   * @return  {@code Boolean.TRUE} if the server did short-circuit during
940   *          candidate set processing before evaluating all elements of the
941   *          search criteria, {@code Boolean.FALSE} if the server evaluated all
942   *          elements of the search criteria, or {@code null} if this
943   *          information is not available (e.g., because extended response data
944   *          was not requested).
945   */
946  @Nullable()
947  public Boolean getShortCircuited()
948  {
949    return shortCircuited;
950  }
951
952
953
954  /**
955   * Indicates whether the server considers the search criteria to be fully
956   * indexed.  Note that if the server short-circuited during candidate set
957   * processing before evaluating all search criteria (the filter and scope),
958   * this may be {@code Boolean.FALSE} even if the search is actually completely
959   * indexed.
960   *
961   * @return  {@code Boolean.TRUE} if the server considers the search criteria
962   *          to be fully indexed, {@code Boolean.FALSE} if the search criteria
963   *          is not known to be fully indexed, or {@code null} if this
964   *          information is not available (e.g., because extended response data
965   *          was not requested).
966   */
967  @Nullable()
968  public Boolean getFullyIndexed()
969  {
970    return fullyIndexed;
971  }
972
973
974
975  /**
976   * Indicates whether the server can determine that all the identified
977   * candidates are within the scope of the search.  Note that even if the
978   * server returns {@code Boolean.FALSE}, it does not necessarily mean that
979   * not all the candidates are within the scope of the search, but just that
980   * the server is not certain that is the case.
981   *
982   * @return  {@code Boolean.TRUE} if the server can determine that all the
983   *          identified candidates are within the scope of the search,
984   *          {@code Boolean.FALSE} if the server cannot determine that all the
985   *          identified candidates are within the scope of the search, or
986   *          {@code null} if this information is not available (e.g., because
987   *          extended response data was not requested).
988   */
989  @Nullable()
990  public Boolean getCandidatesAreInScope()
991  {
992    return candidatesAreInScope;
993  }
994
995
996
997  /**
998   * Retrieves the portion of the filter that was either identified as not
999   * indexed or that was not evaluated during candidate processing (e.g.,
1000   * because the server short-circuited processing before examining all filter
1001   * components).
1002   *
1003   * @return  The portion of the filter that was either identified as not
1004   *          indexed or that was not evaluated during candidate processing, or
1005   *          {@code null} if there was no remaining filter or if this
1006   *          information is not available (e.g., because extended response data
1007   *          was not requested).
1008   */
1009  @Nullable()
1010  public Filter getRemainingFilter()
1011  {
1012    return remainingFilter;
1013  }
1014
1015
1016
1017  /**
1018   * Retrieves a list of messages with debug information about the processing
1019   * performed by the server in the course of obtaining the matching entry
1020   * count.  These messages are intended to be human-readable rather than
1021   * machine-parsable.
1022   *
1023   * @return  A list of messages with debug information about the processing
1024   *          performed by the server in the course of obtaining the matching
1025   *          entry count, or an empty list if no debug messages were provided.
1026   */
1027  @NotNull()
1028  public List<String> getDebugInfo()
1029  {
1030    return debugInfo;
1031  }
1032
1033
1034
1035  /**
1036   * {@inheritDoc}
1037   */
1038  @Override()
1039  @NotNull()
1040  public MatchingEntryCountResponseControl decodeControl(
1041              @NotNull final String oid,
1042              final boolean isCritical,
1043              @Nullable final ASN1OctetString value)
1044         throws LDAPException
1045  {
1046    return new MatchingEntryCountResponseControl(oid, isCritical, value);
1047  }
1048
1049
1050
1051  /**
1052   * Extracts a matching entry count response control from the provided search
1053   * result.
1054   *
1055   * @param  result  The search result from which to retrieve the matching entry
1056   *                 count response control.
1057   *
1058   * @return  The matching entry count response control contained in the
1059   *          provided result, or {@code null} if the result did not contain a
1060   *          matching entry count response control.
1061   *
1062   * @throws  LDAPException  If a problem is encountered while attempting to
1063   *                         decode the matching entry count response control
1064   *                         contained in the provided result.
1065   */
1066  @Nullable()
1067  public static MatchingEntryCountResponseControl get(
1068                     @NotNull final SearchResult result)
1069         throws LDAPException
1070  {
1071    final Control c =
1072         result.getResponseControl(MATCHING_ENTRY_COUNT_RESPONSE_OID);
1073    if (c == null)
1074    {
1075      return null;
1076    }
1077
1078    if (c instanceof MatchingEntryCountResponseControl)
1079    {
1080      return (MatchingEntryCountResponseControl) c;
1081    }
1082    else
1083    {
1084      return new MatchingEntryCountResponseControl(c.getOID(), c.isCritical(),
1085           c.getValue());
1086    }
1087  }
1088
1089
1090
1091  /**
1092   * {@inheritDoc}
1093   */
1094  @Override()
1095  @NotNull()
1096  public String getControlName()
1097  {
1098    return INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get();
1099  }
1100
1101
1102
1103  /**
1104   * {@inheritDoc}
1105   */
1106  @Override()
1107  public void toString(@NotNull final StringBuilder buffer)
1108  {
1109    buffer.append("MatchingEntryCountResponseControl(countType='");
1110    buffer.append(countType.name());
1111    buffer.append('\'');
1112
1113    switch (countType)
1114    {
1115      case EXAMINED_COUNT:
1116      case UNEXAMINED_COUNT:
1117        buffer.append(", count=");
1118        buffer.append(countValue);
1119        break;
1120
1121      case UPPER_BOUND:
1122        buffer.append(", upperBound=");
1123        buffer.append(countValue);
1124        break;
1125    }
1126
1127    buffer.append(", searchIndexed=");
1128    buffer.append(searchIndexed);
1129
1130    if (shortCircuited != null)
1131    {
1132      buffer.append(", shortCircuited=");
1133      buffer.append(shortCircuited);
1134    }
1135
1136    if (fullyIndexed != null)
1137    {
1138      buffer.append(", fullyIndexed=");
1139      buffer.append(fullyIndexed);
1140    }
1141
1142    if (candidatesAreInScope != null)
1143    {
1144      buffer.append(", candidatesAreInScope=");
1145      buffer.append(candidatesAreInScope);
1146    }
1147
1148    if (remainingFilter != null)
1149    {
1150      buffer.append(", remainingFilter='");
1151      remainingFilter.toString(buffer);
1152      buffer.append('\'');
1153    }
1154
1155    if (! debugInfo.isEmpty())
1156    {
1157      buffer.append(", debugInfo={");
1158
1159      final Iterator<String> iterator = debugInfo.iterator();
1160      while (iterator.hasNext())
1161      {
1162        buffer.append('\'');
1163        buffer.append(iterator.next());
1164        buffer.append('\'');
1165
1166        if (iterator.hasNext())
1167        {
1168          buffer.append(", ");
1169        }
1170      }
1171
1172      buffer.append('}');
1173    }
1174
1175    buffer.append(')');
1176  }
1177}