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.Extensible;
037 import com.unboundid.util.InternalUseOnly;
038 import com.unboundid.util.NotMutable;
039 import com.unboundid.util.ThreadSafety;
040 import com.unboundid.util.ThreadSafetyLevel;
041
042 import static com.unboundid.ldap.sdk.LDAPMessages.*;
043 import static com.unboundid.util.Debug.*;
044 import static com.unboundid.util.StaticUtils.*;
045 import 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)
061 public 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 }