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