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