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 }