001 /*
002 * Copyright 2009-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2009-2013 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;
022
023
024
025 import java.util.concurrent.LinkedBlockingQueue;
026 import java.util.concurrent.TimeUnit;
027 import java.util.concurrent.atomic.AtomicBoolean;
028 import java.util.concurrent.atomic.AtomicReference;
029
030 import com.unboundid.util.InternalUseOnly;
031 import com.unboundid.util.ThreadSafety;
032 import com.unboundid.util.ThreadSafetyLevel;
033
034 import static com.unboundid.ldap.sdk.LDAPMessages.*;
035 import static com.unboundid.util.Debug.*;
036 import static com.unboundid.util.Validator.*;
037
038
039
040 /**
041 * This class provides an {@link EntrySource} that will read entries matching a
042 * given set of search criteria from an LDAP directory server. It may
043 * optionally close the associated connection after all entries have been read.
044 * <BR><BR>
045 * This implementation processes the search asynchronously, which provides two
046 * benefits:
047 * <UL>
048 * <LI>It makes it easier to provide a throttling mechanism to prevent the
049 * entries from piling up and causing the client to run out of memory if
050 * the server returns them faster than the client can process them. If
051 * this occurs, then the client will queue up a small number of entries
052 * but will then push back against the server to block it from sending
053 * additional entries until the client can catch up. In this case, no
054 * entries should be lost, although some servers may impose limits on how
055 * long a search may be active or other forms of constraints.</LI>
056 * <LI>It makes it possible to abandon the search if the entry source is no
057 * longer needed (as signified by calling the {@link #close} method) and
058 * the caller intends to stop iterating through the results.</LI>
059 * </UL>
060 * <H2>Example</H2>
061 * The following example demonstrates the process that may be used for iterating
062 * across all entries containing the {@code person} object class using the LDAP
063 * entry source API:
064 * <PRE>
065 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
066 * SearchScope.SUB, "(objectClass=person)");
067 * LDAPEntrySource entrySource = new LDAPEntrySource(connection,
068 * searchRequest, false);
069 *
070 * try
071 * {
072 * while (true)
073 * {
074 * try
075 * {
076 * Entry entry = entrySource.nextEntry();
077 * if (entry == null)
078 * {
079 * // There are no more entries to be read.
080 * break;
081 * }
082 * else
083 * {
084 * // Do something with the entry here.
085 * }
086 * }
087 * catch (SearchResultReferenceEntrySourceException e)
088 * {
089 * // The directory server returned a search result reference.
090 * SearchResultReference searchReference = e.getSearchReference();
091 * }
092 * catch (EntrySourceException e)
093 * {
094 * // Some kind of problem was encountered (e.g., the connection is no
095 * // longer valid). See if we can continue reading entries.
096 * if (! e.mayContinueReading())
097 * {
098 * break;
099 * }
100 * }
101 * }
102 * }
103 * finally
104 * {
105 * entrySource.close();
106 * }
107 * </PRE>
108 */
109 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
110 public final class LDAPEntrySource
111 extends EntrySource
112 implements AsyncSearchResultListener
113 {
114 /**
115 * The bogus entry that will be used to signify the end of the results.
116 */
117 private static final String END_OF_RESULTS = "END OF RESULTS";
118
119
120
121 /**
122 * The serial version UID for this serializable class.
123 */
124 private static final long serialVersionUID = 1080386705549149135L;
125
126
127
128 // The request ID associated with the asynchronous search.
129 private final AsyncRequestID asyncRequestID;
130
131 // Indicates whether this entry source has been closed.
132 private final AtomicBoolean closed;
133
134 // The search result for the search operation.
135 private final AtomicReference<SearchResult> searchResult;
136
137 // Indicates whether to close the connection when this entry source is closed.
138 private final boolean closeConnection;
139
140 // The connection that will be used to read the entries.
141 private final LDAPConnection connection;
142
143 // The queue from which entries will be read.
144 private final LinkedBlockingQueue<Object> queue;
145
146
147
148 /**
149 * Creates a new LDAP entry source with the provided information.
150 *
151 * @param connection The connection to the directory server from which
152 * the entries will be read. It must not be
153 * {@code null}.
154 * @param searchRequest The search request that will be used to identify
155 * which entries should be returned. It must not be
156 * {@code null}, and it must not be configured with a
157 * {@link SearchResultListener}.
158 * @param closeConnection Indicates whether the provided connection should
159 * be closed whenever all of the entries have been
160 * read, or if the {@link #close} method is called.
161 *
162 * @throws LDAPException If there is a problem with the provided search
163 * request or when trying to communicate with the
164 * directory server over the provided connection.
165 */
166 public LDAPEntrySource(final LDAPConnection connection,
167 final SearchRequest searchRequest,
168 final boolean closeConnection)
169 throws LDAPException
170 {
171 this(connection, searchRequest, closeConnection, 100);
172 }
173
174
175
176 /**
177 * Creates a new LDAP entry source with the provided information.
178 *
179 * @param connection The connection to the directory server from which
180 * the entries will be read. It must not be
181 * {@code null}.
182 * @param searchRequest The search request that will be used to identify
183 * which entries should be returned. It must not be
184 * {@code null}, and it must not be configured with a
185 * {@link SearchResultListener}.
186 * @param closeConnection Indicates whether the provided connection should
187 * be closed whenever all of the entries have been
188 * read, or if the {@link #close} method is called.
189 * @param queueSize The size of the internal queue used to hold search
190 * result entries until they can be consumed by the
191 * {@link #nextEntry} method. The value must be
192 * greater than zero.
193 *
194 * @throws LDAPException If there is a problem with the provided search
195 * request or when trying to communicate with the
196 * directory server over the provided connection.
197 */
198 public LDAPEntrySource(final LDAPConnection connection,
199 final SearchRequest searchRequest,
200 final boolean closeConnection,
201 final int queueSize)
202 throws LDAPException
203 {
204 ensureNotNull(connection, searchRequest);
205 ensureTrue(queueSize > 0,
206 "LDAPEntrySource.queueSize must be greater than 0.");
207
208 this.connection = connection;
209 this.closeConnection = closeConnection;
210
211 if (searchRequest.getSearchResultListener() != null)
212 {
213 throw new LDAPException(ResultCode.PARAM_ERROR,
214 ERR_LDAP_ENTRY_SOURCE_REQUEST_HAS_LISTENER.get());
215 }
216
217 closed = new AtomicBoolean(false);
218 searchResult = new AtomicReference<SearchResult>();
219 queue = new LinkedBlockingQueue<Object>(queueSize);
220
221 final SearchRequest r = new SearchRequest(this, searchRequest.getControls(),
222 searchRequest.getBaseDN(), searchRequest.getScope(),
223 searchRequest.getDereferencePolicy(), searchRequest.getSizeLimit(),
224 searchRequest.getTimeLimitSeconds(), searchRequest.typesOnly(),
225 searchRequest.getFilter(), searchRequest.getAttributes());
226 asyncRequestID = connection.asyncSearch(r);
227 }
228
229
230
231 /**
232 * {@inheritDoc}
233 */
234 @Override()
235 public Entry nextEntry()
236 throws EntrySourceException
237 {
238 while (true)
239 {
240 if (closed.get() && queue.isEmpty())
241 {
242 return null;
243 }
244
245 final Object o;
246 try
247 {
248 o = queue.poll(10L, TimeUnit.MILLISECONDS);
249 }
250 catch (InterruptedException ie)
251 {
252 debugException(ie);
253 continue;
254 }
255
256 if (o != null)
257 {
258 if (o == END_OF_RESULTS)
259 {
260 return null;
261 }
262 else if (o instanceof Entry)
263 {
264 return (Entry) o;
265 }
266 else
267 {
268 throw (EntrySourceException) o;
269 }
270 }
271 }
272 }
273
274
275
276 /**
277 * {@inheritDoc}
278 */
279 @Override()
280 public void close()
281 {
282 closeInternal(true);
283 }
284
285
286
287 /**
288 * Closes this LDAP entry source.
289 *
290 * @param abandon Indicates whether to attempt to abandon the search.
291 */
292 private void closeInternal(final boolean abandon)
293 {
294 addToQueue(END_OF_RESULTS);
295
296 if (closed.compareAndSet(false, true))
297 {
298 if (abandon)
299 {
300 try
301 {
302 connection.abandon(asyncRequestID);
303 }
304 catch (Exception e)
305 {
306 debugException(e);
307 }
308 }
309
310 if (closeConnection)
311 {
312 connection.close();
313 }
314 }
315 }
316
317
318
319 /**
320 * Retrieves the search result for the search operation, if available. It
321 * will not be available until the search has completed (as indicated by a
322 * {@code null} return value from the {@link #nextEntry} method).
323 *
324 * @return The search result for the search operation, or {@code null} if it
325 * is not available (e.g., because the search has not yet completed).
326 */
327 public SearchResult getSearchResult()
328 {
329 return searchResult.get();
330 }
331
332
333
334 /**
335 * {@inheritDoc} This is intended for internal use only and should not be
336 * called by anything outside of the LDAP SDK itself.
337 */
338 @InternalUseOnly()
339 public void searchEntryReturned(final SearchResultEntry searchEntry)
340 {
341 addToQueue(searchEntry);
342 }
343
344
345
346 /**
347 * {@inheritDoc} This is intended for internal use only and should not be
348 * called by anything outside of the LDAP SDK itself.
349 */
350 @InternalUseOnly()
351 public void searchReferenceReturned(
352 final SearchResultReference searchReference)
353 {
354 addToQueue(new SearchResultReferenceEntrySourceException(searchReference));
355 }
356
357
358
359 /**
360 * {@inheritDoc} This is intended for internal use only and should not be
361 * called by anything outside of the LDAP SDK itself.
362 */
363 @InternalUseOnly()
364 public void searchResultReceived(final AsyncRequestID requestID,
365 final SearchResult searchResult)
366 {
367 this.searchResult.set(searchResult);
368
369 if (! searchResult.getResultCode().equals(ResultCode.SUCCESS))
370 {
371 addToQueue(new EntrySourceException(false,
372 new LDAPSearchException(searchResult)));
373 }
374
375 closeInternal(false);
376 }
377
378
379
380 /**
381 * Adds the provided object to the queue, waiting as long as needed until it
382 * has been added.
383 *
384 * @param o The object to be added. It must not be {@code null}.
385 */
386 private void addToQueue(final Object o)
387 {
388 while (true)
389 {
390 if (closed.get())
391 {
392 return;
393 }
394
395 try
396 {
397 if (queue.offer(o, 100L, TimeUnit.MILLISECONDS))
398 {
399 return;
400 }
401 }
402 catch (InterruptedException ie)
403 {
404 debugException(ie);
405 }
406 }
407 }
408 }