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