001/* 002 * Copyright 2007-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2022 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-2022 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.util.ArrayList; 041import java.util.List; 042import java.util.concurrent.LinkedBlockingQueue; 043import java.util.concurrent.TimeUnit; 044import java.util.logging.Level; 045 046import com.unboundid.asn1.ASN1Buffer; 047import com.unboundid.asn1.ASN1BufferSequence; 048import com.unboundid.asn1.ASN1Element; 049import com.unboundid.asn1.ASN1OctetString; 050import com.unboundid.asn1.ASN1Sequence; 051import com.unboundid.ldap.protocol.LDAPMessage; 052import com.unboundid.ldap.protocol.LDAPResponse; 053import com.unboundid.ldap.protocol.ProtocolOp; 054import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; 055import com.unboundid.util.Debug; 056import com.unboundid.util.Extensible; 057import com.unboundid.util.InternalUseOnly; 058import com.unboundid.util.NotMutable; 059import com.unboundid.util.NotNull; 060import com.unboundid.util.Nullable; 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.sdk.LDAPMessages.*; 067 068 069 070/** 071 * This class implements the processing necessary to perform an LDAPv3 extended 072 * operation, which provides a way to request actions not included in the core 073 * LDAP protocol. Subclasses can provide logic to help implement more specific 074 * types of extended operations, but it is important to note that if such 075 * subclasses include an extended request value, then the request value must be 076 * kept up-to-date if any changes are made to custom elements in that class that 077 * would impact the request value encoding. 078 */ 079@Extensible() 080@NotMutable() 081@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 082public class ExtendedRequest 083 extends LDAPRequest 084 implements ResponseAcceptor, ProtocolOp 085{ 086 /** 087 * The BER type for the extended request OID element. 088 */ 089 protected static final byte TYPE_EXTENDED_REQUEST_OID = (byte) 0x80; 090 091 092 093 /** 094 * The BER type for the extended request value element. 095 */ 096 protected static final byte TYPE_EXTENDED_REQUEST_VALUE = (byte) 0x81; 097 098 099 100 /** 101 * The serial version UID for this serializable class. 102 */ 103 private static final long serialVersionUID = 5572410770060685796L; 104 105 106 107 // The encoded value for this extended request, if available. 108 @Nullable private final ASN1OctetString value; 109 110 // The message ID from the last LDAP message sent from this request. 111 private int messageID = -1; 112 113 // The queue that will be used to receive response messages from the server. 114 @NotNull private final LinkedBlockingQueue<LDAPResponse> responseQueue = 115 new LinkedBlockingQueue<>(); 116 117 // The OID for this extended request. 118 @NotNull private final String oid; 119 120 121 122 /** 123 * Creates a new extended request with the provided OID and no value. 124 * 125 * @param oid The OID for this extended request. It must not be 126 * {@code null}. 127 */ 128 public ExtendedRequest(@NotNull final String oid) 129 { 130 super(null); 131 132 Validator.ensureNotNull(oid); 133 134 this.oid = oid; 135 136 value = null; 137 } 138 139 140 141 /** 142 * Creates a new extended request with the provided OID and no value. 143 * 144 * @param oid The OID for this extended request. It must not be 145 * {@code null}. 146 * @param controls The set of controls for this extended request. 147 */ 148 public ExtendedRequest(@NotNull final String oid, 149 @Nullable final Control[] controls) 150 { 151 super(controls); 152 153 Validator.ensureNotNull(oid); 154 155 this.oid = oid; 156 157 value = null; 158 } 159 160 161 162 /** 163 * Creates a new extended request with the provided OID and value. 164 * 165 * @param oid The OID for this extended request. It must not be 166 * {@code null}. 167 * @param value The encoded value for this extended request. It may be 168 * {@code null} if this request should not have a value. 169 */ 170 public ExtendedRequest(@NotNull final String oid, 171 @Nullable final ASN1OctetString value) 172 { 173 super(null); 174 175 Validator.ensureNotNull(oid); 176 177 this.oid = oid; 178 this.value = value; 179 } 180 181 182 183 /** 184 * Creates a new extended request with the provided OID and value. 185 * 186 * @param oid The OID for this extended request. It must not be 187 * {@code null}. 188 * @param value The encoded value for this extended request. It may be 189 * {@code null} if this request should not have a value. 190 * @param controls The set of controls for this extended request. 191 */ 192 public ExtendedRequest(@NotNull final String oid, 193 @Nullable final ASN1OctetString value, 194 @Nullable final Control[] controls) 195 { 196 super(controls); 197 198 Validator.ensureNotNull(oid); 199 200 this.oid = oid; 201 this.value = value; 202 } 203 204 205 206 /** 207 * Creates a new extended request with the information from the provided 208 * extended request. 209 * 210 * @param extendedRequest The extended request that should be used to create 211 * this new extended request. 212 */ 213 protected ExtendedRequest(@NotNull final ExtendedRequest extendedRequest) 214 { 215 super(extendedRequest.getControls()); 216 217 messageID = extendedRequest.messageID; 218 oid = extendedRequest.oid; 219 value = extendedRequest.value; 220 } 221 222 223 224 /** 225 * Retrieves the OID for this extended request. 226 * 227 * @return The OID for this extended request. 228 */ 229 @NotNull() 230 public final String getOID() 231 { 232 return oid; 233 } 234 235 236 237 /** 238 * Indicates whether this extended request has a value. 239 * 240 * @return {@code true} if this extended request has a value, or 241 * {@code false} if not. 242 */ 243 public final boolean hasValue() 244 { 245 return (value != null); 246 } 247 248 249 250 /** 251 * Retrieves the encoded value for this extended request, if available. 252 * 253 * @return The encoded value for this extended request, or {@code null} if 254 * this request does not have a value. 255 */ 256 @Nullable() 257 public final ASN1OctetString getValue() 258 { 259 return value; 260 } 261 262 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override() 268 public final byte getProtocolOpType() 269 { 270 return LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST; 271 } 272 273 274 275 /** 276 * {@inheritDoc} 277 */ 278 @Override() 279 public final void writeTo(@NotNull final ASN1Buffer writer) 280 { 281 final ASN1BufferSequence requestSequence = 282 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST); 283 writer.addOctetString(TYPE_EXTENDED_REQUEST_OID, oid); 284 285 if (value != null) 286 { 287 writer.addOctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue()); 288 } 289 requestSequence.end(); 290 } 291 292 293 294 /** 295 * Encodes the extended request protocol op to an ASN.1 element. 296 * 297 * @return The ASN.1 element with the encoded extended request protocol op. 298 */ 299 @Override() 300 @NotNull() 301 public ASN1Element encodeProtocolOp() 302 { 303 // Create the extended request protocol op. 304 final ASN1Element[] protocolOpElements; 305 if (value == null) 306 { 307 protocolOpElements = new ASN1Element[] 308 { 309 new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid) 310 }; 311 } 312 else 313 { 314 protocolOpElements = new ASN1Element[] 315 { 316 new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid), 317 new ASN1OctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue()) 318 }; 319 } 320 321 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST, 322 protocolOpElements); 323 } 324 325 326 327 /** 328 * Sends this extended request to the directory server over the provided 329 * connection and returns the associated response. 330 * 331 * @param connection The connection to use to communicate with the directory 332 * server. 333 * @param depth The current referral depth for this request. It should 334 * always be one for the initial request, and should only 335 * be incremented when following referrals. 336 * 337 * @return An LDAP result object that provides information about the result 338 * of the extended operation processing. 339 * 340 * @throws LDAPException If a problem occurs while sending the request or 341 * reading the response. 342 */ 343 @Override() 344 @NotNull() 345 protected ExtendedResult process(@NotNull final LDAPConnection connection, 346 final int depth) 347 throws LDAPException 348 { 349 if (connection.synchronousMode()) 350 { 351 return processSync(connection); 352 } 353 354 // Create the LDAP message. 355 messageID = connection.nextMessageID(); 356 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 357 358 359 // Register with the connection reader to be notified of responses for the 360 // request that we've created. 361 connection.registerResponseAcceptor(messageID, this); 362 363 364 try 365 { 366 // Send the request to the server. 367 final long responseTimeout = getResponseTimeoutMillis(connection); 368 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 369 370 final LDAPConnectionLogger logger = 371 connection.getConnectionOptions().getConnectionLogger(); 372 if (logger != null) 373 { 374 logger.logExtendedRequest(connection, messageID, this); 375 } 376 377 final long requestTime = System.nanoTime(); 378 connection.getConnectionStatistics().incrementNumExtendedRequests(); 379 if (this instanceof StartTLSExtendedRequest) 380 { 381 connection.sendMessage(message, 50L); 382 } 383 else 384 { 385 connection.sendMessage(message, responseTimeout); 386 } 387 388 // Wait for and process the response. 389 final LDAPResponse response; 390 try 391 { 392 if (responseTimeout > 0) 393 { 394 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 395 } 396 else 397 { 398 response = responseQueue.take(); 399 } 400 } 401 catch (final InterruptedException ie) 402 { 403 Debug.debugException(ie); 404 Thread.currentThread().interrupt(); 405 throw new LDAPException(ResultCode.LOCAL_ERROR, 406 ERR_EXTOP_INTERRUPTED.get(connection.getHostPort()), ie); 407 } 408 409 return handleResponse(connection, response, requestTime); 410 } 411 finally 412 { 413 connection.deregisterResponseAcceptor(messageID); 414 } 415 } 416 417 418 419 /** 420 * Processes this extended operation in synchronous mode, in which the same 421 * thread will send the request and read the response. 422 * 423 * @param connection The connection to use to communicate with the directory 424 * server. 425 * 426 * @return An LDAP result object that provides information about the result 427 * of the extended processing. 428 * 429 * @throws LDAPException If a problem occurs while sending the request or 430 * reading the response. 431 */ 432 @NotNull() 433 private ExtendedResult processSync(@NotNull final LDAPConnection connection) 434 throws LDAPException 435 { 436 // Create the LDAP message. 437 messageID = connection.nextMessageID(); 438 final LDAPMessage message = 439 new LDAPMessage(messageID, this, getControls()); 440 441 442 // Send the request to the server. 443 final long requestTime = System.nanoTime(); 444 Debug.debugLDAPRequest(Level.INFO, this, messageID, connection); 445 446 final LDAPConnectionLogger logger = 447 connection.getConnectionOptions().getConnectionLogger(); 448 if (logger != null) 449 { 450 logger.logExtendedRequest(connection, messageID, this); 451 } 452 453 connection.getConnectionStatistics().incrementNumExtendedRequests(); 454 connection.sendMessage(message, getResponseTimeoutMillis(connection)); 455 456 while (true) 457 { 458 final LDAPResponse response; 459 try 460 { 461 response = connection.readResponse(messageID); 462 } 463 catch (final LDAPException le) 464 { 465 Debug.debugException(le); 466 467 if ((le.getResultCode() == ResultCode.TIMEOUT) && 468 connection.getConnectionOptions().abandonOnTimeout()) 469 { 470 connection.abandon(messageID); 471 } 472 473 throw le; 474 } 475 476 if (response instanceof IntermediateResponse) 477 { 478 final IntermediateResponseListener listener = 479 getIntermediateResponseListener(); 480 if (listener != null) 481 { 482 listener.intermediateResponseReturned( 483 (IntermediateResponse) response); 484 } 485 } 486 else 487 { 488 return handleResponse(connection, response, requestTime); 489 } 490 } 491 } 492 493 494 495 /** 496 * Performs the necessary processing for handling a response. 497 * 498 * @param connection The connection used to read the response. 499 * @param response The response to be processed. 500 * @param requestTime The time the request was sent to the server. 501 * 502 * @return The extended result. 503 * 504 * @throws LDAPException If a problem occurs. 505 */ 506 @NotNull() 507 private ExtendedResult handleResponse( 508 @NotNull final LDAPConnection connection, 509 @Nullable final LDAPResponse response, 510 final long requestTime) 511 throws LDAPException 512 { 513 if (response == null) 514 { 515 final long waitTime = 516 StaticUtils.nanosToMillis(System.nanoTime() - requestTime); 517 if (connection.getConnectionOptions().abandonOnTimeout()) 518 { 519 connection.abandon(messageID); 520 } 521 522 throw new LDAPException(ResultCode.TIMEOUT, 523 ERR_EXTENDED_CLIENT_TIMEOUT.get(waitTime, messageID, oid, 524 connection.getHostPort())); 525 } 526 527 if (response instanceof ConnectionClosedResponse) 528 { 529 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 530 final String msg = ccr.getMessage(); 531 if (msg == null) 532 { 533 // The connection was closed while waiting for the response. 534 throw new LDAPException(ccr.getResultCode(), 535 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE.get( 536 connection.getHostPort(), toString())); 537 } 538 else 539 { 540 // The connection was closed while waiting for the response. 541 throw new LDAPException(ccr.getResultCode(), 542 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE_WITH_MESSAGE.get( 543 connection.getHostPort(), toString(), msg)); 544 } 545 } 546 547 connection.getConnectionStatistics().incrementNumExtendedResponses( 548 System.nanoTime() - requestTime); 549 return (ExtendedResult) response; 550 } 551 552 553 554 /** 555 * {@inheritDoc} 556 */ 557 @InternalUseOnly() 558 @Override() 559 public final void responseReceived(@NotNull final LDAPResponse response) 560 throws LDAPException 561 { 562 try 563 { 564 responseQueue.put(response); 565 } 566 catch (final Exception e) 567 { 568 Debug.debugException(e); 569 570 if (e instanceof InterruptedException) 571 { 572 Thread.currentThread().interrupt(); 573 } 574 575 throw new LDAPException(ResultCode.LOCAL_ERROR, 576 ERR_EXCEPTION_HANDLING_RESPONSE.get( 577 StaticUtils.getExceptionMessage(e)), 578 e); 579 } 580 } 581 582 583 584 /** 585 * {@inheritDoc} 586 */ 587 @Override() 588 public final int getLastMessageID() 589 { 590 return messageID; 591 } 592 593 594 595 /** 596 * {@inheritDoc} 597 */ 598 @Override() 599 @NotNull() 600 public final OperationType getOperationType() 601 { 602 return OperationType.EXTENDED; 603 } 604 605 606 607 /** 608 * {@inheritDoc}. Subclasses should override this method to return a 609 * duplicate of the appropriate type. 610 */ 611 @Override() 612 @NotNull() 613 public ExtendedRequest duplicate() 614 { 615 return duplicate(getControls()); 616 } 617 618 619 620 /** 621 * {@inheritDoc}. Subclasses should override this method to return a 622 * duplicate of the appropriate type. 623 */ 624 @Override() 625 @NotNull() 626 public ExtendedRequest duplicate(@Nullable final Control[] controls) 627 { 628 final ExtendedRequest r = new ExtendedRequest(oid, value, controls); 629 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 630 return r; 631 } 632 633 634 635 /** 636 * Retrieves the user-friendly name for the extended request, if available. 637 * If no user-friendly name has been defined, then the OID will be returned. 638 * 639 * @return The user-friendly name for this extended request, or the OID if no 640 * user-friendly name is available. 641 */ 642 @NotNull() 643 public String getExtendedRequestName() 644 { 645 // By default, we will return the OID. Subclasses should override this to 646 // provide the user-friendly name. 647 return oid; 648 } 649 650 651 652 /** 653 * {@inheritDoc} 654 */ 655 @Override() 656 public void toString(@NotNull final StringBuilder buffer) 657 { 658 buffer.append("ExtendedRequest(oid='"); 659 buffer.append(oid); 660 buffer.append('\''); 661 662 final Control[] controls = getControls(); 663 if (controls.length > 0) 664 { 665 buffer.append(", controls={"); 666 for (int i=0; i < controls.length; i++) 667 { 668 if (i > 0) 669 { 670 buffer.append(", "); 671 } 672 673 buffer.append(controls[i]); 674 } 675 buffer.append('}'); 676 } 677 678 buffer.append(')'); 679 } 680 681 682 683 /** 684 * {@inheritDoc} 685 */ 686 @Override() 687 public void toCode(@NotNull final List<String> lineList, 688 @NotNull final String requestID, 689 final int indentSpaces, final boolean includeProcessing) 690 { 691 // Create the request variable. 692 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(3); 693 constructorArgs.add(ToCodeArgHelper.createString(oid, "Request OID")); 694 constructorArgs.add(ToCodeArgHelper.createASN1OctetString(value, 695 "Request Value")); 696 697 final Control[] controls = getControls(); 698 if (controls.length > 0) 699 { 700 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 701 "Request Controls")); 702 } 703 704 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ExtendedRequest", 705 requestID + "Request", "new ExtendedRequest", constructorArgs); 706 707 708 // Add lines for processing the request and obtaining the result. 709 if (includeProcessing) 710 { 711 // Generate a string with the appropriate indent. 712 final StringBuilder buffer = new StringBuilder(); 713 for (int i=0; i < indentSpaces; i++) 714 { 715 buffer.append(' '); 716 } 717 final String indent = buffer.toString(); 718 719 lineList.add(""); 720 lineList.add(indent + "try"); 721 lineList.add(indent + '{'); 722 lineList.add(indent + " ExtendedResult " + requestID + 723 "Result = connection.processExtendedOperation(" + requestID + 724 "Request);"); 725 lineList.add(indent + " // The extended operation was processed and " + 726 "we have a result."); 727 lineList.add(indent + " // This does not necessarily mean that the " + 728 "operation was successful."); 729 lineList.add(indent + " // Examine the result details for more " + 730 "information."); 731 lineList.add(indent + " ResultCode resultCode = " + requestID + 732 "Result.getResultCode();"); 733 lineList.add(indent + " String message = " + requestID + 734 "Result.getMessage();"); 735 lineList.add(indent + " String matchedDN = " + requestID + 736 "Result.getMatchedDN();"); 737 lineList.add(indent + " String[] referralURLs = " + requestID + 738 "Result.getReferralURLs();"); 739 lineList.add(indent + " String responseOID = " + requestID + 740 "Result.getOID();"); 741 lineList.add(indent + " ASN1OctetString responseValue = " + requestID + 742 "Result.getValue();"); 743 lineList.add(indent + " Control[] responseControls = " + requestID + 744 "Result.getResponseControls();"); 745 lineList.add(indent + '}'); 746 lineList.add(indent + "catch (LDAPException e)"); 747 lineList.add(indent + '{'); 748 lineList.add(indent + " // A problem was encountered while attempting " + 749 "to process the extended operation."); 750 lineList.add(indent + " // Maybe the following will help explain why."); 751 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 752 lineList.add(indent + " String message = e.getMessage();"); 753 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 754 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 755 lineList.add(indent + " Control[] responseControls = " + 756 "e.getResponseControls();"); 757 lineList.add(indent + '}'); 758 } 759 } 760}