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