001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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, 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    }