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