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 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.io.Serializable; 026import java.util.concurrent.ArrayBlockingQueue; 027import java.util.concurrent.Future; 028import java.util.concurrent.TimeoutException; 029import java.util.concurrent.TimeUnit; 030import java.util.concurrent.atomic.AtomicBoolean; 031import java.util.concurrent.atomic.AtomicReference; 032 033import com.unboundid.util.Debug; 034import com.unboundid.util.NotMutable; 035import com.unboundid.util.StaticUtils; 036import com.unboundid.util.ThreadSafety; 037import com.unboundid.util.ThreadSafetyLevel; 038 039import 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) 070public 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}