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