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 }