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