001/* 002 * Copyright 2009-2016 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-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 */ 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 o = new SearchResult(-1, ResultCode.USER_CANCELED, null, null, null, 0, 0, 240 null); 241 count.incrementAndGet(); 242 } 243 244 nextResult.set(o); 245 return o; 246 } 247 248 249 250 /** 251 * Indicates whether there are any more search results to return. 252 * 253 * @return {@code true} if there are more search results to return, or 254 * {@code false} if not. 255 */ 256 public boolean hasMoreElements() 257 { 258 final Object o = nextObject(); 259 if (o == null) 260 { 261 return false; 262 } 263 264 if (o instanceof SearchResult) 265 { 266 final SearchResult r = (SearchResult) o; 267 if (r.getResultCode().equals(ResultCode.SUCCESS)) 268 { 269 lastControls.set(r.getResponseControls()); 270 searchDone.set(true); 271 nextResult.set(null); 272 return false; 273 } 274 } 275 276 return true; 277 } 278 279 280 281 /** 282 * Retrieves the next element in the set of search results. 283 * 284 * @return The next element in the set of search results. 285 * 286 * @throws NoSuchElementException If there are no more results. 287 */ 288 public Object nextElement() 289 throws NoSuchElementException 290 { 291 final Object o = nextObject(); 292 if (o == null) 293 { 294 throw new NoSuchElementException(); 295 } 296 297 nextResult.set(null); 298 count.decrementAndGet(); 299 300 if (o instanceof SearchResultEntry) 301 { 302 final SearchResultEntry e = (SearchResultEntry) o; 303 lastControls.set(e.getControls()); 304 return new LDAPEntry(e); 305 } 306 else if (o instanceof SearchResultReference) 307 { 308 final SearchResultReference r = (SearchResultReference) o; 309 lastControls.set(r.getControls()); 310 return new LDAPReferralException(r); 311 } 312 else 313 { 314 final SearchResult r = (SearchResult) o; 315 searchDone.set(true); 316 nextResult.set(null); 317 lastControls.set(r.getResponseControls()); 318 return new LDAPException(r.getDiagnosticMessage(), 319 r.getResultCode().intValue(), r.getDiagnosticMessage(), 320 r.getMatchedDN()); 321 } 322 } 323 324 325 326 /** 327 * Retrieves the next entry from the set of search results. 328 * 329 * @return The next entry from the set of search results. 330 * 331 * @throws LDAPException If there are no more elements to return, or if 332 * the next element in the set of results is not an 333 * entry. 334 */ 335 public LDAPEntry next() 336 throws LDAPException 337 { 338 if (! hasMoreElements()) 339 { 340 throw new LDAPException(null, ResultCode.NO_RESULTS_RETURNED_INT_VALUE); 341 } 342 343 final Object o = nextElement(); 344 if (o instanceof LDAPEntry) 345 { 346 return (LDAPEntry) o; 347 } 348 349 throw (LDAPException) o; 350 } 351 352 353 354 /** 355 * Retrieves the number of results that are available for immediate 356 * processing. 357 * 358 * @return The number of results that are available for immediate processing. 359 */ 360 public int getCount() 361 { 362 return count.get(); 363 } 364 365 366 367 /** 368 * Retrieves the response controls for the last result element returned, or 369 * for the search itself if the search has completed. 370 * 371 * @return The response controls for the last result element returned, or 372 * {@code null} if no elements have yet been returned or if the last 373 * element did not include any controls. 374 */ 375 public LDAPControl[] getResponseControls() 376 { 377 final Control[] controls = lastControls.get(); 378 if ((controls == null) || (controls.length == 0)) 379 { 380 return null; 381 } 382 383 return LDAPControl.toLDAPControls(controls); 384 } 385 386 387 388 /** 389 * {@inheritDoc} 390 */ 391 @InternalUseOnly() 392 public void searchEntryReturned(final SearchResultEntry searchEntry) 393 { 394 if (searchDone.get()) 395 { 396 return; 397 } 398 399 try 400 { 401 resultQueue.put(searchEntry); 402 count.incrementAndGet(); 403 } 404 catch (Exception e) 405 { 406 // This should never happen. 407 debugException(e); 408 searchDone.set(true); 409 } 410 } 411 412 413 414 /** 415 * {@inheritDoc} 416 */ 417 @InternalUseOnly() 418 public void searchReferenceReturned( 419 final SearchResultReference searchReference) 420 { 421 if (searchDone.get()) 422 { 423 return; 424 } 425 426 try 427 { 428 resultQueue.put(searchReference); 429 count.incrementAndGet(); 430 } 431 catch (Exception e) 432 { 433 // This should never happen. 434 debugException(e); 435 searchDone.set(true); 436 } 437 } 438 439 440 441 /** 442 * Indicates that the provided search result has been received in response to 443 * an asynchronous search operation. Note that automatic referral following 444 * is not supported for asynchronous operations, so it is possible that this 445 * result could include a referral. 446 * 447 * @param requestID The async request ID of the request for which the 448 * response was received. 449 * @param searchResult The search result that has been received. 450 */ 451 @InternalUseOnly() 452 public void searchResultReceived(final AsyncRequestID requestID, 453 final SearchResult searchResult) 454 { 455 if (searchDone.get()) 456 { 457 return; 458 } 459 460 try 461 { 462 resultQueue.put(searchResult); 463 if (! searchResult.getResultCode().equals(ResultCode.SUCCESS)) 464 { 465 count.incrementAndGet(); 466 } 467 } 468 catch (Exception e) 469 { 470 // This should never happen. 471 debugException(e); 472 searchDone.set(true); 473 } 474 } 475}