001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-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) 2007-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; 037 038 039 040import java.io.Serializable; 041import java.util.ArrayList; 042import java.util.List; 043 044import com.unboundid.asn1.ASN1Exception; 045import com.unboundid.asn1.ASN1StreamReader; 046import com.unboundid.asn1.ASN1StreamReaderSequence; 047import com.unboundid.ldap.protocol.LDAPMessage; 048import com.unboundid.ldap.protocol.LDAPResponse; 049import com.unboundid.util.Debug; 050import com.unboundid.util.Extensible; 051import com.unboundid.util.NotMutable; 052import com.unboundid.util.NotNull; 053import com.unboundid.util.Nullable; 054import com.unboundid.util.StaticUtils; 055import com.unboundid.util.ThreadSafety; 056import com.unboundid.util.ThreadSafetyLevel; 057 058import static com.unboundid.ldap.sdk.LDAPMessages.*; 059 060 061 062/** 063 * This class provides a data structure for holding the elements that are common 064 * to most types of LDAP responses. The elements contained in an LDAP result 065 * include: 066 * <UL> 067 * <LI>Result Code -- An integer value that provides information about the 068 * status of the operation. See the {@link ResultCode} class for 069 * information about a number of result codes defined in LDAP.</LI> 070 * <LI>Diagnostic Message -- An optional string that may provide additional 071 * information about the operation. For example, if the operation failed, 072 * it may include information about the reason for the failure. It will 073 * often (but not always) be absent in the result for successful 074 * operations, and it may be absent in the result for failed 075 * operations.</LI> 076 * <LI>Matched DN -- An optional DN which specifies the entry that most 077 * closely matched the DN of a non-existent entry in the server. For 078 * example, if an operation failed because the target entry did not exist, 079 * then the matched DN field may specify the DN of the closest ancestor 080 * to that entry that does exist in the server.</LI> 081 * <LI>Referral URLs -- An optional set of LDAP URLs which refer to other 082 * directories and/or locations within the DIT in which the operation may 083 * be attempted. If multiple referral URLs are provided, then they should 084 * all be considered equivalent for the purpose of attempting the 085 * operation (e.g., the different URLs may simply refer to different 086 * servers in which the operation could be processed).</LI> 087 * <LI>Response Controls -- An optional set of controls included in the 088 * response from the server. If any controls are included, then they may 089 * provide additional information about the processing that was performed 090 * by the server.</LI> 091 * </UL> 092 * <BR><BR> 093 * Note that even though this class is marked with the @Extensible annotation 094 * type, it should not be directly subclassed by third-party code. Only the 095 * {@link BindResult} and {@link ExtendedResult} subclasses are actually 096 * intended to be extended by third-party code. 097 */ 098@Extensible() 099@NotMutable() 100@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 101public class LDAPResult 102 implements Serializable, LDAPResponse 103{ 104 /** 105 * The BER type for the set of referral URLs. 106 */ 107 static final byte TYPE_REFERRAL_URLS = (byte) 0xA3; 108 109 110 111 /** 112 * The serial version UID for this serializable class. 113 */ 114 private static final long serialVersionUID = 2215819095653175991L; 115 116 117 118 // The protocol op type for this result, if available. 119 @Nullable private final Byte protocolOpType; 120 121 // The set of controls from the response. 122 @NotNull private final Control[] responseControls; 123 124 // The message ID for the LDAP message that is associated with this LDAP 125 // result. 126 private final int messageID; 127 128 // The result code from the response. 129 @NotNull private final ResultCode resultCode; 130 131 // The diagnostic message from the response, if available. 132 @Nullable private final String diagnosticMessage; 133 134 // The matched DN from the response, if available. 135 @Nullable private final String matchedDN; 136 137 // The set of referral URLs from the response, if available. 138 @NotNull private final String[] referralURLs; 139 140 141 142 /** 143 * Creates a new LDAP result object based on the provided result. 144 * 145 * @param result The LDAP result object to use to initialize this result. 146 */ 147 protected LDAPResult(@NotNull final LDAPResult result) 148 { 149 protocolOpType = result.protocolOpType; 150 messageID = result.messageID; 151 resultCode = result.resultCode; 152 diagnosticMessage = result.diagnosticMessage; 153 matchedDN = result.matchedDN; 154 referralURLs = result.referralURLs; 155 responseControls = result.responseControls; 156 } 157 158 159 160 /** 161 * Creates a new LDAP result object with the provided message ID and result 162 * code, and no other information. 163 * 164 * @param messageID The message ID for the LDAP message that is associated 165 * with this LDAP result. 166 * @param resultCode The result code from the response. 167 */ 168 public LDAPResult(final int messageID, @NotNull final ResultCode resultCode) 169 { 170 this(null, messageID, resultCode, null, null, StaticUtils.NO_STRINGS, 171 NO_CONTROLS); 172 } 173 174 175 176 /** 177 * Creates a new LDAP result object with the provided information. 178 * 179 * @param messageID The message ID for the LDAP message that is 180 * associated with this LDAP result. 181 * @param resultCode The result code from the response. 182 * @param diagnosticMessage The diagnostic message from the response, if 183 * available. 184 * @param matchedDN The matched DN from the response, if available. 185 * @param referralURLs The set of referral URLs from the response, if 186 * available. 187 * @param responseControls The set of controls from the response, if 188 * available. 189 */ 190 public LDAPResult(final int messageID, @NotNull final ResultCode resultCode, 191 @Nullable final String diagnosticMessage, 192 @Nullable final String matchedDN, 193 @Nullable final String[] referralURLs, 194 @Nullable final Control[] responseControls) 195 { 196 this(null, messageID, resultCode, diagnosticMessage, matchedDN, 197 referralURLs, responseControls); 198 } 199 200 201 202 /** 203 * Creates a new LDAP result object with the provided information. 204 * 205 * @param messageID The message ID for the LDAP message that is 206 * associated with this LDAP result. 207 * @param resultCode The result code from the response. 208 * @param diagnosticMessage The diagnostic message from the response, if 209 * available. 210 * @param matchedDN The matched DN from the response, if available. 211 * @param referralURLs The set of referral URLs from the response, if 212 * available. 213 * @param responseControls The set of controls from the response, if 214 * available. 215 */ 216 public LDAPResult(final int messageID, @NotNull final ResultCode resultCode, 217 @Nullable final String diagnosticMessage, 218 @Nullable final String matchedDN, 219 @Nullable final List<String> referralURLs, 220 @Nullable final List<Control> responseControls) 221 { 222 this(null, messageID, resultCode, diagnosticMessage, matchedDN, 223 referralURLs, responseControls); 224 } 225 226 227 228 /** 229 * Creates a new LDAP result object with the provided information. 230 * 231 * @param protocolOpType The protocol op type for this result, if 232 * available. 233 * @param messageID The message ID for the LDAP message that is 234 * associated with this LDAP result. 235 * @param resultCode The result code from the response. 236 * @param diagnosticMessage The diagnostic message from the response, if 237 * available. 238 * @param matchedDN The matched DN from the response, if available. 239 * @param referralURLs The set of referral URLs from the response, if 240 * available. 241 * @param responseControls The set of controls from the response, if 242 * available. 243 */ 244 private LDAPResult(@Nullable final Byte protocolOpType, final int messageID, 245 @NotNull final ResultCode resultCode, 246 @Nullable final String diagnosticMessage, 247 @Nullable final String matchedDN, 248 @Nullable final String[] referralURLs, 249 @Nullable final Control[] responseControls) 250 { 251 this.protocolOpType = protocolOpType; 252 this.messageID = messageID; 253 this.resultCode = resultCode; 254 this.diagnosticMessage = diagnosticMessage; 255 this.matchedDN = matchedDN; 256 257 if (referralURLs == null) 258 { 259 this.referralURLs = StaticUtils.NO_STRINGS; 260 } 261 else 262 { 263 this.referralURLs = referralURLs; 264 } 265 266 if (responseControls == null) 267 { 268 this.responseControls = NO_CONTROLS; 269 } 270 else 271 { 272 this.responseControls = responseControls; 273 } 274 } 275 276 277 278 /** 279 * Creates a new LDAP result object with the provided information. 280 * 281 * @param protocolOpType The protocol op type for this result, if 282 * available. 283 * @param messageID The message ID for the LDAP message that is 284 * associated with this LDAP result. 285 * @param resultCode The result code from the response. 286 * @param diagnosticMessage The diagnostic message from the response, if 287 * available. 288 * @param matchedDN The matched DN from the response, if available. 289 * @param referralURLs The set of referral URLs from the response, if 290 * available. 291 * @param responseControls The set of controls from the response, if 292 * available. 293 */ 294 private LDAPResult(@Nullable final Byte protocolOpType, final int messageID, 295 @NotNull final ResultCode resultCode, 296 @Nullable final String diagnosticMessage, 297 @Nullable final String matchedDN, 298 @Nullable final List<String> referralURLs, 299 @Nullable final List<Control> responseControls) 300 { 301 this.protocolOpType = protocolOpType; 302 this.messageID = messageID; 303 this.resultCode = resultCode; 304 this.diagnosticMessage = diagnosticMessage; 305 this.matchedDN = matchedDN; 306 307 if ((referralURLs == null) || referralURLs.isEmpty()) 308 { 309 this.referralURLs = StaticUtils.NO_STRINGS; 310 } 311 else 312 { 313 this.referralURLs = new String[referralURLs.size()]; 314 referralURLs.toArray(this.referralURLs); 315 } 316 317 if ((responseControls == null) || responseControls.isEmpty()) 318 { 319 this.responseControls = NO_CONTROLS; 320 } 321 else 322 { 323 this.responseControls = new Control[responseControls.size()]; 324 responseControls.toArray(this.responseControls); 325 } 326 } 327 328 329 330 /** 331 * Creates a new LDAP result object with the provided message ID and with the 332 * protocol op and controls read from the given ASN.1 stream reader. 333 * 334 * @param messageID The LDAP message ID for the LDAP message that is 335 * associated with this LDAP result. 336 * @param messageSequence The ASN.1 stream reader sequence used in the 337 * course of reading the LDAP message elements. 338 * @param reader The ASN.1 stream reader from which to read the 339 * protocol op and controls. 340 * 341 * @return The decoded LDAP result. 342 * 343 * @throws LDAPException If a problem occurs while reading or decoding data 344 * from the ASN.1 stream reader. 345 */ 346 @NotNull() 347 static LDAPResult readLDAPResultFrom(final int messageID, 348 @NotNull final ASN1StreamReaderSequence messageSequence, 349 @NotNull final ASN1StreamReader reader) 350 throws LDAPException 351 { 352 try 353 { 354 final ASN1StreamReaderSequence protocolOpSequence = 355 reader.beginSequence(); 356 final byte protocolOpType = protocolOpSequence.getType(); 357 358 final ResultCode resultCode = ResultCode.valueOf(reader.readEnumerated()); 359 360 String matchedDN = reader.readString(); 361 if (matchedDN.isEmpty()) 362 { 363 matchedDN = null; 364 } 365 366 String diagnosticMessage = reader.readString(); 367 if (diagnosticMessage.isEmpty()) 368 { 369 diagnosticMessage = null; 370 } 371 372 String[] referralURLs = StaticUtils.NO_STRINGS; 373 if (protocolOpSequence.hasMoreElements()) 374 { 375 final ArrayList<String> refList = new ArrayList<>(1); 376 final ASN1StreamReaderSequence refSequence = reader.beginSequence(); 377 while (refSequence.hasMoreElements()) 378 { 379 refList.add(reader.readString()); 380 } 381 382 referralURLs = new String[refList.size()]; 383 refList.toArray(referralURLs); 384 } 385 386 Control[] responseControls = NO_CONTROLS; 387 if (messageSequence.hasMoreElements()) 388 { 389 final ArrayList<Control> controlList = new ArrayList<>(1); 390 final ASN1StreamReaderSequence controlSequence = reader.beginSequence(); 391 while (controlSequence.hasMoreElements()) 392 { 393 controlList.add(Control.readFrom(reader)); 394 } 395 396 responseControls = new Control[controlList.size()]; 397 controlList.toArray(responseControls); 398 } 399 400 return new LDAPResult(protocolOpType, messageID, resultCode, 401 diagnosticMessage, matchedDN, referralURLs, responseControls); 402 } 403 catch (final LDAPException le) 404 { 405 Debug.debugException(le); 406 throw le; 407 } 408 catch (final ASN1Exception ae) 409 { 410 Debug.debugException(ae); 411 throw new LDAPException(ResultCode.DECODING_ERROR, 412 ERR_RESULT_CANNOT_DECODE.get(ae.getMessage()), ae); 413 } 414 catch (final Exception e) 415 { 416 Debug.debugException(e); 417 throw new LDAPException(ResultCode.DECODING_ERROR, 418 ERR_RESULT_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e); 419 } 420 } 421 422 423 424 /** 425 * Retrieves the message ID for the LDAP message with which this LDAP result 426 * is associated. 427 * 428 * @return The message ID for the LDAP message with which this LDAP result 429 * is associated. 430 */ 431 @Override() 432 public final int getMessageID() 433 { 434 return messageID; 435 } 436 437 438 439 /** 440 * Retrieves the type of operation that triggered this result, if available. 441 * 442 * @return The type of operation that triggered this result, or {@code null} 443 * if the operation type is not available. 444 * 445 * Retrieves the BER type for the LDAP protocol op from which this 446 */ 447 @Nullable() 448 public final OperationType getOperationType() 449 { 450 if (protocolOpType != null) 451 { 452 switch (protocolOpType) 453 { 454 case LDAPMessage.PROTOCOL_OP_TYPE_ADD_RESPONSE: 455 return OperationType.ADD; 456 case LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE: 457 return OperationType.BIND; 458 case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_RESPONSE: 459 return OperationType.COMPARE; 460 case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_RESPONSE: 461 return OperationType.DELETE; 462 case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE: 463 return OperationType.EXTENDED; 464 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_RESPONSE: 465 return OperationType.MODIFY; 466 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE: 467 return OperationType.MODIFY_DN; 468 case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE: 469 return OperationType.SEARCH; 470 } 471 } 472 473 return null; 474 } 475 476 477 478 /** 479 * Retrieves the result code from the response. 480 * 481 * @return The result code from the response. 482 */ 483 @NotNull() 484 public final ResultCode getResultCode() 485 { 486 return resultCode; 487 } 488 489 490 491 /** 492 * Retrieves the diagnostic message from the response, if available. 493 * 494 * @return The diagnostic message from the response, or {@code null} if none 495 * was provided. 496 */ 497 @Nullable() 498 public final String getDiagnosticMessage() 499 { 500 return diagnosticMessage; 501 } 502 503 504 505 /** 506 * Retrieves the matched DN from the response, if available. 507 * 508 * @return The matched DN from the response, or {@code null} if none was 509 * provided. 510 */ 511 @Nullable() 512 public final String getMatchedDN() 513 { 514 return matchedDN; 515 } 516 517 518 519 /** 520 * Retrieves the set of referral URLs from the response, if available. 521 * 522 * @return The set of referral URLs from the response. The array returned 523 * may be empty if the response did not include any referral URLs. 524 */ 525 @NotNull() 526 public final String[] getReferralURLs() 527 { 528 return referralURLs; 529 } 530 531 532 533 /** 534 * Retrieves the set of controls from the response, if available. Individual 535 * response controls of a specific type may be retrieved and decoded using the 536 * {@code get} method in the response control class. 537 * 538 * @return The set of controls from the response. The array returned may be 539 * empty if the response did not include any controls. 540 */ 541 @NotNull() 542 public final Control[] getResponseControls() 543 { 544 return responseControls; 545 } 546 547 548 549 /** 550 * Indicates whether this result contains at least one control. 551 * 552 * @return {@code true} if this result contains at least one control, or 553 * {@code false} if not. 554 */ 555 public final boolean hasResponseControl() 556 { 557 return (responseControls.length > 0); 558 } 559 560 561 562 /** 563 * Indicates whether this result contains at least one control with the 564 * specified OID. 565 * 566 * @param oid The object identifier for which to make the determination. It 567 * must not be {@code null}. 568 * 569 * @return {@code true} if this result contains at least one control with 570 * the specified OID, or {@code false} if not. 571 */ 572 public final boolean hasResponseControl(@NotNull final String oid) 573 { 574 for (final Control c : responseControls) 575 { 576 if (c.getOID().equals(oid)) 577 { 578 return true; 579 } 580 } 581 582 return false; 583 } 584 585 586 587 /** 588 * Retrieves the response control with the specified OID. If there is more 589 * than one response control with the specified OID, then the first will be 590 * returned. 591 * 592 * @param oid The OID for the response control to retrieve. 593 * 594 * @return The requested response control, or {@code null} if there is no 595 * such response control. 596 */ 597 @Nullable() 598 public final Control getResponseControl(@NotNull final String oid) 599 { 600 for (final Control c : responseControls) 601 { 602 if (c.getOID().equals(oid)) 603 { 604 return c; 605 } 606 } 607 608 return null; 609 } 610 611 612 613 /** 614 * Retrieves a string representation of this LDAP result, consisting of 615 * the result code, diagnostic message (if present), matched DN (if present), 616 * and referral URLs (if present). 617 * 618 * @return A string representation of this LDAP result. 619 */ 620 @NotNull() 621 public String getResultString() 622 { 623 final StringBuilder buffer = new StringBuilder(); 624 buffer.append("result code='"); 625 buffer.append(resultCode); 626 buffer.append('\''); 627 628 if ((diagnosticMessage != null) && (! diagnosticMessage.isEmpty())) 629 { 630 buffer.append(" diagnostic message='"); 631 buffer.append(diagnosticMessage); 632 buffer.append('\''); 633 } 634 635 if ((matchedDN != null) && (! matchedDN.isEmpty())) 636 { 637 buffer.append(" matched DN='"); 638 buffer.append(matchedDN); 639 buffer.append('\''); 640 } 641 642 if ((referralURLs != null) && (referralURLs.length > 0)) 643 { 644 buffer.append(" referral URLs={"); 645 646 for (int i=0; i < referralURLs.length; i++) 647 { 648 if (i > 0) 649 { 650 buffer.append(", "); 651 } 652 653 buffer.append('\''); 654 buffer.append(referralURLs[i]); 655 buffer.append('\''); 656 } 657 658 buffer.append('}'); 659 } 660 661 return buffer.toString(); 662 } 663 664 665 666 /** 667 * Retrieves a string representation of this LDAP result. 668 * 669 * @return A string representation of this LDAP result. 670 */ 671 @Override() 672 @NotNull() 673 public String toString() 674 { 675 final StringBuilder buffer = new StringBuilder(); 676 toString(buffer); 677 return buffer.toString(); 678 } 679 680 681 682 /** 683 * Appends a string representation of this LDAP result to the provided buffer. 684 * 685 * @param buffer The buffer to which to append a string representation of 686 * this LDAP result. 687 */ 688 @Override() 689 public void toString(@NotNull final StringBuilder buffer) 690 { 691 buffer.append("LDAPResult(resultCode="); 692 buffer.append(resultCode); 693 694 if (messageID >= 0) 695 { 696 buffer.append(", messageID="); 697 buffer.append(messageID); 698 } 699 700 if (protocolOpType != null) 701 { 702 switch (protocolOpType) 703 { 704 case LDAPMessage.PROTOCOL_OP_TYPE_ADD_RESPONSE: 705 buffer.append(", opType='add'"); 706 break; 707 case LDAPMessage.PROTOCOL_OP_TYPE_BIND_RESPONSE: 708 buffer.append(", opType='bind'"); 709 break; 710 case LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_RESPONSE: 711 buffer.append(", opType='compare'"); 712 break; 713 case LDAPMessage.PROTOCOL_OP_TYPE_DELETE_RESPONSE: 714 buffer.append(", opType='delete'"); 715 break; 716 case LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE: 717 buffer.append(", opType='extended'"); 718 break; 719 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_RESPONSE: 720 buffer.append(", opType='modify'"); 721 break; 722 case LDAPMessage.PROTOCOL_OP_TYPE_MODIFY_DN_RESPONSE: 723 buffer.append(", opType='modify DN'"); 724 break; 725 case LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_RESULT_DONE: 726 buffer.append(", opType='search'"); 727 break; 728 } 729 } 730 731 if (diagnosticMessage != null) 732 { 733 buffer.append(", diagnosticMessage='"); 734 buffer.append(diagnosticMessage); 735 buffer.append('\''); 736 } 737 738 if (matchedDN != null) 739 { 740 buffer.append(", matchedDN='"); 741 buffer.append(matchedDN); 742 buffer.append('\''); 743 } 744 745 if (referralURLs.length > 0) 746 { 747 buffer.append(", referralURLs={"); 748 for (int i=0; i < referralURLs.length; i++) 749 { 750 if (i > 0) 751 { 752 buffer.append(", "); 753 } 754 755 buffer.append('\''); 756 buffer.append(referralURLs[i]); 757 buffer.append('\''); 758 } 759 buffer.append('}'); 760 } 761 762 if (responseControls.length > 0) 763 { 764 buffer.append(", responseControls={"); 765 for (int i=0; i < responseControls.length; i++) 766 { 767 if (i > 0) 768 { 769 buffer.append(", "); 770 } 771 772 buffer.append(responseControls[i]); 773 } 774 buffer.append('}'); 775 } 776 777 buffer.append(')'); 778 } 779}