001 /*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2017 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 Thread.currentThread().interrupt();
360 throw new LDAPException(ResultCode.LOCAL_ERROR,
361 ERR_EXTOP_INTERRUPTED.get(connection.getHostPort()), ie);
362 }
363
364 return handleResponse(connection, response, requestTime);
365 }
366 finally
367 {
368 connection.deregisterResponseAcceptor(messageID);
369 }
370 }
371
372
373
374 /**
375 * Processes this extended operation in synchronous mode, in which the same
376 * thread will send the request and read the response.
377 *
378 * @param connection The connection to use to communicate with the directory
379 * server.
380 *
381 * @return An LDAP result object that provides information about the result
382 * of the extended processing.
383 *
384 * @throws LDAPException If a problem occurs while sending the request or
385 * reading the response.
386 */
387 private ExtendedResult processSync(final LDAPConnection connection)
388 throws LDAPException
389 {
390 // Create the LDAP message.
391 messageID = connection.nextMessageID();
392 final LDAPMessage message =
393 new LDAPMessage(messageID, this, getControls());
394
395
396 // Set the appropriate timeout on the socket.
397 try
398 {
399 connection.getConnectionInternals(true).getSocket().setSoTimeout(
400 (int) getResponseTimeoutMillis(connection));
401 }
402 catch (Exception e)
403 {
404 debugException(e);
405 }
406
407
408 // Send the request to the server.
409 final long requestTime = System.nanoTime();
410 debugLDAPRequest(this);
411 connection.getConnectionStatistics().incrementNumExtendedRequests();
412 connection.sendMessage(message);
413
414 while (true)
415 {
416 final LDAPResponse response;
417 try
418 {
419 response = connection.readResponse(messageID);
420 }
421 catch (final LDAPException le)
422 {
423 debugException(le);
424
425 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
426 connection.getConnectionOptions().abandonOnTimeout())
427 {
428 connection.abandon(messageID);
429 }
430
431 throw le;
432 }
433
434 if (response instanceof IntermediateResponse)
435 {
436 final IntermediateResponseListener listener =
437 getIntermediateResponseListener();
438 if (listener != null)
439 {
440 listener.intermediateResponseReturned(
441 (IntermediateResponse) response);
442 }
443 }
444 else
445 {
446 return handleResponse(connection, response, requestTime);
447 }
448 }
449 }
450
451
452
453 /**
454 * Performs the necessary processing for handling a response.
455 *
456 * @param connection The connection used to read the response.
457 * @param response The response to be processed.
458 * @param requestTime The time the request was sent to the server.
459 *
460 * @return The extended result.
461 *
462 * @throws LDAPException If a problem occurs.
463 */
464 private ExtendedResult handleResponse(final LDAPConnection connection,
465 final LDAPResponse response,
466 final long requestTime)
467 throws LDAPException
468 {
469 if (response == null)
470 {
471 final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
472 if (connection.getConnectionOptions().abandonOnTimeout())
473 {
474 connection.abandon(messageID);
475 }
476
477 throw new LDAPException(ResultCode.TIMEOUT,
478 ERR_EXTENDED_CLIENT_TIMEOUT.get(waitTime, messageID, oid,
479 connection.getHostPort()));
480 }
481
482 if (response instanceof ConnectionClosedResponse)
483 {
484 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
485 final String msg = ccr.getMessage();
486 if (msg == null)
487 {
488 // The connection was closed while waiting for the response.
489 throw new LDAPException(ccr.getResultCode(),
490 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE.get(
491 connection.getHostPort(), toString()));
492 }
493 else
494 {
495 // The connection was closed while waiting for the response.
496 throw new LDAPException(ccr.getResultCode(),
497 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE_WITH_MESSAGE.get(
498 connection.getHostPort(), toString(), msg));
499 }
500 }
501
502 connection.getConnectionStatistics().incrementNumExtendedResponses(
503 System.nanoTime() - requestTime);
504 return (ExtendedResult) response;
505 }
506
507
508
509 /**
510 * {@inheritDoc}
511 */
512 @InternalUseOnly()
513 public final void responseReceived(final LDAPResponse response)
514 throws LDAPException
515 {
516 try
517 {
518 responseQueue.put(response);
519 }
520 catch (Exception e)
521 {
522 debugException(e);
523
524 if (e instanceof InterruptedException)
525 {
526 Thread.currentThread().interrupt();
527 }
528
529 throw new LDAPException(ResultCode.LOCAL_ERROR,
530 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
531 }
532 }
533
534
535
536 /**
537 * {@inheritDoc}
538 */
539 @Override()
540 public final int getLastMessageID()
541 {
542 return messageID;
543 }
544
545
546
547 /**
548 * {@inheritDoc}
549 */
550 @Override()
551 public final OperationType getOperationType()
552 {
553 return OperationType.EXTENDED;
554 }
555
556
557
558 /**
559 * {@inheritDoc}. Subclasses should override this method to return a
560 * duplicate of the appropriate type.
561 */
562 public ExtendedRequest duplicate()
563 {
564 return duplicate(getControls());
565 }
566
567
568
569 /**
570 * {@inheritDoc}. Subclasses should override this method to return a
571 * duplicate of the appropriate type.
572 */
573 public ExtendedRequest duplicate(final Control[] controls)
574 {
575 final ExtendedRequest r = new ExtendedRequest(oid, value, controls);
576 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
577 return r;
578 }
579
580
581
582 /**
583 * Retrieves the user-friendly name for the extended request, if available.
584 * If no user-friendly name has been defined, then the OID will be returned.
585 *
586 * @return The user-friendly name for this extended request, or the OID if no
587 * user-friendly name is available.
588 */
589 public String getExtendedRequestName()
590 {
591 // By default, we will return the OID. Subclasses should override this to
592 // provide the user-friendly name.
593 return oid;
594 }
595
596
597
598 /**
599 * {@inheritDoc}
600 */
601 @Override()
602 public void toString(final StringBuilder buffer)
603 {
604 buffer.append("ExtendedRequest(oid='");
605 buffer.append(oid);
606 buffer.append('\'');
607
608 final Control[] controls = getControls();
609 if (controls.length > 0)
610 {
611 buffer.append(", controls={");
612 for (int i=0; i < controls.length; i++)
613 {
614 if (i > 0)
615 {
616 buffer.append(", ");
617 }
618
619 buffer.append(controls[i]);
620 }
621 buffer.append('}');
622 }
623
624 buffer.append(')');
625 }
626
627
628
629 /**
630 * {@inheritDoc}
631 */
632 public void toCode(final List<String> lineList, final String requestID,
633 final int indentSpaces, final boolean includeProcessing)
634 {
635 // Create the request variable.
636 final ArrayList<ToCodeArgHelper> constructorArgs =
637 new ArrayList<ToCodeArgHelper>(3);
638 constructorArgs.add(ToCodeArgHelper.createString(oid, "Request OID"));
639 constructorArgs.add(ToCodeArgHelper.createASN1OctetString(value,
640 "Request Value"));
641
642 final Control[] controls = getControls();
643 if (controls.length > 0)
644 {
645 constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
646 "Request Controls"));
647 }
648
649 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ExtendedRequest",
650 requestID + "Request", "new ExtendedRequest", constructorArgs);
651
652
653 // Add lines for processing the request and obtaining the result.
654 if (includeProcessing)
655 {
656 // Generate a string with the appropriate indent.
657 final StringBuilder buffer = new StringBuilder();
658 for (int i=0; i < indentSpaces; i++)
659 {
660 buffer.append(' ');
661 }
662 final String indent = buffer.toString();
663
664 lineList.add("");
665 lineList.add(indent + "try");
666 lineList.add(indent + '{');
667 lineList.add(indent + " ExtendedResult " + requestID +
668 "Result = connection.processExtendedOperation(" + requestID +
669 "Request);");
670 lineList.add(indent + " // The extended operation was processed and " +
671 "we have a result.");
672 lineList.add(indent + " // This does not necessarily mean that the " +
673 "operation was successful.");
674 lineList.add(indent + " // Examine the result details for more " +
675 "information.");
676 lineList.add(indent + " ResultCode resultCode = " + requestID +
677 "Result.getResultCode();");
678 lineList.add(indent + " String message = " + requestID +
679 "Result.getMessage();");
680 lineList.add(indent + " String matchedDN = " + requestID +
681 "Result.getMatchedDN();");
682 lineList.add(indent + " String[] referralURLs = " + requestID +
683 "Result.getReferralURLs();");
684 lineList.add(indent + " String responseOID = " + requestID +
685 "Result.getOID();");
686 lineList.add(indent + " ASN1OctetString responseValue = " + requestID +
687 "Result.getValue();");
688 lineList.add(indent + " Control[] responseControls = " + requestID +
689 "Result.getResponseControls();");
690 lineList.add(indent + '}');
691 lineList.add(indent + "catch (LDAPException e)");
692 lineList.add(indent + '{');
693 lineList.add(indent + " // A problem was encountered while attempting " +
694 "to process the extended operation.");
695 lineList.add(indent + " // Maybe the following will help explain why.");
696 lineList.add(indent + " ResultCode resultCode = e.getResultCode();");
697 lineList.add(indent + " String message = e.getMessage();");
698 lineList.add(indent + " String matchedDN = e.getMatchedDN();");
699 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();");
700 lineList.add(indent + " Control[] responseControls = " +
701 "e.getResponseControls();");
702 lineList.add(indent + '}');
703 }
704 }
705 }