001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 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.Extensible; 041import com.unboundid.util.InternalUseOnly; 042import com.unboundid.util.NotMutable; 043import com.unboundid.util.ThreadSafety; 044import com.unboundid.util.ThreadSafetyLevel; 045 046import static com.unboundid.ldap.sdk.LDAPMessages.*; 047import static com.unboundid.util.Debug.*; 048import static com.unboundid.util.StaticUtils.*; 049import static com.unboundid.util.Validator.*; 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<LDAPResponse>(); 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 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 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 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 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 oid = extendedRequest.oid; 198 value = extendedRequest.value; 199 } 200 201 202 203 /** 204 * Retrieves the OID for this extended request. 205 * 206 * @return The OID for this extended request. 207 */ 208 public final String getOID() 209 { 210 return oid; 211 } 212 213 214 215 /** 216 * Indicates whether this extended request has a value. 217 * 218 * @return {@code true} if this extended request has a value, or 219 * {@code false} if not. 220 */ 221 public final boolean hasValue() 222 { 223 return (value != null); 224 } 225 226 227 228 /** 229 * Retrieves the encoded value for this extended request, if available. 230 * 231 * @return The encoded value for this extended request, or {@code null} if 232 * this request does not have a value. 233 */ 234 public final ASN1OctetString getValue() 235 { 236 return value; 237 } 238 239 240 241 /** 242 * {@inheritDoc} 243 */ 244 @Override() 245 public final byte getProtocolOpType() 246 { 247 return LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST; 248 } 249 250 251 252 /** 253 * {@inheritDoc} 254 */ 255 @Override() 256 public final void writeTo(final ASN1Buffer writer) 257 { 258 final ASN1BufferSequence requestSequence = 259 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST); 260 writer.addOctetString(TYPE_EXTENDED_REQUEST_OID, oid); 261 262 if (value != null) 263 { 264 writer.addOctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue()); 265 } 266 requestSequence.end(); 267 } 268 269 270 271 /** 272 * Encodes the extended request protocol op to an ASN.1 element. 273 * 274 * @return The ASN.1 element with the encoded extended request protocol op. 275 */ 276 @Override() 277 public ASN1Element encodeProtocolOp() 278 { 279 // Create the extended request protocol op. 280 final ASN1Element[] protocolOpElements; 281 if (value == null) 282 { 283 protocolOpElements = new ASN1Element[] 284 { 285 new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid) 286 }; 287 } 288 else 289 { 290 protocolOpElements = new ASN1Element[] 291 { 292 new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid), 293 new ASN1OctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue()) 294 }; 295 } 296 297 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST, 298 protocolOpElements); 299 } 300 301 302 303 /** 304 * Sends this extended request to the directory server over the provided 305 * connection and returns the associated response. 306 * 307 * @param connection The connection to use to communicate with the directory 308 * server. 309 * @param depth The current referral depth for this request. It should 310 * always be one for the initial request, and should only 311 * be incremented when following referrals. 312 * 313 * @return An LDAP result object that provides information about the result 314 * of the extended operation processing. 315 * 316 * @throws LDAPException If a problem occurs while sending the request or 317 * reading the response. 318 */ 319 @Override() 320 protected ExtendedResult process(final LDAPConnection connection, 321 final int depth) 322 throws LDAPException 323 { 324 if (connection.synchronousMode()) 325 { 326 return processSync(connection); 327 } 328 329 // Create the LDAP message. 330 messageID = connection.nextMessageID(); 331 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 332 333 334 // Register with the connection reader to be notified of responses for the 335 // request that we've created. 336 connection.registerResponseAcceptor(messageID, this); 337 338 339 try 340 { 341 // Send the request to the server. 342 final long responseTimeout = getResponseTimeoutMillis(connection); 343 debugLDAPRequest(Level.INFO, this, messageID, connection); 344 final long requestTime = System.nanoTime(); 345 connection.getConnectionStatistics().incrementNumExtendedRequests(); 346 if (this instanceof StartTLSExtendedRequest) 347 { 348 connection.sendMessage(message, 50L); 349 } 350 else 351 { 352 connection.sendMessage(message, responseTimeout); 353 } 354 355 // Wait for and process the response. 356 final LDAPResponse response; 357 try 358 { 359 if (responseTimeout > 0) 360 { 361 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 362 } 363 else 364 { 365 response = responseQueue.take(); 366 } 367 } 368 catch (final InterruptedException ie) 369 { 370 debugException(ie); 371 Thread.currentThread().interrupt(); 372 throw new LDAPException(ResultCode.LOCAL_ERROR, 373 ERR_EXTOP_INTERRUPTED.get(connection.getHostPort()), ie); 374 } 375 376 return handleResponse(connection, response, requestTime); 377 } 378 finally 379 { 380 connection.deregisterResponseAcceptor(messageID); 381 } 382 } 383 384 385 386 /** 387 * Processes this extended operation in synchronous mode, in which the same 388 * thread will send the request and read the response. 389 * 390 * @param connection The connection to use to communicate with the directory 391 * server. 392 * 393 * @return An LDAP result object that provides information about the result 394 * of the extended processing. 395 * 396 * @throws LDAPException If a problem occurs while sending the request or 397 * reading the response. 398 */ 399 private ExtendedResult processSync(final LDAPConnection connection) 400 throws LDAPException 401 { 402 // Create the LDAP message. 403 messageID = connection.nextMessageID(); 404 final LDAPMessage message = 405 new LDAPMessage(messageID, this, getControls()); 406 407 408 // Send the request to the server. 409 final long requestTime = System.nanoTime(); 410 debugLDAPRequest(Level.INFO, this, messageID, connection); 411 connection.getConnectionStatistics().incrementNumExtendedRequests(); 412 connection.sendMessage(message, getResponseTimeoutMillis(connection)); 413 414 while (true) 415 { 416 final LDAPResponse response; 417 try 418 { 419 response = connection.readResponse(messageID); 420 } 421 catch (final LDAPException le) 422 { 423 debugException(le); 424 425 if ((le.getResultCode() == ResultCode.TIMEOUT) && 426 connection.getConnectionOptions().abandonOnTimeout()) 427 { 428 connection.abandon(messageID); 429 } 430 431 throw le; 432 } 433 434 if (response instanceof IntermediateResponse) 435 { 436 final IntermediateResponseListener listener = 437 getIntermediateResponseListener(); 438 if (listener != null) 439 { 440 listener.intermediateResponseReturned( 441 (IntermediateResponse) response); 442 } 443 } 444 else 445 { 446 return handleResponse(connection, response, requestTime); 447 } 448 } 449 } 450 451 452 453 /** 454 * Performs the necessary processing for handling a response. 455 * 456 * @param connection The connection used to read the response. 457 * @param response The response to be processed. 458 * @param requestTime The time the request was sent to the server. 459 * 460 * @return The extended result. 461 * 462 * @throws LDAPException If a problem occurs. 463 */ 464 private ExtendedResult handleResponse(final LDAPConnection connection, 465 final LDAPResponse response, 466 final long requestTime) 467 throws LDAPException 468 { 469 if (response == null) 470 { 471 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 472 if (connection.getConnectionOptions().abandonOnTimeout()) 473 { 474 connection.abandon(messageID); 475 } 476 477 throw new LDAPException(ResultCode.TIMEOUT, 478 ERR_EXTENDED_CLIENT_TIMEOUT.get(waitTime, messageID, oid, 479 connection.getHostPort())); 480 } 481 482 if (response instanceof ConnectionClosedResponse) 483 { 484 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 485 final String msg = ccr.getMessage(); 486 if (msg == null) 487 { 488 // The connection was closed while waiting for the response. 489 throw new LDAPException(ccr.getResultCode(), 490 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE.get( 491 connection.getHostPort(), toString())); 492 } 493 else 494 { 495 // The connection was closed while waiting for the response. 496 throw new LDAPException(ccr.getResultCode(), 497 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE_WITH_MESSAGE.get( 498 connection.getHostPort(), toString(), msg)); 499 } 500 } 501 502 connection.getConnectionStatistics().incrementNumExtendedResponses( 503 System.nanoTime() - requestTime); 504 return (ExtendedResult) response; 505 } 506 507 508 509 /** 510 * {@inheritDoc} 511 */ 512 @InternalUseOnly() 513 @Override() 514 public final void responseReceived(final LDAPResponse response) 515 throws LDAPException 516 { 517 try 518 { 519 responseQueue.put(response); 520 } 521 catch (final Exception e) 522 { 523 debugException(e); 524 525 if (e instanceof InterruptedException) 526 { 527 Thread.currentThread().interrupt(); 528 } 529 530 throw new LDAPException(ResultCode.LOCAL_ERROR, 531 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 532 } 533 } 534 535 536 537 /** 538 * {@inheritDoc} 539 */ 540 @Override() 541 public final int getLastMessageID() 542 { 543 return messageID; 544 } 545 546 547 548 /** 549 * {@inheritDoc} 550 */ 551 @Override() 552 public final OperationType getOperationType() 553 { 554 return OperationType.EXTENDED; 555 } 556 557 558 559 /** 560 * {@inheritDoc}. Subclasses should override this method to return a 561 * duplicate of the appropriate type. 562 */ 563 @Override() 564 public ExtendedRequest duplicate() 565 { 566 return duplicate(getControls()); 567 } 568 569 570 571 /** 572 * {@inheritDoc}. Subclasses should override this method to return a 573 * duplicate of the appropriate type. 574 */ 575 @Override() 576 public ExtendedRequest duplicate(final Control[] controls) 577 { 578 final ExtendedRequest r = new ExtendedRequest(oid, value, controls); 579 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 580 return r; 581 } 582 583 584 585 /** 586 * Retrieves the user-friendly name for the extended request, if available. 587 * If no user-friendly name has been defined, then the OID will be returned. 588 * 589 * @return The user-friendly name for this extended request, or the OID if no 590 * user-friendly name is available. 591 */ 592 public String getExtendedRequestName() 593 { 594 // By default, we will return the OID. Subclasses should override this to 595 // provide the user-friendly name. 596 return oid; 597 } 598 599 600 601 /** 602 * {@inheritDoc} 603 */ 604 @Override() 605 public void toString(final StringBuilder buffer) 606 { 607 buffer.append("ExtendedRequest(oid='"); 608 buffer.append(oid); 609 buffer.append('\''); 610 611 final Control[] controls = getControls(); 612 if (controls.length > 0) 613 { 614 buffer.append(", controls={"); 615 for (int i=0; i < controls.length; i++) 616 { 617 if (i > 0) 618 { 619 buffer.append(", "); 620 } 621 622 buffer.append(controls[i]); 623 } 624 buffer.append('}'); 625 } 626 627 buffer.append(')'); 628 } 629 630 631 632 /** 633 * {@inheritDoc} 634 */ 635 @Override() 636 public void toCode(final List<String> lineList, final String requestID, 637 final int indentSpaces, final boolean includeProcessing) 638 { 639 // Create the request variable. 640 final ArrayList<ToCodeArgHelper> constructorArgs = 641 new ArrayList<ToCodeArgHelper>(3); 642 constructorArgs.add(ToCodeArgHelper.createString(oid, "Request OID")); 643 constructorArgs.add(ToCodeArgHelper.createASN1OctetString(value, 644 "Request Value")); 645 646 final Control[] controls = getControls(); 647 if (controls.length > 0) 648 { 649 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 650 "Request Controls")); 651 } 652 653 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ExtendedRequest", 654 requestID + "Request", "new ExtendedRequest", constructorArgs); 655 656 657 // Add lines for processing the request and obtaining the result. 658 if (includeProcessing) 659 { 660 // Generate a string with the appropriate indent. 661 final StringBuilder buffer = new StringBuilder(); 662 for (int i=0; i < indentSpaces; i++) 663 { 664 buffer.append(' '); 665 } 666 final String indent = buffer.toString(); 667 668 lineList.add(""); 669 lineList.add(indent + "try"); 670 lineList.add(indent + '{'); 671 lineList.add(indent + " ExtendedResult " + requestID + 672 "Result = connection.processExtendedOperation(" + requestID + 673 "Request);"); 674 lineList.add(indent + " // The extended operation was processed and " + 675 "we have a result."); 676 lineList.add(indent + " // This does not necessarily mean that the " + 677 "operation was successful."); 678 lineList.add(indent + " // Examine the result details for more " + 679 "information."); 680 lineList.add(indent + " ResultCode resultCode = " + requestID + 681 "Result.getResultCode();"); 682 lineList.add(indent + " String message = " + requestID + 683 "Result.getMessage();"); 684 lineList.add(indent + " String matchedDN = " + requestID + 685 "Result.getMatchedDN();"); 686 lineList.add(indent + " String[] referralURLs = " + requestID + 687 "Result.getReferralURLs();"); 688 lineList.add(indent + " String responseOID = " + requestID + 689 "Result.getOID();"); 690 lineList.add(indent + " ASN1OctetString responseValue = " + requestID + 691 "Result.getValue();"); 692 lineList.add(indent + " Control[] responseControls = " + requestID + 693 "Result.getResponseControls();"); 694 lineList.add(indent + '}'); 695 lineList.add(indent + "catch (LDAPException e)"); 696 lineList.add(indent + '{'); 697 lineList.add(indent + " // A problem was encountered while attempting " + 698 "to process the extended operation."); 699 lineList.add(indent + " // Maybe the following will help explain why."); 700 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 701 lineList.add(indent + " String message = e.getMessage();"); 702 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 703 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 704 lineList.add(indent + " Control[] responseControls = " + 705 "e.getResponseControls();"); 706 lineList.add(indent + '}'); 707 } 708 } 709}