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 */
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 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 }