001/*
002 * Copyright 2007-2017 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 Ping Identity Corporation
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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.ArrayList;
026import java.util.List;
027import java.util.concurrent.LinkedBlockingQueue;
028import java.util.concurrent.TimeUnit;
029
030import com.unboundid.asn1.ASN1Buffer;
031import com.unboundid.asn1.ASN1BufferSequence;
032import com.unboundid.asn1.ASN1Element;
033import com.unboundid.asn1.ASN1OctetString;
034import com.unboundid.asn1.ASN1Sequence;
035import com.unboundid.ldap.protocol.LDAPMessage;
036import com.unboundid.ldap.protocol.LDAPResponse;
037import com.unboundid.ldap.protocol.ProtocolOp;
038import com.unboundid.util.Extensible;
039import com.unboundid.util.InternalUseOnly;
040import com.unboundid.util.NotMutable;
041import com.unboundid.util.ThreadSafety;
042import com.unboundid.util.ThreadSafetyLevel;
043
044import static com.unboundid.ldap.sdk.LDAPMessages.*;
045import static com.unboundid.util.Debug.*;
046import static com.unboundid.util.StaticUtils.*;
047import 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)
063public 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  @Override()
243  public final byte getProtocolOpType()
244  {
245    return LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST;
246  }
247
248
249
250  /**
251   * {@inheritDoc}
252   */
253  @Override()
254  public final void writeTo(final ASN1Buffer writer)
255  {
256    final ASN1BufferSequence requestSequence =
257         writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST);
258    writer.addOctetString(TYPE_EXTENDED_REQUEST_OID, oid);
259
260    if (value != null)
261    {
262      writer.addOctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue());
263    }
264    requestSequence.end();
265  }
266
267
268
269  /**
270   * Encodes the extended request protocol op to an ASN.1 element.
271   *
272   * @return  The ASN.1 element with the encoded extended request protocol op.
273   */
274  @Override()
275  public ASN1Element encodeProtocolOp()
276  {
277    // Create the extended request protocol op.
278    final ASN1Element[] protocolOpElements;
279    if (value == null)
280    {
281      protocolOpElements = new ASN1Element[]
282      {
283        new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid)
284      };
285    }
286    else
287    {
288      protocolOpElements = new ASN1Element[]
289      {
290        new ASN1OctetString(TYPE_EXTENDED_REQUEST_OID, oid),
291        new ASN1OctetString(TYPE_EXTENDED_REQUEST_VALUE, value.getValue())
292      };
293    }
294
295    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_EXTENDED_REQUEST,
296                            protocolOpElements);
297  }
298
299
300
301  /**
302   * Sends this extended request to the directory server over the provided
303   * connection and returns the associated response.
304   *
305   * @param  connection  The connection to use to communicate with the directory
306   *                     server.
307   * @param  depth       The current referral depth for this request.  It should
308   *                     always be one for the initial request, and should only
309   *                     be incremented when following referrals.
310   *
311   * @return  An LDAP result object that provides information about the result
312   *          of the extended operation processing.
313   *
314   * @throws  LDAPException  If a problem occurs while sending the request or
315   *                         reading the response.
316   */
317  @Override()
318  protected ExtendedResult process(final LDAPConnection connection,
319                                   final int depth)
320            throws LDAPException
321  {
322    if (connection.synchronousMode())
323    {
324      return processSync(connection);
325    }
326
327    // Create the LDAP message.
328    messageID = connection.nextMessageID();
329    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
330
331
332    // Register with the connection reader to be notified of responses for the
333    // request that we've created.
334    connection.registerResponseAcceptor(messageID, this);
335
336
337    try
338    {
339      // Send the request to the server.
340      debugLDAPRequest(this);
341      final long requestTime = System.nanoTime();
342      connection.getConnectionStatistics().incrementNumExtendedRequests();
343      connection.sendMessage(message);
344
345      // Wait for and process the response.
346      final LDAPResponse response;
347      try
348      {
349        final long responseTimeout = getResponseTimeoutMillis(connection);
350        if (responseTimeout > 0)
351        {
352          response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
353        }
354        else
355        {
356          response = responseQueue.take();
357        }
358      }
359      catch (final InterruptedException ie)
360      {
361        debugException(ie);
362        Thread.currentThread().interrupt();
363        throw new LDAPException(ResultCode.LOCAL_ERROR,
364             ERR_EXTOP_INTERRUPTED.get(connection.getHostPort()), ie);
365      }
366
367      return handleResponse(connection, response, requestTime);
368    }
369    finally
370    {
371      connection.deregisterResponseAcceptor(messageID);
372    }
373  }
374
375
376
377  /**
378   * Processes this extended operation in synchronous mode, in which the same
379   * thread will send the request and read the response.
380   *
381   * @param  connection  The connection to use to communicate with the directory
382   *                     server.
383   *
384   * @return  An LDAP result object that provides information about the result
385   *          of the extended processing.
386   *
387   * @throws  LDAPException  If a problem occurs while sending the request or
388   *                         reading the response.
389   */
390  private ExtendedResult processSync(final LDAPConnection connection)
391          throws LDAPException
392  {
393    // Create the LDAP message.
394    messageID = connection.nextMessageID();
395    final LDAPMessage message =
396         new LDAPMessage(messageID,  this, getControls());
397
398
399    // Set the appropriate timeout on the socket.
400    try
401    {
402      connection.getConnectionInternals(true).getSocket().setSoTimeout(
403           (int) getResponseTimeoutMillis(connection));
404    }
405    catch (final Exception e)
406    {
407      debugException(e);
408    }
409
410
411    // Send the request to the server.
412    final long requestTime = System.nanoTime();
413    debugLDAPRequest(this);
414    connection.getConnectionStatistics().incrementNumExtendedRequests();
415    connection.sendMessage(message);
416
417    while (true)
418    {
419      final LDAPResponse response;
420      try
421      {
422        response = connection.readResponse(messageID);
423      }
424      catch (final LDAPException le)
425      {
426        debugException(le);
427
428        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
429            connection.getConnectionOptions().abandonOnTimeout())
430        {
431          connection.abandon(messageID);
432        }
433
434        throw le;
435      }
436
437      if (response instanceof IntermediateResponse)
438      {
439        final IntermediateResponseListener listener =
440             getIntermediateResponseListener();
441        if (listener != null)
442        {
443          listener.intermediateResponseReturned(
444               (IntermediateResponse) response);
445        }
446      }
447      else
448      {
449        return handleResponse(connection, response, requestTime);
450      }
451    }
452  }
453
454
455
456  /**
457   * Performs the necessary processing for handling a response.
458   *
459   * @param  connection   The connection used to read the response.
460   * @param  response     The response to be processed.
461   * @param  requestTime  The time the request was sent to the server.
462   *
463   * @return  The extended result.
464   *
465   * @throws  LDAPException  If a problem occurs.
466   */
467  private ExtendedResult handleResponse(final LDAPConnection connection,
468                                        final LDAPResponse response,
469                                        final long requestTime)
470          throws LDAPException
471  {
472    if (response == null)
473    {
474      final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
475      if (connection.getConnectionOptions().abandonOnTimeout())
476      {
477        connection.abandon(messageID);
478      }
479
480      throw new LDAPException(ResultCode.TIMEOUT,
481           ERR_EXTENDED_CLIENT_TIMEOUT.get(waitTime, messageID, oid,
482                connection.getHostPort()));
483    }
484
485    if (response instanceof ConnectionClosedResponse)
486    {
487      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
488      final String msg = ccr.getMessage();
489      if (msg == null)
490      {
491        // The connection was closed while waiting for the response.
492        throw new LDAPException(ccr.getResultCode(),
493             ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE.get(
494                  connection.getHostPort(), toString()));
495      }
496      else
497      {
498        // The connection was closed while waiting for the response.
499        throw new LDAPException(ccr.getResultCode(),
500             ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE_WITH_MESSAGE.get(
501                  connection.getHostPort(), toString(), msg));
502      }
503    }
504
505    connection.getConnectionStatistics().incrementNumExtendedResponses(
506         System.nanoTime() - requestTime);
507    return (ExtendedResult) response;
508  }
509
510
511
512  /**
513   * {@inheritDoc}
514   */
515  @InternalUseOnly()
516  @Override()
517  public final void responseReceived(final LDAPResponse response)
518         throws LDAPException
519  {
520    try
521    {
522      responseQueue.put(response);
523    }
524    catch (final Exception e)
525    {
526      debugException(e);
527
528      if (e instanceof InterruptedException)
529      {
530        Thread.currentThread().interrupt();
531      }
532
533      throw new LDAPException(ResultCode.LOCAL_ERROR,
534           ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
535    }
536  }
537
538
539
540  /**
541   * {@inheritDoc}
542   */
543  @Override()
544  public final int getLastMessageID()
545  {
546    return messageID;
547  }
548
549
550
551  /**
552   * {@inheritDoc}
553   */
554  @Override()
555  public final OperationType getOperationType()
556  {
557    return OperationType.EXTENDED;
558  }
559
560
561
562  /**
563   * {@inheritDoc}.  Subclasses should override this method to return a
564   * duplicate of the appropriate type.
565   */
566  @Override()
567  public ExtendedRequest duplicate()
568  {
569    return duplicate(getControls());
570  }
571
572
573
574  /**
575   * {@inheritDoc}.  Subclasses should override this method to return a
576   * duplicate of the appropriate type.
577   */
578  @Override()
579  public ExtendedRequest duplicate(final Control[] controls)
580  {
581    final ExtendedRequest r = new ExtendedRequest(oid, value, controls);
582    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
583    return r;
584  }
585
586
587
588  /**
589   * Retrieves the user-friendly name for the extended request, if available.
590   * If no user-friendly name has been defined, then the OID will be returned.
591   *
592   * @return  The user-friendly name for this extended request, or the OID if no
593   *          user-friendly name is available.
594   */
595  public String getExtendedRequestName()
596  {
597    // By default, we will return the OID.  Subclasses should override this to
598    // provide the user-friendly name.
599    return oid;
600  }
601
602
603
604  /**
605   * {@inheritDoc}
606   */
607  @Override()
608  public void toString(final StringBuilder buffer)
609  {
610    buffer.append("ExtendedRequest(oid='");
611    buffer.append(oid);
612    buffer.append('\'');
613
614    final Control[] controls = getControls();
615    if (controls.length > 0)
616    {
617      buffer.append(", controls={");
618      for (int i=0; i < controls.length; i++)
619      {
620        if (i > 0)
621        {
622          buffer.append(", ");
623        }
624
625        buffer.append(controls[i]);
626      }
627      buffer.append('}');
628    }
629
630    buffer.append(')');
631  }
632
633
634
635  /**
636   * {@inheritDoc}
637   */
638  @Override()
639  public void toCode(final List<String> lineList, final String requestID,
640                     final int indentSpaces, final boolean includeProcessing)
641  {
642    // Create the request variable.
643    final ArrayList<ToCodeArgHelper> constructorArgs =
644         new ArrayList<ToCodeArgHelper>(3);
645    constructorArgs.add(ToCodeArgHelper.createString(oid, "Request OID"));
646    constructorArgs.add(ToCodeArgHelper.createASN1OctetString(value,
647         "Request Value"));
648
649    final Control[] controls = getControls();
650    if (controls.length > 0)
651    {
652      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
653           "Request Controls"));
654    }
655
656    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ExtendedRequest",
657         requestID + "Request", "new ExtendedRequest", constructorArgs);
658
659
660    // Add lines for processing the request and obtaining the result.
661    if (includeProcessing)
662    {
663      // Generate a string with the appropriate indent.
664      final StringBuilder buffer = new StringBuilder();
665      for (int i=0; i < indentSpaces; i++)
666      {
667        buffer.append(' ');
668      }
669      final String indent = buffer.toString();
670
671      lineList.add("");
672      lineList.add(indent + "try");
673      lineList.add(indent + '{');
674      lineList.add(indent + "  ExtendedResult " + requestID +
675           "Result = connection.processExtendedOperation(" + requestID +
676           "Request);");
677      lineList.add(indent + "  // The extended operation was processed and " +
678           "we have a result.");
679      lineList.add(indent + "  // This does not necessarily mean that the " +
680           "operation was successful.");
681      lineList.add(indent + "  // Examine the result details for more " +
682           "information.");
683      lineList.add(indent + "  ResultCode resultCode = " + requestID +
684           "Result.getResultCode();");
685      lineList.add(indent + "  String message = " + requestID +
686           "Result.getMessage();");
687      lineList.add(indent + "  String matchedDN = " + requestID +
688           "Result.getMatchedDN();");
689      lineList.add(indent + "  String[] referralURLs = " + requestID +
690           "Result.getReferralURLs();");
691      lineList.add(indent + "  String responseOID = " + requestID +
692           "Result.getOID();");
693      lineList.add(indent + "  ASN1OctetString responseValue = " + requestID +
694           "Result.getValue();");
695      lineList.add(indent + "  Control[] responseControls = " + requestID +
696           "Result.getResponseControls();");
697      lineList.add(indent + '}');
698      lineList.add(indent + "catch (LDAPException e)");
699      lineList.add(indent + '{');
700      lineList.add(indent + "  // A problem was encountered while attempting " +
701           "to process the extended operation.");
702      lineList.add(indent + "  // Maybe the following will help explain why.");
703      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
704      lineList.add(indent + "  String message = e.getMessage();");
705      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
706      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
707      lineList.add(indent + "  Control[] responseControls = " +
708           "e.getResponseControls();");
709      lineList.add(indent + '}');
710    }
711  }
712}