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