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