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