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