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