001 /*
002 * Copyright 2009-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2014 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 // Indicates whether the end of the result set has been reached.
074 private final AtomicBoolean searchDone;
075
076 // The number of items that can be read immediately without blocking.
077 private final AtomicInteger count;
078
079 // The set of controls for the last result element returned.
080 private final AtomicReference<Control[]> lastControls;
081
082 // The next object to be returned.
083 private final AtomicReference<Object> nextResult;
084
085 // The search result done message for the search.
086 private final AtomicReference<SearchResult> searchResult;
087
088 // The maximum length of time in milliseconds to wait for a response.
089 private final long maxWaitTime;
090
091 // The queue used to hold results.
092 private final LinkedBlockingQueue<Object> resultQueue;
093
094
095
096 /**
097 * Creates a new LDAP search results object.
098 */
099 public LDAPSearchResults()
100 {
101 this(0L);
102 }
103
104
105
106 /**
107 * Creates a new LDAP search results object with the specified maximum wait
108 * time.
109 *
110 * @param maxWaitTime The maximum wait time in milliseconds.
111 */
112 public LDAPSearchResults(final long maxWaitTime)
113 {
114 this.maxWaitTime = maxWaitTime;
115
116 searchDone = new AtomicBoolean(false);
117 count = new AtomicInteger(0);
118 lastControls = new AtomicReference<Control[]>();
119 nextResult = new AtomicReference<Object>();
120 searchResult = new AtomicReference<SearchResult>();
121 resultQueue = new LinkedBlockingQueue<Object>(50);
122 }
123
124
125
126 /**
127 * Retrieves the next object returned from the server, if possible. When this
128 * method returns, then the {@code nextResult} reference will also contain the
129 * object that was returned.
130 *
131 * @return The next object returned from the server, or {@code null} if there
132 * are no more objects to return.
133 */
134 private Object nextObject()
135 {
136 Object o = nextResult.get();
137 if (o != null)
138 {
139 return o;
140 }
141
142 o = resultQueue.poll();
143 if (o != null)
144 {
145 nextResult.set(o);
146 return o;
147 }
148
149 if (searchDone.get())
150 {
151 return null;
152 }
153
154 try
155 {
156 if (maxWaitTime > 0)
157 {
158 o = resultQueue.poll(maxWaitTime, TimeUnit.MILLISECONDS);
159 if (o == null)
160 {
161 o = new SearchResult(-1, ResultCode.TIMEOUT, null, null, null, 0, 0,
162 null);
163 count.incrementAndGet();
164 }
165 }
166 else
167 {
168 o = resultQueue.take();
169 }
170 }
171 catch (Exception e)
172 {
173 debugException(e);
174
175 o = new SearchResult(-1, ResultCode.USER_CANCELED, null, null, null, 0, 0,
176 null);
177 count.incrementAndGet();
178 }
179
180 nextResult.set(o);
181 return o;
182 }
183
184
185
186 /**
187 * Indicates whether there are any more search results to return.
188 *
189 * @return {@code true} if there are more search results to return, or
190 * {@code false} if not.
191 */
192 public boolean hasMoreElements()
193 {
194 final Object o = nextObject();
195 if (o == null)
196 {
197 return false;
198 }
199
200 if (o instanceof SearchResult)
201 {
202 final SearchResult r = (SearchResult) o;
203 if (r.getResultCode().equals(ResultCode.SUCCESS))
204 {
205 lastControls.set(r.getResponseControls());
206 searchDone.set(true);
207 nextResult.set(null);
208 return false;
209 }
210 }
211
212 return true;
213 }
214
215
216
217 /**
218 * Retrieves the next element in the set of search results.
219 *
220 * @return The next element in the set of search results.
221 *
222 * @throws NoSuchElementException If there are no more results.
223 */
224 public Object nextElement()
225 throws NoSuchElementException
226 {
227 final Object o = nextObject();
228 if (o == null)
229 {
230 throw new NoSuchElementException();
231 }
232
233 nextResult.set(null);
234 count.decrementAndGet();
235
236 if (o instanceof SearchResultEntry)
237 {
238 final SearchResultEntry e = (SearchResultEntry) o;
239 lastControls.set(e.getControls());
240 return new LDAPEntry(e);
241 }
242 else if (o instanceof SearchResultReference)
243 {
244 final SearchResultReference r = (SearchResultReference) o;
245 lastControls.set(r.getControls());
246 return new LDAPReferralException(r);
247 }
248 else
249 {
250 final SearchResult r = (SearchResult) o;
251 searchDone.set(true);
252 nextResult.set(null);
253 lastControls.set(r.getResponseControls());
254 return new LDAPException(r.getDiagnosticMessage(),
255 r.getResultCode().intValue(), r.getDiagnosticMessage(),
256 r.getMatchedDN());
257 }
258 }
259
260
261
262 /**
263 * Retrieves the next entry from the set of search results.
264 *
265 * @return The next entry from the set of search results.
266 *
267 * @throws LDAPException If there are no more elements to return, or if
268 * the next element in the set of results is not an
269 * entry.
270 */
271 public LDAPEntry next()
272 throws LDAPException
273 {
274 if (! hasMoreElements())
275 {
276 throw new LDAPException(null, ResultCode.NO_RESULTS_RETURNED_INT_VALUE);
277 }
278
279 final Object o = nextElement();
280 if (o instanceof LDAPEntry)
281 {
282 return (LDAPEntry) o;
283 }
284
285 throw (LDAPException) o;
286 }
287
288
289
290 /**
291 * Retrieves the number of results that are available for immediate
292 * processing.
293 *
294 * @return The number of results that are available for immediate processing.
295 */
296 public int getCount()
297 {
298 return count.get();
299 }
300
301
302
303 /**
304 * Retrieves the response controls for the last result element returned, or
305 * for the search itself if the search has completed.
306 *
307 * @return The response controls for the last result element returned, or
308 * {@code null} if no elements have yet been returned or if the last
309 * element did not include any controls.
310 */
311 public LDAPControl[] getResponseControls()
312 {
313 final Control[] controls = lastControls.get();
314 if ((controls == null) || (controls.length == 0))
315 {
316 return null;
317 }
318
319 return LDAPControl.toLDAPControls(controls);
320 }
321
322
323
324 /**
325 * {@inheritDoc}
326 */
327 @InternalUseOnly()
328 public void searchEntryReturned(final SearchResultEntry searchEntry)
329 {
330 if (searchDone.get())
331 {
332 return;
333 }
334
335 try
336 {
337 resultQueue.put(searchEntry);
338 count.incrementAndGet();
339 }
340 catch (Exception e)
341 {
342 // This should never happen.
343 debugException(e);
344 searchDone.set(true);
345 }
346 }
347
348
349
350 /**
351 * {@inheritDoc}
352 */
353 @InternalUseOnly()
354 public void searchReferenceReturned(
355 final SearchResultReference searchReference)
356 {
357 if (searchDone.get())
358 {
359 return;
360 }
361
362 try
363 {
364 resultQueue.put(searchReference);
365 count.incrementAndGet();
366 }
367 catch (Exception e)
368 {
369 // This should never happen.
370 debugException(e);
371 searchDone.set(true);
372 }
373 }
374
375
376
377 /**
378 * Indicates that the provided search result has been received in response to
379 * an asynchronous search operation. Note that automatic referral following
380 * is not supported for asynchronous operations, so it is possible that this
381 * result could include a referral.
382 *
383 * @param requestID The async request ID of the request for which the
384 * response was received.
385 * @param searchResult The search result that has been received.
386 */
387 @InternalUseOnly()
388 public void searchResultReceived(final AsyncRequestID requestID,
389 final SearchResult searchResult)
390 {
391 if (searchDone.get())
392 {
393 return;
394 }
395
396 try
397 {
398 resultQueue.put(searchResult);
399 if (! searchResult.getResultCode().equals(ResultCode.SUCCESS))
400 {
401 count.incrementAndGet();
402 }
403 }
404 catch (Exception e)
405 {
406 // This should never happen.
407 debugException(e);
408 searchDone.set(true);
409 }
410 }
411 }