001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2015 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      // 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    }