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