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 */
021 package com.unboundid.ldap.sdk.migrate.ldapjdk;
022
023
024
025 import java.util.Enumeration;
026 import java.util.NoSuchElementException;
027 import java.util.concurrent.LinkedBlockingQueue;
028 import java.util.concurrent.TimeUnit;
029 import java.util.concurrent.atomic.AtomicBoolean;
030 import java.util.concurrent.atomic.AtomicInteger;
031 import java.util.concurrent.atomic.AtomicReference;
032
033 import com.unboundid.ldap.sdk.AsyncRequestID;
034 import com.unboundid.ldap.sdk.AsyncSearchResultListener;
035 import com.unboundid.ldap.sdk.Control;
036 import com.unboundid.ldap.sdk.ResultCode;
037 import com.unboundid.ldap.sdk.SearchResult;
038 import com.unboundid.ldap.sdk.SearchResultEntry;
039 import com.unboundid.ldap.sdk.SearchResultReference;
040 import com.unboundid.util.InternalUseOnly;
041 import com.unboundid.util.Mutable;
042 import com.unboundid.util.NotExtensible;
043 import com.unboundid.util.ThreadSafety;
044 import com.unboundid.util.ThreadSafetyLevel;
045
046 import 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)
063 public 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 }