001/* 002 * Copyright 2007-2016 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2016 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 throw new LDAPException(ResultCode.LOCAL_ERROR, 360 ERR_EXTOP_INTERRUPTED.get(connection.getHostPort()), ie); 361 } 362 363 return handleResponse(connection, response, requestTime); 364 } 365 finally 366 { 367 connection.deregisterResponseAcceptor(messageID); 368 } 369 } 370 371 372 373 /** 374 * Processes this extended operation in synchronous mode, in which the same 375 * thread will send the request and read the response. 376 * 377 * @param connection The connection to use to communicate with the directory 378 * server. 379 * 380 * @return An LDAP result object that provides information about the result 381 * of the extended processing. 382 * 383 * @throws LDAPException If a problem occurs while sending the request or 384 * reading the response. 385 */ 386 private ExtendedResult processSync(final LDAPConnection connection) 387 throws LDAPException 388 { 389 // Create the LDAP message. 390 messageID = connection.nextMessageID(); 391 final LDAPMessage message = 392 new LDAPMessage(messageID, this, getControls()); 393 394 395 // Set the appropriate timeout on the socket. 396 try 397 { 398 connection.getConnectionInternals(true).getSocket().setSoTimeout( 399 (int) getResponseTimeoutMillis(connection)); 400 } 401 catch (Exception e) 402 { 403 debugException(e); 404 } 405 406 407 // Send the request to the server. 408 final long requestTime = System.nanoTime(); 409 debugLDAPRequest(this); 410 connection.getConnectionStatistics().incrementNumExtendedRequests(); 411 connection.sendMessage(message); 412 413 while (true) 414 { 415 final LDAPResponse response; 416 try 417 { 418 response = connection.readResponse(messageID); 419 } 420 catch (final LDAPException le) 421 { 422 debugException(le); 423 424 if ((le.getResultCode() == ResultCode.TIMEOUT) && 425 connection.getConnectionOptions().abandonOnTimeout()) 426 { 427 connection.abandon(messageID); 428 } 429 430 throw le; 431 } 432 433 if (response instanceof IntermediateResponse) 434 { 435 final IntermediateResponseListener listener = 436 getIntermediateResponseListener(); 437 if (listener != null) 438 { 439 listener.intermediateResponseReturned( 440 (IntermediateResponse) response); 441 } 442 } 443 else 444 { 445 return handleResponse(connection, response, requestTime); 446 } 447 } 448 } 449 450 451 452 /** 453 * Performs the necessary processing for handling a response. 454 * 455 * @param connection The connection used to read the response. 456 * @param response The response to be processed. 457 * @param requestTime The time the request was sent to the server. 458 * 459 * @return The extended result. 460 * 461 * @throws LDAPException If a problem occurs. 462 */ 463 private ExtendedResult handleResponse(final LDAPConnection connection, 464 final LDAPResponse response, 465 final long requestTime) 466 throws LDAPException 467 { 468 if (response == null) 469 { 470 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 471 if (connection.getConnectionOptions().abandonOnTimeout()) 472 { 473 connection.abandon(messageID); 474 } 475 476 throw new LDAPException(ResultCode.TIMEOUT, 477 ERR_EXTENDED_CLIENT_TIMEOUT.get(waitTime, messageID, oid, 478 connection.getHostPort())); 479 } 480 481 if (response instanceof ConnectionClosedResponse) 482 { 483 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 484 final String msg = ccr.getMessage(); 485 if (msg == null) 486 { 487 // The connection was closed while waiting for the response. 488 throw new LDAPException(ccr.getResultCode(), 489 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE.get( 490 connection.getHostPort(), toString())); 491 } 492 else 493 { 494 // The connection was closed while waiting for the response. 495 throw new LDAPException(ccr.getResultCode(), 496 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE_WITH_MESSAGE.get( 497 connection.getHostPort(), toString(), msg)); 498 } 499 } 500 501 connection.getConnectionStatistics().incrementNumExtendedResponses( 502 System.nanoTime() - requestTime); 503 return (ExtendedResult) response; 504 } 505 506 507 508 /** 509 * {@inheritDoc} 510 */ 511 @InternalUseOnly() 512 public final void responseReceived(final LDAPResponse response) 513 throws LDAPException 514 { 515 try 516 { 517 responseQueue.put(response); 518 } 519 catch (Exception e) 520 { 521 debugException(e); 522 throw new LDAPException(ResultCode.LOCAL_ERROR, 523 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 524 } 525 } 526 527 528 529 /** 530 * {@inheritDoc} 531 */ 532 @Override() 533 public final int getLastMessageID() 534 { 535 return messageID; 536 } 537 538 539 540 /** 541 * {@inheritDoc} 542 */ 543 @Override() 544 public final OperationType getOperationType() 545 { 546 return OperationType.EXTENDED; 547 } 548 549 550 551 /** 552 * {@inheritDoc}. Subclasses should override this method to return a 553 * duplicate of the appropriate type. 554 */ 555 public ExtendedRequest duplicate() 556 { 557 return duplicate(getControls()); 558 } 559 560 561 562 /** 563 * {@inheritDoc}. Subclasses should override this method to return a 564 * duplicate of the appropriate type. 565 */ 566 public ExtendedRequest duplicate(final Control[] controls) 567 { 568 final ExtendedRequest r = new ExtendedRequest(oid, value, controls); 569 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 570 return r; 571 } 572 573 574 575 /** 576 * Retrieves the user-friendly name for the extended request, if available. 577 * If no user-friendly name has been defined, then the OID will be returned. 578 * 579 * @return The user-friendly name for this extended request, or the OID if no 580 * user-friendly name is available. 581 */ 582 public String getExtendedRequestName() 583 { 584 // By default, we will return the OID. Subclasses should override this to 585 // provide the user-friendly name. 586 return oid; 587 } 588 589 590 591 /** 592 * {@inheritDoc} 593 */ 594 @Override() 595 public void toString(final StringBuilder buffer) 596 { 597 buffer.append("ExtendedRequest(oid='"); 598 buffer.append(oid); 599 buffer.append('\''); 600 601 final Control[] controls = getControls(); 602 if (controls.length > 0) 603 { 604 buffer.append(", controls={"); 605 for (int i=0; i < controls.length; i++) 606 { 607 if (i > 0) 608 { 609 buffer.append(", "); 610 } 611 612 buffer.append(controls[i]); 613 } 614 buffer.append('}'); 615 } 616 617 buffer.append(')'); 618 } 619 620 621 622 /** 623 * {@inheritDoc} 624 */ 625 public void toCode(final List<String> lineList, final String requestID, 626 final int indentSpaces, final boolean includeProcessing) 627 { 628 // Create the request variable. 629 final ArrayList<ToCodeArgHelper> constructorArgs = 630 new ArrayList<ToCodeArgHelper>(3); 631 constructorArgs.add(ToCodeArgHelper.createString(oid, "Request OID")); 632 constructorArgs.add(ToCodeArgHelper.createASN1OctetString(value, 633 "Request Value")); 634 635 final Control[] controls = getControls(); 636 if (controls.length > 0) 637 { 638 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 639 "Request Controls")); 640 } 641 642 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ExtendedRequest", 643 requestID + "Request", "new ExtendedRequest", constructorArgs); 644 645 646 // Add lines for processing the request and obtaining the result. 647 if (includeProcessing) 648 { 649 // Generate a string with the appropriate indent. 650 final StringBuilder buffer = new StringBuilder(); 651 for (int i=0; i < indentSpaces; i++) 652 { 653 buffer.append(' '); 654 } 655 final String indent = buffer.toString(); 656 657 lineList.add(""); 658 lineList.add(indent + "try"); 659 lineList.add(indent + '{'); 660 lineList.add(indent + " ExtendedResult " + requestID + 661 "Result = connection.processExtendedOperation(" + requestID + 662 "Request);"); 663 lineList.add(indent + " // The extended operation was processed and " + 664 "we have a result."); 665 lineList.add(indent + " // This does not necessarily mean that the " + 666 "operation was successful."); 667 lineList.add(indent + " // Examine the result details for more " + 668 "information."); 669 lineList.add(indent + " ResultCode resultCode = " + requestID + 670 "Result.getResultCode();"); 671 lineList.add(indent + " String message = " + requestID + 672 "Result.getMessage();"); 673 lineList.add(indent + " String matchedDN = " + requestID + 674 "Result.getMatchedDN();"); 675 lineList.add(indent + " String[] referralURLs = " + requestID + 676 "Result.getReferralURLs();"); 677 lineList.add(indent + " String responseOID = " + requestID + 678 "Result.getOID();"); 679 lineList.add(indent + " ASN1OctetString responseValue = " + requestID + 680 "Result.getValue();"); 681 lineList.add(indent + " Control[] responseControls = " + requestID + 682 "Result.getResponseControls();"); 683 lineList.add(indent + '}'); 684 lineList.add(indent + "catch (LDAPException e)"); 685 lineList.add(indent + '{'); 686 lineList.add(indent + " // A problem was encountered while attempting " + 687 "to process the extended operation."); 688 lineList.add(indent + " // Maybe the following will help explain why."); 689 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 690 lineList.add(indent + " String message = e.getMessage();"); 691 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 692 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 693 lineList.add(indent + " Control[] responseControls = " + 694 "e.getResponseControls();"); 695 lineList.add(indent + '}'); 696 } 697 } 698}