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