001/*
002 * Copyright 2009-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2009-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.migrate.ldapjdk;
037
038
039
040import java.util.Enumeration;
041import java.util.NoSuchElementException;
042import java.util.concurrent.LinkedBlockingQueue;
043import java.util.concurrent.TimeUnit;
044import java.util.concurrent.atomic.AtomicBoolean;
045import java.util.concurrent.atomic.AtomicInteger;
046import java.util.concurrent.atomic.AtomicReference;
047
048import com.unboundid.ldap.sdk.AsyncRequestID;
049import com.unboundid.ldap.sdk.AsyncSearchResultListener;
050import com.unboundid.ldap.sdk.Control;
051import com.unboundid.ldap.sdk.ResultCode;
052import com.unboundid.ldap.sdk.SearchResult;
053import com.unboundid.ldap.sdk.SearchResultEntry;
054import com.unboundid.ldap.sdk.SearchResultReference;
055import com.unboundid.util.Debug;
056import com.unboundid.util.InternalUseOnly;
057import com.unboundid.util.Mutable;
058import com.unboundid.util.NotExtensible;
059import com.unboundid.util.ThreadSafety;
060import com.unboundid.util.ThreadSafetyLevel;
061
062
063
064/**
065 * This class provides a data structure that provides access to data returned
066 * in response to a search operation.
067 * <BR><BR>
068 * This class is primarily intended to be used in the process of updating
069 * applications which use the Netscape Directory SDK for Java to switch to or
070 * coexist with the UnboundID LDAP SDK for Java.  For applications not written
071 * using the Netscape Directory SDK for Java, the {@link SearchResult} class
072 * should be used instead.
073 */
074@Mutable()
075@NotExtensible()
076@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
077public class LDAPSearchResults
078       implements Enumeration<Object>, AsyncSearchResultListener
079{
080  /**
081   * The serial version UID for this serializable class.
082   */
083  private static final long serialVersionUID = 7884355145560496230L;
084
085
086
087  // The asynchronous request ID for these search results.
088  private volatile AsyncRequestID asyncRequestID;
089
090  // Indicates whether the search has been abandoned.
091  private final AtomicBoolean searchAbandoned;
092
093  // Indicates whether the end of the result set has been reached.
094  private final AtomicBoolean searchDone;
095
096  // The number of items that can be read immediately without blocking.
097  private final AtomicInteger count;
098
099  // The set of controls for the last result element returned.
100  private final AtomicReference<Control[]> lastControls;
101
102  // The next object to be returned.
103  private final AtomicReference<Object> nextResult;
104
105  // The search result done message for the search.
106  private final AtomicReference<SearchResult> searchResult;
107
108  // The maximum length of time in milliseconds to wait for a response.
109  private final long maxWaitTime;
110
111  // The queue used to hold results.
112  private final LinkedBlockingQueue<Object> resultQueue;
113
114
115
116  /**
117   * Creates a new LDAP search results object.
118   */
119  public LDAPSearchResults()
120  {
121    this(0L);
122  }
123
124
125
126  /**
127   * Creates a new LDAP search results object with the specified maximum wait
128   * time.
129   *
130   * @param  maxWaitTime  The maximum wait time in milliseconds.
131   */
132  public LDAPSearchResults(final long maxWaitTime)
133  {
134    this.maxWaitTime = maxWaitTime;
135
136    asyncRequestID = null;
137    searchAbandoned = new AtomicBoolean(false);
138    searchDone      = new AtomicBoolean(false);
139    count           = new AtomicInteger(0);
140    lastControls    = new AtomicReference<>();
141    nextResult      = new AtomicReference<>();
142    searchResult    = new AtomicReference<>();
143    resultQueue     = new LinkedBlockingQueue<>(50);
144  }
145
146
147
148  /**
149   * Indicates that this search request has been abandoned.
150   */
151  void setAbandoned()
152  {
153    searchAbandoned.set(true);
154  }
155
156
157
158  /**
159   * Retrieves the asynchronous request ID for the associates search operation.
160   *
161   * @return  The asynchronous request ID for the associates search operation.
162   */
163  AsyncRequestID getAsyncRequestID()
164  {
165    return asyncRequestID;
166  }
167
168
169
170  /**
171   * Sets the asynchronous request ID for the associated search operation.
172   *
173   * @param  asyncRequestID  The asynchronous request ID for the associated
174   *                         search operation.
175   */
176  void setAsyncRequestID(final AsyncRequestID asyncRequestID)
177  {
178    this.asyncRequestID = asyncRequestID;
179  }
180
181
182
183  /**
184   * Retrieves the next object returned from the server, if possible.  When this
185   * method returns, then the {@code nextResult} reference will also contain the
186   * object that was returned.
187   *
188   * @return  The next object returned from the server, or {@code null} if there
189   *          are no more objects to return.
190   */
191  private Object nextObject()
192  {
193    Object o = nextResult.get();
194    if (o != null)
195    {
196      return o;
197    }
198
199    o = resultQueue.poll();
200    if (o != null)
201    {
202      nextResult.set(o);
203      return o;
204    }
205
206    if (searchDone.get() || searchAbandoned.get())
207    {
208      return null;
209    }
210
211    try
212    {
213      final long stopWaitTime;
214      if (maxWaitTime > 0L)
215      {
216        stopWaitTime = System.currentTimeMillis() + maxWaitTime;
217      }
218      else
219      {
220        stopWaitTime = Long.MAX_VALUE;
221      }
222
223      while ((! searchAbandoned.get()) &&
224             (System.currentTimeMillis() < stopWaitTime))
225      {
226        o = resultQueue.poll(100L, TimeUnit.MILLISECONDS);
227        if (o != null)
228        {
229          break;
230        }
231      }
232
233      if (o == null)
234      {
235        if (searchAbandoned.get())
236        {
237          o = new SearchResult(-1, ResultCode.USER_CANCELED, null, null, null,
238               0, 0, null);
239          count.incrementAndGet();
240        }
241        else
242        {
243          o = new SearchResult(-1, ResultCode.TIMEOUT, null, null, null, 0, 0,
244               null);
245          count.incrementAndGet();
246        }
247      }
248    }
249    catch (final Exception e)
250    {
251      Debug.debugException(e);
252
253      if (e instanceof InterruptedException)
254      {
255        Thread.currentThread().interrupt();
256      }
257
258      o = new SearchResult(-1, ResultCode.USER_CANCELED, null, null, null, 0, 0,
259           null);
260      count.incrementAndGet();
261    }
262
263    nextResult.set(o);
264    return o;
265  }
266
267
268
269  /**
270   * Indicates whether there are any more search results to return.
271   *
272   * @return  {@code true} if there are more search results to return, or
273   *          {@code false} if not.
274   */
275  @Override()
276  public boolean hasMoreElements()
277  {
278    final Object o = nextObject();
279    if (o == null)
280    {
281      return false;
282    }
283
284    if (o instanceof SearchResult)
285    {
286      final SearchResult r = (SearchResult) o;
287      if (r.getResultCode().equals(ResultCode.SUCCESS))
288      {
289        lastControls.set(r.getResponseControls());
290        searchDone.set(true);
291        nextResult.set(null);
292        return false;
293      }
294    }
295
296    return true;
297  }
298
299
300
301  /**
302   * Retrieves the next element in the set of search results.
303   *
304   * @return  The next element in the set of search results.
305   *
306   * @throws  NoSuchElementException  If there are no more results.
307   */
308  @Override()
309  public Object nextElement()
310         throws NoSuchElementException
311  {
312    final Object o = nextObject();
313    if (o == null)
314    {
315      throw new NoSuchElementException();
316    }
317
318    nextResult.set(null);
319    count.decrementAndGet();
320
321    if (o instanceof SearchResultEntry)
322    {
323      final SearchResultEntry e = (SearchResultEntry) o;
324      lastControls.set(e.getControls());
325      return new LDAPEntry(e);
326    }
327    else if (o instanceof SearchResultReference)
328    {
329      final SearchResultReference r = (SearchResultReference) o;
330      lastControls.set(r.getControls());
331      return new LDAPReferralException(r);
332    }
333    else
334    {
335      final SearchResult r = (SearchResult) o;
336      searchDone.set(true);
337      nextResult.set(null);
338      lastControls.set(r.getResponseControls());
339      return new LDAPException(r.getDiagnosticMessage(),
340           r.getResultCode().intValue(), r.getDiagnosticMessage(),
341           r.getMatchedDN());
342    }
343  }
344
345
346
347  /**
348   * Retrieves the next entry from the set of search results.
349   *
350   * @return  The next entry from the set of search results.
351   *
352   * @throws  LDAPException  If there are no more elements to return, or if
353   *                         the next element in the set of results is not an
354   *                         entry.
355   */
356  public LDAPEntry next()
357         throws LDAPException
358  {
359    if (! hasMoreElements())
360    {
361      throw new LDAPException(null, ResultCode.NO_RESULTS_RETURNED_INT_VALUE);
362    }
363
364    final Object o = nextElement();
365    if (o instanceof LDAPEntry)
366    {
367      return (LDAPEntry) o;
368    }
369
370    throw (LDAPException) o;
371  }
372
373
374
375  /**
376   * Retrieves the number of results that are available for immediate
377   * processing.
378   *
379   * @return  The number of results that are available for immediate processing.
380   */
381  public int getCount()
382  {
383    return count.get();
384  }
385
386
387
388  /**
389   * Retrieves the response controls for the last result element returned, or
390   * for the search itself if the search has completed.
391   *
392   * @return  The response controls for the last result element returned, or
393   *          {@code null} if no elements have yet been returned or if the last
394   *          element did not include any controls.
395   */
396  public LDAPControl[] getResponseControls()
397  {
398    final Control[] controls = lastControls.get();
399    if ((controls == null) || (controls.length == 0))
400    {
401      return null;
402    }
403
404    return LDAPControl.toLDAPControls(controls);
405  }
406
407
408
409  /**
410   * {@inheritDoc}
411   */
412  @InternalUseOnly()
413  @Override()
414  public void searchEntryReturned(final SearchResultEntry searchEntry)
415  {
416    if (searchDone.get())
417    {
418      return;
419    }
420
421    try
422    {
423      resultQueue.put(searchEntry);
424      count.incrementAndGet();
425    }
426    catch (final Exception e)
427    {
428      // This should never happen.
429      Debug.debugException(e);
430
431      if (e instanceof InterruptedException)
432      {
433        Thread.currentThread().interrupt();
434      }
435
436      searchDone.set(true);
437    }
438  }
439
440
441
442  /**
443   * {@inheritDoc}
444   */
445  @InternalUseOnly()
446  @Override()
447  public void searchReferenceReturned(
448                   final SearchResultReference searchReference)
449  {
450    if (searchDone.get())
451    {
452      return;
453    }
454
455    try
456    {
457      resultQueue.put(searchReference);
458      count.incrementAndGet();
459    }
460    catch (final Exception e)
461    {
462      // This should never happen.
463      Debug.debugException(e);
464
465      if (e instanceof InterruptedException)
466      {
467        Thread.currentThread().interrupt();
468      }
469
470      searchDone.set(true);
471    }
472  }
473
474
475
476  /**
477   * Indicates that the provided search result has been received in response to
478   * an asynchronous search operation.  Note that automatic referral following
479   * is not supported for asynchronous operations, so it is possible that this
480   * result could include a referral.
481   *
482   * @param  requestID     The async request ID of the request for which the
483   *                       response was received.
484   * @param  searchResult  The search result that has been received.
485   */
486  @InternalUseOnly()
487  @Override()
488  public void searchResultReceived(final AsyncRequestID requestID,
489                                   final SearchResult searchResult)
490  {
491    if (searchDone.get())
492    {
493      return;
494    }
495
496    try
497    {
498      resultQueue.put(searchResult);
499      if (! searchResult.getResultCode().equals(ResultCode.SUCCESS))
500      {
501        count.incrementAndGet();
502      }
503    }
504    catch (final Exception e)
505    {
506      // This should never happen.
507      Debug.debugException(e);
508
509      if (e instanceof InterruptedException)
510      {
511        Thread.currentThread().interrupt();
512      }
513
514      searchDone.set(true);
515    }
516  }
517}