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