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