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