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