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 }