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