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