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