001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 UnboundID Corp. 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 public final byte getProtocolOpType() 243 { 244 return LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST; 245 } 246 247 248 249 /** 250 * {@inheritDoc} 251 */ 252 public final void writeTo(final ASN1Buffer writer) 253 { 254 final ASN1BufferSequence requestSequence = 255 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST); 256 writer.addOctetString(TYPE_EXTENDED_REQUEST_OID, oid); 257 258 if (value != null) 259 { 260 writer.addOctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue()); 261 } 262 requestSequence.end(); 263 } 264 265 266 267 /** 268 * Encodes the extended request protocol op to an ASN.1 element. 269 * 270 * @return The ASN.1 element with the encoded extended request protocol op. 271 */ 272 public ASN1Element encodeProtocolOp() 273 { 274 // Create the extended request protocol op. 275 final ASN1Element[] protocolOpElements; 276 if (value == null) 277 { 278 protocolOpElements = new ASN1Element[] 279 { 280 new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid) 281 }; 282 } 283 else 284 { 285 protocolOpElements = new ASN1Element[] 286 { 287 new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid), 288 new ASN1OctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue()) 289 }; 290 } 291 292 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST, 293 protocolOpElements); 294 } 295 296 297 298 /** 299 * Sends this extended request to the directory server over the provided 300 * connection and returns the associated response. 301 * 302 * @param connection The connection to use to communicate with the directory 303 * server. 304 * @param depth The current referral depth for this request. It should 305 * always be one for the initial request, and should only 306 * be incremented when following referrals. 307 * 308 * @return An LDAP result object that provides information about the result 309 * of the extended operation processing. 310 * 311 * @throws LDAPException If a problem occurs while sending the request or 312 * reading the response. 313 */ 314 @Override() 315 protected ExtendedResult process(final LDAPConnection connection, 316 final int depth) 317 throws LDAPException 318 { 319 if (connection.synchronousMode()) 320 { 321 return processSync(connection); 322 } 323 324 // Create the LDAP message. 325 messageID = connection.nextMessageID(); 326 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 327 328 329 // Register with the connection reader to be notified of responses for the 330 // request that we've created. 331 connection.registerResponseAcceptor(messageID, this); 332 333 334 try 335 { 336 // Send the request to the server. 337 debugLDAPRequest(this); 338 final long requestTime = System.nanoTime(); 339 connection.getConnectionStatistics().incrementNumExtendedRequests(); 340 connection.sendMessage(message); 341 342 // Wait for and process the response. 343 final LDAPResponse response; 344 try 345 { 346 final long responseTimeout = getResponseTimeoutMillis(connection); 347 if (responseTimeout > 0) 348 { 349 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 350 } 351 else 352 { 353 response = responseQueue.take(); 354 } 355 } 356 catch (InterruptedException ie) 357 { 358 debugException(ie); 359 Thread.currentThread().interrupt(); 360 throw new LDAPException(ResultCode.LOCAL_ERROR, 361 ERR_EXTOP_INTERRUPTED.get(connection.getHostPort()), ie); 362 } 363 364 return handleResponse(connection, response, requestTime); 365 } 366 finally 367 { 368 connection.deregisterResponseAcceptor(messageID); 369 } 370 } 371 372 373 374 /** 375 * Processes this extended operation in synchronous mode, in which the same 376 * thread will send the request and read the response. 377 * 378 * @param connection The connection to use to communicate with the directory 379 * server. 380 * 381 * @return An LDAP result object that provides information about the result 382 * of the extended processing. 383 * 384 * @throws LDAPException If a problem occurs while sending the request or 385 * reading the response. 386 */ 387 private ExtendedResult processSync(final LDAPConnection connection) 388 throws LDAPException 389 { 390 // Create the LDAP message. 391 messageID = connection.nextMessageID(); 392 final LDAPMessage message = 393 new LDAPMessage(messageID, this, getControls()); 394 395 396 // Set the appropriate timeout on the socket. 397 try 398 { 399 connection.getConnectionInternals(true).getSocket().setSoTimeout( 400 (int) getResponseTimeoutMillis(connection)); 401 } 402 catch (Exception e) 403 { 404 debugException(e); 405 } 406 407 408 // Send the request to the server. 409 final long requestTime = System.nanoTime(); 410 debugLDAPRequest(this); 411 connection.getConnectionStatistics().incrementNumExtendedRequests(); 412 connection.sendMessage(message); 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 public final void responseReceived(final LDAPResponse response) 514 throws LDAPException 515 { 516 try 517 { 518 responseQueue.put(response); 519 } 520 catch (Exception e) 521 { 522 debugException(e); 523 524 if (e instanceof InterruptedException) 525 { 526 Thread.currentThread().interrupt(); 527 } 528 529 throw new LDAPException(ResultCode.LOCAL_ERROR, 530 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 531 } 532 } 533 534 535 536 /** 537 * {@inheritDoc} 538 */ 539 @Override() 540 public final int getLastMessageID() 541 { 542 return messageID; 543 } 544 545 546 547 /** 548 * {@inheritDoc} 549 */ 550 @Override() 551 public final OperationType getOperationType() 552 { 553 return OperationType.EXTENDED; 554 } 555 556 557 558 /** 559 * {@inheritDoc}. Subclasses should override this method to return a 560 * duplicate of the appropriate type. 561 */ 562 public ExtendedRequest duplicate() 563 { 564 return duplicate(getControls()); 565 } 566 567 568 569 /** 570 * {@inheritDoc}. Subclasses should override this method to return a 571 * duplicate of the appropriate type. 572 */ 573 public ExtendedRequest duplicate(final Control[] controls) 574 { 575 final ExtendedRequest r = new ExtendedRequest(oid, value, controls); 576 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 577 return r; 578 } 579 580 581 582 /** 583 * Retrieves the user-friendly name for the extended request, if available. 584 * If no user-friendly name has been defined, then the OID will be returned. 585 * 586 * @return The user-friendly name for this extended request, or the OID if no 587 * user-friendly name is available. 588 */ 589 public String getExtendedRequestName() 590 { 591 // By default, we will return the OID. Subclasses should override this to 592 // provide the user-friendly name. 593 return oid; 594 } 595 596 597 598 /** 599 * {@inheritDoc} 600 */ 601 @Override() 602 public void toString(final StringBuilder buffer) 603 { 604 buffer.append("ExtendedRequest(oid='"); 605 buffer.append(oid); 606 buffer.append('\''); 607 608 final Control[] controls = getControls(); 609 if (controls.length > 0) 610 { 611 buffer.append(", controls={"); 612 for (int i=0; i < controls.length; i++) 613 { 614 if (i > 0) 615 { 616 buffer.append(", "); 617 } 618 619 buffer.append(controls[i]); 620 } 621 buffer.append('}'); 622 } 623 624 buffer.append(')'); 625 } 626 627 628 629 /** 630 * {@inheritDoc} 631 */ 632 public void toCode(final List<String> lineList, final String requestID, 633 final int indentSpaces, final boolean includeProcessing) 634 { 635 // Create the request variable. 636 final ArrayList<ToCodeArgHelper> constructorArgs = 637 new ArrayList<ToCodeArgHelper>(3); 638 constructorArgs.add(ToCodeArgHelper.createString(oid, "Request OID")); 639 constructorArgs.add(ToCodeArgHelper.createASN1OctetString(value, 640 "Request Value")); 641 642 final Control[] controls = getControls(); 643 if (controls.length > 0) 644 { 645 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 646 "Request Controls")); 647 } 648 649 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ExtendedRequest", 650 requestID + "Request", "new ExtendedRequest", constructorArgs); 651 652 653 // Add lines for processing the request and obtaining the result. 654 if (includeProcessing) 655 { 656 // Generate a string with the appropriate indent. 657 final StringBuilder buffer = new StringBuilder(); 658 for (int i=0; i < indentSpaces; i++) 659 { 660 buffer.append(' '); 661 } 662 final String indent = buffer.toString(); 663 664 lineList.add(""); 665 lineList.add(indent + "try"); 666 lineList.add(indent + '{'); 667 lineList.add(indent + " ExtendedResult " + requestID + 668 "Result = connection.processExtendedOperation(" + requestID + 669 "Request);"); 670 lineList.add(indent + " // The extended operation was processed and " + 671 "we have a result."); 672 lineList.add(indent + " // This does not necessarily mean that the " + 673 "operation was successful."); 674 lineList.add(indent + " // Examine the result details for more " + 675 "information."); 676 lineList.add(indent + " ResultCode resultCode = " + requestID + 677 "Result.getResultCode();"); 678 lineList.add(indent + " String message = " + requestID + 679 "Result.getMessage();"); 680 lineList.add(indent + " String matchedDN = " + requestID + 681 "Result.getMatchedDN();"); 682 lineList.add(indent + " String[] referralURLs = " + requestID + 683 "Result.getReferralURLs();"); 684 lineList.add(indent + " String responseOID = " + requestID + 685 "Result.getOID();"); 686 lineList.add(indent + " ASN1OctetString responseValue = " + requestID + 687 "Result.getValue();"); 688 lineList.add(indent + " Control[] responseControls = " + requestID + 689 "Result.getResponseControls();"); 690 lineList.add(indent + '}'); 691 lineList.add(indent + "catch (LDAPException e)"); 692 lineList.add(indent + '{'); 693 lineList.add(indent + " // A problem was encountered while attempting " + 694 "to process the extended operation."); 695 lineList.add(indent + " // Maybe the following will help explain why."); 696 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 697 lineList.add(indent + " String message = e.getMessage();"); 698 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 699 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 700 lineList.add(indent + " Control[] responseControls = " + 701 "e.getResponseControls();"); 702 lineList.add(indent + '}'); 703 } 704 } 705}