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