001    /*
002     * Copyright 2008-2014 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2014 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.io.Serializable;
026    import java.util.concurrent.ArrayBlockingQueue;
027    import java.util.concurrent.Future;
028    import java.util.concurrent.TimeoutException;
029    import java.util.concurrent.TimeUnit;
030    import java.util.concurrent.atomic.AtomicBoolean;
031    import java.util.concurrent.atomic.AtomicReference;
032    
033    import com.unboundid.util.Debug;
034    import com.unboundid.util.StaticUtils;
035    
036    import static com.unboundid.ldap.sdk.LDAPMessages.*;
037    
038    
039    
040    /**
041     * This class defines an object that provides information about a request that
042     * was initiated asynchronously.  It may be used to abandon or cancel the
043     * associated request.  This class also implements the
044     * {@code java.util.concurrent.Future} interface, so it may be used in that
045     * manner.
046     * <BR><BR>
047     * <H2>Example</H2>
048     * The following example initiates an asynchronous modify operation and then
049     * attempts to abandon it:
050     * <PRE>
051     * Modification mod = new Modification(ModificationType.REPLACE,
052     *      "description", "This is the new description.");
053     * ModifyRequest modifyRequest =
054     *      new ModifyRequest("dc=example,dc=com", mod);
055     *
056     * AsyncRequestID asyncRequestID =
057     *      connection.asyncModify(modifyRequest, myAsyncResultListener);
058     *
059     * // Assume that we've waited a reasonable amount of time but the modify
060     * // hasn't completed yet so we'll try to abandon it.
061     *
062     * connection.abandon(asyncRequestID);
063     * </PRE>
064     */
065    public final class AsyncRequestID
066           implements Serializable, Future<LDAPResult>
067    {
068      /**
069       * The serial version UID for this serializable class.
070       */
071      private static final long serialVersionUID = 8244005138437962030L;
072    
073    
074    
075      // The queue used to receive the result for the associated operation.
076      private final ArrayBlockingQueue<LDAPResult> resultQueue;
077    
078      // A flag indicating whether a request has been made to cancel the operation.
079      private final AtomicBoolean cancelRequested;
080    
081      // The result for the associated operation.
082      private final AtomicReference<LDAPResult> result;
083    
084      // The message ID for the request message.
085      private final int messageID;
086    
087      // The connection used to process the asynchronous operation.
088      private final LDAPConnection connection;
089    
090      // The timer task that will allow the associated request to be cancelled.
091      private volatile AsyncTimeoutTimerTask timerTask;
092    
093    
094    
095      /**
096       * Creates a new async request ID with the provided message ID.
097       *
098       * @param  messageID   The message ID for the associated request.
099       * @param  connection  The connection used to process the asynchronous
100       *                     operation.
101       */
102      AsyncRequestID(final int messageID, final LDAPConnection connection)
103      {
104        this.messageID  = messageID;
105        this.connection = connection;
106    
107        resultQueue     = new ArrayBlockingQueue<LDAPResult>(1);
108        cancelRequested = new AtomicBoolean(false);
109        result          = new AtomicReference<LDAPResult>();
110        timerTask       = null;
111      }
112    
113    
114    
115      /**
116       * Retrieves the message ID for the associated request.
117       *
118       * @return  The message ID for the associated request.
119       */
120      public int getMessageID()
121      {
122        return messageID;
123      }
124    
125    
126    
127      /**
128       * Attempts to cancel the associated asynchronous operation operation.  This
129       * will cause an abandon request to be sent to the server for the associated
130       * request, but because there is no response to an abandon operation then
131       * there is no way that we can determine whether the operation was actually
132       * abandoned.
133       *
134       * @param  mayInterruptIfRunning  Indicates whether to interrupt the thread
135       *                                running the associated task.  This will be
136       *                                ignored.
137       *
138       * @return  {@code true} if an abandon request was sent to cancel the
139       *          associated operation, or {@code false} if it was not possible to
140       *          send an abandon request because the operation has already
141       *          completed, because an abandon request has already been sent, or
142       *          because an error occurred while trying to send the cancel request.
143       */
144      public boolean cancel(final boolean mayInterruptIfRunning)
145      {
146        // If the operation has already completed, then we can't cancel it.
147        if (isDone())
148        {
149          return false;
150        }
151    
152        // Try to send a request to cancel the operation.
153        try
154        {
155          cancelRequested.set(true);
156          result.compareAndSet(null,
157               new LDAPResult(messageID, ResultCode.USER_CANCELED,
158                    INFO_ASYNC_REQUEST_USER_CANCELED.get(), null,
159                    StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS));
160    
161          connection.abandon(this);
162        }
163        catch (final Exception e)
164        {
165          Debug.debugException(e);
166        }
167    
168        return true;
169      }
170    
171    
172    
173      /**
174       * Indicates whether an attempt has been made to cancel the associated
175       * operation before it completed.
176       *
177       * @return  {@code true} if an attempt was made to cancel the operation, or
178       *          {@code false} if no cancel attempt was made, or if the operation
179       *          completed before it could be canceled.
180       */
181      public boolean isCancelled()
182      {
183        return cancelRequested.get();
184      }
185    
186    
187    
188      /**
189       * Indicates whether the associated operation has completed, regardless of
190       * whether it completed normally, completed with an error, or was canceled
191       * before starting.
192       *
193       * @return  {@code true} if the associated operation has completed, or if an
194       *          attempt has been made to cancel it, or {@code false} if the
195       *          operation has not yet completed and no cancel attempt has been
196       *          made.
197       */
198      public boolean isDone()
199      {
200        if (cancelRequested.get())
201        {
202          return true;
203        }
204    
205        if (result.get() != null)
206        {
207          return true;
208        }
209    
210        final LDAPResult newResult = resultQueue.poll();
211        if (newResult != null)
212        {
213          result.set(newResult);
214          return true;
215        }
216    
217        return false;
218      }
219    
220    
221    
222      /**
223       * Attempts to get the result for the associated operation, waiting if
224       * necessary for it to complete.  Note that this method will differ from the
225       * behavior defined in the {@code java.util.concurrent.Future} API in that it
226       * will not wait forever.  Rather, it will wait for no more than the length of
227       * time specified as the maximum response time defined in the connection
228       * options for the connection used to send the asynchronous request.  This is
229       * necessary because the operation may have been abandoned or otherwise
230       * interrupted, or the associated connection may have become invalidated, in
231       * a way that the LDAP SDK cannot detect.
232       *
233       * @return  The result for the associated operation.  If the operation has
234       *          been canceled, or if no result has been received within the
235       *          response timeout period, then a generated response will be
236       *          returned.
237       *
238       * @throws  InterruptedException  If the thread calling this method was
239       *                                interrupted before a result was received.
240       */
241      public LDAPResult get()
242             throws InterruptedException
243      {
244        final long maxWaitTime =
245             connection.getConnectionOptions().getResponseTimeoutMillis();
246    
247        try
248        {
249          return get(maxWaitTime, TimeUnit.MILLISECONDS);
250        }
251        catch (final TimeoutException te)
252        {
253          Debug.debugException(te);
254          return new LDAPResult(messageID, ResultCode.TIMEOUT, te.getMessage(),
255               null, StaticUtils.NO_STRINGS, StaticUtils.NO_CONTROLS);
256        }
257      }
258    
259    
260    
261      /**
262       * Attempts to get the result for the associated operation, waiting if
263       * necessary for up to the specified length of time for the operation to
264       * complete.
265       *
266       * @param  timeout   The maximum length of time to wait for the response.
267       * @param  timeUnit  The time unit for the provided {@code timeout} value.
268       *
269       * @return  The result for the associated operation.  If the operation has
270       *          been canceled, then a generated response will be returned.
271       *
272       * @throws  InterruptedException  If the thread calling this method was
273       *                                interrupted before a result was received.
274       *
275       * @throws  TimeoutException  If a timeout was encountered before the result
276       *                            could be obtained.
277       */
278      public LDAPResult get(final long timeout, final TimeUnit timeUnit)
279             throws InterruptedException, TimeoutException
280      {
281        final LDAPResult newResult = resultQueue.poll();
282        if (newResult != null)
283        {
284          result.set(newResult);
285          return newResult;
286        }
287    
288        final LDAPResult previousResult = result.get();
289        if (previousResult != null)
290        {
291          return previousResult;
292        }
293    
294        final LDAPResult resultAfterWaiting = resultQueue.poll(timeout, timeUnit);
295        if (resultAfterWaiting == null)
296        {
297          final long timeoutMillis = timeUnit.toMillis(timeout);
298          throw new TimeoutException(
299               WARN_ASYNC_REQUEST_GET_TIMEOUT.get(timeoutMillis));
300        }
301        else
302        {
303          result.set(resultAfterWaiting);
304          return resultAfterWaiting;
305        }
306      }
307    
308    
309    
310      /**
311       * Sets the timer task that may be used to cancel this result after a period
312       * of time.
313       *
314       * @param  timerTask  The timer task that may be used to cancel this result
315       *                    after a period of time.  It may be {@code null} if no
316       *                    timer task should be used.
317       */
318      void setTimerTask(final AsyncTimeoutTimerTask timerTask)
319      {
320        this.timerTask = timerTask;
321      }
322    
323    
324    
325      /**
326       * Sets the result for the associated operation.
327       *
328       * @param  result  The result for the associated operation.  It must not be
329       *                 {@code null}.
330       */
331      void setResult(final LDAPResult result)
332      {
333        resultQueue.offer(result);
334    
335        final AsyncTimeoutTimerTask t = timerTask;
336        if (t != null)
337        {
338          t.cancel();
339          connection.getTimer().purge();
340          timerTask = null;
341        }
342      }
343    
344    
345    
346      /**
347       * Retrieves a hash code for this async request ID.
348       *
349       * @return  A hash code for this async request ID.
350       */
351      @Override()
352      public int hashCode()
353      {
354        return messageID;
355      }
356    
357    
358    
359      /**
360       * Indicates whether the provided object is equal to this async request ID.
361       *
362       * @param  o  The object for which to make the determination.
363       *
364       * @return  {@code true} if the provided object is equal to this async request
365       *          ID, or {@code false} if not.
366       */
367      @Override()
368      public boolean equals(final Object o)
369      {
370        if (o == null)
371        {
372          return false;
373        }
374    
375        if (o == this)
376        {
377          return true;
378        }
379    
380        if (o instanceof AsyncRequestID)
381        {
382          return (((AsyncRequestID) o).messageID == messageID);
383        }
384        else
385        {
386          return false;
387        }
388      }
389    
390    
391    
392      /**
393       * Retrieves a string representation of this async request ID.
394       *
395       * @return  A string representation of this async request ID.
396       */
397      @Override()
398      public String toString()
399      {
400        return "AsyncRequestID(messageID=" + messageID + ')';
401      }
402    }