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