001 /*
002 * Copyright 2008-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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 }