001/* 002 * Copyright 2009-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2017 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.migrate.ldapjdk; 022 023 024 025import java.util.Enumeration; 026import java.util.NoSuchElementException; 027import java.util.concurrent.LinkedBlockingQueue; 028import java.util.concurrent.TimeUnit; 029import java.util.concurrent.atomic.AtomicBoolean; 030import java.util.concurrent.atomic.AtomicInteger; 031import java.util.concurrent.atomic.AtomicReference; 032 033import com.unboundid.ldap.sdk.AsyncRequestID; 034import com.unboundid.ldap.sdk.AsyncSearchResultListener; 035import com.unboundid.ldap.sdk.Control; 036import com.unboundid.ldap.sdk.ResultCode; 037import com.unboundid.ldap.sdk.SearchResult; 038import com.unboundid.ldap.sdk.SearchResultEntry; 039import com.unboundid.ldap.sdk.SearchResultReference; 040import com.unboundid.util.InternalUseOnly; 041import com.unboundid.util.Mutable; 042import com.unboundid.util.NotExtensible; 043import com.unboundid.util.ThreadSafety; 044import com.unboundid.util.ThreadSafetyLevel; 045 046import static com.unboundid.util.Debug.*; 047 048 049 050/** 051 * This class provides a data structure that provides access to data returned 052 * in response to a search operation. 053 * <BR><BR> 054 * This class is primarily intended to be used in the process of updating 055 * applications which use the Netscape Directory SDK for Java to switch to or 056 * coexist with the UnboundID LDAP SDK for Java. For applications not written 057 * using the Netscape Directory SDK for Java, the {@link SearchResult} class 058 * should be used instead. 059 */ 060@Mutable() 061@NotExtensible() 062@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 063public class LDAPSearchResults 064 implements Enumeration<Object>, AsyncSearchResultListener 065{ 066 /** 067 * The serial version UID for this serializable class. 068 */ 069 private static final long serialVersionUID = 7884355145560496230L; 070 071 072 073 // The asynchronous request ID for these search results. 074 private volatile AsyncRequestID asyncRequestID; 075 076 // Indicates whether the search has been abandoned. 077 private final AtomicBoolean searchAbandoned; 078 079 // Indicates whether the end of the result set has been reached. 080 private final AtomicBoolean searchDone; 081 082 // The number of items that can be read immediately without blocking. 083 private final AtomicInteger count; 084 085 // The set of controls for the last result element returned. 086 private final AtomicReference<Control[]> lastControls; 087 088 // The next object to be returned. 089 private final AtomicReference<Object> nextResult; 090 091 // The search result done message for the search. 092 private final AtomicReference<SearchResult> searchResult; 093 094 // The maximum length of time in milliseconds to wait for a response. 095 private final long maxWaitTime; 096 097 // The queue used to hold results. 098 private final LinkedBlockingQueue<Object> resultQueue; 099 100 101 102 /** 103 * Creates a new LDAP search results object. 104 */ 105 public LDAPSearchResults() 106 { 107 this(0L); 108 } 109 110 111 112 /** 113 * Creates a new LDAP search results object with the specified maximum wait 114 * time. 115 * 116 * @param maxWaitTime The maximum wait time in milliseconds. 117 */ 118 public LDAPSearchResults(final long maxWaitTime) 119 { 120 this.maxWaitTime = maxWaitTime; 121 122 asyncRequestID = null; 123 searchAbandoned = new AtomicBoolean(false); 124 searchDone = new AtomicBoolean(false); 125 count = new AtomicInteger(0); 126 lastControls = new AtomicReference<Control[]>(); 127 nextResult = new AtomicReference<Object>(); 128 searchResult = new AtomicReference<SearchResult>(); 129 resultQueue = new LinkedBlockingQueue<Object>(50); 130 } 131 132 133 134 /** 135 * Indicates that this search request has been abandoned. 136 */ 137 void setAbandoned() 138 { 139 searchAbandoned.set(true); 140 } 141 142 143 144 /** 145 * Retrieves the asynchronous request ID for the associates search operation. 146 * 147 * @return The asynchronous request ID for the associates search operation. 148 */ 149 AsyncRequestID getAsyncRequestID() 150 { 151 return asyncRequestID; 152 } 153 154 155 156 /** 157 * Sets the asynchronous request ID for the associated search operation. 158 * 159 * @param asyncRequestID The asynchronous request ID for the associated 160 * search operation. 161 */ 162 void setAsyncRequestID(final AsyncRequestID asyncRequestID) 163 { 164 this.asyncRequestID = asyncRequestID; 165 } 166 167 168 169 /** 170 * Retrieves the next object returned from the server, if possible. When this 171 * method returns, then the {@code nextResult} reference will also contain the 172 * object that was returned. 173 * 174 * @return The next object returned from the server, or {@code null} if there 175 * are no more objects to return. 176 */ 177 private Object nextObject() 178 { 179 Object o = nextResult.get(); 180 if (o != null) 181 { 182 return o; 183 } 184 185 o = resultQueue.poll(); 186 if (o != null) 187 { 188 nextResult.set(o); 189 return o; 190 } 191 192 if (searchDone.get() || searchAbandoned.get()) 193 { 194 return null; 195 } 196 197 try 198 { 199 final long stopWaitTime; 200 if (maxWaitTime > 0L) 201 { 202 stopWaitTime = System.currentTimeMillis() + maxWaitTime; 203 } 204 else 205 { 206 stopWaitTime = Long.MAX_VALUE; 207 } 208 209 while ((! searchAbandoned.get()) && 210 (System.currentTimeMillis() < stopWaitTime)) 211 { 212 o = resultQueue.poll(100L, TimeUnit.MILLISECONDS); 213 if (o != null) 214 { 215 break; 216 } 217 } 218 219 if (o == null) 220 { 221 if (searchAbandoned.get()) 222 { 223 o = new SearchResult(-1, ResultCode.USER_CANCELED, null, null, null, 224 0, 0, null); 225 count.incrementAndGet(); 226 } 227 else 228 { 229 o = new SearchResult(-1, ResultCode.TIMEOUT, null, null, null, 0, 0, 230 null); 231 count.incrementAndGet(); 232 } 233 } 234 } 235 catch (Exception e) 236 { 237 debugException(e); 238 239 if (e instanceof InterruptedException) 240 { 241 Thread.currentThread().interrupt(); 242 } 243 244 o = new SearchResult(-1, ResultCode.USER_CANCELED, null, null, null, 0, 0, 245 null); 246 count.incrementAndGet(); 247 } 248 249 nextResult.set(o); 250 return o; 251 } 252 253 254 255 /** 256 * Indicates whether there are any more search results to return. 257 * 258 * @return {@code true} if there are more search results to return, or 259 * {@code false} if not. 260 */ 261 public boolean hasMoreElements() 262 { 263 final Object o = nextObject(); 264 if (o == null) 265 { 266 return false; 267 } 268 269 if (o instanceof SearchResult) 270 { 271 final SearchResult r = (SearchResult) o; 272 if (r.getResultCode().equals(ResultCode.SUCCESS)) 273 { 274 lastControls.set(r.getResponseControls()); 275 searchDone.set(true); 276 nextResult.set(null); 277 return false; 278 } 279 } 280 281 return true; 282 } 283 284 285 286 /** 287 * Retrieves the next element in the set of search results. 288 * 289 * @return The next element in the set of search results. 290 * 291 * @throws NoSuchElementException If there are no more results. 292 */ 293 public Object nextElement() 294 throws NoSuchElementException 295 { 296 final Object o = nextObject(); 297 if (o == null) 298 { 299 throw new NoSuchElementException(); 300 } 301 302 nextResult.set(null); 303 count.decrementAndGet(); 304 305 if (o instanceof SearchResultEntry) 306 { 307 final SearchResultEntry e = (SearchResultEntry) o; 308 lastControls.set(e.getControls()); 309 return new LDAPEntry(e); 310 } 311 else if (o instanceof SearchResultReference) 312 { 313 final SearchResultReference r = (SearchResultReference) o; 314 lastControls.set(r.getControls()); 315 return new LDAPReferralException(r); 316 } 317 else 318 { 319 final SearchResult r = (SearchResult) o; 320 searchDone.set(true); 321 nextResult.set(null); 322 lastControls.set(r.getResponseControls()); 323 return new LDAPException(r.getDiagnosticMessage(), 324 r.getResultCode().intValue(), r.getDiagnosticMessage(), 325 r.getMatchedDN()); 326 } 327 } 328 329 330 331 /** 332 * Retrieves the next entry from the set of search results. 333 * 334 * @return The next entry from the set of search results. 335 * 336 * @throws LDAPException If there are no more elements to return, or if 337 * the next element in the set of results is not an 338 * entry. 339 */ 340 public LDAPEntry next() 341 throws LDAPException 342 { 343 if (! hasMoreElements()) 344 { 345 throw new LDAPException(null, ResultCode.NO_RESULTS_RETURNED_INT_VALUE); 346 } 347 348 final Object o = nextElement(); 349 if (o instanceof LDAPEntry) 350 { 351 return (LDAPEntry) o; 352 } 353 354 throw (LDAPException) o; 355 } 356 357 358 359 /** 360 * Retrieves the number of results that are available for immediate 361 * processing. 362 * 363 * @return The number of results that are available for immediate processing. 364 */ 365 public int getCount() 366 { 367 return count.get(); 368 } 369 370 371 372 /** 373 * Retrieves the response controls for the last result element returned, or 374 * for the search itself if the search has completed. 375 * 376 * @return The response controls for the last result element returned, or 377 * {@code null} if no elements have yet been returned or if the last 378 * element did not include any controls. 379 */ 380 public LDAPControl[] getResponseControls() 381 { 382 final Control[] controls = lastControls.get(); 383 if ((controls == null) || (controls.length == 0)) 384 { 385 return null; 386 } 387 388 return LDAPControl.toLDAPControls(controls); 389 } 390 391 392 393 /** 394 * {@inheritDoc} 395 */ 396 @InternalUseOnly() 397 public void searchEntryReturned(final SearchResultEntry searchEntry) 398 { 399 if (searchDone.get()) 400 { 401 return; 402 } 403 404 try 405 { 406 resultQueue.put(searchEntry); 407 count.incrementAndGet(); 408 } 409 catch (Exception e) 410 { 411 // This should never happen. 412 debugException(e); 413 414 if (e instanceof InterruptedException) 415 { 416 Thread.currentThread().interrupt(); 417 } 418 419 searchDone.set(true); 420 } 421 } 422 423 424 425 /** 426 * {@inheritDoc} 427 */ 428 @InternalUseOnly() 429 public void searchReferenceReturned( 430 final SearchResultReference searchReference) 431 { 432 if (searchDone.get()) 433 { 434 return; 435 } 436 437 try 438 { 439 resultQueue.put(searchReference); 440 count.incrementAndGet(); 441 } 442 catch (Exception e) 443 { 444 // This should never happen. 445 debugException(e); 446 447 if (e instanceof InterruptedException) 448 { 449 Thread.currentThread().interrupt(); 450 } 451 452 searchDone.set(true); 453 } 454 } 455 456 457 458 /** 459 * Indicates that the provided search result has been received in response to 460 * an asynchronous search operation. Note that automatic referral following 461 * is not supported for asynchronous operations, so it is possible that this 462 * result could include a referral. 463 * 464 * @param requestID The async request ID of the request for which the 465 * response was received. 466 * @param searchResult The search result that has been received. 467 */ 468 @InternalUseOnly() 469 public void searchResultReceived(final AsyncRequestID requestID, 470 final SearchResult searchResult) 471 { 472 if (searchDone.get()) 473 { 474 return; 475 } 476 477 try 478 { 479 resultQueue.put(searchResult); 480 if (! searchResult.getResultCode().equals(ResultCode.SUCCESS)) 481 { 482 count.incrementAndGet(); 483 } 484 } 485 catch (Exception e) 486 { 487 // This should never happen. 488 debugException(e); 489 490 if (e instanceof InterruptedException) 491 { 492 Thread.currentThread().interrupt(); 493 } 494 495 searchDone.set(true); 496 } 497 } 498}