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