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.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 }