001    /*
002     * Copyright 2010-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.extensions;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.List;
028    
029    import com.unboundid.asn1.ASN1Boolean;
030    import com.unboundid.asn1.ASN1Element;
031    import com.unboundid.asn1.ASN1Integer;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.sdk.Control;
035    import com.unboundid.ldap.sdk.ExtendedResult;
036    import com.unboundid.ldap.sdk.LDAPException;
037    import com.unboundid.ldap.sdk.LDAPResult;
038    import com.unboundid.ldap.sdk.ResultCode;
039    import com.unboundid.util.Base64;
040    import com.unboundid.util.Debug;
041    import com.unboundid.util.NotMutable;
042    import com.unboundid.util.StaticUtils;
043    import com.unboundid.util.ThreadSafety;
044    import com.unboundid.util.ThreadSafetyLevel;
045    
046    import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
047    
048    
049    
050    /**
051     * <BLOCKQUOTE>
052     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
053     *   LDAP SDK for Java.  It is not available for use in applications that
054     *   include only the Standard Edition of the LDAP SDK, and is not supported for
055     *   use in conjunction with non-UnboundID products.
056     * </BLOCKQUOTE>
057     * This class provides an extended result that may be used to obtain information
058     * about the results of processing a get changelog batch extended request.  The
059     * The changelog batch result value is encoded as follows:
060     * <PRE>
061     *   ChangelogBatchResult ::= SEQUENCE {
062     *        resumeToken                   [0] OCTET STRING OPTIONAL,
063     *        moreChangesAvailable          [1] BOOLEAN,
064     *        changesAlreadyPurged          [2] BOOLEAN DEFAULT FALSE,
065     *        additionalInfo                [3] OCTET STRING OPTIONAL,
066     *        estimatedChangesRemaining     [4] INTEGER (0 .. MAXINT) OPTIONAL,
067     *        ... }
068     * </PRE>
069     * <BR><BR>
070     * See the documentation for the {@link GetChangelogBatchExtendedRequest} class
071     * for an example demonstrating its use.
072     */
073    @NotMutable()
074    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
075    public final class GetChangelogBatchExtendedResult
076           extends ExtendedResult
077    {
078      /**
079       * The BER type for the resume token element.
080       */
081      private static final byte TYPE_RESUME_TOKEN = (byte) 0x80;
082    
083    
084    
085      /**
086       * The BER type for the more changes available element.
087       */
088      private static final byte TYPE_MORE_CHANGES_AVAILABLE = (byte) 0x81;
089    
090    
091    
092      /**
093       * The BER type for the changes already purged element.
094       */
095      private static final byte TYPE_CHANGES_ALREADY_PURGED = (byte) 0x82;
096    
097    
098    
099      /**
100       * The BER type for the additional info element.
101       */
102      private static final byte TYPE_ADDITIONAL_INFO = (byte) 0x83;
103    
104    
105    
106      /**
107       * The BER type for the estimated changes remaining element.
108       */
109      private static final byte TYPE_ESTIMATED_CHANGES_REMAINING = (byte) 0x84;
110    
111    
112    
113      /**
114       * The serial version UID for this serializable object.
115       */
116      private static final long serialVersionUID = -1997815252100989148L;
117    
118    
119    
120      // The resume token for this extended result.
121      private final ASN1OctetString resumeToken;
122    
123      // Indicates whether some changes in the requested batch may have already
124      // been purged.
125      private final boolean changesAlreadyPurged;
126    
127      // Indicates whether the server has additional results that are immediately
128      // available without waiting.
129      private final boolean moreChangesAvailable;
130    
131      // The estimated number of remaining changes, if available.
132      private final int estimatedChangesRemaining;
133    
134      // The number of entries returned to the client.
135      private final int entryCount;
136    
137      // A list of the entries returned to the client.
138      private final List<ChangelogEntryIntermediateResponse> entryList;
139    
140      // A message with additional information about the result.
141      private final String additionalInfo;
142    
143    
144    
145      /**
146       * Creates a new get changelog batch extended result with only the generic
147       * LDAP result information and no extended value.
148       *
149       * @param  r  An LDAP result with general details of the response.  It must
150       *            not be {@code null}.
151       */
152      public GetChangelogBatchExtendedResult(final LDAPResult r)
153      {
154        super(r.getMessageID(), r.getResultCode(), r.getDiagnosticMessage(),
155             r.getMatchedDN(), r.getReferralURLs(), null, null,
156             r.getResponseControls());
157    
158        resumeToken               = null;
159        changesAlreadyPurged      = false;
160        moreChangesAvailable      = false;
161        estimatedChangesRemaining = -1;
162        entryCount                = -1;
163        entryList                 = null;
164        additionalInfo            = null;
165      }
166    
167    
168    
169      /**
170       * Creates a new get changelog batch extended result with the provided
171       * information.
172       *
173       * @param  r                     An LDAP result with general details of the
174       *                               response.  It must not be {@code null}.
175       * @param  entryCount            The number of entries returned.  It may be
176       *                               less than zero to indicate that the number of
177       *                               entries is unknown.
178       * @param  resumeToken           A token which may be used to resume
179       *                               retrieving changes at the point immediately
180       *                               after the last change returned.  It may be
181       *                               {@code null} only if this result represents
182       *                               an error that prevented the operation from
183       *                               being successfully processed.
184       * @param  moreChangesAvailable  Indicates whether there may be more changes
185       *                               immediately available to retrieve from the
186       *                               server.
187       * @param  changesAlreadyPurged  Indicates whether the server may have already
188       *                               purged changes after the starting point
189       *                               referenced by the associated request.
190       * @param  additionalInfo        A message with additional information about
191       *                               the status of the processing.  It may be
192       *                               {@code null} if no additional message is
193       *                               available.
194       */
195      public GetChangelogBatchExtendedResult(final LDAPResult r,
196                  final int entryCount, final ASN1OctetString resumeToken,
197                  final boolean moreChangesAvailable,
198                  final boolean changesAlreadyPurged, final String additionalInfo)
199      {
200        this(r, entryCount, resumeToken, moreChangesAvailable, -1,
201             changesAlreadyPurged, additionalInfo);
202      }
203    
204    
205    
206      /**
207       * Creates a new get changelog batch extended result with the provided
208       * information.
209       *
210       * @param  r                          An LDAP result with general details of
211       *                                    the response.  It must not be
212       *                                    {@code null}.
213       * @param  entryCount                 The number of entries returned.  It may
214       *                                    be less than zero to indicate that the
215       *                                    number of entries is unknown.
216       * @param  resumeToken                A token which may be used to resume
217       *                                    retrieving changes at the point
218       *                                    immediately after the last change
219       *                                    returned.  It may be {@code null} only
220       *                                    if this result represents an error that
221       *                                    prevented the operation from being
222       *                                    successfully processed.
223       * @param  moreChangesAvailable       Indicates whether there may be more
224       *                                    changes immediately available to
225       *                                    retrieve from the server.
226       * @param  estimatedChangesRemaining  An estimate of the number of changes
227       *                                    remaining to be retrieved.  A value less
228       *                                    than zero will be interpreted as
229       *                                    "unknown".
230       * @param  changesAlreadyPurged       Indicates whether the server may have
231       *                                    already purged changes after the
232       *                                    starting point referenced by the
233       *                                    associated request.
234       * @param  additionalInfo             A message with additional information
235       *                                    about the status of the processing.  It
236       *                                    may be {@code null} if no additional
237       *                                    message is available.
238       */
239      public GetChangelogBatchExtendedResult(final LDAPResult r,
240                  final int entryCount, final ASN1OctetString resumeToken,
241                  final boolean moreChangesAvailable,
242                  final int estimatedChangesRemaining,
243                  final boolean changesAlreadyPurged, final String additionalInfo)
244      {
245        super(r.getMessageID(), r.getResultCode(), r.getDiagnosticMessage(),
246             r.getMatchedDN(), r.getReferralURLs(), null,
247             encodeValue(resumeToken, moreChangesAvailable,
248                  estimatedChangesRemaining, changesAlreadyPurged, additionalInfo),
249             r.getResponseControls());
250    
251        this.resumeToken          = resumeToken;
252        this.moreChangesAvailable = moreChangesAvailable;
253        this.changesAlreadyPurged = changesAlreadyPurged;
254        this.additionalInfo       = additionalInfo;
255    
256        if (estimatedChangesRemaining >= 0)
257        {
258          this.estimatedChangesRemaining = estimatedChangesRemaining;
259        }
260        else
261        {
262          this.estimatedChangesRemaining = -1;
263        }
264    
265        entryList = null;
266        if (entryCount < 0)
267        {
268          this.entryCount = -1;
269        }
270        else
271        {
272          this.entryCount = entryCount;
273        }
274      }
275    
276    
277    
278      /**
279       * Creates a new get changelog batch extended result with the provided
280       * information.
281       *
282       * @param  extendedResult  A generic extended result to be parsed as a get
283       *                         changelog batch extended result.  It must not be
284       *                         {@code null}.
285       * @param  entryCount      The number of entries returned to the client.  It
286       *                         may be less than zero to indicate that the entry
287       *                         count is unknown.
288       *
289       * @throws  LDAPException  If the provided extended result cannot be parsed as
290       *                         a get changelog batch result.
291       */
292      public GetChangelogBatchExtendedResult(final ExtendedResult extendedResult,
293                                             final int entryCount)
294             throws LDAPException
295      {
296        this(extendedResult, entryCount, null);
297      }
298    
299    
300    
301      /**
302       * Creates a new get changelog batch extended result with the provided
303       * information.
304       *
305       * @param  extendedResult  A generic extended result to be parsed as a get
306       *                         changelog batch extended result.  It must not be
307       *                         {@code null}.
308       * @param  entryList       A list of the entries returned to the client.  It
309       *                         may be empty to indicate that no entries were
310       *                         returned, but it must not be {@code null}.
311       *
312       * @throws  LDAPException  If the provided extended result cannot be parsed as
313       *                         a get changelog batch result.
314       */
315      public GetChangelogBatchExtendedResult(final ExtendedResult extendedResult,
316                  final List<ChangelogEntryIntermediateResponse> entryList)
317             throws LDAPException
318      {
319        this(extendedResult, entryList.size(), entryList);
320      }
321    
322    
323    
324      /**
325       * Creates a new get changelog batch extended result with the provided
326       * information.
327       *
328       * @param  r           A generic extended result to be parsed as a get
329       *                     changelog batch extended result.  It must not be
330       *                     {@code null}.
331       * @param  entryCount  The number of entries returned to the client.  It may
332       *                     be less than zero to indicate that the entry count is
333       *                     unknown.
334       * @param  entryList   A list of the entries returned to the client.  It may
335       *                     be empty to indicate that no entries were returned, or
336       *                     {@code null} if the entry list is not available.
337       *
338       * @throws  LDAPException  If the provided extended result cannot be parsed as
339       *                         a get changelog batch result.
340       */
341      private GetChangelogBatchExtendedResult(final ExtendedResult r,
342                  final int entryCount,
343                  final List<ChangelogEntryIntermediateResponse> entryList)
344              throws LDAPException
345      {
346        super(r);
347    
348        if (entryList == null)
349        {
350          this.entryList = null;
351        }
352        else
353        {
354          this.entryList = Collections.unmodifiableList(entryList);
355        }
356    
357        if (entryCount < 0)
358        {
359          this.entryCount = -1;
360        }
361        else
362        {
363          this.entryCount = entryCount;
364        }
365    
366        final ASN1OctetString value = r.getValue();
367        if (value == null)
368        {
369          // See if an entry list was provided and we can get a resume token from
370          // it.
371          if ((entryList != null) && (! entryList.isEmpty()))
372          {
373            resumeToken = entryList.get(entryList.size() - 1).getResumeToken();
374          }
375          else
376          {
377            resumeToken = null;
378          }
379    
380          moreChangesAvailable      = false;
381          estimatedChangesRemaining = -1;
382          changesAlreadyPurged      = false;
383          additionalInfo            = null;
384          return;
385        }
386    
387        final ASN1Element[] valueElements;
388        try
389        {
390          valueElements =
391               ASN1Sequence.decodeAsSequence(value.getValue()).elements();
392        }
393        catch (final Exception e)
394        {
395          Debug.debugException(e);
396          throw new LDAPException(ResultCode.DECODING_ERROR,
397               ERR_GET_CHANGELOG_BATCH_RES_VALUE_NOT_SEQUENCE.get(
398                    StaticUtils.getExceptionMessage(e)), e);
399        }
400    
401        ASN1OctetString token = null;
402        Boolean moreChanges = null;
403        boolean missingChanges = false;
404        int changesRemaining = -1;
405        String message = null;
406    
407        try
408        {
409          for (final ASN1Element e : valueElements)
410          {
411            final byte type = e.getType();
412            switch (type)
413            {
414              case TYPE_RESUME_TOKEN:
415                token = ASN1OctetString.decodeAsOctetString(e);
416                break;
417              case TYPE_MORE_CHANGES_AVAILABLE:
418                moreChanges = ASN1Boolean.decodeAsBoolean(e).booleanValue();
419                break;
420              case TYPE_CHANGES_ALREADY_PURGED:
421                missingChanges = ASN1Boolean.decodeAsBoolean(e).booleanValue();
422                break;
423              case TYPE_ADDITIONAL_INFO:
424                message = ASN1OctetString.decodeAsOctetString(e).stringValue();
425                break;
426              case TYPE_ESTIMATED_CHANGES_REMAINING:
427                changesRemaining = ASN1Integer.decodeAsInteger(e).intValue();
428                if (changesRemaining < 0)
429                {
430                  changesRemaining = -1;
431                }
432                break;
433              default:
434                throw new LDAPException(ResultCode.DECODING_ERROR,
435                     ERR_GET_CHANGELOG_BATCH_RES_UNEXPECTED_VALUE_ELEMENT.get(
436                          StaticUtils.toHex(type)));
437            }
438          }
439        }
440        catch (final LDAPException le)
441        {
442          Debug.debugException(le);
443          throw le;
444        }
445        catch (final Exception e)
446        {
447          Debug.debugException(e);
448          throw new LDAPException(ResultCode.DECODING_ERROR,
449               ERR_GET_CHANGELOG_BATCH_RES_ERROR_PARSING_VALUE.get(
450                    StaticUtils.getExceptionMessage(e)), e);
451        }
452    
453        if (moreChanges == null)
454        {
455          throw new LDAPException(ResultCode.DECODING_ERROR,
456               ERR_GET_CHANGELOG_BATCH_RES_MISSING_MORE.get());
457        }
458    
459        resumeToken               = token;
460        moreChangesAvailable      = moreChanges;
461        changesAlreadyPurged      = missingChanges;
462        estimatedChangesRemaining = changesRemaining;
463        additionalInfo            = message;
464      }
465    
466    
467    
468      /**
469       * Encodes the provided information in a form suitable for use as the value of
470       * this extended result.
471       *
472       * @param  resumeToken                A token which may be used to resume
473       *                                    retrieving changes at the point
474       *                                    immediately after the last change
475       *                                    returned.  It may be {@code null} only
476       *                                    if this result represents an error that
477       *                                    prevented the operation from being
478       *                                    successfully processed.
479       * @param  moreChangesAvailable       Indicates whether there may be more
480       *                                    changes immediately available to
481       *                                    retrieve from the server.
482       * @param  estimatedChangesRemaining  An estimate of the number of changes
483       *                                    remaining to be retrieved.  A value less
484       *                                    than zero will be interpreted as
485       *                                    "unknown".
486       * @param  changesAlreadyPurged       Indicates whether the server may have
487       *                                    already purged changes after the
488       *                                    starting point referenced by the
489       *                                    associated request.
490       * @param  additionalInfo             A message with additional information
491       *                                    about the status of the processing.  It
492       *                                    may be {@code null} if no additional
493       *                                    message is available.
494       *
495       * @return  The ASN.1 octet string to use as the result, or {@code null} if
496       *          there should be no value.
497       */
498      private static ASN1OctetString encodeValue(final ASN1OctetString resumeToken,
499                                          final boolean moreChangesAvailable,
500                                          final int estimatedChangesRemaining,
501                                          final boolean changesAlreadyPurged,
502                                          final String additionalInfo)
503      {
504        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(5);
505    
506        if (resumeToken != null)
507        {
508          elements.add(new ASN1OctetString(TYPE_RESUME_TOKEN,
509               resumeToken.getValue()));
510        }
511    
512        elements.add(new ASN1Boolean(TYPE_MORE_CHANGES_AVAILABLE,
513             moreChangesAvailable));
514    
515        if (estimatedChangesRemaining >= 0)
516        {
517          elements.add(new ASN1Integer(TYPE_ESTIMATED_CHANGES_REMAINING,
518               estimatedChangesRemaining));
519        }
520    
521        if (changesAlreadyPurged)
522        {
523          elements.add(new ASN1Boolean(TYPE_CHANGES_ALREADY_PURGED,
524               changesAlreadyPurged));
525        }
526    
527        if (additionalInfo != null)
528        {
529          elements.add(new ASN1OctetString(TYPE_ADDITIONAL_INFO, additionalInfo));
530        }
531    
532        return new ASN1OctetString(new ASN1Sequence(elements).encode());
533      }
534    
535    
536    
537      /**
538       * Retrieves a token that may be used to resume the process of retrieving
539       * changes at the point after the last change received.  It may be
540       * {@code null} if this result represents an error that prevented the
541       * operation from being processed successfully.
542       *
543       * @return  A token that may be used to resume the process of retrieving
544       *          changes at the point after the last change received, or
545       *          {@code null} if none is available.
546       */
547      public ASN1OctetString getResumeToken()
548      {
549        return resumeToken;
550      }
551    
552    
553    
554      /**
555       * Indicates whether the server indicated that more changes may be immediately
556       * available without waiting.  The value of this argument is only meaningful
557       * if {@link #hasValue()} returns {@code true}.
558       *
559       * @return  {@code true} if the server indicated that more changes may be
560       *          immediately available without waiting, or {@code false} if not.
561       */
562      public boolean moreChangesAvailable()
563      {
564        return moreChangesAvailable;
565      }
566    
567    
568    
569      /**
570       * Retrieves an estimate of the number of changes that may be immediately
571       * available to be retrieved from the server, if available.
572       *
573       * @return  An estimate of the number of changes that may be immediately
574       *          available to be retrieved from the server, or -1 if that
575       *          information is not available.
576       */
577      public int getEstimatedChangesRemaining()
578      {
579        return estimatedChangesRemaining;
580      }
581    
582    
583    
584      /**
585       * Indicates whether the server indicated that it may have already purged one
586       * or more changes after the starting point for the associated request and
587       * therefore the results returned may be missing changes.  The value of this
588       * argument is only meaningful if {@link #hasValue()} returns {@code true}.
589       *
590       * @return  {@code true} if the server indicated that it may have already
591       *          purged one or more changes after the starting point, or
592       *          {@code false} if not.
593       */
594      public boolean changesAlreadyPurged()
595      {
596        return changesAlreadyPurged;
597      }
598    
599    
600    
601      /**
602       * Retrieves a message with additional information about the processing that
603       * occurred, if available.
604       *
605       * @return  A message with additional information about the processing that
606       *          occurred, or {@code null} if none is available.
607       */
608      public String getAdditionalInfo()
609      {
610        return additionalInfo;
611      }
612    
613    
614    
615      /**
616       * Retrieves the number of entries returned by the server in the course of
617       * processing the extended operation.  A value of -1 indicates that the entry
618       * count is not known.
619       *
620       * @return  The number of entries returned by the server in the course of
621       *          processing the extended operation, 0 if no entries were returned,
622       *          or -1 if the entry count is not known.
623       */
624      public int getEntryCount()
625      {
626        return entryCount;
627      }
628    
629    
630    
631      /**
632       * Retrieves a list containing the entries that were returned by the server in
633       * the course of processing the extended operation, if available.  An entry
634       * list will not be available if a custom {@link ChangelogEntryListener} was
635       * used for the request, and it may not be available if an error was
636       * encountered during processing.
637       *
638       * @return  A list containing the entries that were returned by the server in
639       *          the course of processing the extended operation, or {@code null}
640       *          if an entry list is not available.
641       */
642      public List<ChangelogEntryIntermediateResponse> getChangelogEntries()
643      {
644        return entryList;
645      }
646    
647    
648    
649      /**
650       * {@inheritDoc}
651       */
652      @Override()
653      public String getExtendedResultName()
654      {
655        return INFO_GET_CHANGELOG_BATCH_RES_NAME.get();
656      }
657    
658    
659    
660      /**
661       * {@inheritDoc}
662       */
663      @Override()
664      public void toString(final StringBuilder buffer)
665      {
666        buffer.append("ExtendedResult(resultCode=");
667        buffer.append(getResultCode());
668    
669        final int messageID = getMessageID();
670        if (messageID >= 0)
671        {
672          buffer.append(", messageID=");
673          buffer.append(messageID);
674        }
675    
676        final String diagnosticMessage = getDiagnosticMessage();
677        if (diagnosticMessage != null)
678        {
679          buffer.append(", diagnosticMessage='");
680          buffer.append(diagnosticMessage);
681          buffer.append('\'');
682        }
683    
684        final String matchedDN = getMatchedDN();
685        if (matchedDN != null)
686        {
687          buffer.append(", matchedDN='");
688          buffer.append(matchedDN);
689          buffer.append('\'');
690        }
691    
692        final String[] referralURLs = getReferralURLs();
693        if (referralURLs.length > 0)
694        {
695          buffer.append(", referralURLs={");
696          for (int i=0; i < referralURLs.length; i++)
697          {
698            if (i > 0)
699            {
700              buffer.append(", ");
701            }
702    
703            buffer.append(referralURLs[i]);
704          }
705          buffer.append('}');
706        }
707    
708        if (resumeToken != null)
709        {
710          buffer.append(", resumeToken='");
711          Base64.encode(resumeToken.getValue(), buffer);
712          buffer.append('\'');
713        }
714    
715        buffer.append(", moreChangesAvailable=");
716        buffer.append(moreChangesAvailable);
717    
718        buffer.append(", estimatedChangesRemaining=");
719        buffer.append(estimatedChangesRemaining);
720    
721        buffer.append(", changesAlreadyPurged=");
722        buffer.append(changesAlreadyPurged);
723    
724        if (additionalInfo != null)
725        {
726          buffer.append(", additionalInfo='");
727          buffer.append(additionalInfo);
728          buffer.append('\'');
729        }
730    
731        buffer.append(", entryCount=");
732        buffer.append(entryCount);
733    
734    
735        final Control[] responseControls = getResponseControls();
736        if (responseControls.length > 0)
737        {
738          buffer.append(", responseControls={");
739          for (int i=0; i < responseControls.length; i++)
740          {
741            if (i > 0)
742            {
743              buffer.append(", ");
744            }
745    
746            buffer.append(responseControls[i]);
747          }
748          buffer.append('}');
749        }
750    
751        buffer.append(')');
752      }
753    }