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