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