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