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