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}