001/*
002 * Copyright 2014-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2014-2024 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-2024 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.LinkedHashMap;
045import java.util.List;
046import java.util.Map;
047
048import com.unboundid.asn1.ASN1Boolean;
049import com.unboundid.asn1.ASN1Element;
050import com.unboundid.asn1.ASN1Integer;
051import com.unboundid.asn1.ASN1Null;
052import com.unboundid.asn1.ASN1OctetString;
053import com.unboundid.asn1.ASN1Sequence;
054import com.unboundid.ldap.sdk.Control;
055import com.unboundid.ldap.sdk.DecodeableControl;
056import com.unboundid.ldap.sdk.Filter;
057import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
058import com.unboundid.ldap.sdk.LDAPException;
059import com.unboundid.ldap.sdk.ResultCode;
060import com.unboundid.ldap.sdk.SearchResult;
061import com.unboundid.util.Debug;
062import com.unboundid.util.NotMutable;
063import com.unboundid.util.NotNull;
064import com.unboundid.util.Nullable;
065import com.unboundid.util.StaticUtils;
066import com.unboundid.util.ThreadSafety;
067import com.unboundid.util.ThreadSafetyLevel;
068import com.unboundid.util.Validator;
069import com.unboundid.util.json.JSONArray;
070import com.unboundid.util.json.JSONBoolean;
071import com.unboundid.util.json.JSONField;
072import com.unboundid.util.json.JSONNumber;
073import com.unboundid.util.json.JSONObject;
074import com.unboundid.util.json.JSONString;
075import com.unboundid.util.json.JSONValue;
076
077import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
078
079
080
081/**
082 * This class provides a response control that may be used to provide
083 * information about the number of entries that match a given set of search
084 * criteria.  The control will be included in the search result done message
085 * for any successful search operation in which the request contained a matching
086 * entry count request control.
087 * <BR>
088 * <BLOCKQUOTE>
089 *   <B>NOTE:</B>  This class, and other classes within the
090 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
091 *   supported for use against Ping Identity, UnboundID, and
092 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
093 *   for proprietary functionality or for external specifications that are not
094 *   considered stable or mature enough to be guaranteed to work in an
095 *   interoperable way with other types of LDAP servers.
096 * </BLOCKQUOTE>
097 * <BR>
098 * The matching entry count response control has an OID of
099 * "1.3.6.1.4.1.30221.2.5.37", a criticality of false, and a value with the
100 * following encoding:
101 * <PRE>
102 *   MatchingEntryCountResponse ::= SEQUENCE {
103 *        entryCount               CHOICE {
104 *             examinedCount            [0] INTEGER,
105 *             unexaminedCount          [1] INTEGER,
106 *             upperBound               [2] INTEGER,
107 *             unknown                  [3] NULL,
108 *             ... }
109 *        debugInfo                [0] SEQUENCE OF OCTET STRING OPTIONAL,
110 *        searchIndexed            [1] BOOLEAN DEFAULT TRUE,
111 *        shortCircuited           [2] BOOLEAN OPTIONAL,
112 *        fullyIndexed             [3] BOOLEAN OPTIONAL,
113 *        candidatesAreInScope     [4] BOOLEAN OPTIONAL,
114 *        remainingFilter          [5] Filter OPTIONAL,
115 *        ... }
116 * </PRE>
117 *
118 * @see  MatchingEntryCountRequestControl
119 */
120@NotMutable()
121@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
122public final class MatchingEntryCountResponseControl
123       extends Control
124       implements DecodeableControl
125{
126  /**
127   * The OID (1.3.6.1.4.1.30221.2.5.37) for the matching entry count response
128   * control.
129   */
130  @NotNull public static final String MATCHING_ENTRY_COUNT_RESPONSE_OID =
131       "1.3.6.1.4.1.30221.2.5.37";
132
133
134
135  /**
136   * The BER type for the element used to hold the list of debug messages.
137   */
138  private static final byte TYPE_DEBUG_INFO = (byte) 0xA0;
139
140
141
142  /**
143   * The BER type for the element used to indicate whether the search criteria
144   * is at least partially indexed.
145   */
146  private static final byte TYPE_SEARCH_INDEXED = (byte) 0x81;
147
148
149
150  /**
151   * The BER type for the element used to indicate whether the server
152   * short-circuited during candidate set processing before evaluating all
153   * elements of the search criteria (the filter and scope).
154   */
155  private static final byte TYPE_SHORT_CIRCUITED = (byte) 0x82;
156
157
158
159  /**
160   * The BER type for the element used to indicate whether the search criteria
161   * is fully indexed.
162   */
163  private static final byte TYPE_FULLY_INDEXED = (byte) 0x83;
164
165
166
167  /**
168   * The BER type for the element used to indicate whether all the identified
169   * candidate entries are within the scope of the search.
170   */
171  private static final byte TYPE_CANDIDATES_ARE_IN_SCOPE = (byte) 0x84;
172
173
174
175  /**
176   * The BER type for the element used to provide the remaining filter for the
177   * search operation, which is the portion of the filter that was determined
178   * to be unindexed, or that was unevaluated if processing short-circuited in
179   * the course of building the candidate set.
180   */
181  private static final byte TYPE_REMAINING_FILTER = (byte) 0xA5;
182
183
184
185  /**
186   * The name of the field used to hold the candidates are in scope flag in the
187   * JSON representation of this control.
188   */
189  @NotNull private static final String JSON_FIELD_CANDIDATES_ARE_IN_SCOPE =
190       "candidates-are-in-scope";
191
192
193
194  /**
195   * The name of the field used to hold the count type in the JSON
196   * representation of this control.
197   */
198  @NotNull private static final String JSON_FIELD_COUNT_TYPE = "count-type";
199
200
201
202  /**
203   * The name of the field used to hold the count value in the JSON
204   * representation of this control.
205   */
206  @NotNull private static final String JSON_FIELD_COUNT_VALUE = "count-value";
207
208
209
210  /**
211   * The name of the field used to hold the debug info in the JSON
212   * representation of this control.
213   */
214  @NotNull private static final String JSON_FIELD_DEBUG_INFO = "debug-info";
215
216
217
218  /**
219   * The name of the field used to hold the fully indexed flag in the JSON
220   * representation of this control.
221   */
222  @NotNull private static final String JSON_FIELD_FULLY_INDEXED =
223       "fully-indexed";
224
225
226
227  /**
228   * The name of the field used to hold the remaining filter in the JSON
229   * representation of this control.
230   */
231  @NotNull private static final String JSON_FIELD_REMAINING_FILTER =
232       "remaining-filter";
233
234
235
236  /**
237   * The name of the field used to hold the search indexed flag in the JSON
238   * representation of this control.
239   */
240  @NotNull private static final String JSON_FIELD_SEARCH_INDEXED =
241       "search-indexed";
242
243
244
245  /**
246   * The name of the field used to hold the short-circuited flag in the JSON
247   * representation of this control.
248   */
249  @NotNull private static final String JSON_FIELD_SHORT_CIRCUITED =
250       "short-circuited";
251
252
253
254  /**
255   * The result-type value that will be used for an examined count in the JSON
256   * representation of this control.
257   */
258  @NotNull private static final String JSON_COUNT_TYPE_EXAMINED_COUNT =
259       "examined-count";
260
261
262
263  /**
264   * The result-type value that will be used for an unexamined count in the JSON
265   * representation of this control.
266   */
267  @NotNull private static final String JSON_COUNT_TYPE_UNEXAMINED_COUNT =
268       "unexamined-count";
269
270
271
272  /**
273   * The result-type value that will be used for an unknown count in the JSON
274   * representation of this control.
275   */
276  @NotNull private static final String JSON_COUNT_TYPE_UNKNOWN = "unknown";
277
278
279
280  /**
281   * The result-type value that will be used for an upper-bound count in the
282   * JSON representation of this control.
283   */
284  @NotNull private static final String JSON_COUNT_TYPE_UPPER_BOUND =
285       "upper-bound";
286
287
288
289  /**
290   * The serial version UID for this serializable class.
291   */
292  private static final long serialVersionUID = -7808452580964236458L;
293
294
295
296  // Indicates whether the search criteria is considered at least partially
297  // indexed by the server.
298  private final boolean searchIndexed;
299
300  // Indicates whether all the identified candidate entries are within the scope
301  // of the search.
302  @Nullable private final Boolean candidatesAreInScope;
303
304  // Indicates whether the search criteria is considered fully indexed.
305  @Nullable private final Boolean fullyIndexed;
306
307  // Indicates whether the server short-circuited during candidate set
308  // processing before evaluating all elements of the search criteria (the
309  // filter and scope).
310  @Nullable private final Boolean shortCircuited;
311
312  // The portion of the filter that was either identified as unindexed or that
313  // was not evaluated in the course of building the candidate set.
314  @Nullable private final Filter remainingFilter;
315
316  // The count value for this matching entry count response control.
317  private final int countValue;
318
319  // A list of messages providing debug information about the processing
320  // performed by the server.
321  @NotNull private final List<String> debugInfo;
322
323  // The count type for this matching entry count response control.
324  @NotNull private final MatchingEntryCountType countType;
325
326
327
328  /**
329   * Creates a new empty control instance that is intended to be used only for
330   * decoding controls via the {@code DecodeableControl} interface.
331   */
332  MatchingEntryCountResponseControl()
333  {
334    searchIndexed = false;
335    candidatesAreInScope = null;
336    fullyIndexed = null;
337    shortCircuited = null;
338    remainingFilter = null;
339    countValue = -1;
340    countType = null;
341    debugInfo = null;
342  }
343
344
345
346  /**
347   * Creates a new matching entry count response control with the provided
348   * information.
349   *
350   * @param  countType             The matching entry count type.  It must not
351   *                               be {@code null}.
352   * @param  countValue            The matching entry count value.  It must be
353   *                               greater than or equal to zero for a count
354   *                               type of either {@code EXAMINED_COUNT} or
355   *                               {@code UNEXAMINED_COUNT}.  It must be greater
356   *                               than zero for a count type of
357   *                               {@code UPPER_BOUND}.  It must be -1 for a
358   *                               count type of {@code UNKNOWN}.
359   * @param  searchIndexed         Indicates whether the search criteria is
360   *                               considered at least partially indexed and
361   *                               could be processed more efficiently than
362   *                               examining all entries with a full database
363   *                               scan.
364   * @param  shortCircuited        Indicates whether the server short-circuited
365   *                               during candidate set processing before
366   *                               evaluating all elements of the search
367   *                               criteria (the filter and scope).  This may be
368   *                               {@code null} if it is not available (e.g.,
369   *                               because extended response data was not
370   *                               requested).
371   * @param  fullyIndexed          Indicates whether the search is considered
372   *                               fully indexed.  Note that this may be
373   *                               {@code false} even if the filter is actually
374   *                               fully indexed if server index processing
375   *                               short-circuited before evaluating all
376   *                               components of the filter.  To avoid this,
377   *                               issue the request control with both fast and
378   *                               slow short-circuit thresholds set to zero.
379   *                               This may be {@code null} if this is not
380   *                               available (e.g., because extended response
381   *                               data was not requested).
382   * @param  candidatesAreInScope  Indicates whether all the identified
383   *                               candidate entries are within the scope of
384   *                               the search.  It may be {@code null} if this
385   *                               is not available (e.g., because extended
386   *                               response data was not requested).
387   * @param  remainingFilter       The portion of the filter that was either
388   *                               identified as unindexed or that was not
389   *                               evaluated because processing short-circuited
390   *                               in the course of building the candidate set.
391   *                               It may be {@code null} if there is no
392   *                               remaining filter or if this information is
393   *                               not available (e.g., because extended
394   *                               response data was not requested).
395   * @param  debugInfo             An optional list of messages providing debug
396   *                               information about the processing performed by
397   *                               the server.  It may be {@code null} or empty
398   *                               if no debug messages should be included.
399   */
400  private MatchingEntryCountResponseControl(
401               @NotNull final MatchingEntryCountType countType,
402               final int countValue,
403               final boolean searchIndexed,
404               @Nullable final Boolean shortCircuited,
405               @Nullable final Boolean fullyIndexed,
406               @Nullable final Boolean candidatesAreInScope,
407               @Nullable final Filter remainingFilter,
408               @Nullable final Collection<String> debugInfo)
409  {
410    super(MATCHING_ENTRY_COUNT_RESPONSE_OID, false,
411         encodeValue(countType, countValue, searchIndexed, shortCircuited,
412              fullyIndexed, candidatesAreInScope, remainingFilter, debugInfo));
413
414    this.countType = countType;
415    this.countValue = countValue;
416    this.searchIndexed = searchIndexed;
417    this.shortCircuited = shortCircuited;
418    this.fullyIndexed = fullyIndexed;
419    this.candidatesAreInScope = candidatesAreInScope;
420    this.remainingFilter = remainingFilter;
421
422    if (debugInfo == null)
423    {
424      this.debugInfo = Collections.emptyList();
425    }
426    else
427    {
428      this.debugInfo =
429           Collections.unmodifiableList(new ArrayList<>(debugInfo));
430    }
431  }
432
433
434
435  /**
436   * Creates a new matching entry count response control decoded from the given
437   * generic control contents.
438   *
439   * @param  oid         The OID for the control.
440   * @param  isCritical  Indicates whether this control should be marked
441   *                     critical.
442   * @param  value       The encoded value for the control.
443   *
444   * @throws LDAPException  If a problem occurs while attempting to decode the
445   *                        generic control as a matching entry count response
446   *                        control.
447   */
448  public MatchingEntryCountResponseControl(@NotNull final String oid,
449              final boolean isCritical,
450              @Nullable final ASN1OctetString value)
451         throws LDAPException
452  {
453    super(oid, isCritical, value);
454
455    if (value == null)
456    {
457      throw new LDAPException(ResultCode.DECODING_ERROR,
458           ERR_MATCHING_ENTRY_COUNT_RESPONSE_MISSING_VALUE.get());
459    }
460
461    try
462    {
463      final ASN1Element[] elements =
464           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
465      countType = MatchingEntryCountType.valueOf(elements[0].getType());
466      if (countType == null)
467      {
468        throw new LDAPException(ResultCode.DECODING_ERROR,
469             ERR_MATCHING_ENTRY_COUNT_RESPONSE_INVALID_COUNT_TYPE.get(
470                  StaticUtils.toHex(elements[0].getType())));
471      }
472
473      switch (countType)
474      {
475        case EXAMINED_COUNT:
476        case UNEXAMINED_COUNT:
477          countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue();
478          if (countValue < 0)
479          {
480            throw new LDAPException(ResultCode.DECODING_ERROR,
481                 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NEGATIVE_EXACT_COUNT.get());
482          }
483          break;
484
485        case UPPER_BOUND:
486          countValue = ASN1Integer.decodeAsInteger(elements[0]).intValue();
487          if (countValue <= 0)
488          {
489            throw new LDAPException(ResultCode.DECODING_ERROR,
490                 ERR_MATCHING_ENTRY_COUNT_RESPONSE_NON_POSITIVE_UPPER_BOUND.
491                      get());
492          }
493          break;
494
495        case UNKNOWN:
496        default:
497          countValue = -1;
498          break;
499      }
500
501      boolean decodedSearchIndexed =
502           (countType != MatchingEntryCountType.UNKNOWN);
503      Boolean decodedFullyIndexed = null;
504      Boolean decodedCandidatesAreInScope = null;
505      Boolean decodedShortCircuited = null;
506      Filter decodedRemainingFilter = null;
507      List<String> debugMessages = Collections.emptyList();
508      for (int i=1; i < elements.length; i++)
509      {
510        switch (elements[i].getType())
511        {
512          case TYPE_DEBUG_INFO:
513            final ASN1Element[] debugElements =
514                 ASN1Sequence.decodeAsSequence(elements[i]).elements();
515            debugMessages = new ArrayList<>(debugElements.length);
516            for (final ASN1Element e : debugElements)
517            {
518              debugMessages.add(
519                   ASN1OctetString.decodeAsOctetString(e).stringValue());
520            }
521            break;
522
523          case TYPE_SEARCH_INDEXED:
524            decodedSearchIndexed =
525                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
526            break;
527
528          case TYPE_SHORT_CIRCUITED:
529            decodedShortCircuited =
530                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
531            break;
532
533          case TYPE_FULLY_INDEXED:
534            decodedFullyIndexed =
535                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
536            break;
537
538          case TYPE_CANDIDATES_ARE_IN_SCOPE:
539            decodedCandidatesAreInScope =
540                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
541            break;
542
543          case TYPE_REMAINING_FILTER:
544            final ASN1Element filterElement =
545                 ASN1Element.decode(elements[i].getValue());
546            decodedRemainingFilter = Filter.decode(filterElement);
547            break;
548        }
549      }
550
551      searchIndexed = decodedSearchIndexed;
552      shortCircuited = decodedShortCircuited;
553      fullyIndexed = decodedFullyIndexed;
554      candidatesAreInScope = decodedCandidatesAreInScope;
555      remainingFilter = decodedRemainingFilter;
556      debugInfo = Collections.unmodifiableList(debugMessages);
557    }
558    catch (final LDAPException le)
559    {
560      Debug.debugException(le);
561      throw le;
562    }
563    catch (final Exception e)
564    {
565      Debug.debugException(e);
566      throw new LDAPException(ResultCode.DECODING_ERROR,
567           ERR_GET_BACKEND_SET_ID_RESPONSE_CANNOT_DECODE.get(
568                StaticUtils.getExceptionMessage(e)),
569           e);
570    }
571  }
572
573
574
575  /**
576   * Creates a new matching entry count response control for the case in which
577   * the exact number of matching entries is known.
578   *
579   * @param  count      The exact number of entries matching the associated
580   *                    search criteria.  It must be greater than or equal to
581   *                    zero.
582   * @param  examined   Indicates whether the server examined the entries to
583   *                    exclude those entries that would not be returned to the
584   *                    client in a normal search with the same criteria.
585   * @param  debugInfo  An optional list of messages providing debug information
586   *                    about the processing performed by the server.  It may be
587   *                    {@code null} or empty if no debug messages should be
588   *                    included.
589   *
590   * @return  The matching entry count response control that was created.
591   */
592  @NotNull()
593  public static MatchingEntryCountResponseControl createExactCountResponse(
594                     final int count, final boolean examined,
595                     @Nullable final Collection<String> debugInfo)
596  {
597    return createExactCountResponse(count, examined, true, debugInfo);
598  }
599
600
601
602  /**
603   * Creates a new matching entry count response control for the case in which
604   * the exact number of matching entries is known.
605   *
606   * @param  count          The exact number of entries matching the associated
607   *                        search criteria.  It must be greater than or equal
608   *                        to zero.
609   * @param  examined       Indicates whether the server examined the entries to
610   *                        exclude those entries that would not be returned to
611   *                        the client in a normal search with the same
612   *                        criteria.
613   * @param  searchIndexed  Indicates whether the search criteria is considered
614   *                        at least partially indexed and could be processed
615   *                        more efficiently than examining all entries with a
616   *                        full database scan.
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 createExactCountResponse(
626                     final int count, final boolean examined,
627                     final boolean searchIndexed,
628                     @Nullable final Collection<String> debugInfo)
629  {
630    return createExactCountResponse(count, examined, searchIndexed, null, null,
631         null, null, debugInfo);
632  }
633
634
635
636  /**
637   * Creates a new matching entry count response control for the case in which
638   * the exact number of matching entries is known.
639   *
640   * @param  count                 The exact number of entries matching the
641   *                               associated search criteria.  It must be
642   *                               greater than or equal to zero.
643   * @param  examined              Indicates whether the server examined the
644   *                               entries to exclude those entries that would
645   *                               not be returned to the client in a normal
646   *                               search with the same criteria.
647   * @param  searchIndexed         Indicates whether the search criteria is
648   *                               considered at least partially indexed and
649   *                               could be processed more efficiently than
650   *                               examining all entries with a full database
651   *                               scan.
652   * @param  shortCircuited        Indicates whether the server short-circuited
653   *                               during candidate set processing before
654   *                               evaluating all elements of the search
655   *                               criteria (the filter and scope).  This may be
656   *                               {@code null} if it is not available (e.g.,
657   *                               because extended response data was not
658   *                               requested).
659   * @param  fullyIndexed          Indicates whether the search is considered
660   *                               fully indexed.  Note that this may be
661   *                               {@code false} even if the filter is actually
662   *                               fully indexed if server index processing
663   *                               short-circuited before evaluating all
664   *                               components of the filter.  To avoid this,
665   *                               issue the request control with both fast and
666   *                               slow short-circuit thresholds set to zero.
667   *                               This may be {@code null} if this is not
668   *                               available (e.g., because extended response
669   *                               data was not requested).
670   * @param  candidatesAreInScope  Indicates whether all the identified
671   *                               candidate entries are within the scope of
672   *                               the search.  It may be {@code null} if this
673   *                               is not available (e.g., because extended
674   *                               response data was not requested).
675   * @param  remainingFilter       The portion of the filter that was either
676   *                               identified as unindexed or that was not
677   *                               evaluated because processing short-circuited
678   *                               in the course of building the candidate set.
679   *                               It may be {@code null} if there is no
680   *                               remaining filter or if this information is
681   *                               not available (e.g., because extended
682   *                               response data was not requested).
683   * @param  debugInfo             An optional list of messages providing debug
684   *                               information about the processing performed by
685   *                               the server.  It may be {@code null} or empty
686   *                               if no debug messages should be included.
687   *
688   * @return  The matching entry count response control that was created.
689   */
690  @NotNull()
691  public static MatchingEntryCountResponseControl createExactCountResponse(
692                     final int count, final boolean examined,
693                     final boolean searchIndexed,
694                     @Nullable final Boolean shortCircuited,
695                     @Nullable final Boolean fullyIndexed,
696                     @Nullable final Boolean candidatesAreInScope,
697                     @Nullable final Filter remainingFilter,
698                     @Nullable final Collection<String> debugInfo)
699  {
700    Validator.ensureTrue(count >= 0);
701
702    final MatchingEntryCountType countType;
703    if (examined)
704    {
705      countType = MatchingEntryCountType.EXAMINED_COUNT;
706    }
707    else
708    {
709      countType = MatchingEntryCountType.UNEXAMINED_COUNT;
710    }
711
712    return new MatchingEntryCountResponseControl(countType, count,
713         searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope,
714         remainingFilter, debugInfo);
715  }
716
717
718
719  /**
720   * Creates a new matching entry count response control for the case in which
721   * the exact number of matching entries is not known, but the server was able
722   * to determine an upper bound on the number of matching entries.  This upper
723   * bound count may include entries that do not match the search filter, that
724   * are outside the scope of the search, and/or that match the search criteria
725   * but would not have been returned to the client in a normal search with the
726   * same criteria.
727   *
728   * @param  upperBound  The upper bound on the number of entries that match the
729   *                     associated search criteria.  It must be greater than
730   *                     zero.
731   * @param  debugInfo   An optional list of messages providing debug
732   *                     information about the processing performed by the
733   *                     server.  It may be {@code null} or empty if no debug
734   *                     messages should be included.
735   *
736   * @return  The matching entry count response control that was created.
737   */
738  @NotNull()
739  public static MatchingEntryCountResponseControl createUpperBoundResponse(
740                     final int upperBound,
741                     @Nullable final Collection<String> debugInfo)
742  {
743    return createUpperBoundResponse(upperBound, true, debugInfo);
744  }
745
746
747
748  /**
749   * Creates a new matching entry count response control for the case in which
750   * the exact number of matching entries is not known, but the server was able
751   * to determine an upper bound on the number of matching entries.  This upper
752   * bound count may include entries that do not match the search filter, that
753   * are outside the scope of the search, and/or that match the search criteria
754   * but would not have been returned to the client in a normal search with the
755   * same criteria.
756   *
757   * @param  upperBound     The upper bound on the number of entries that match
758   *                        the associated search criteria.  It must be greater
759   *                        than zero.
760   * @param  searchIndexed  Indicates whether the search criteria is considered
761   *                        at least partially indexed and could be processed
762   *                        more efficiently than examining all entries with a
763   *                        full database scan.
764   * @param  debugInfo      An optional list of messages providing debug
765   *                        information about the processing performed by the
766   *                        server.  It may be {@code null} or empty if no debug
767   *                        messages should be included.
768   *
769   * @return  The matching entry count response control that was created.
770   */
771  @NotNull()
772  public static MatchingEntryCountResponseControl createUpperBoundResponse(
773                     final int upperBound, final boolean searchIndexed,
774                     @Nullable final Collection<String> debugInfo)
775  {
776    return createUpperBoundResponse(upperBound, searchIndexed, null, null, null,
777         null, debugInfo);
778  }
779
780
781
782  /**
783   * Creates a new matching entry count response control for the case in which
784   * the exact number of matching entries is not known, but the server was able
785   * to determine an upper bound on the number of matching entries.  This upper
786   * bound count may include entries that do not match the search filter, that
787   * are outside the scope of the search, and/or that match the search criteria
788   * but would not have been returned to the client in a normal search with the
789   * same criteria.
790   *
791   * @param  upperBound            The upper bound on the number of entries that
792   *                               match the associated search criteria.  It
793   *                               must be greater than zero.
794   * @param  searchIndexed         Indicates whether the search criteria is
795   *                               considered at least partially indexed and
796   *                               could be processed more efficiently than
797   *                               examining all entries with a full database
798   *                               scan.
799   * @param  shortCircuited        Indicates whether the server short-circuited
800   *                               during candidate set processing before
801   *                               evaluating all elements of the search
802   *                               criteria (the filter and scope).  This may be
803   *                               {@code null} if it is not available (e.g.,
804   *                               because extended response data was not
805   *                               requested).
806   * @param  fullyIndexed          Indicates whether the search is considered
807   *                               fully indexed.  Note that this may be
808   *                               {@code false} even if the filter is actually
809   *                               fully indexed if server index processing
810   *                               short-circuited before evaluating all
811   *                               components of the filter.  To avoid this,
812   *                               issue the request control with both fast and
813   *                               slow short-circuit thresholds set to zero.
814   *                               This may be {@code null} if this is not
815   *                               available (e.g., because extended response
816   *                               data was not requested).
817   * @param  candidatesAreInScope  Indicates whether all the identified
818   *                               candidate entries are within the scope of
819   *                               the search.  It may be {@code null} if this
820   *                               is not available (e.g., because extended
821   *                               response data was not requested).
822   * @param  remainingFilter       The portion of the filter that was either
823   *                               identified as unindexed or that was not
824   *                               evaluated because processing short-circuited
825   *                               in the course of building the candidate set.
826   *                               It may be {@code null} if there is no
827   *                               remaining filter or if this information is
828   *                               not available (e.g., because extended
829   *                               response data was not requested).
830   * @param  debugInfo             An optional list of messages providing debug
831   *                               information about the processing performed by
832   *                               the server.  It may be {@code null} or empty
833   *                               if no debug messages should be included.
834   *
835   * @return  The matching entry count response control that was created.
836   */
837  @NotNull()
838  public static MatchingEntryCountResponseControl createUpperBoundResponse(
839                     final int upperBound, final boolean searchIndexed,
840                     @Nullable final Boolean shortCircuited,
841                     @Nullable final Boolean fullyIndexed,
842                     @Nullable final Boolean candidatesAreInScope,
843                     @Nullable final Filter remainingFilter,
844                     @Nullable final Collection<String> debugInfo)
845  {
846    Validator.ensureTrue(upperBound > 0);
847
848    return new MatchingEntryCountResponseControl(
849         MatchingEntryCountType.UPPER_BOUND, upperBound, searchIndexed,
850         shortCircuited, fullyIndexed, candidatesAreInScope, remainingFilter,
851         debugInfo);
852  }
853
854
855
856  /**
857   * Creates a new matching entry count response control for the case in which
858   * the server was unable to make any meaningful determination about the number
859   * of entries matching the search criteria.
860   *
861   * @param  debugInfo  An optional list of messages providing debug information
862   *                    about the processing performed by the server.  It may be
863   *                    {@code null} or empty if no debug messages should be
864   *                    included.
865   *
866   * @return  The matching entry count response control that was created.
867   */
868  @NotNull()
869  public static MatchingEntryCountResponseControl createUnknownCountResponse(
870                     @Nullable final Collection<String> debugInfo)
871  {
872    return new MatchingEntryCountResponseControl(MatchingEntryCountType.UNKNOWN,
873         -1, false, null, null, null, null, debugInfo);
874  }
875
876
877
878  /**
879   * Encodes a control value with the provided information.
880   *
881   * @param  countType             The matching entry count type.  It must not
882   *                               be {@code null}.
883   * @param  countValue            The matching entry count value.  It must be
884   *                               greater than or equal to zero for a count
885   *                               type of either {@code EXAMINED_COUNT} or
886   *                               {@code UNEXAMINED_COUNT}.  It must be greater
887   *                               than zero for a count type of
888   *                               {@code UPPER_BOUND}.  It must be -1 for a
889   *                               count type of {@code UNKNOWN}.
890   * @param  searchIndexed         Indicates whether the search criteria is
891   *                               considered at least partially indexed and
892   *                               could be processed more efficiently than
893   *                               examining all entries with a full database
894   *                               scan.
895   * @param  shortCircuited        Indicates whether the server short-circuited
896   *                               during candidate set processing before
897   *                               evaluating all elements of the search
898   *                               criteria (the filter and scope).  This may be
899   *                               {@code null} if it is not available (e.g.,
900   *                               because extended response data was not
901   *                               requested).
902   * @param  fullyIndexed          Indicates whether the search is considered
903   *                               fully indexed.  Note that this may be
904   *                               {@code false} even if the filter is actually
905   *                               fully indexed if server index processing
906   *                               short-circuited before evaluating all
907   *                               components of the filter.  To avoid this,
908   *                               issue the request control with both fast and
909   *                               slow short-circuit thresholds set to zero.
910   *                               This may be {@code null} if this is not
911   *                               available (e.g., because extended response
912   *                               data was not requested).
913   * @param  candidatesAreInScope  Indicates whether all the identified
914   *                               candidate entries are within the scope of
915   *                               the search.  It may be {@code null} if this
916   *                               is not available (e.g., because extended
917   *                               response data was not requested).
918   * @param  remainingFilter       The portion of the filter that was either
919   *                               identified as unindexed or that was not
920   *                               evaluated because processing short-circuited
921   *                               in the course of building the candidate set.
922   *                               It may be {@code null} if there is no
923   *                               remaining filter or if this information is
924   *                               not available (e.g., because extended
925   *                               response data was not requested).
926   * @param  debugInfo             An optional list of messages providing debug
927   *                               information about the processing performed by
928   *                               the server.  It may be {@code null} or empty
929   *                               if no debug messages should be included.
930   *
931   * @return  The encoded control value.
932   */
933  @NotNull()
934  private static ASN1OctetString encodeValue(
935               @NotNull final MatchingEntryCountType countType,
936               final int countValue, final boolean searchIndexed,
937               @Nullable final Boolean shortCircuited,
938               @Nullable final Boolean fullyIndexed,
939               @Nullable final Boolean candidatesAreInScope,
940               @Nullable final Filter remainingFilter,
941               @Nullable final Collection<String> debugInfo)
942  {
943    final ArrayList<ASN1Element> elements = new ArrayList<>(3);
944
945    switch (countType)
946    {
947      case EXAMINED_COUNT:
948      case UNEXAMINED_COUNT:
949      case UPPER_BOUND:
950        elements.add(new ASN1Integer(countType.getBERType(), countValue));
951        break;
952      case UNKNOWN:
953        elements.add(new ASN1Null(countType.getBERType()));
954        break;
955    }
956
957    if (debugInfo != null)
958    {
959      final ArrayList<ASN1Element> debugElements =
960           new ArrayList<>(debugInfo.size());
961      for (final String s : debugInfo)
962      {
963        debugElements.add(new ASN1OctetString(s));
964      }
965
966      elements.add(new ASN1Sequence(TYPE_DEBUG_INFO, debugElements));
967    }
968
969    if (! searchIndexed)
970    {
971      elements.add(new ASN1Boolean(TYPE_SEARCH_INDEXED, searchIndexed));
972    }
973
974    if (shortCircuited != null)
975    {
976      elements.add(new ASN1Boolean(TYPE_SHORT_CIRCUITED, shortCircuited));
977    }
978
979    if (fullyIndexed != null)
980    {
981      elements.add(new ASN1Boolean(TYPE_FULLY_INDEXED, fullyIndexed));
982    }
983
984    if (candidatesAreInScope != null)
985    {
986      elements.add(new ASN1Boolean(TYPE_CANDIDATES_ARE_IN_SCOPE,
987           candidatesAreInScope));
988    }
989
990    if (remainingFilter != null)
991    {
992      elements.add(new ASN1OctetString(TYPE_REMAINING_FILTER,
993           remainingFilter.encode().encode()));
994    }
995
996    return new ASN1OctetString(new ASN1Sequence(elements).encode());
997  }
998
999
1000
1001  /**
1002   * Retrieves the matching entry count type for the response control.
1003   *
1004   * @return  The matching entry count type for the response control.
1005   */
1006  @NotNull()
1007  public MatchingEntryCountType getCountType()
1008  {
1009    return countType;
1010  }
1011
1012
1013
1014  /**
1015   * Retrieves the matching entry count value for the response control.  For a
1016   * count type of {@code EXAMINED_COUNT} or {@code UNEXAMINED_COUNT}, this is
1017   * the exact number of matching entries.  For a count type of
1018   * {@code UPPER_BOUND}, this is the maximum number of entries that may match
1019   * the search criteria, but it may also include entries that do not match the
1020   * criteria.  For a count type of {@code UNKNOWN}, this will always be -1.
1021   *
1022   * @return  The exact count or upper bound of the number of entries in the
1023   *          server that may match the search criteria, or -1 if the server
1024   *          could not determine the number of matching entries.
1025   */
1026  public int getCountValue()
1027  {
1028    return countValue;
1029  }
1030
1031
1032
1033  /**
1034   * Indicates whether the server considers the search criteria to be indexed
1035   * and therefore it could be processed more efficiently than examining all
1036   * entries with a full database scan.
1037   *
1038   * @return  {@code true} if the server considers the search criteria to be
1039   *          indexed, or {@code false} if not.
1040   */
1041  public boolean searchIndexed()
1042  {
1043    return searchIndexed;
1044  }
1045
1046
1047
1048  /**
1049   * Indicates whether the server short-circuited during candidate set
1050   * processing before evaluating all elements of the search criteria (the
1051   * filter and scope).
1052   *
1053   * @return  {@code Boolean.TRUE} if the server did short-circuit during
1054   *          candidate set processing before evaluating all elements of the
1055   *          search criteria, {@code Boolean.FALSE} if the server evaluated all
1056   *          elements of the search criteria, or {@code null} if this
1057   *          information is not available (e.g., because extended response data
1058   *          was not requested).
1059   */
1060  @Nullable()
1061  public Boolean getShortCircuited()
1062  {
1063    return shortCircuited;
1064  }
1065
1066
1067
1068  /**
1069   * Indicates whether the server considers the search criteria to be fully
1070   * indexed.  Note that if the server short-circuited during candidate set
1071   * processing before evaluating all search criteria (the filter and scope),
1072   * this may be {@code Boolean.FALSE} even if the search is actually completely
1073   * indexed.
1074   *
1075   * @return  {@code Boolean.TRUE} if the server considers the search criteria
1076   *          to be fully indexed, {@code Boolean.FALSE} if the search criteria
1077   *          is not known to be fully indexed, or {@code null} if this
1078   *          information is not available (e.g., because extended response data
1079   *          was not requested).
1080   */
1081  @Nullable()
1082  public Boolean getFullyIndexed()
1083  {
1084    return fullyIndexed;
1085  }
1086
1087
1088
1089  /**
1090   * Indicates whether the server can determine that all the identified
1091   * candidates are within the scope of the search.  Note that even if the
1092   * server returns {@code Boolean.FALSE}, it does not necessarily mean that
1093   * not all the candidates are within the scope of the search, but just that
1094   * the server is not certain that is the case.
1095   *
1096   * @return  {@code Boolean.TRUE} if the server can determine that all the
1097   *          identified candidates are within the scope of the search,
1098   *          {@code Boolean.FALSE} if the server cannot determine that all the
1099   *          identified candidates are within the scope of the search, or
1100   *          {@code null} if this information is not available (e.g., because
1101   *          extended response data was not requested).
1102   */
1103  @Nullable()
1104  public Boolean getCandidatesAreInScope()
1105  {
1106    return candidatesAreInScope;
1107  }
1108
1109
1110
1111  /**
1112   * Retrieves the portion of the filter that was either identified as not
1113   * indexed or that was not evaluated during candidate processing (e.g.,
1114   * because the server short-circuited processing before examining all filter
1115   * components).
1116   *
1117   * @return  The portion of the filter that was either identified as not
1118   *          indexed or that was not evaluated during candidate processing, or
1119   *          {@code null} if there was no remaining filter or if this
1120   *          information is not available (e.g., because extended response data
1121   *          was not requested).
1122   */
1123  @Nullable()
1124  public Filter getRemainingFilter()
1125  {
1126    return remainingFilter;
1127  }
1128
1129
1130
1131  /**
1132   * Retrieves a list of messages with debug information about the processing
1133   * performed by the server in the course of obtaining the matching entry
1134   * count.  These messages are intended to be human-readable rather than
1135   * machine-parsable.
1136   *
1137   * @return  A list of messages with debug information about the processing
1138   *          performed by the server in the course of obtaining the matching
1139   *          entry count, or an empty list if no debug messages were provided.
1140   */
1141  @NotNull()
1142  public List<String> getDebugInfo()
1143  {
1144    return debugInfo;
1145  }
1146
1147
1148
1149  /**
1150   * {@inheritDoc}
1151   */
1152  @Override()
1153  @NotNull()
1154  public MatchingEntryCountResponseControl decodeControl(
1155              @NotNull final String oid,
1156              final boolean isCritical,
1157              @Nullable final ASN1OctetString value)
1158         throws LDAPException
1159  {
1160    return new MatchingEntryCountResponseControl(oid, isCritical, value);
1161  }
1162
1163
1164
1165  /**
1166   * Extracts a matching entry count response control from the provided search
1167   * result.
1168   *
1169   * @param  result  The search result from which to retrieve the matching entry
1170   *                 count response control.
1171   *
1172   * @return  The matching entry count response control contained in the
1173   *          provided result, or {@code null} if the result did not contain a
1174   *          matching entry count response control.
1175   *
1176   * @throws  LDAPException  If a problem is encountered while attempting to
1177   *                         decode the matching entry count response control
1178   *                         contained in the provided result.
1179   */
1180  @Nullable()
1181  public static MatchingEntryCountResponseControl get(
1182                     @NotNull final SearchResult result)
1183         throws LDAPException
1184  {
1185    final Control c =
1186         result.getResponseControl(MATCHING_ENTRY_COUNT_RESPONSE_OID);
1187    if (c == null)
1188    {
1189      return null;
1190    }
1191
1192    if (c instanceof MatchingEntryCountResponseControl)
1193    {
1194      return (MatchingEntryCountResponseControl) c;
1195    }
1196    else
1197    {
1198      return new MatchingEntryCountResponseControl(c.getOID(), c.isCritical(),
1199           c.getValue());
1200    }
1201  }
1202
1203
1204
1205  /**
1206   * {@inheritDoc}
1207   */
1208  @Override()
1209  @NotNull()
1210  public String getControlName()
1211  {
1212    return INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get();
1213  }
1214
1215
1216
1217  /**
1218   * Retrieves a representation of this matching entry count response control as
1219   * a JSON object.  The JSON object uses the following fields:
1220   * <UL>
1221   *   <LI>
1222   *     {@code oid} -- A mandatory string field whose value is the object
1223   *     identifier for this control.  For the matching entry count response
1224   *     control, the OID is "1.3.6.1.4.1.30221.2.5.37".
1225   *   </LI>
1226   *   <LI>
1227   *     {@code control-name} -- An optional string field whose value is a
1228   *     human-readable name for this control.  This field is only intended for
1229   *     descriptive purposes, and when decoding a control, the {@code oid}
1230   *     field should be used to identify the type of control.
1231   *   </LI>
1232   *   <LI>
1233   *     {@code criticality} -- A mandatory Boolean field used to indicate
1234   *     whether this control is considered critical.
1235   *   </LI>
1236   *   <LI>
1237   *     {@code value-base64} -- An optional string field whose value is a
1238   *     base64-encoded representation of the raw value for this matching entry
1239   *     count response control.  Exactly one of the {@code value-base64} and
1240   *     {@code value-json} fields must be present.
1241   *   </LI>
1242   *   <LI>
1243   *     {@code value-json} -- An optional JSON object field whose value is a
1244   *     user-friendly representation of the value for this matching entry count
1245   *     response control.  Exactly one of the {@code value-base64} and
1246   *     {@code value-json} fields must be present, and if the
1247   *     {@code value-json} field is used, then it will use the following
1248   *     fields:
1249   *     <UL>
1250   *       <LI>
1251   *         {@code count-type} -- A string field whose value indicates how
1252   *         accurate the count is.  The value will be one of
1253   *         "{@code examined-count}", "{@code unexamined-count}",
1254   *         "{@code upper-bound}", or "{@code unknown}".
1255   *       </LI>
1256   *       <LI>
1257   *         {@code count-value} -- An optional integer field whose value is the
1258   *         matching entry count estimate returned by the server.  This will
1259   *         be absent for a {@code count-type} value of "{@code unknown}", and
1260   *         will be present for other {@code count-type} values.
1261   *       </LI>
1262   *       <LI>
1263   *         {@code search-indexed} -- A Boolean field that indicates whether
1264   *         the server considers the search to be at least partially indexed.
1265   *       </LI>
1266   *       <LI>
1267   *         {@code fully-indexed} -- An optional Boolean field that indicates
1268   *         whether the server considers the search to be fully indexed.
1269   *       </LI>
1270   *       <LI>
1271   *         {@code short-circuited} -- An optional Boolean field that indicates
1272   *         whether the server short-circuited at any point in evaluating the
1273   *         search criteria.
1274   *       </LI>
1275   *       <LI>
1276   *         {@code candidates-are-in-scope} -- An optional Boolean field that
1277   *         indicates whether the server knows that all identified candidate
1278   *         entries are within the scope of the search.
1279   *       </LI>
1280   *       <LI>
1281   *         {@code remaining-filter} -- An optional string field whose value is
1282   *         the portion of the filter that was not evaluated during the course
1283   *         of coming up with the estimate.
1284   *       </LI>
1285   *       <LI>
1286   *         {@code debug-info} -- An optional array field whose values are
1287   *         strings with debug information about the processing performed by
1288   *         the server in the course of determining the matching entry count
1289   *         estimate.
1290   *       </LI>
1291   *     </UL>
1292   *   </LI>
1293   * </UL>
1294   *
1295   * @return  A JSON object that contains a representation of this control.
1296   */
1297  @Override()
1298  @NotNull()
1299  public JSONObject toJSONControl()
1300  {
1301    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
1302
1303    switch (countType)
1304    {
1305      case EXAMINED_COUNT:
1306        valueFields.put(JSON_FIELD_COUNT_TYPE,
1307             new JSONString(JSON_COUNT_TYPE_EXAMINED_COUNT));
1308        valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue));
1309        break;
1310      case UNEXAMINED_COUNT:
1311        valueFields.put(JSON_FIELD_COUNT_TYPE,
1312             new JSONString(JSON_COUNT_TYPE_UNEXAMINED_COUNT));
1313        valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue));
1314        break;
1315      case UPPER_BOUND:
1316        valueFields.put(JSON_FIELD_COUNT_TYPE,
1317             new JSONString(JSON_COUNT_TYPE_UPPER_BOUND));
1318        valueFields.put(JSON_FIELD_COUNT_VALUE, new JSONNumber(countValue));
1319        break;
1320      case UNKNOWN:
1321        valueFields.put(JSON_FIELD_COUNT_TYPE,
1322             new JSONString(JSON_COUNT_TYPE_UNKNOWN));
1323        break;
1324    }
1325
1326    valueFields.put(JSON_FIELD_SEARCH_INDEXED, new JSONBoolean(searchIndexed));
1327
1328    if (fullyIndexed != null)
1329    {
1330      valueFields.put(JSON_FIELD_FULLY_INDEXED,
1331           new JSONBoolean(fullyIndexed));
1332    }
1333
1334    if (shortCircuited != null)
1335    {
1336      valueFields.put(JSON_FIELD_SHORT_CIRCUITED,
1337           new JSONBoolean(shortCircuited));
1338    }
1339
1340    if (candidatesAreInScope != null)
1341    {
1342      valueFields.put(JSON_FIELD_CANDIDATES_ARE_IN_SCOPE,
1343           new JSONBoolean(candidatesAreInScope));
1344    }
1345
1346    if (remainingFilter != null)
1347    {
1348      valueFields.put(JSON_FIELD_REMAINING_FILTER,
1349           new JSONString(remainingFilter.toString()));
1350    }
1351
1352    if ((debugInfo != null) && (! debugInfo.isEmpty()))
1353    {
1354      final List<JSONString> debugInfoValues =
1355           new ArrayList<>(debugInfo.size());
1356      for (final String s : debugInfo)
1357      {
1358        debugInfoValues.add(new JSONString(s));
1359      }
1360
1361      valueFields.put(JSON_FIELD_DEBUG_INFO, new JSONArray(debugInfoValues));
1362    }
1363
1364
1365    return new JSONObject(
1366         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
1367              MATCHING_ENTRY_COUNT_RESPONSE_OID),
1368         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
1369              INFO_CONTROL_NAME_MATCHING_ENTRY_COUNT_RESPONSE.get()),
1370         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
1371              isCritical()),
1372         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
1373              new JSONObject(valueFields)));
1374  }
1375
1376
1377
1378  /**
1379   * Attempts to decode the provided object as a JSON representation of a
1380   * matching entry count response control.
1381   *
1382   * @param  controlObject  The JSON object to be decoded.  It must not be
1383   *                        {@code null}.
1384   * @param  strict         Indicates whether to use strict mode when decoding
1385   *                        the provided JSON object.  If this is {@code true},
1386   *                        then this method will throw an exception if the
1387   *                        provided JSON object contains any unrecognized
1388   *                        fields.  If this is {@code false}, then unrecognized
1389   *                        fields will be ignored.
1390   *
1391   * @return  The matching entry count response control that was decoded from
1392   *          the provided JSON object.
1393   *
1394   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
1395   *                         valid matching entry count response control.
1396   */
1397  @NotNull()
1398  public static MatchingEntryCountResponseControl decodeJSONControl(
1399              @NotNull final JSONObject controlObject,
1400              final boolean strict)
1401         throws LDAPException
1402  {
1403    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
1404         controlObject, strict, true, true);
1405
1406    final ASN1OctetString rawValue = jsonControl.getRawValue();
1407    if (rawValue != null)
1408    {
1409      return new MatchingEntryCountResponseControl(jsonControl.getOID(),
1410           jsonControl.getCriticality(), rawValue);
1411    }
1412
1413
1414    final JSONObject valueObject = jsonControl.getValueObject();
1415
1416    final MatchingEntryCountType countType;
1417    final String countTypeStr =
1418         valueObject.getFieldAsString(JSON_FIELD_COUNT_TYPE);
1419    Integer countValue = valueObject.getFieldAsInteger(JSON_FIELD_COUNT_VALUE);
1420    if (countTypeStr == null)
1421    {
1422      throw new LDAPException(ResultCode.DECODING_ERROR,
1423           ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_FIELD.get(
1424                controlObject.toSingleLineString(),
1425                JSON_FIELD_COUNT_TYPE));
1426    }
1427
1428    switch (countTypeStr)
1429    {
1430      case JSON_COUNT_TYPE_EXAMINED_COUNT:
1431        countType = MatchingEntryCountType.EXAMINED_COUNT;
1432        if (countValue == null)
1433        {
1434          throw new LDAPException(ResultCode.DECODING_ERROR,
1435               ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get(
1436                    controlObject.toSingleLineString(),
1437                    JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE,
1438                    countTypeStr));
1439        }
1440        break;
1441
1442      case JSON_COUNT_TYPE_UNEXAMINED_COUNT:
1443        countType = MatchingEntryCountType.UNEXAMINED_COUNT;
1444        if (countValue == null)
1445        {
1446          throw new LDAPException(ResultCode.DECODING_ERROR,
1447               ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get(
1448                    controlObject.toSingleLineString(),
1449                    JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE,
1450                    countTypeStr));
1451        }
1452        break;
1453
1454      case JSON_COUNT_TYPE_UPPER_BOUND:
1455        countType = MatchingEntryCountType.UPPER_BOUND;
1456        if (countValue == null)
1457        {
1458          throw new LDAPException(ResultCode.DECODING_ERROR,
1459               ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_COUNT_VALUE.get(
1460                    controlObject.toSingleLineString(),
1461                    JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE,
1462                    countTypeStr));
1463        }
1464        break;
1465
1466      case JSON_COUNT_TYPE_UNKNOWN:
1467        countType = MatchingEntryCountType.UNKNOWN;
1468        if (countValue == null)
1469        {
1470          countValue = -1;
1471        }
1472        else
1473        {
1474          throw new LDAPException(ResultCode.DECODING_ERROR,
1475               ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNEXPECTED_COUNT_VALUE.
1476                    get(controlObject.toSingleLineString(),
1477                         JSON_FIELD_COUNT_VALUE, JSON_FIELD_COUNT_TYPE,
1478                         countTypeStr));
1479        }
1480        break;
1481
1482      default:
1483        throw new LDAPException(ResultCode.DECODING_ERROR,
1484             ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNKNOWN_COUNT_TYPE.get(
1485                  controlObject.toSingleLineString(),
1486                  JSON_FIELD_COUNT_TYPE, countTypeStr));
1487    }
1488
1489
1490    final Boolean searchIndexed =
1491         valueObject.getFieldAsBoolean(JSON_FIELD_SEARCH_INDEXED);
1492    if (searchIndexed == null)
1493    {
1494      throw new LDAPException(ResultCode.DECODING_ERROR,
1495           ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_MISSING_FIELD.get(
1496                controlObject.toSingleLineString(),
1497                JSON_FIELD_SEARCH_INDEXED));
1498    }
1499
1500
1501    final Boolean fullyIndexed =
1502         valueObject.getFieldAsBoolean(JSON_FIELD_FULLY_INDEXED);
1503    final Boolean shortCircuited =
1504         valueObject.getFieldAsBoolean(JSON_FIELD_SHORT_CIRCUITED);
1505    final Boolean candidatesAreInScope =
1506         valueObject.getFieldAsBoolean(JSON_FIELD_CANDIDATES_ARE_IN_SCOPE);
1507
1508
1509    final Filter remainingFilter;
1510    final String remainingFilterStr =
1511         valueObject.getFieldAsString(JSON_FIELD_REMAINING_FILTER);
1512    if (remainingFilterStr == null)
1513    {
1514      remainingFilter = null;
1515    }
1516    else
1517    {
1518      try
1519      {
1520        remainingFilter = Filter.create(remainingFilterStr);
1521      }
1522      catch (final LDAPException e)
1523      {
1524        Debug.debugException(e);
1525        throw new LDAPException(ResultCode.DECODING_ERROR,
1526             ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_INVALID_FILTER.get(
1527                  controlObject.toSingleLineString(),
1528                  JSON_FIELD_REMAINING_FILTER, remainingFilterStr),
1529             e);
1530      }
1531    }
1532
1533
1534    final List<String> debugInfo;
1535    final List<JSONValue> debugInfoValues =
1536         valueObject.getFieldAsArray(JSON_FIELD_DEBUG_INFO);
1537    if (debugInfoValues == null)
1538    {
1539      debugInfo = null;
1540    }
1541    else
1542    {
1543      debugInfo = new ArrayList<>(debugInfoValues.size());
1544      for (final JSONValue v : debugInfoValues)
1545      {
1546        if (v instanceof JSONString)
1547        {
1548          debugInfo.add(((JSONString) v).stringValue());
1549        }
1550        else
1551        {
1552          throw new LDAPException(ResultCode.DECODING_ERROR,
1553               ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_DEBUG_INFO_NOT_STRING.get(
1554                    controlObject.toSingleLineString(), JSON_FIELD_DEBUG_INFO));
1555        }
1556      }
1557    }
1558
1559
1560    if (strict)
1561    {
1562      final List<String> unrecognizedFields =
1563           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1564                valueObject, JSON_FIELD_COUNT_TYPE, JSON_FIELD_COUNT_VALUE,
1565                JSON_FIELD_SEARCH_INDEXED, JSON_FIELD_FULLY_INDEXED,
1566                JSON_FIELD_SHORT_CIRCUITED, JSON_FIELD_CANDIDATES_ARE_IN_SCOPE,
1567                JSON_FIELD_REMAINING_FILTER, JSON_FIELD_DEBUG_INFO);
1568      if (! unrecognizedFields.isEmpty())
1569      {
1570        throw new LDAPException(ResultCode.DECODING_ERROR,
1571             ERR_MATCHING_ENTRY_COUNT_RESPONSE_JSON_UNRECOGNIZED_FIELD.get(
1572                  controlObject.toSingleLineString(),
1573                  unrecognizedFields.get(0)));
1574      }
1575    }
1576
1577
1578    return new MatchingEntryCountResponseControl(countType, countValue,
1579         searchIndexed, shortCircuited, fullyIndexed, candidatesAreInScope,
1580         remainingFilter, debugInfo);
1581  }
1582
1583
1584
1585  /**
1586   * {@inheritDoc}
1587   */
1588  @Override()
1589  public void toString(@NotNull final StringBuilder buffer)
1590  {
1591    buffer.append("MatchingEntryCountResponseControl(countType='");
1592    buffer.append(countType.name());
1593    buffer.append('\'');
1594
1595    switch (countType)
1596    {
1597      case EXAMINED_COUNT:
1598      case UNEXAMINED_COUNT:
1599        buffer.append(", count=");
1600        buffer.append(countValue);
1601        break;
1602
1603      case UPPER_BOUND:
1604        buffer.append(", upperBound=");
1605        buffer.append(countValue);
1606        break;
1607    }
1608
1609    buffer.append(", searchIndexed=");
1610    buffer.append(searchIndexed);
1611
1612    if (shortCircuited != null)
1613    {
1614      buffer.append(", shortCircuited=");
1615      buffer.append(shortCircuited);
1616    }
1617
1618    if (fullyIndexed != null)
1619    {
1620      buffer.append(", fullyIndexed=");
1621      buffer.append(fullyIndexed);
1622    }
1623
1624    if (candidatesAreInScope != null)
1625    {
1626      buffer.append(", candidatesAreInScope=");
1627      buffer.append(candidatesAreInScope);
1628    }
1629
1630    if (remainingFilter != null)
1631    {
1632      buffer.append(", remainingFilter='");
1633      remainingFilter.toString(buffer);
1634      buffer.append('\'');
1635    }
1636
1637    if (! debugInfo.isEmpty())
1638    {
1639      buffer.append(", debugInfo={");
1640
1641      final Iterator<String> iterator = debugInfo.iterator();
1642      while (iterator.hasNext())
1643      {
1644        buffer.append('\'');
1645        buffer.append(iterator.next());
1646        buffer.append('\'');
1647
1648        if (iterator.hasNext())
1649        {
1650          buffer.append(", ");
1651        }
1652      }
1653
1654      buffer.append('}');
1655    }
1656
1657    buffer.append(')');
1658  }
1659}