001    /*
002     * Copyright 2007-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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.Extensible;
037    import com.unboundid.util.InternalUseOnly;
038    import com.unboundid.util.NotMutable;
039    import com.unboundid.util.ThreadSafety;
040    import com.unboundid.util.ThreadSafetyLevel;
041    
042    import static com.unboundid.ldap.sdk.LDAPMessages.*;
043    import static com.unboundid.util.Debug.*;
044    import static com.unboundid.util.StaticUtils.*;
045    import 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)
061    public 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, connection.getHostPort()));
476        }
477    
478        if (response instanceof ConnectionClosedResponse)
479        {
480          final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
481          final String msg = ccr.getMessage();
482          if (msg == null)
483          {
484            // The connection was closed while waiting for the response.
485            throw new LDAPException(ccr.getResultCode(),
486                 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE.get(
487                      connection.getHostPort(), toString()));
488          }
489          else
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_WITH_MESSAGE.get(
494                      connection.getHostPort(), toString(), msg));
495          }
496        }
497    
498        connection.getConnectionStatistics().incrementNumExtendedResponses(
499             System.nanoTime() - requestTime);
500        return (ExtendedResult) response;
501      }
502    
503    
504    
505      /**
506       * {@inheritDoc}
507       */
508      @InternalUseOnly()
509      public final void responseReceived(final LDAPResponse response)
510             throws LDAPException
511      {
512        try
513        {
514          responseQueue.put(response);
515        }
516        catch (Exception e)
517        {
518          debugException(e);
519          throw new LDAPException(ResultCode.LOCAL_ERROR,
520               ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
521        }
522      }
523    
524    
525    
526      /**
527       * {@inheritDoc}
528       */
529      @Override()
530      public final int getLastMessageID()
531      {
532        return messageID;
533      }
534    
535    
536    
537      /**
538       * {@inheritDoc}
539       */
540      @Override()
541      public final OperationType getOperationType()
542      {
543        return OperationType.EXTENDED;
544      }
545    
546    
547    
548      /**
549       * {@inheritDoc}.  Subclasses should override this method to return a
550       * duplicate of the appropriate type.
551       */
552      public ExtendedRequest duplicate()
553      {
554        return duplicate(getControls());
555      }
556    
557    
558    
559      /**
560       * {@inheritDoc}.  Subclasses should override this method to return a
561       * duplicate of the appropriate type.
562       */
563      public ExtendedRequest duplicate(final Control[] controls)
564      {
565        final ExtendedRequest r = new ExtendedRequest(oid, value, controls);
566        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
567        return r;
568      }
569    
570    
571    
572      /**
573       * Retrieves the user-friendly name for the extended request, if available.
574       * If no user-friendly name has been defined, then the OID will be returned.
575       *
576       * @return  The user-friendly name for this extended request, or the OID if no
577       *          user-friendly name is available.
578       */
579      public String getExtendedRequestName()
580      {
581        // By default, we will return the OID.  Subclasses should override this to
582        // provide the user-friendly name.
583        return oid;
584      }
585    
586    
587    
588      /**
589       * {@inheritDoc}
590       */
591      @Override()
592      public void toString(final StringBuilder buffer)
593      {
594        buffer.append("ExtendedRequest(oid='");
595        buffer.append(oid);
596        buffer.append('\'');
597    
598        final Control[] controls = getControls();
599        if (controls.length > 0)
600        {
601          buffer.append(", controls={");
602          for (int i=0; i < controls.length; i++)
603          {
604            if (i > 0)
605            {
606              buffer.append(", ");
607            }
608    
609            buffer.append(controls[i]);
610          }
611          buffer.append('}');
612        }
613    
614        buffer.append(')');
615      }
616    }