001/* 002 * Copyright 2009-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-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) 2009-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.protocol; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.Iterator; 043import java.util.List; 044 045import com.unboundid.asn1.ASN1Buffer; 046import com.unboundid.asn1.ASN1BufferSequence; 047import com.unboundid.asn1.ASN1Element; 048import com.unboundid.asn1.ASN1Enumerated; 049import com.unboundid.asn1.ASN1OctetString; 050import com.unboundid.asn1.ASN1Sequence; 051import com.unboundid.asn1.ASN1StreamReader; 052import com.unboundid.asn1.ASN1StreamReaderSequence; 053import com.unboundid.ldap.sdk.Control; 054import com.unboundid.ldap.sdk.ExtendedResult; 055import com.unboundid.ldap.sdk.LDAPException; 056import com.unboundid.ldap.sdk.LDAPResult; 057import com.unboundid.ldap.sdk.ResultCode; 058import com.unboundid.util.Debug; 059import com.unboundid.util.InternalUseOnly; 060import com.unboundid.util.NotMutable; 061import com.unboundid.util.NotNull; 062import com.unboundid.util.Nullable; 063import com.unboundid.util.StaticUtils; 064import com.unboundid.util.ThreadSafety; 065import com.unboundid.util.ThreadSafetyLevel; 066import com.unboundid.util.Validator; 067 068import static com.unboundid.ldap.protocol.ProtocolMessages.*; 069 070 071 072/** 073 * This class provides an implementation of a extended response protocol op. 074 */ 075@InternalUseOnly() 076@NotMutable() 077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 078public final class ExtendedResponseProtocolOp 079 implements ProtocolOp 080{ 081 /** 082 * The BER type for the response OID element. 083 */ 084 public static final byte TYPE_RESPONSE_OID = (byte) 0x8A; 085 086 087 088 /** 089 * The BER type for the response value element. 090 */ 091 public static final byte TYPE_RESPONSE_VALUE = (byte) 0x8B; 092 093 094 095 /** 096 * The serial version UID for this serializable class. 097 */ 098 private static final long serialVersionUID = -7757619031268544913L; 099 100 101 102 // The value for this extended response. 103 @Nullable private final ASN1OctetString responseValue; 104 105 // The result code for this extended response. 106 private final int resultCode; 107 108 // The referral URLs for this extended response. 109 @NotNull private final List<String> referralURLs; 110 111 // The diagnostic message for this extended response. 112 @Nullable private final String diagnosticMessage; 113 114 // The matched DN for this extended response. 115 @Nullable private final String matchedDN; 116 117 // The OID for this extended response. 118 @Nullable private final String responseOID; 119 120 121 122 /** 123 * Creates a new instance of this extended response protocol op with the 124 * provided information. 125 * 126 * @param resultCode The result code for this response. 127 * @param matchedDN The matched DN for this response, if available. 128 * @param diagnosticMessage The diagnostic message for this response, if 129 * any. 130 * @param referralURLs The list of referral URLs for this response, if 131 * any. 132 * @param responseOID The response OID for this response, if any. 133 * @param responseValue The value for this response, if any. 134 */ 135 public ExtendedResponseProtocolOp(final int resultCode, 136 @Nullable final String matchedDN, 137 @Nullable final String diagnosticMessage, 138 @Nullable final List<String> referralURLs, 139 @Nullable final String responseOID, 140 @Nullable final ASN1OctetString responseValue) 141 { 142 this.resultCode = resultCode; 143 this.matchedDN = matchedDN; 144 this.diagnosticMessage = diagnosticMessage; 145 this.responseOID = responseOID; 146 147 if (referralURLs == null) 148 { 149 this.referralURLs = Collections.emptyList(); 150 } 151 else 152 { 153 this.referralURLs = Collections.unmodifiableList(referralURLs); 154 } 155 156 if (responseValue == null) 157 { 158 this.responseValue = null; 159 } 160 else 161 { 162 this.responseValue = 163 new ASN1OctetString(TYPE_RESPONSE_VALUE, responseValue.getValue()); 164 } 165 } 166 167 168 169 /** 170 * Creates a new extended response protocol op from the provided extended 171 * result object. 172 * 173 * @param result The extended result object to use to create this protocol 174 * op. 175 */ 176 public ExtendedResponseProtocolOp(@NotNull final LDAPResult result) 177 { 178 resultCode = result.getResultCode().intValue(); 179 matchedDN = result.getMatchedDN(); 180 diagnosticMessage = result.getDiagnosticMessage(); 181 referralURLs = StaticUtils.toList(result.getReferralURLs()); 182 183 if (result instanceof ExtendedResult) 184 { 185 final ExtendedResult r = (ExtendedResult) result; 186 responseOID = r.getOID(); 187 responseValue = r.getValue(); 188 } 189 else 190 { 191 responseOID = null; 192 responseValue = null; 193 } 194 } 195 196 197 198 /** 199 * Creates a new extended response protocol op read from the provided ASN.1 200 * stream reader. 201 * 202 * @param reader The ASN.1 stream reader from which to read the extended 203 * response. 204 * 205 * @throws LDAPException If a problem occurs while reading or parsing the 206 * extended response. 207 */ 208 ExtendedResponseProtocolOp(@NotNull final ASN1StreamReader reader) 209 throws LDAPException 210 { 211 try 212 { 213 final ASN1StreamReaderSequence opSequence = reader.beginSequence(); 214 resultCode = reader.readEnumerated(); 215 216 String s = reader.readString(); 217 Validator.ensureNotNull(s); 218 if (s.isEmpty()) 219 { 220 matchedDN = null; 221 } 222 else 223 { 224 matchedDN = s; 225 } 226 227 s = reader.readString(); 228 Validator.ensureNotNull(s); 229 if (s.isEmpty()) 230 { 231 diagnosticMessage = null; 232 } 233 else 234 { 235 diagnosticMessage = s; 236 } 237 238 ASN1OctetString value = null; 239 String oid = null; 240 final ArrayList<String> refs = new ArrayList<>(1); 241 while (opSequence.hasMoreElements()) 242 { 243 final byte type = (byte) reader.peek(); 244 if (type == GenericResponseProtocolOp.TYPE_REFERRALS) 245 { 246 final ASN1StreamReaderSequence refSequence = reader.beginSequence(); 247 while (refSequence.hasMoreElements()) 248 { 249 refs.add(reader.readString()); 250 } 251 } 252 else if (type == TYPE_RESPONSE_OID) 253 { 254 oid = reader.readString(); 255 } 256 else if (type == TYPE_RESPONSE_VALUE) 257 { 258 value = new ASN1OctetString(type, reader.readBytes()); 259 } 260 else 261 { 262 throw new LDAPException(ResultCode.DECODING_ERROR, 263 ERR_EXTENDED_RESPONSE_INVALID_ELEMENT.get( 264 StaticUtils.toHex(type))); 265 } 266 } 267 268 referralURLs = Collections.unmodifiableList(refs); 269 responseOID = oid; 270 responseValue = value; 271 } 272 catch (final LDAPException le) 273 { 274 Debug.debugException(le); 275 throw le; 276 } 277 catch (final Exception e) 278 { 279 Debug.debugException(e); 280 throw new LDAPException(ResultCode.DECODING_ERROR, 281 ERR_EXTENDED_RESPONSE_CANNOT_DECODE.get( 282 StaticUtils.getExceptionMessage(e)), e); 283 } 284 } 285 286 287 288 /** 289 * Retrieves the result code for this extended response. 290 * 291 * @return The result code for this extended response. 292 */ 293 public int getResultCode() 294 { 295 return resultCode; 296 } 297 298 299 300 /** 301 * Retrieves the matched DN for this extended response, if any. 302 * 303 * @return The matched DN for this extended response, or {@code null} if 304 * there is no matched DN. 305 */ 306 @Nullable() 307 public String getMatchedDN() 308 { 309 return matchedDN; 310 } 311 312 313 314 /** 315 * Retrieves the diagnostic message for this extended response, if any. 316 * 317 * @return The diagnostic message for this extended response, or {@code null} 318 * if there is no diagnostic message. 319 */ 320 @Nullable() 321 public String getDiagnosticMessage() 322 { 323 return diagnosticMessage; 324 } 325 326 327 328 /** 329 * Retrieves the list of referral URLs for this extended response. 330 * 331 * @return The list of referral URLs for this extended response, or an empty 332 * list if there are no referral URLs. 333 */ 334 @NotNull() 335 public List<String> getReferralURLs() 336 { 337 return referralURLs; 338 } 339 340 341 342 /** 343 * Retrieves the OID for this extended response, if any. 344 * 345 * @return The OID for this extended response, or {@code null} if there is no 346 * response OID. 347 */ 348 @Nullable() 349 public String getResponseOID() 350 { 351 return responseOID; 352 } 353 354 355 356 /** 357 * Retrieves the value for this extended response, if any. 358 * 359 * @return The value for this extended response, or {@code null} if there is 360 * no response value. 361 */ 362 @Nullable() 363 public ASN1OctetString getResponseValue() 364 { 365 return responseValue; 366 } 367 368 369 370 /** 371 * {@inheritDoc} 372 */ 373 @Override() 374 public byte getProtocolOpType() 375 { 376 return LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE; 377 } 378 379 380 381 /** 382 * {@inheritDoc} 383 */ 384 @Override() 385 @NotNull() 386 public ASN1Element encodeProtocolOp() 387 { 388 final ArrayList<ASN1Element> elements = new ArrayList<>(6); 389 elements.add(new ASN1Enumerated(getResultCode())); 390 391 final String mdn = getMatchedDN(); 392 if (mdn == null) 393 { 394 elements.add(new ASN1OctetString()); 395 } 396 else 397 { 398 elements.add(new ASN1OctetString(mdn)); 399 } 400 401 final String dm = getDiagnosticMessage(); 402 if (dm == null) 403 { 404 elements.add(new ASN1OctetString()); 405 } 406 else 407 { 408 elements.add(new ASN1OctetString(dm)); 409 } 410 411 final List<String> refs = getReferralURLs(); 412 if (! refs.isEmpty()) 413 { 414 final ArrayList<ASN1Element> refElements = new ArrayList<>(refs.size()); 415 for (final String r : refs) 416 { 417 refElements.add(new ASN1OctetString(r)); 418 } 419 elements.add(new ASN1Sequence(GenericResponseProtocolOp.TYPE_REFERRALS, 420 refElements)); 421 } 422 423 if (responseOID != null) 424 { 425 elements.add(new ASN1OctetString(TYPE_RESPONSE_OID, responseOID)); 426 } 427 428 if (responseValue != null) 429 { 430 elements.add(responseValue); 431 } 432 433 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE, 434 elements); 435 } 436 437 438 439 /** 440 * Decodes the provided ASN.1 element as an extended response protocol op. 441 * 442 * @param element The ASN.1 element to be decoded. 443 * 444 * @return The decoded extended response protocol op. 445 * 446 * @throws LDAPException If the provided ASN.1 element cannot be decoded as 447 * an extended response protocol op. 448 */ 449 @NotNull() 450 public static ExtendedResponseProtocolOp decodeProtocolOp( 451 @NotNull final ASN1Element element) 452 throws LDAPException 453 { 454 try 455 { 456 final ASN1Element[] elements = 457 ASN1Sequence.decodeAsSequence(element).elements(); 458 final int resultCode = 459 ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue(); 460 461 final String matchedDN; 462 final String md = 463 ASN1OctetString.decodeAsOctetString(elements[1]).stringValue(); 464 if (! md.isEmpty()) 465 { 466 matchedDN = md; 467 } 468 else 469 { 470 matchedDN = null; 471 } 472 473 final String diagnosticMessage; 474 final String dm = 475 ASN1OctetString.decodeAsOctetString(elements[2]).stringValue(); 476 if (! dm.isEmpty()) 477 { 478 diagnosticMessage = dm; 479 } 480 else 481 { 482 diagnosticMessage = null; 483 } 484 485 ASN1OctetString responseValue = null; 486 List<String> referralURLs = null; 487 String responseOID = null; 488 if (elements.length > 3) 489 { 490 for (int i=3; i < elements.length; i++) 491 { 492 switch (elements[i].getType()) 493 { 494 case GenericResponseProtocolOp.TYPE_REFERRALS: 495 final ASN1Element[] refElements = 496 ASN1Sequence.decodeAsSequence(elements[3]).elements(); 497 referralURLs = new ArrayList<>(refElements.length); 498 for (final ASN1Element e : refElements) 499 { 500 referralURLs.add( 501 ASN1OctetString.decodeAsOctetString(e).stringValue()); 502 } 503 break; 504 505 case TYPE_RESPONSE_OID: 506 responseOID = ASN1OctetString.decodeAsOctetString(elements[i]). 507 stringValue(); 508 break; 509 510 case TYPE_RESPONSE_VALUE: 511 responseValue = ASN1OctetString.decodeAsOctetString(elements[i]); 512 break; 513 514 default: 515 throw new LDAPException(ResultCode.DECODING_ERROR, 516 ERR_EXTENDED_RESPONSE_INVALID_ELEMENT.get( 517 StaticUtils.toHex(elements[i].getType()))); 518 } 519 } 520 } 521 522 return new ExtendedResponseProtocolOp(resultCode, matchedDN, 523 diagnosticMessage, referralURLs, responseOID, responseValue); 524 } 525 catch (final LDAPException le) 526 { 527 Debug.debugException(le); 528 throw le; 529 } 530 catch (final Exception e) 531 { 532 Debug.debugException(e); 533 throw new LDAPException(ResultCode.DECODING_ERROR, 534 ERR_EXTENDED_RESPONSE_CANNOT_DECODE.get( 535 StaticUtils.getExceptionMessage(e)), 536 e); 537 } 538 } 539 540 541 542 /** 543 * {@inheritDoc} 544 */ 545 @Override() 546 public void writeTo(@NotNull final ASN1Buffer buffer) 547 { 548 final ASN1BufferSequence opSequence = 549 buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_RESPONSE); 550 buffer.addEnumerated(resultCode); 551 buffer.addOctetString(matchedDN); 552 buffer.addOctetString(diagnosticMessage); 553 554 if (! referralURLs.isEmpty()) 555 { 556 final ASN1BufferSequence refSequence = 557 buffer.beginSequence(GenericResponseProtocolOp.TYPE_REFERRALS); 558 for (final String s : referralURLs) 559 { 560 buffer.addOctetString(s); 561 } 562 refSequence.end(); 563 } 564 565 if (responseOID != null) 566 { 567 buffer.addOctetString(TYPE_RESPONSE_OID, responseOID); 568 } 569 570 if (responseValue != null) 571 { 572 buffer.addOctetString(TYPE_RESPONSE_VALUE, responseValue.getValue()); 573 } 574 575 opSequence.end(); 576 } 577 578 579 580 /** 581 * Creates a extended result from this protocol op. 582 * 583 * @param controls The set of controls to include in the extended result. 584 * It may be empty or {@code null} if no controls should be 585 * included. 586 * 587 * @return The extended result that was created. 588 */ 589 @NotNull() 590 public ExtendedResult toExtendedResult(@Nullable final Control... controls) 591 { 592 final String[] referralArray = new String[referralURLs.size()]; 593 referralURLs.toArray(referralArray); 594 595 return new ExtendedResult(-1, ResultCode.valueOf(resultCode), 596 diagnosticMessage, matchedDN, referralArray, responseOID, 597 responseValue, controls); 598 } 599 600 601 602 /** 603 * Retrieves a string representation of this protocol op. 604 * 605 * @return A string representation of this protocol op. 606 */ 607 @Override() 608 @NotNull() 609 public String toString() 610 { 611 final StringBuilder buffer = new StringBuilder(); 612 toString(buffer); 613 return buffer.toString(); 614 } 615 616 617 618 /** 619 * {@inheritDoc} 620 */ 621 @Override() 622 public void toString(@NotNull final StringBuilder buffer) 623 { 624 buffer.append("ExtendedResponseProtocolOp(resultCode="); 625 buffer.append(resultCode); 626 627 if (matchedDN != null) 628 { 629 buffer.append(", matchedDN='"); 630 buffer.append(matchedDN); 631 buffer.append('\''); 632 } 633 634 if (diagnosticMessage != null) 635 { 636 buffer.append(", diagnosticMessage='"); 637 buffer.append(diagnosticMessage); 638 buffer.append('\''); 639 } 640 641 if (! referralURLs.isEmpty()) 642 { 643 buffer.append(", referralURLs={"); 644 645 final Iterator<String> iterator = referralURLs.iterator(); 646 while (iterator.hasNext()) 647 { 648 buffer.append('\''); 649 buffer.append(iterator.next()); 650 buffer.append('\''); 651 if (iterator.hasNext()) 652 { 653 buffer.append(','); 654 } 655 } 656 657 buffer.append('}'); 658 } 659 660 if (responseOID != null) 661 { 662 buffer.append(", responseOID='"); 663 buffer.append(responseOID); 664 buffer.append('\''); 665 } 666 667 buffer.append(')'); 668 } 669}