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.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            throw new LDAPException(ResultCode.LOCAL_ERROR,
360                 ERR_EXTOP_INTERRUPTED.get(connection.getHostPort()), ie);
361          }
362    
363          return handleResponse(connection, response, requestTime);
364        }
365        finally
366        {
367          connection.deregisterResponseAcceptor(messageID);
368        }
369      }
370    
371    
372    
373      /**
374       * Processes this extended operation in synchronous mode, in which the same
375       * thread will send the request and read the response.
376       *
377       * @param  connection  The connection to use to communicate with the directory
378       *                     server.
379       *
380       * @return  An LDAP result object that provides information about the result
381       *          of the extended processing.
382       *
383       * @throws  LDAPException  If a problem occurs while sending the request or
384       *                         reading the response.
385       */
386      private ExtendedResult processSync(final LDAPConnection connection)
387              throws LDAPException
388      {
389        // Create the LDAP message.
390        messageID = connection.nextMessageID();
391        final LDAPMessage message =
392             new LDAPMessage(messageID,  this, getControls());
393    
394    
395        // Set the appropriate timeout on the socket.
396        try
397        {
398          connection.getConnectionInternals(true).getSocket().setSoTimeout(
399               (int) getResponseTimeoutMillis(connection));
400        }
401        catch (Exception e)
402        {
403          debugException(e);
404        }
405    
406    
407        // Send the request to the server.
408        final long requestTime = System.nanoTime();
409        debugLDAPRequest(this);
410        connection.getConnectionStatistics().incrementNumExtendedRequests();
411        connection.sendMessage(message);
412    
413        while (true)
414        {
415          final LDAPResponse response;
416          try
417          {
418            response = connection.readResponse(messageID);
419          }
420          catch (final LDAPException le)
421          {
422            debugException(le);
423    
424            if ((le.getResultCode() == ResultCode.TIMEOUT) &&
425                connection.getConnectionOptions().abandonOnTimeout())
426            {
427              connection.abandon(messageID);
428            }
429    
430            throw le;
431          }
432    
433          if (response instanceof IntermediateResponse)
434          {
435            final IntermediateResponseListener listener =
436                 getIntermediateResponseListener();
437            if (listener != null)
438            {
439              listener.intermediateResponseReturned(
440                   (IntermediateResponse) response);
441            }
442          }
443          else
444          {
445            return handleResponse(connection, response, requestTime);
446          }
447        }
448      }
449    
450    
451    
452      /**
453       * Performs the necessary processing for handling a response.
454       *
455       * @param  connection   The connection used to read the response.
456       * @param  response     The response to be processed.
457       * @param  requestTime  The time the request was sent to the server.
458       *
459       * @return  The extended result.
460       *
461       * @throws  LDAPException  If a problem occurs.
462       */
463      private ExtendedResult handleResponse(final LDAPConnection connection,
464                                            final LDAPResponse response,
465                                            final long requestTime)
466              throws LDAPException
467      {
468        if (response == null)
469        {
470          final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
471          if (connection.getConnectionOptions().abandonOnTimeout())
472          {
473            connection.abandon(messageID);
474          }
475    
476          throw new LDAPException(ResultCode.TIMEOUT,
477               ERR_EXTENDED_CLIENT_TIMEOUT.get(waitTime, messageID, oid,
478                    connection.getHostPort()));
479        }
480    
481        if (response instanceof ConnectionClosedResponse)
482        {
483          final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
484          final String msg = ccr.getMessage();
485          if (msg == null)
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.get(
490                      connection.getHostPort(), toString()));
491          }
492          else
493          {
494            // The connection was closed while waiting for the response.
495            throw new LDAPException(ccr.getResultCode(),
496                 ERR_CONN_CLOSED_WAITING_FOR_EXTENDED_RESPONSE_WITH_MESSAGE.get(
497                      connection.getHostPort(), toString(), msg));
498          }
499        }
500    
501        connection.getConnectionStatistics().incrementNumExtendedResponses(
502             System.nanoTime() - requestTime);
503        return (ExtendedResult) response;
504      }
505    
506    
507    
508      /**
509       * {@inheritDoc}
510       */
511      @InternalUseOnly()
512      public final void responseReceived(final LDAPResponse response)
513             throws LDAPException
514      {
515        try
516        {
517          responseQueue.put(response);
518        }
519        catch (Exception e)
520        {
521          debugException(e);
522          throw new LDAPException(ResultCode.LOCAL_ERROR,
523               ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
524        }
525      }
526    
527    
528    
529      /**
530       * {@inheritDoc}
531       */
532      @Override()
533      public final int getLastMessageID()
534      {
535        return messageID;
536      }
537    
538    
539    
540      /**
541       * {@inheritDoc}
542       */
543      @Override()
544      public final OperationType getOperationType()
545      {
546        return OperationType.EXTENDED;
547      }
548    
549    
550    
551      /**
552       * {@inheritDoc}.  Subclasses should override this method to return a
553       * duplicate of the appropriate type.
554       */
555      public ExtendedRequest duplicate()
556      {
557        return duplicate(getControls());
558      }
559    
560    
561    
562      /**
563       * {@inheritDoc}.  Subclasses should override this method to return a
564       * duplicate of the appropriate type.
565       */
566      public ExtendedRequest duplicate(final Control[] controls)
567      {
568        final ExtendedRequest r = new ExtendedRequest(oid, value, controls);
569        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
570        return r;
571      }
572    
573    
574    
575      /**
576       * Retrieves the user-friendly name for the extended request, if available.
577       * If no user-friendly name has been defined, then the OID will be returned.
578       *
579       * @return  The user-friendly name for this extended request, or the OID if no
580       *          user-friendly name is available.
581       */
582      public String getExtendedRequestName()
583      {
584        // By default, we will return the OID.  Subclasses should override this to
585        // provide the user-friendly name.
586        return oid;
587      }
588    
589    
590    
591      /**
592       * {@inheritDoc}
593       */
594      @Override()
595      public void toString(final StringBuilder buffer)
596      {
597        buffer.append("ExtendedRequest(oid='");
598        buffer.append(oid);
599        buffer.append('\'');
600    
601        final Control[] controls = getControls();
602        if (controls.length > 0)
603        {
604          buffer.append(", controls={");
605          for (int i=0; i < controls.length; i++)
606          {
607            if (i > 0)
608            {
609              buffer.append(", ");
610            }
611    
612            buffer.append(controls[i]);
613          }
614          buffer.append('}');
615        }
616    
617        buffer.append(')');
618      }
619    
620    
621    
622      /**
623       * {@inheritDoc}
624       */
625      public void toCode(final List<String> lineList, final String requestID,
626                         final int indentSpaces, final boolean includeProcessing)
627      {
628        // Create the request variable.
629        final ArrayList<ToCodeArgHelper> constructorArgs =
630             new ArrayList<ToCodeArgHelper>(3);
631        constructorArgs.add(ToCodeArgHelper.createString(oid, "Request OID"));
632        constructorArgs.add(ToCodeArgHelper.createASN1OctetString(value,
633             "Request Value"));
634    
635        final Control[] controls = getControls();
636        if (controls.length > 0)
637        {
638          constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
639               "Request Controls"));
640        }
641    
642        ToCodeHelper.generateMethodCall(lineList, indentSpaces, "ExtendedRequest",
643             requestID + "Request", "new ExtendedRequest", constructorArgs);
644    
645    
646        // Add lines for processing the request and obtaining the result.
647        if (includeProcessing)
648        {
649          // Generate a string with the appropriate indent.
650          final StringBuilder buffer = new StringBuilder();
651          for (int i=0; i < indentSpaces; i++)
652          {
653            buffer.append(' ');
654          }
655          final String indent = buffer.toString();
656    
657          lineList.add("");
658          lineList.add(indent + "try");
659          lineList.add(indent + '{');
660          lineList.add(indent + "  ExtendedResult " + requestID +
661               "Result = connection.processExtendedOperation(" + requestID +
662               "Request);");
663          lineList.add(indent + "  // The extended operation was processed and " +
664               "we have a result.");
665          lineList.add(indent + "  // This does not necessarily mean that the " +
666               "operation was successful.");
667          lineList.add(indent + "  // Examine the result details for more " +
668               "information.");
669          lineList.add(indent + "  ResultCode resultCode = " + requestID +
670               "Result.getResultCode();");
671          lineList.add(indent + "  String message = " + requestID +
672               "Result.getMessage();");
673          lineList.add(indent + "  String matchedDN = " + requestID +
674               "Result.getMatchedDN();");
675          lineList.add(indent + "  String[] referralURLs = " + requestID +
676               "Result.getReferralURLs();");
677          lineList.add(indent + "  String responseOID = " + requestID +
678               "Result.getOID();");
679          lineList.add(indent + "  ASN1OctetString responseValue = " + requestID +
680               "Result.getValue();");
681          lineList.add(indent + "  Control[] responseControls = " + requestID +
682               "Result.getResponseControls();");
683          lineList.add(indent + '}');
684          lineList.add(indent + "catch (LDAPException e)");
685          lineList.add(indent + '{');
686          lineList.add(indent + "  // A problem was encountered while attempting " +
687               "to process the extended operation.");
688          lineList.add(indent + "  // Maybe the following will help explain why.");
689          lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
690          lineList.add(indent + "  String message = e.getMessage();");
691          lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
692          lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
693          lineList.add(indent + "  Control[] responseControls = " +
694               "e.getResponseControls();");
695          lineList.add(indent + '}');
696        }
697      }
698    }