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