001 /*
002 * Copyright 2007-2012 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2012 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.ArrayList;
026 import java.util.Arrays;
027 import java.util.Collections;
028 import java.util.List;
029 import java.util.Timer;
030 import java.util.concurrent.LinkedBlockingQueue;
031 import java.util.concurrent.TimeUnit;
032
033 import com.unboundid.asn1.ASN1Boolean;
034 import com.unboundid.asn1.ASN1Buffer;
035 import com.unboundid.asn1.ASN1BufferSequence;
036 import com.unboundid.asn1.ASN1Element;
037 import com.unboundid.asn1.ASN1Enumerated;
038 import com.unboundid.asn1.ASN1Integer;
039 import com.unboundid.asn1.ASN1OctetString;
040 import com.unboundid.asn1.ASN1Sequence;
041 import com.unboundid.ldap.protocol.LDAPMessage;
042 import com.unboundid.ldap.protocol.LDAPResponse;
043 import com.unboundid.ldap.protocol.ProtocolOp;
044 import com.unboundid.util.InternalUseOnly;
045 import com.unboundid.util.Mutable;
046 import com.unboundid.util.ThreadSafety;
047 import com.unboundid.util.ThreadSafetyLevel;
048
049 import static com.unboundid.ldap.sdk.LDAPMessages.*;
050 import static com.unboundid.util.Debug.*;
051 import static com.unboundid.util.StaticUtils.*;
052 import static com.unboundid.util.Validator.*;
053
054
055
056 /**
057 * This class implements the processing necessary to perform an LDAPv3 search
058 * operation, which can be used to retrieve entries that match a given set of
059 * criteria. A search request may include the following elements:
060 * <UL>
061 * <LI>Base DN -- Specifies the base DN for the search. Only entries at or
062 * below this location in the server (based on the scope) will be
063 * considered potential matches.</LI>
064 * <LI>Scope -- Specifies the range of entries relative to the base DN that
065 * may be considered potential matches.</LI>
066 * <LI>Dereference Policy -- Specifies the behavior that the server should
067 * exhibit if any alias entries are encountered while processing the
068 * search. If no dereference policy is provided, then a default of
069 * {@code DereferencePolicy.NEVER} will be used.</LI>
070 * <LI>Size Limit -- Specifies the maximum number of entries that should be
071 * returned from the search. A value of zero indicates that there should
072 * not be any limit enforced. Note that the directory server may also
073 * be configured with a server-side size limit which can also limit the
074 * number of entries that may be returned to the client and in that case
075 * the smaller of the client-side and server-side limits will be
076 * used. If no size limit is provided, then a default of zero (unlimited)
077 * will be used.</LI>
078 * <LI>Time Limit -- Specifies the maximum length of time in seconds that the
079 * server should spend processing the search. A value of zero indicates
080 * that there should not be any limit enforced. Note that the directory
081 * server may also be configured with a server-side time limit which can
082 * also limit the processing time, and in that case the smaller of the
083 * client-side and server-side limits will be used. If no time limit is
084 * provided, then a default of zero (unlimited) will be used.</LI>
085 * <LI>Types Only -- Indicates whether matching entries should include only
086 * attribute names, or both attribute names and values. If no value is
087 * provided, then a default of {@code false} will be used.</LI>
088 * <LI>Filter -- Specifies the criteria for determining which entries should
089 * be returned. See the {@link Filter} class for the types of filters
090 * that may be used.
091 * <BR><BR>
092 * Note that filters can be specified using either their string
093 * representations or as {@link Filter} objects. As noted in the
094 * documentation for the {@link Filter} class, using the string
095 * representation may be somewhat dangerous if the data is not properly
096 * sanitized because special characters contained in the filter may cause
097 * it to be invalid or worse expose a vulnerability that could cause the
098 * filter to request more information than was intended. As a result, if
099 * the filter may include special characters or user-provided strings,
100 * then it is recommended that you use {@link Filter} objects created from
101 * their individual components rather than their string representations.
102 * </LI>
103 * <LI>Attributes -- Specifies the set of attributes that should be included
104 * in matching entries. If no attributes are provided, then the server
105 * will default to returning all user attributes. If a specified set of
106 * attributes is given, then only those attributes will be included.
107 * Values that may be included to indicate a special meaning include:
108 * <UL>
109 * <LI>{@code NO_ATTRIBUTES} -- Indicates that no attributes should be
110 * returned. That is, only the DNs of matching entries will be
111 * returned.</LI>
112 * <LI>{@code ALL_USER_ATTRIBUTES} -- Indicates that all user attributes
113 * should be included in matching entries. This is the default if
114 * no attributes are provided, but this special value may be
115 * included if a specific set of operational attributes should be
116 * included along with all user attributes.</LI>
117 * <LI>{@code ALL_OPERATIONAL_ATTRIBUTES} -- Indicates that all
118 * operational attributes should be included in matching
119 * entries.</LI>
120 * </UL>
121 * These special values may be used alone or in conjunction with each
122 * other and/or any specific attribute names or OIDs.</LI>
123 * <LI>An optional set of controls to include in the request to send to the
124 * server.</LI>
125 * <LI>An optional {@link SearchResultListener} which may be used to process
126 * search result entries and search result references returned by the
127 * server in the course of processing the request. If this is
128 * {@code null}, then the entries and references will be collected and
129 * returned in the {@link SearchResult} object that is returned.</LI>
130 * </UL>
131 * When processing a search operation, there are three ways that the returned
132 * entries and references may be accessed:
133 * <UL>
134 * <LI>If the {@link LDAPInterface#search(SearchRequest)} method is used and
135 * the provided search request does not include a
136 * {@link SearchResultListener} object, then the entries and references
137 * will be collected internally and made available in the
138 * {@link SearchResult} object that is returned.</LI>
139 * <LI>If the {@link LDAPInterface#search(SearchRequest)} method is used and
140 * the provided search request does include a {@link SearchResultListener}
141 * object, then that listener will be used to provide access to the
142 * entries and references, and they will not be present in the
143 * {@link SearchResult} object (although the number of entries and
144 * references returned will still be available).</LI>
145 * <LI>The {@link LDAPEntrySource} object may be used to access the entries
146 * and references returned from the search. It uses an
147 * {@code Iterator}-like API to provide access to the entries that are
148 * returned, and any references returned will be included in the
149 * {@link EntrySourceException} thrown on the appropriate call to
150 * {@link LDAPEntrySource#nextEntry()}.</LI>
151 * </UL>
152 * <BR><BR>
153 * {@code SearchRequest} objects are mutable and therefore can be altered and
154 * re-used for multiple requests. Note, however, that {@code SearchRequest}
155 * objects are not threadsafe and therefore a single {@code SearchRequest}
156 * object instance should not be used to process multiple requests at the same
157 * time.
158 * <BR><BR>
159 * <H2>Example</H2>
160 * The following example demonstrates a simple search operation in which the
161 * client performs a search to find all users in the "Sales" department and then
162 * prints out the name and e-mail address for each matching user:
163 * <PRE>
164 * Filter filter = Filter.createEqualityFilter("ou", "Sales");
165 *
166 * SearchRequest searchRequest =
167 * new SearchRequest("dc=example,dc=com", SearchScope.SUB, filter,
168 * "cn", "mail");
169 *
170 * try
171 * {
172 * SearchResult searchResult = connection.search(searchRequest);
173 *
174 * for (SearchResultEntry entry : searchResult.getSearchEntries())
175 * {
176 * String name = entry.getAttributeValue("cn");
177 * String mail = entry.getAttributeValue("mail");
178 * System.out.println(name + "\t" + mail);
179 * }
180 * }
181 * catch (LDAPSearchException lse)
182 * {
183 * System.err.println("The search failed.");
184 * }
185 * </PRE>
186 */
187 @Mutable()
188 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
189 public final class SearchRequest
190 extends UpdatableLDAPRequest
191 implements ReadOnlySearchRequest, ResponseAcceptor, ProtocolOp
192 {
193 /**
194 * The special value "*" that can be included in the set of requested
195 * attributes to indicate that all user attributes should be returned.
196 */
197 public static final String ALL_USER_ATTRIBUTES = "*";
198
199
200
201 /**
202 * The special value "+" that can be included in the set of requested
203 * attributes to indicate that all operational attributes should be returned.
204 */
205 public static final String ALL_OPERATIONAL_ATTRIBUTES = "+";
206
207
208
209 /**
210 * The special value "1.1" that can be included in the set of requested
211 * attributes to indicate that no attributes should be returned, with the
212 * exception of any other attributes explicitly named in the set of requested
213 * attributes.
214 */
215 public static final String NO_ATTRIBUTES = "1.1";
216
217
218
219 /**
220 * The default set of requested attributes that will be used, which will
221 * return all user attributes but no operational attributes.
222 */
223 public static final String[] REQUEST_ATTRS_DEFAULT = NO_STRINGS;
224
225
226
227 /**
228 * The serial version UID for this serializable class.
229 */
230 private static final long serialVersionUID = 1500219434086474893L;
231
232
233
234 // The set of requested attributes.
235 private String[] attributes;
236
237 // Indicates whether to retrieve attribute types only or both types and
238 // values.
239 private boolean typesOnly;
240
241 // The behavior to use when aliases are encountered.
242 private DereferencePolicy derefPolicy;
243
244 // The message ID from the last LDAP message sent from this request.
245 private int messageID = -1;
246
247 // The size limit for this search request.
248 private int sizeLimit;
249
250 // The time limit for this search request.
251 private int timeLimit;
252
253 // The parsed filter for this search request.
254 private Filter filter;
255
256 // The queue that will be used to receive response messages from the server.
257 private final LinkedBlockingQueue<LDAPResponse> responseQueue =
258 new LinkedBlockingQueue<LDAPResponse>(50);
259
260 // The search result listener that should be used to return results
261 // interactively to the requester.
262 private final SearchResultListener searchResultListener;
263
264 // The scope for this search request.
265 private SearchScope scope;
266
267 // The base DN for this search request.
268 private String baseDN;
269
270
271
272 /**
273 * Creates a new search request with the provided information. Search result
274 * entries and references will be collected internally and included in the
275 * {@code SearchResult} object returned when search processing is completed.
276 *
277 * @param baseDN The base DN for the search request. It must not be
278 * {@code null}.
279 * @param scope The scope that specifies the range of entries that
280 * should be examined for the search.
281 * @param filter The string representation of the filter to use to
282 * identify matching entries. It must not be
283 * {@code null}.
284 * @param attributes The set of attributes that should be returned in
285 * matching entries. It may be {@code null} or empty if
286 * the default attribute set (all user attributes) is to
287 * be requested.
288 *
289 * @throws LDAPException If the provided filter string cannot be parsed as
290 * an LDAP filter.
291 */
292 public SearchRequest(final String baseDN, final SearchScope scope,
293 final String filter, final String... attributes)
294 throws LDAPException
295 {
296 this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false,
297 Filter.create(filter), attributes);
298 }
299
300
301
302 /**
303 * Creates a new search request with the provided information. Search result
304 * entries and references will be collected internally and included in the
305 * {@code SearchResult} object returned when search processing is completed.
306 *
307 * @param baseDN The base DN for the search request. It must not be
308 * {@code null}.
309 * @param scope The scope that specifies the range of entries that
310 * should be examined for the search.
311 * @param filter The string representation of the filter to use to
312 * identify matching entries. It must not be
313 * {@code null}.
314 * @param attributes The set of attributes that should be returned in
315 * matching entries. It may be {@code null} or empty if
316 * the default attribute set (all user attributes) is to
317 * be requested.
318 */
319 public SearchRequest(final String baseDN, final SearchScope scope,
320 final Filter filter, final String... attributes)
321 {
322 this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false,
323 filter, attributes);
324 }
325
326
327
328 /**
329 * Creates a new search request with the provided information.
330 *
331 * @param searchResultListener The search result listener that should be
332 * used to return results to the client. It may
333 * be {@code null} if the search results should
334 * be collected internally and returned in the
335 * {@code SearchResult} object.
336 * @param baseDN The base DN for the search request. It must
337 * not be {@code null}.
338 * @param scope The scope that specifies the range of entries
339 * that should be examined for the search.
340 * @param filter The string representation of the filter to
341 * use to identify matching entries. It must
342 * not be {@code null}.
343 * @param attributes The set of attributes that should be returned
344 * in matching entries. It may be {@code null}
345 * or empty if the default attribute set (all
346 * user attributes) is to be requested.
347 *
348 * @throws LDAPException If the provided filter string cannot be parsed as
349 * an LDAP filter.
350 */
351 public SearchRequest(final SearchResultListener searchResultListener,
352 final String baseDN, final SearchScope scope,
353 final String filter, final String... attributes)
354 throws LDAPException
355 {
356 this(searchResultListener, null, baseDN, scope, DereferencePolicy.NEVER, 0,
357 0, false, Filter.create(filter), attributes);
358 }
359
360
361
362 /**
363 * Creates a new search request with the provided information.
364 *
365 * @param searchResultListener The search result listener that should be
366 * used to return results to the client. It may
367 * be {@code null} if the search results should
368 * be collected internally and returned in the
369 * {@code SearchResult} object.
370 * @param baseDN The base DN for the search request. It must
371 * not be {@code null}.
372 * @param scope The scope that specifies the range of entries
373 * that should be examined for the search.
374 * @param filter The string representation of the filter to
375 * use to identify matching entries. It must
376 * not be {@code null}.
377 * @param attributes The set of attributes that should be returned
378 * in matching entries. It may be {@code null}
379 * or empty if the default attribute set (all
380 * user attributes) is to be requested.
381 */
382 public SearchRequest(final SearchResultListener searchResultListener,
383 final String baseDN, final SearchScope scope,
384 final Filter filter, final String... attributes)
385 {
386 this(searchResultListener, null, baseDN, scope, DereferencePolicy.NEVER, 0,
387 0, false, filter, attributes);
388 }
389
390
391
392 /**
393 * Creates a new search request with the provided information. Search result
394 * entries and references will be collected internally and included in the
395 * {@code SearchResult} object returned when search processing is completed.
396 *
397 * @param baseDN The base DN for the search request. It must not be
398 * {@code null}.
399 * @param scope The scope that specifies the range of entries that
400 * should be examined for the search.
401 * @param derefPolicy The dereference policy the server should use for any
402 * aliases encountered while processing the search.
403 * @param sizeLimit The maximum number of entries that the server should
404 * return for the search. A value of zero indicates that
405 * there should be no limit.
406 * @param timeLimit The maximum length of time in seconds that the server
407 * should spend processing this search request. A value
408 * of zero indicates that there should be no limit.
409 * @param typesOnly Indicates whether to return only attribute names in
410 * matching entries, or both attribute names and values.
411 * @param filter The filter to use to identify matching entries. It
412 * must not be {@code null}.
413 * @param attributes The set of attributes that should be returned in
414 * matching entries. It may be {@code null} or empty if
415 * the default attribute set (all user attributes) is to
416 * be requested.
417 *
418 * @throws LDAPException If the provided filter string cannot be parsed as
419 * an LDAP filter.
420 */
421 public SearchRequest(final String baseDN, final SearchScope scope,
422 final DereferencePolicy derefPolicy, final int sizeLimit,
423 final int timeLimit, final boolean typesOnly,
424 final String filter, final String... attributes)
425 throws LDAPException
426 {
427 this(null, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit,
428 typesOnly, Filter.create(filter), attributes);
429 }
430
431
432
433 /**
434 * Creates a new search request with the provided information. Search result
435 * entries and references will be collected internally and included in the
436 * {@code SearchResult} object returned when search processing is completed.
437 *
438 * @param baseDN The base DN for the search request. It must not be
439 * {@code null}.
440 * @param scope The scope that specifies the range of entries that
441 * should be examined for the search.
442 * @param derefPolicy The dereference policy the server should use for any
443 * aliases encountered while processing the search.
444 * @param sizeLimit The maximum number of entries that the server should
445 * return for the search. A value of zero indicates that
446 * there should be no limit.
447 * @param timeLimit The maximum length of time in seconds that the server
448 * should spend processing this search request. A value
449 * of zero indicates that there should be no limit.
450 * @param typesOnly Indicates whether to return only attribute names in
451 * matching entries, or both attribute names and values.
452 * @param filter The filter to use to identify matching entries. It
453 * must not be {@code null}.
454 * @param attributes The set of attributes that should be returned in
455 * matching entries. It may be {@code null} or empty if
456 * the default attribute set (all user attributes) is to
457 * be requested.
458 */
459 public SearchRequest(final String baseDN, final SearchScope scope,
460 final DereferencePolicy derefPolicy, final int sizeLimit,
461 final int timeLimit, final boolean typesOnly,
462 final Filter filter, final String... attributes)
463 {
464 this(null, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit,
465 typesOnly, filter, attributes);
466 }
467
468
469
470 /**
471 * Creates a new search request with the provided information.
472 *
473 * @param searchResultListener The search result listener that should be
474 * used to return results to the client. It may
475 * be {@code null} if the search results should
476 * be collected internally and returned in the
477 * {@code SearchResult} object.
478 * @param baseDN The base DN for the search request. It must
479 * not be {@code null}.
480 * @param scope The scope that specifies the range of entries
481 * that should be examined for the search.
482 * @param derefPolicy The dereference policy the server should use
483 * for any aliases encountered while processing
484 * the search.
485 * @param sizeLimit The maximum number of entries that the server
486 * should return for the search. A value of
487 * zero indicates that there should be no limit.
488 * @param timeLimit The maximum length of time in seconds that
489 * the server should spend processing this
490 * search request. A value of zero indicates
491 * that there should be no limit.
492 * @param typesOnly Indicates whether to return only attribute
493 * names in matching entries, or both attribute
494 * names and values.
495 * @param filter The filter to use to identify matching
496 * entries. It must not be {@code null}.
497 * @param attributes The set of attributes that should be returned
498 * in matching entries. It may be {@code null}
499 * or empty if the default attribute set (all
500 * user attributes) is to be requested.
501 *
502 * @throws LDAPException If the provided filter string cannot be parsed as
503 * an LDAP filter.
504 */
505 public SearchRequest(final SearchResultListener searchResultListener,
506 final String baseDN, final SearchScope scope,
507 final DereferencePolicy derefPolicy, final int sizeLimit,
508 final int timeLimit, final boolean typesOnly,
509 final String filter, final String... attributes)
510 throws LDAPException
511 {
512 this(searchResultListener, null, baseDN, scope, derefPolicy, sizeLimit,
513 timeLimit, typesOnly, Filter.create(filter), attributes);
514 }
515
516
517
518 /**
519 * Creates a new search request with the provided information.
520 *
521 * @param searchResultListener The search result listener that should be
522 * used to return results to the client. It may
523 * be {@code null} if the search results should
524 * be collected internally and returned in the
525 * {@code SearchResult} object.
526 * @param baseDN The base DN for the search request. It must
527 * not be {@code null}.
528 * @param scope The scope that specifies the range of entries
529 * that should be examined for the search.
530 * @param derefPolicy The dereference policy the server should use
531 * for any aliases encountered while processing
532 * the search.
533 * @param sizeLimit The maximum number of entries that the server
534 * should return for the search. A value of
535 * zero indicates that there should be no limit.
536 * @param timeLimit The maximum length of time in seconds that
537 * the server should spend processing this
538 * search request. A value of zero indicates
539 * that there should be no limit.
540 * @param typesOnly Indicates whether to return only attribute
541 * names in matching entries, or both attribute
542 * names and values.
543 * @param filter The filter to use to identify matching
544 * entries. It must not be {@code null}.
545 * @param attributes The set of attributes that should be returned
546 * in matching entries. It may be {@code null}
547 * or empty if the default attribute set (all
548 * user attributes) is to be requested.
549 */
550 public SearchRequest(final SearchResultListener searchResultListener,
551 final String baseDN, final SearchScope scope,
552 final DereferencePolicy derefPolicy, final int sizeLimit,
553 final int timeLimit, final boolean typesOnly,
554 final Filter filter, final String... attributes)
555 {
556 this(searchResultListener, null, baseDN, scope, derefPolicy, sizeLimit,
557 timeLimit, typesOnly, filter, attributes);
558 }
559
560
561
562 /**
563 * Creates a new search request with the provided information.
564 *
565 * @param searchResultListener The search result listener that should be
566 * used to return results to the client. It may
567 * be {@code null} if the search results should
568 * be collected internally and returned in the
569 * {@code SearchResult} object.
570 * @param controls The set of controls to include in the
571 * request. It may be {@code null} or empty if
572 * no controls should be included in the
573 * request.
574 * @param baseDN The base DN for the search request. It must
575 * not be {@code null}.
576 * @param scope The scope that specifies the range of entries
577 * that should be examined for the search.
578 * @param derefPolicy The dereference policy the server should use
579 * for any aliases encountered while processing
580 * the search.
581 * @param sizeLimit The maximum number of entries that the server
582 * should return for the search. A value of
583 * zero indicates that there should be no limit.
584 * @param timeLimit The maximum length of time in seconds that
585 * the server should spend processing this
586 * search request. A value of zero indicates
587 * that there should be no limit.
588 * @param typesOnly Indicates whether to return only attribute
589 * names in matching entries, or both attribute
590 * names and values.
591 * @param filter The filter to use to identify matching
592 * entries. It must not be {@code null}.
593 * @param attributes The set of attributes that should be returned
594 * in matching entries. It may be {@code null}
595 * or empty if the default attribute set (all
596 * user attributes) is to be requested.
597 *
598 * @throws LDAPException If the provided filter string cannot be parsed as
599 * an LDAP filter.
600 */
601 public SearchRequest(final SearchResultListener searchResultListener,
602 final Control[] controls, final String baseDN,
603 final SearchScope scope,
604 final DereferencePolicy derefPolicy, final int sizeLimit,
605 final int timeLimit, final boolean typesOnly,
606 final String filter, final String... attributes)
607 throws LDAPException
608 {
609 this(searchResultListener, controls, baseDN, scope, derefPolicy, sizeLimit,
610 timeLimit, typesOnly, Filter.create(filter), attributes);
611 }
612
613
614
615 /**
616 * Creates a new search request with the provided information.
617 *
618 * @param searchResultListener The search result listener that should be
619 * used to return results to the client. It may
620 * be {@code null} if the search results should
621 * be collected internally and returned in the
622 * {@code SearchResult} object.
623 * @param controls The set of controls to include in the
624 * request. It may be {@code null} or empty if
625 * no controls should be included in the
626 * request.
627 * @param baseDN The base DN for the search request. It must
628 * not be {@code null}.
629 * @param scope The scope that specifies the range of entries
630 * that should be examined for the search.
631 * @param derefPolicy The dereference policy the server should use
632 * for any aliases encountered while processing
633 * the search.
634 * @param sizeLimit The maximum number of entries that the server
635 * should return for the search. A value of
636 * zero indicates that there should be no limit.
637 * @param timeLimit The maximum length of time in seconds that
638 * the server should spend processing this
639 * search request. A value of zero indicates
640 * that there should be no limit.
641 * @param typesOnly Indicates whether to return only attribute
642 * names in matching entries, or both attribute
643 * names and values.
644 * @param filter The filter to use to identify matching
645 * entries. It must not be {@code null}.
646 * @param attributes The set of attributes that should be returned
647 * in matching entries. It may be {@code null}
648 * or empty if the default attribute set (all
649 * user attributes) is to be requested.
650 */
651 public SearchRequest(final SearchResultListener searchResultListener,
652 final Control[] controls, final String baseDN,
653 final SearchScope scope,
654 final DereferencePolicy derefPolicy, final int sizeLimit,
655 final int timeLimit, final boolean typesOnly,
656 final Filter filter, final String... attributes)
657 {
658 super(controls);
659
660 ensureNotNull(baseDN, filter);
661
662 this.baseDN = baseDN;
663 this.scope = scope;
664 this.derefPolicy = derefPolicy;
665 this.typesOnly = typesOnly;
666 this.filter = filter;
667 this.searchResultListener = searchResultListener;
668
669 if (sizeLimit < 0)
670 {
671 this.sizeLimit = 0;
672 }
673 else
674 {
675 this.sizeLimit = sizeLimit;
676 }
677
678 if (timeLimit < 0)
679 {
680 this.timeLimit = 0;
681 }
682 else
683 {
684 this.timeLimit = timeLimit;
685 }
686
687 if (attributes == null)
688 {
689 this.attributes = REQUEST_ATTRS_DEFAULT;
690 }
691 else
692 {
693 this.attributes = attributes;
694 }
695 }
696
697
698
699 /**
700 * {@inheritDoc}
701 */
702 public String getBaseDN()
703 {
704 return baseDN;
705 }
706
707
708
709 /**
710 * Specifies the base DN for this search request.
711 *
712 * @param baseDN The base DN for this search request. It must not be
713 * {@code null}.
714 */
715 public void setBaseDN(final String baseDN)
716 {
717 ensureNotNull(baseDN);
718
719 this.baseDN = baseDN;
720 }
721
722
723
724 /**
725 * Specifies the base DN for this search request.
726 *
727 * @param baseDN The base DN for this search request. It must not be
728 * {@code null}.
729 */
730 public void setBaseDN(final DN baseDN)
731 {
732 ensureNotNull(baseDN);
733
734 this.baseDN = baseDN.toString();
735 }
736
737
738
739 /**
740 * {@inheritDoc}
741 */
742 public SearchScope getScope()
743 {
744 return scope;
745 }
746
747
748
749 /**
750 * Specifies the scope for this search request.
751 *
752 * @param scope The scope for this search request.
753 */
754 public void setScope(final SearchScope scope)
755 {
756 this.scope = scope;
757 }
758
759
760
761 /**
762 * {@inheritDoc}
763 */
764 public DereferencePolicy getDereferencePolicy()
765 {
766 return derefPolicy;
767 }
768
769
770
771 /**
772 * Specifies the dereference policy that should be used by the server for any
773 * aliases encountered during search processing.
774 *
775 * @param derefPolicy The dereference policy that should be used by the
776 * server for any aliases encountered during search
777 * processing.
778 */
779 public void setDerefPolicy(final DereferencePolicy derefPolicy)
780 {
781 this.derefPolicy = derefPolicy;
782 }
783
784
785
786 /**
787 * {@inheritDoc}
788 */
789 public int getSizeLimit()
790 {
791 return sizeLimit;
792 }
793
794
795
796 /**
797 * Specifies the maximum number of entries that should be returned by the
798 * server when processing this search request. A value of zero indicates that
799 * there should be no limit.
800 *
801 * @param sizeLimit The maximum number of entries that should be returned by
802 * the server when processing this search request.
803 */
804 public void setSizeLimit(final int sizeLimit)
805 {
806 if (sizeLimit < 0)
807 {
808 this.sizeLimit = 0;
809 }
810 else
811 {
812 this.sizeLimit = sizeLimit;
813 }
814 }
815
816
817
818 /**
819 * {@inheritDoc}
820 */
821 public int getTimeLimitSeconds()
822 {
823 return timeLimit;
824 }
825
826
827
828 /**
829 * Specifies the maximum length of time in seconds that the server should
830 * spend processing this search request. A value of zero indicates that there
831 * should be no limit.
832 *
833 * @param timeLimit The maximum length of time in seconds that the server
834 * should spend processing this search request.
835 */
836 public void setTimeLimitSeconds(final int timeLimit)
837 {
838 if (timeLimit < 0)
839 {
840 this.timeLimit = 0;
841 }
842 else
843 {
844 this.timeLimit = timeLimit;
845 }
846 }
847
848
849
850 /**
851 * {@inheritDoc}
852 */
853 public boolean typesOnly()
854 {
855 return typesOnly;
856 }
857
858
859
860 /**
861 * Specifies whether the server should return only attribute names in matching
862 * entries, rather than both names and values.
863 *
864 * @param typesOnly Specifies whether the server should return only
865 * attribute names in matching entries, rather than both
866 * names and values.
867 */
868 public void setTypesOnly(final boolean typesOnly)
869 {
870 this.typesOnly = typesOnly;
871 }
872
873
874
875 /**
876 * {@inheritDoc}
877 */
878 public Filter getFilter()
879 {
880 return filter;
881 }
882
883
884
885 /**
886 * Specifies the filter that should be used to identify matching entries.
887 *
888 * @param filter The string representation for the filter that should be
889 * used to identify matching entries. It must not be
890 * {@code null}.
891 *
892 * @throws LDAPException If the provided filter string cannot be parsed as a
893 * search filter.
894 */
895 public void setFilter(final String filter)
896 throws LDAPException
897 {
898 ensureNotNull(filter);
899
900 this.filter = Filter.create(filter);
901 }
902
903
904
905 /**
906 * Specifies the filter that should be used to identify matching entries.
907 *
908 * @param filter The filter that should be used to identify matching
909 * entries. It must not be {@code null}.
910 */
911 public void setFilter(final Filter filter)
912 {
913 ensureNotNull(filter);
914
915 this.filter = filter;
916 }
917
918
919
920 /**
921 * Retrieves the set of requested attributes to include in matching entries.
922 * The caller must not attempt to alter the contents of the array.
923 *
924 * @return The set of requested attributes to include in matching entries, or
925 * an empty array if the default set of attributes (all user
926 * attributes but no operational attributes) should be requested.
927 */
928 public String[] getAttributes()
929 {
930 return attributes;
931 }
932
933
934
935 /**
936 * {@inheritDoc}
937 */
938 public List<String> getAttributeList()
939 {
940 return Collections.unmodifiableList(Arrays.asList(attributes));
941 }
942
943
944
945 /**
946 * Specifies the set of requested attributes to include in matching entries.
947 *
948 * @param attributes The set of requested attributes to include in matching
949 * entries. It may be {@code null} if the default set of
950 * attributes (all user attributes but no operational
951 * attributes) should be requested.
952 */
953 public void setAttributes(final String... attributes)
954 {
955 if (attributes == null)
956 {
957 this.attributes = REQUEST_ATTRS_DEFAULT;
958 }
959 else
960 {
961 this.attributes = attributes;
962 }
963 }
964
965
966
967 /**
968 * Specifies the set of requested attributes to include in matching entries.
969 *
970 * @param attributes The set of requested attributes to include in matching
971 * entries. It may be {@code null} if the default set of
972 * attributes (all user attributes but no operational
973 * attributes) should be requested.
974 */
975 public void setAttributes(final List<String> attributes)
976 {
977 if (attributes == null)
978 {
979 this.attributes = REQUEST_ATTRS_DEFAULT;
980 }
981 else
982 {
983 this.attributes = new String[attributes.size()];
984 for (int i=0; i < this.attributes.length; i++)
985 {
986 this.attributes[i] = attributes.get(i);
987 }
988 }
989 }
990
991
992
993 /**
994 * Retrieves the search result listener for this search request, if available.
995 *
996 * @return The search result listener for this search request, or
997 * {@code null} if none has been configured.
998 */
999 public SearchResultListener getSearchResultListener()
1000 {
1001 return searchResultListener;
1002 }
1003
1004
1005
1006 /**
1007 * {@inheritDoc}
1008 */
1009 public byte getProtocolOpType()
1010 {
1011 return LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST;
1012 }
1013
1014
1015
1016 /**
1017 * {@inheritDoc}
1018 */
1019 public void writeTo(final ASN1Buffer writer)
1020 {
1021 final ASN1BufferSequence requestSequence =
1022 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST);
1023 writer.addOctetString(baseDN);
1024 writer.addEnumerated(scope.intValue());
1025 writer.addEnumerated(derefPolicy.intValue());
1026 writer.addInteger(sizeLimit);
1027 writer.addInteger(timeLimit);
1028 writer.addBoolean(typesOnly);
1029 filter.writeTo(writer);
1030
1031 final ASN1BufferSequence attrSequence = writer.beginSequence();
1032 for (final String s : attributes)
1033 {
1034 writer.addOctetString(s);
1035 }
1036 attrSequence.end();
1037 requestSequence.end();
1038 }
1039
1040
1041
1042 /**
1043 * Encodes the search request protocol op to an ASN.1 element.
1044 *
1045 * @return The ASN.1 element with the encoded search request protocol op.
1046 */
1047 ASN1Element encodeProtocolOp()
1048 {
1049 // Create the search request protocol op.
1050 final ASN1Element[] attrElements = new ASN1Element[attributes.length];
1051 for (int i=0; i < attrElements.length; i++)
1052 {
1053 attrElements[i] = new ASN1OctetString(attributes[i]);
1054 }
1055
1056 final ASN1Element[] protocolOpElements =
1057 {
1058 new ASN1OctetString(baseDN),
1059 new ASN1Enumerated(scope.intValue()),
1060 new ASN1Enumerated(derefPolicy.intValue()),
1061 new ASN1Integer(sizeLimit),
1062 new ASN1Integer(timeLimit),
1063 new ASN1Boolean(typesOnly),
1064 filter.encode(),
1065 new ASN1Sequence(attrElements)
1066 };
1067
1068 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST,
1069 protocolOpElements);
1070 }
1071
1072
1073
1074 /**
1075 * Sends this search request to the directory server over the provided
1076 * connection and returns the associated response. The search result entries
1077 * and references will either be collected and returned in the
1078 * {@code SearchResult} object that is returned, or will be interactively
1079 * returned via the {@code SearchResultListener} interface.
1080 *
1081 * @param connection The connection to use to communicate with the directory
1082 * server.
1083 * @param depth The current referral depth for this request. It should
1084 * always be one for the initial request, and should only
1085 * be incremented when following referrals.
1086 *
1087 * @return An object that provides information about the result of the
1088 * search processing, potentially including the sets of matching
1089 * entries and/or search references.
1090 *
1091 * @throws LDAPException If a problem occurs while sending the request or
1092 * reading the response.
1093 */
1094 @Override()
1095 protected SearchResult process(final LDAPConnection connection,
1096 final int depth)
1097 throws LDAPException
1098 {
1099 if (connection.synchronousMode())
1100 {
1101 return processSync(connection, depth);
1102 }
1103
1104 final long requestTime = System.nanoTime();
1105 processAsync(connection, null);
1106
1107 try
1108 {
1109 // Wait for and process the response.
1110 final ArrayList<SearchResultEntry> entryList;
1111 final ArrayList<SearchResultReference> referenceList;
1112 if (searchResultListener == null)
1113 {
1114 entryList = new ArrayList<SearchResultEntry>(5);
1115 referenceList = new ArrayList<SearchResultReference>(5);
1116 }
1117 else
1118 {
1119 entryList = null;
1120 referenceList = null;
1121 }
1122
1123 int numEntries = 0;
1124 int numReferences = 0;
1125 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1126 final long responseTimeout = getResponseTimeoutMillis(connection);
1127 while (true)
1128 {
1129 final LDAPResponse response;
1130 try
1131 {
1132 if (responseTimeout > 0)
1133 {
1134 response =
1135 responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
1136 }
1137 else
1138 {
1139 response = responseQueue.take();
1140 }
1141 }
1142 catch (InterruptedException ie)
1143 {
1144 debugException(ie);
1145 throw new LDAPException(ResultCode.LOCAL_ERROR,
1146 ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie);
1147 }
1148
1149 if (response == null)
1150 {
1151 if (connection.getConnectionOptions().abandonOnTimeout())
1152 {
1153 connection.abandon(messageID);
1154 }
1155
1156 final SearchResult searchResult =
1157 new SearchResult(messageID, ResultCode.TIMEOUT,
1158 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout,
1159 connection.getHostPort()),
1160 null, null, entryList, referenceList, numEntries,
1161 numReferences, null);
1162 throw new LDAPSearchException(searchResult);
1163 }
1164
1165 if (response instanceof ConnectionClosedResponse)
1166 {
1167 final ConnectionClosedResponse ccr =
1168 (ConnectionClosedResponse) response;
1169 final String message = ccr.getMessage();
1170 if (message == null)
1171 {
1172 // The connection was closed while waiting for the response.
1173 final SearchResult searchResult =
1174 new SearchResult(messageID, ccr.getResultCode(),
1175 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1176 connection.getHostPort(), toString()),
1177 null, null, entryList, referenceList, numEntries,
1178 numReferences, null);
1179 throw new LDAPSearchException(searchResult);
1180 }
1181 else
1182 {
1183 // The connection was closed while waiting for the response.
1184 final SearchResult searchResult =
1185 new SearchResult(messageID, ccr.getResultCode(),
1186 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1187 get(connection.getHostPort(), toString(), message),
1188 null, null, entryList, referenceList, numEntries,
1189 numReferences, null);
1190 throw new LDAPSearchException(searchResult);
1191 }
1192 }
1193 else if (response instanceof SearchResultEntry)
1194 {
1195 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1196 numEntries++;
1197 if (searchResultListener == null)
1198 {
1199 entryList.add(searchEntry);
1200 }
1201 else
1202 {
1203 searchResultListener.searchEntryReturned(searchEntry);
1204 }
1205 }
1206 else if (response instanceof SearchResultReference)
1207 {
1208 final SearchResultReference searchReference =
1209 (SearchResultReference) response;
1210 if (followReferrals(connection))
1211 {
1212 final LDAPResult result = followSearchReference(messageID,
1213 searchReference, connection, depth);
1214 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1215 {
1216 // We couldn't follow the reference. We don't want to fail the
1217 // entire search because of this right now, so treat it as if
1218 // referral following had not been enabled. Also, set the
1219 // intermediate result code to match that of the result.
1220 numReferences++;
1221 if (searchResultListener == null)
1222 {
1223 referenceList.add(searchReference);
1224 }
1225 else
1226 {
1227 searchResultListener.searchReferenceReturned(searchReference);
1228 }
1229
1230 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1231 {
1232 intermediateResultCode = result.getResultCode();
1233 }
1234 }
1235 else if (result instanceof SearchResult)
1236 {
1237 final SearchResult searchResult = (SearchResult) result;
1238 numEntries += searchResult.getEntryCount();
1239 if (searchResultListener == null)
1240 {
1241 entryList.addAll(searchResult.getSearchEntries());
1242 }
1243 }
1244 }
1245 else
1246 {
1247 numReferences++;
1248 if (searchResultListener == null)
1249 {
1250 referenceList.add(searchReference);
1251 }
1252 else
1253 {
1254 searchResultListener.searchReferenceReturned(searchReference);
1255 }
1256 }
1257 }
1258 else
1259 {
1260 connection.getConnectionStatistics().incrementNumSearchResponses(
1261 numEntries, numReferences,
1262 (System.nanoTime() - requestTime));
1263 SearchResult result = (SearchResult) response;
1264 result.setCounts(numEntries, entryList, numReferences, referenceList);
1265
1266 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1267 followReferrals(connection))
1268 {
1269 if (depth >=
1270 connection.getConnectionOptions().getReferralHopLimit())
1271 {
1272 return new SearchResult(messageID,
1273 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1274 ERR_TOO_MANY_REFERRALS.get(),
1275 result.getMatchedDN(),
1276 result.getReferralURLs(), entryList,
1277 referenceList, numEntries,
1278 numReferences,
1279 result.getResponseControls());
1280 }
1281
1282 result = followReferral(result, connection, depth);
1283 }
1284
1285 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1286 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1287 {
1288 return new SearchResult(messageID, intermediateResultCode,
1289 result.getDiagnosticMessage(),
1290 result.getMatchedDN(),
1291 result.getReferralURLs(),
1292 entryList, referenceList, numEntries,
1293 numReferences,
1294 result.getResponseControls());
1295 }
1296
1297 return result;
1298 }
1299 }
1300 }
1301 finally
1302 {
1303 connection.deregisterResponseAcceptor(messageID);
1304 }
1305 }
1306
1307
1308
1309 /**
1310 * Sends this search request to the directory server over the provided
1311 * connection and returns the message ID for the request.
1312 *
1313 * @param connection The connection to use to communicate with the
1314 * directory server.
1315 * @param resultListener The async result listener that is to be notified
1316 * when the response is received. It may be
1317 * {@code null} only if the result is to be processed
1318 * by this class.
1319 *
1320 * @return The async request ID created for the operation, or {@code null} if
1321 * the provided {@code resultListener} is {@code null} and the
1322 * operation will not actually be processed asynchronously.
1323 *
1324 * @throws LDAPException If a problem occurs while sending the request.
1325 */
1326 AsyncRequestID processAsync(final LDAPConnection connection,
1327 final AsyncSearchResultListener resultListener)
1328 throws LDAPException
1329 {
1330 // Create the LDAP message.
1331 messageID = connection.nextMessageID();
1332 final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
1333
1334
1335 // If the provided async result listener is {@code null}, then we'll use
1336 // this class as the message acceptor. Otherwise, create an async helper
1337 // and use it as the message acceptor.
1338 final AsyncRequestID asyncRequestID;
1339 if (resultListener == null)
1340 {
1341 asyncRequestID = null;
1342 connection.registerResponseAcceptor(messageID, this);
1343 }
1344 else
1345 {
1346 final AsyncSearchHelper helper = new AsyncSearchHelper(connection,
1347 messageID, resultListener, getIntermediateResponseListener());
1348 connection.registerResponseAcceptor(messageID, helper);
1349 asyncRequestID = helper.getAsyncRequestID();
1350
1351 final long timeout = getResponseTimeoutMillis(connection);
1352 if (timeout > 0L)
1353 {
1354 final Timer timer = connection.getTimer();
1355 final AsyncTimeoutTimerTask timerTask =
1356 new AsyncTimeoutTimerTask(helper);
1357 timer.schedule(timerTask, timeout);
1358 asyncRequestID.setTimerTask(timerTask);
1359 }
1360 }
1361
1362
1363 // Send the request to the server.
1364 try
1365 {
1366 debugLDAPRequest(this);
1367 connection.getConnectionStatistics().incrementNumSearchRequests();
1368 connection.sendMessage(message);
1369 return asyncRequestID;
1370 }
1371 catch (LDAPException le)
1372 {
1373 debugException(le);
1374
1375 connection.deregisterResponseAcceptor(messageID);
1376 throw le;
1377 }
1378 }
1379
1380
1381
1382 /**
1383 * Processes this search operation in synchronous mode, in which the same
1384 * thread will send the request and read the response.
1385 *
1386 * @param connection The connection to use to communicate with the directory
1387 * server.
1388 * @param depth The current referral depth for this request. It should
1389 * always be one for the initial request, and should only
1390 * be incremented when following referrals.
1391 *
1392 * @return An LDAP result object that provides information about the result
1393 * of the search processing.
1394 *
1395 * @throws LDAPException If a problem occurs while sending the request or
1396 * reading the response.
1397 */
1398 private SearchResult processSync(final LDAPConnection connection,
1399 final int depth)
1400 throws LDAPException
1401 {
1402 // Create the LDAP message.
1403 messageID = connection.nextMessageID();
1404 final LDAPMessage message =
1405 new LDAPMessage(messageID, this, getControls());
1406
1407
1408 // Set the appropriate timeout on the socket.
1409 final long responseTimeout = getResponseTimeoutMillis(connection);
1410 try
1411 {
1412 connection.getConnectionInternals().getSocket().setSoTimeout(
1413 (int) responseTimeout);
1414 }
1415 catch (Exception e)
1416 {
1417 debugException(e);
1418 }
1419
1420
1421 // Send the request to the server.
1422 final long requestTime = System.nanoTime();
1423 debugLDAPRequest(this);
1424 connection.getConnectionStatistics().incrementNumSearchRequests();
1425 connection.sendMessage(message);
1426
1427 final ArrayList<SearchResultEntry> entryList;
1428 final ArrayList<SearchResultReference> referenceList;
1429 if (searchResultListener == null)
1430 {
1431 entryList = new ArrayList<SearchResultEntry>(5);
1432 referenceList = new ArrayList<SearchResultReference>(5);
1433 }
1434 else
1435 {
1436 entryList = null;
1437 referenceList = null;
1438 }
1439
1440 int numEntries = 0;
1441 int numReferences = 0;
1442 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1443 while (true)
1444 {
1445 final LDAPResponse response;
1446 try
1447 {
1448 response = connection.readResponse(messageID);
1449 }
1450 catch (final LDAPException le)
1451 {
1452 debugException(le);
1453
1454 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1455 connection.getConnectionOptions().abandonOnTimeout())
1456 {
1457 connection.abandon(messageID);
1458 }
1459
1460 throw le;
1461 }
1462
1463 if (response == null)
1464 {
1465 if (connection.getConnectionOptions().abandonOnTimeout())
1466 {
1467 connection.abandon(messageID);
1468 }
1469
1470 throw new LDAPException(ResultCode.TIMEOUT,
1471 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout,
1472 connection.getHostPort()));
1473 }
1474 else if (response instanceof ConnectionClosedResponse)
1475 {
1476 final ConnectionClosedResponse ccr =
1477 (ConnectionClosedResponse) response;
1478 final String msg = ccr.getMessage();
1479 if (msg == null)
1480 {
1481 // The connection was closed while waiting for the response.
1482 final SearchResult searchResult =
1483 new SearchResult(messageID, ccr.getResultCode(),
1484 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1485 connection.getHostPort(), toString()),
1486 null, null, entryList, referenceList, numEntries,
1487 numReferences, null);
1488 throw new LDAPSearchException(searchResult);
1489 }
1490 else
1491 {
1492 // The connection was closed while waiting for the response.
1493 final SearchResult searchResult =
1494 new SearchResult(messageID, ccr.getResultCode(),
1495 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1496 get(connection.getHostPort(), toString(), msg),
1497 null, null, entryList, referenceList, numEntries,
1498 numReferences, null);
1499 throw new LDAPSearchException(searchResult);
1500 }
1501 }
1502 else if (response instanceof IntermediateResponse)
1503 {
1504 final IntermediateResponseListener listener =
1505 getIntermediateResponseListener();
1506 if (listener != null)
1507 {
1508 listener.intermediateResponseReturned(
1509 (IntermediateResponse) response);
1510 }
1511 }
1512 else if (response instanceof SearchResultEntry)
1513 {
1514 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1515 numEntries++;
1516 if (searchResultListener == null)
1517 {
1518 entryList.add(searchEntry);
1519 }
1520 else
1521 {
1522 searchResultListener.searchEntryReturned(searchEntry);
1523 }
1524 }
1525 else if (response instanceof SearchResultReference)
1526 {
1527 final SearchResultReference searchReference =
1528 (SearchResultReference) response;
1529 if (followReferrals(connection))
1530 {
1531 final LDAPResult result = followSearchReference(messageID,
1532 searchReference, connection, depth);
1533 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1534 {
1535 // We couldn't follow the reference. We don't want to fail the
1536 // entire search because of this right now, so treat it as if
1537 // referral following had not been enabled. Also, set the
1538 // intermediate result code to match that of the result.
1539 numReferences++;
1540 if (searchResultListener == null)
1541 {
1542 referenceList.add(searchReference);
1543 }
1544 else
1545 {
1546 searchResultListener.searchReferenceReturned(searchReference);
1547 }
1548
1549 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1550 {
1551 intermediateResultCode = result.getResultCode();
1552 }
1553 }
1554 else if (result instanceof SearchResult)
1555 {
1556 final SearchResult searchResult = (SearchResult) result;
1557 numEntries += searchResult.getEntryCount();
1558 if (searchResultListener == null)
1559 {
1560 entryList.addAll(searchResult.getSearchEntries());
1561 }
1562 }
1563 }
1564 else
1565 {
1566 numReferences++;
1567 if (searchResultListener == null)
1568 {
1569 referenceList.add(searchReference);
1570 }
1571 else
1572 {
1573 searchResultListener.searchReferenceReturned(searchReference);
1574 }
1575 }
1576 }
1577 else
1578 {
1579 return handleResponse(connection, response, requestTime, depth,
1580 numEntries, numReferences, entryList,
1581 referenceList, intermediateResultCode);
1582 }
1583 }
1584 }
1585
1586
1587
1588 /**
1589 * Performs the necessary processing for handling a response.
1590 *
1591 * @param connection The connection used to read the response.
1592 * @param response The response to be processed.
1593 * @param requestTime The time the request was sent to the
1594 * server.
1595 * @param depth The current referral depth for this
1596 * request. It should always be one for the
1597 * initial request, and should only be
1598 * incremented when following referrals.
1599 * @param numEntries The number of entries received from the
1600 * server.
1601 * @param numReferences The number of references received from
1602 * the server.
1603 * @param entryList The list of search result entries received
1604 * from the server, if applicable.
1605 * @param referenceList The list of search result references
1606 * received from the server, if applicable.
1607 * @param intermediateResultCode The intermediate result code so far for the
1608 * search operation.
1609 *
1610 * @return The search result.
1611 *
1612 * @throws LDAPException If a problem occurs.
1613 */
1614 private SearchResult handleResponse(final LDAPConnection connection,
1615 final LDAPResponse response, final long requestTime,
1616 final int depth, final int numEntries, final int numReferences,
1617 final List<SearchResultEntry> entryList,
1618 final List<SearchResultReference> referenceList,
1619 final ResultCode intermediateResultCode)
1620 throws LDAPException
1621 {
1622 connection.getConnectionStatistics().incrementNumSearchResponses(
1623 numEntries, numReferences,
1624 (System.nanoTime() - requestTime));
1625 SearchResult result = (SearchResult) response;
1626 result.setCounts(numEntries, entryList, numReferences, referenceList);
1627
1628 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1629 followReferrals(connection))
1630 {
1631 if (depth >=
1632 connection.getConnectionOptions().getReferralHopLimit())
1633 {
1634 return new SearchResult(messageID,
1635 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1636 ERR_TOO_MANY_REFERRALS.get(),
1637 result.getMatchedDN(),
1638 result.getReferralURLs(), entryList,
1639 referenceList, numEntries,
1640 numReferences,
1641 result.getResponseControls());
1642 }
1643
1644 result = followReferral(result, connection, depth);
1645 }
1646
1647 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1648 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1649 {
1650 return new SearchResult(messageID, intermediateResultCode,
1651 result.getDiagnosticMessage(),
1652 result.getMatchedDN(),
1653 result.getReferralURLs(),
1654 entryList, referenceList, numEntries,
1655 numReferences,
1656 result.getResponseControls());
1657 }
1658
1659 return result;
1660 }
1661
1662
1663
1664 /**
1665 * Attempts to follow a search result reference to continue a search in a
1666 * remote server.
1667 *
1668 * @param messageID The message ID for the LDAP message that is
1669 * associated with this result.
1670 * @param searchReference The search result reference to follow.
1671 * @param connection The connection on which the reference was
1672 * received.
1673 * @param depth The number of referrals followed in the course of
1674 * processing this request.
1675 *
1676 * @return The result of attempting to follow the search result reference.
1677 *
1678 * @throws LDAPException If a problem occurs while attempting to establish
1679 * the referral connection, sending the request, or
1680 * reading the result.
1681 */
1682 private LDAPResult followSearchReference(final int messageID,
1683 final SearchResultReference searchReference,
1684 final LDAPConnection connection, final int depth)
1685 throws LDAPException
1686 {
1687 for (final String urlString : searchReference.getReferralURLs())
1688 {
1689 try
1690 {
1691 final LDAPURL referralURL = new LDAPURL(urlString);
1692 final String host = referralURL.getHost();
1693
1694 if (host == null)
1695 {
1696 // We can't handle a referral in which there is no host.
1697 continue;
1698 }
1699
1700 final String requestBaseDN;
1701 if (referralURL.baseDNProvided())
1702 {
1703 requestBaseDN = referralURL.getBaseDN().toString();
1704 }
1705 else
1706 {
1707 requestBaseDN = baseDN;
1708 }
1709
1710 final SearchScope requestScope;
1711 if (referralURL.scopeProvided())
1712 {
1713 requestScope = referralURL.getScope();
1714 }
1715 else
1716 {
1717 requestScope = scope;
1718 }
1719
1720 final Filter requestFilter;
1721 if (referralURL.filterProvided())
1722 {
1723 requestFilter = referralURL.getFilter();
1724 }
1725 else
1726 {
1727 requestFilter = filter;
1728 }
1729
1730
1731 final SearchRequest searchRequest =
1732 new SearchRequest(searchResultListener, getControls(),
1733 requestBaseDN, requestScope, derefPolicy,
1734 sizeLimit, timeLimit, typesOnly, requestFilter,
1735 attributes);
1736
1737 final LDAPConnection referralConn = connection.getReferralConnector().
1738 getReferralConnection(referralURL, connection);
1739
1740 try
1741 {
1742 return searchRequest.process(referralConn, depth+1);
1743 }
1744 finally
1745 {
1746 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1747 referralConn.close();
1748 }
1749 }
1750 catch (LDAPException le)
1751 {
1752 debugException(le);
1753
1754 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1755 {
1756 throw le;
1757 }
1758 }
1759 }
1760
1761 // If we've gotten here, then we could not follow any of the referral URLs,
1762 // so we'll create a failure result.
1763 return new SearchResult(messageID, ResultCode.REFERRAL, null, null,
1764 searchReference.getReferralURLs(), 0, 0, null);
1765 }
1766
1767
1768
1769 /**
1770 * Attempts to follow a referral to perform an add operation in the target
1771 * server.
1772 *
1773 * @param referralResult The LDAP result object containing information about
1774 * the referral to follow.
1775 * @param connection The connection on which the referral was received.
1776 * @param depth The number of referrals followed in the course of
1777 * processing this request.
1778 *
1779 * @return The result of attempting to process the add operation by following
1780 * the referral.
1781 *
1782 * @throws LDAPException If a problem occurs while attempting to establish
1783 * the referral connection, sending the request, or
1784 * reading the result.
1785 */
1786 private SearchResult followReferral(final SearchResult referralResult,
1787 final LDAPConnection connection,
1788 final int depth)
1789 throws LDAPException
1790 {
1791 for (final String urlString : referralResult.getReferralURLs())
1792 {
1793 try
1794 {
1795 final LDAPURL referralURL = new LDAPURL(urlString);
1796 final String host = referralURL.getHost();
1797
1798 if (host == null)
1799 {
1800 // We can't handle a referral in which there is no host.
1801 continue;
1802 }
1803
1804 final String requestBaseDN;
1805 if (referralURL.baseDNProvided())
1806 {
1807 requestBaseDN = referralURL.getBaseDN().toString();
1808 }
1809 else
1810 {
1811 requestBaseDN = baseDN;
1812 }
1813
1814 final SearchScope requestScope;
1815 if (referralURL.scopeProvided())
1816 {
1817 requestScope = referralURL.getScope();
1818 }
1819 else
1820 {
1821 requestScope = scope;
1822 }
1823
1824 final Filter requestFilter;
1825 if (referralURL.filterProvided())
1826 {
1827 requestFilter = referralURL.getFilter();
1828 }
1829 else
1830 {
1831 requestFilter = filter;
1832 }
1833
1834
1835 final SearchRequest searchRequest =
1836 new SearchRequest(searchResultListener, getControls(),
1837 requestBaseDN, requestScope, derefPolicy,
1838 sizeLimit, timeLimit, typesOnly, requestFilter,
1839 attributes);
1840
1841 final LDAPConnection referralConn = connection.getReferralConnector().
1842 getReferralConnection(referralURL, connection);
1843 try
1844 {
1845 return searchRequest.process(referralConn, depth+1);
1846 }
1847 finally
1848 {
1849 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1850 referralConn.close();
1851 }
1852 }
1853 catch (LDAPException le)
1854 {
1855 debugException(le);
1856
1857 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1858 {
1859 throw le;
1860 }
1861 }
1862 }
1863
1864 // If we've gotten here, then we could not follow any of the referral URLs,
1865 // so we'll just return the original referral result.
1866 return referralResult;
1867 }
1868
1869
1870
1871 /**
1872 * {@inheritDoc}
1873 */
1874 @InternalUseOnly()
1875 public void responseReceived(final LDAPResponse response)
1876 throws LDAPException
1877 {
1878 try
1879 {
1880 responseQueue.put(response);
1881 }
1882 catch (Exception e)
1883 {
1884 debugException(e);
1885 throw new LDAPException(ResultCode.LOCAL_ERROR,
1886 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1887 }
1888 }
1889
1890
1891
1892 /**
1893 * {@inheritDoc}
1894 */
1895 @Override()
1896 public int getLastMessageID()
1897 {
1898 return messageID;
1899 }
1900
1901
1902
1903 /**
1904 * {@inheritDoc}
1905 */
1906 @Override()
1907 public OperationType getOperationType()
1908 {
1909 return OperationType.SEARCH;
1910 }
1911
1912
1913
1914 /**
1915 * {@inheritDoc}
1916 */
1917 public SearchRequest duplicate()
1918 {
1919 return duplicate(getControls());
1920 }
1921
1922
1923
1924 /**
1925 * {@inheritDoc}
1926 */
1927 public SearchRequest duplicate(final Control[] controls)
1928 {
1929 final SearchRequest r = new SearchRequest(searchResultListener, controls,
1930 baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter,
1931 attributes);
1932 if (followReferralsInternal() != null)
1933 {
1934 r.setFollowReferrals(followReferralsInternal());
1935 }
1936
1937 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1938
1939 return r;
1940 }
1941
1942
1943
1944 /**
1945 * {@inheritDoc}
1946 */
1947 @Override()
1948 public void toString(final StringBuilder buffer)
1949 {
1950 buffer.append("SearchRequest(baseDN='");
1951 buffer.append(baseDN);
1952 buffer.append("', scope=");
1953 buffer.append(scope);
1954 buffer.append(", deref=");
1955 buffer.append(derefPolicy);
1956 buffer.append(", sizeLimit=");
1957 buffer.append(sizeLimit);
1958 buffer.append(", timeLimit=");
1959 buffer.append(timeLimit);
1960 buffer.append(", filter='");
1961 buffer.append(filter);
1962 buffer.append("', attrs={");
1963
1964 for (int i=0; i < attributes.length; i++)
1965 {
1966 if (i > 0)
1967 {
1968 buffer.append(", ");
1969 }
1970
1971 buffer.append(attributes[i]);
1972 }
1973 buffer.append('}');
1974
1975 final Control[] controls = getControls();
1976 if (controls.length > 0)
1977 {
1978 buffer.append(", controls={");
1979 for (int i=0; i < controls.length; i++)
1980 {
1981 if (i > 0)
1982 {
1983 buffer.append(", ");
1984 }
1985
1986 buffer.append(controls[i]);
1987 }
1988 buffer.append('}');
1989 }
1990
1991 buffer.append(')');
1992 }
1993 }