001 /*
002 * Copyright 2007-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-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.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 * <BR><BR>
801 * Note that if an attempt to process a search operation fails because the
802 * size limit has been exceeded, an {@link LDAPSearchException} will be
803 * thrown. If one or more entries or references have already been returned
804 * for the search, then the {@code LDAPSearchException} methods like
805 * {@code getEntryCount}, {@code getSearchEntries}, {@code getReferenceCount},
806 * and {@code getSearchReferences} may be used to obtain information about
807 * those entries and references (although if a search result listener was
808 * provided, then it will have been used to make any entries and references
809 * available, and they will not be available through the
810 * {@code getSearchEntries} and {@code getSearchReferences} methods).
811 *
812 * @param sizeLimit The maximum number of entries that should be returned by
813 * the server when processing this search request.
814 */
815 public void setSizeLimit(final int sizeLimit)
816 {
817 if (sizeLimit < 0)
818 {
819 this.sizeLimit = 0;
820 }
821 else
822 {
823 this.sizeLimit = sizeLimit;
824 }
825 }
826
827
828
829 /**
830 * {@inheritDoc}
831 */
832 public int getTimeLimitSeconds()
833 {
834 return timeLimit;
835 }
836
837
838
839 /**
840 * Specifies the maximum length of time in seconds that the server should
841 * spend processing this search request. A value of zero indicates that there
842 * should be no limit.
843 * <BR><BR>
844 * Note that if an attempt to process a search operation fails because the
845 * time limit has been exceeded, an {@link LDAPSearchException} will be
846 * thrown. If one or more entries or references have already been returned
847 * for the search, then the {@code LDAPSearchException} methods like
848 * {@code getEntryCount}, {@code getSearchEntries}, {@code getReferenceCount},
849 * and {@code getSearchReferences} may be used to obtain information about
850 * those entries and references (although if a search result listener was
851 * provided, then it will have been used to make any entries and references
852 * available, and they will not be available through the
853 * {@code getSearchEntries} and {@code getSearchReferences} methods).
854 *
855 * @param timeLimit The maximum length of time in seconds that the server
856 * should spend processing this search request.
857 */
858 public void setTimeLimitSeconds(final int timeLimit)
859 {
860 if (timeLimit < 0)
861 {
862 this.timeLimit = 0;
863 }
864 else
865 {
866 this.timeLimit = timeLimit;
867 }
868 }
869
870
871
872 /**
873 * {@inheritDoc}
874 */
875 public boolean typesOnly()
876 {
877 return typesOnly;
878 }
879
880
881
882 /**
883 * Specifies whether the server should return only attribute names in matching
884 * entries, rather than both names and values.
885 *
886 * @param typesOnly Specifies whether the server should return only
887 * attribute names in matching entries, rather than both
888 * names and values.
889 */
890 public void setTypesOnly(final boolean typesOnly)
891 {
892 this.typesOnly = typesOnly;
893 }
894
895
896
897 /**
898 * {@inheritDoc}
899 */
900 public Filter getFilter()
901 {
902 return filter;
903 }
904
905
906
907 /**
908 * Specifies the filter that should be used to identify matching entries.
909 *
910 * @param filter The string representation for the filter that should be
911 * used to identify matching entries. It must not be
912 * {@code null}.
913 *
914 * @throws LDAPException If the provided filter string cannot be parsed as a
915 * search filter.
916 */
917 public void setFilter(final String filter)
918 throws LDAPException
919 {
920 ensureNotNull(filter);
921
922 this.filter = Filter.create(filter);
923 }
924
925
926
927 /**
928 * Specifies the filter that should be used to identify matching entries.
929 *
930 * @param filter The filter that should be used to identify matching
931 * entries. It must not be {@code null}.
932 */
933 public void setFilter(final Filter filter)
934 {
935 ensureNotNull(filter);
936
937 this.filter = filter;
938 }
939
940
941
942 /**
943 * Retrieves the set of requested attributes to include in matching entries.
944 * The caller must not attempt to alter the contents of the array.
945 *
946 * @return The set of requested attributes to include in matching entries, or
947 * an empty array if the default set of attributes (all user
948 * attributes but no operational attributes) should be requested.
949 */
950 public String[] getAttributes()
951 {
952 return attributes;
953 }
954
955
956
957 /**
958 * {@inheritDoc}
959 */
960 public List<String> getAttributeList()
961 {
962 return Collections.unmodifiableList(Arrays.asList(attributes));
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 String... attributes)
976 {
977 if (attributes == null)
978 {
979 this.attributes = REQUEST_ATTRS_DEFAULT;
980 }
981 else
982 {
983 this.attributes = attributes;
984 }
985 }
986
987
988
989 /**
990 * Specifies the set of requested attributes to include in matching entries.
991 *
992 * @param attributes The set of requested attributes to include in matching
993 * entries. It may be {@code null} if the default set of
994 * attributes (all user attributes but no operational
995 * attributes) should be requested.
996 */
997 public void setAttributes(final List<String> attributes)
998 {
999 if (attributes == null)
1000 {
1001 this.attributes = REQUEST_ATTRS_DEFAULT;
1002 }
1003 else
1004 {
1005 this.attributes = new String[attributes.size()];
1006 for (int i=0; i < this.attributes.length; i++)
1007 {
1008 this.attributes[i] = attributes.get(i);
1009 }
1010 }
1011 }
1012
1013
1014
1015 /**
1016 * Retrieves the search result listener for this search request, if available.
1017 *
1018 * @return The search result listener for this search request, or
1019 * {@code null} if none has been configured.
1020 */
1021 public SearchResultListener getSearchResultListener()
1022 {
1023 return searchResultListener;
1024 }
1025
1026
1027
1028 /**
1029 * {@inheritDoc}
1030 */
1031 public byte getProtocolOpType()
1032 {
1033 return LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST;
1034 }
1035
1036
1037
1038 /**
1039 * {@inheritDoc}
1040 */
1041 public void writeTo(final ASN1Buffer writer)
1042 {
1043 final ASN1BufferSequence requestSequence =
1044 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST);
1045 writer.addOctetString(baseDN);
1046 writer.addEnumerated(scope.intValue());
1047 writer.addEnumerated(derefPolicy.intValue());
1048 writer.addInteger(sizeLimit);
1049 writer.addInteger(timeLimit);
1050 writer.addBoolean(typesOnly);
1051 filter.writeTo(writer);
1052
1053 final ASN1BufferSequence attrSequence = writer.beginSequence();
1054 for (final String s : attributes)
1055 {
1056 writer.addOctetString(s);
1057 }
1058 attrSequence.end();
1059 requestSequence.end();
1060 }
1061
1062
1063
1064 /**
1065 * Encodes the search request protocol op to an ASN.1 element.
1066 *
1067 * @return The ASN.1 element with the encoded search request protocol op.
1068 */
1069 public ASN1Element encodeProtocolOp()
1070 {
1071 // Create the search request protocol op.
1072 final ASN1Element[] attrElements = new ASN1Element[attributes.length];
1073 for (int i=0; i < attrElements.length; i++)
1074 {
1075 attrElements[i] = new ASN1OctetString(attributes[i]);
1076 }
1077
1078 final ASN1Element[] protocolOpElements =
1079 {
1080 new ASN1OctetString(baseDN),
1081 new ASN1Enumerated(scope.intValue()),
1082 new ASN1Enumerated(derefPolicy.intValue()),
1083 new ASN1Integer(sizeLimit),
1084 new ASN1Integer(timeLimit),
1085 new ASN1Boolean(typesOnly),
1086 filter.encode(),
1087 new ASN1Sequence(attrElements)
1088 };
1089
1090 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST,
1091 protocolOpElements);
1092 }
1093
1094
1095
1096 /**
1097 * Sends this search request to the directory server over the provided
1098 * connection and returns the associated response. The search result entries
1099 * and references will either be collected and returned in the
1100 * {@code SearchResult} object that is returned, or will be interactively
1101 * returned via the {@code SearchResultListener} interface.
1102 *
1103 * @param connection The connection to use to communicate with the directory
1104 * server.
1105 * @param depth The current referral depth for this request. It should
1106 * always be one for the initial request, and should only
1107 * be incremented when following referrals.
1108 *
1109 * @return An object that provides information about the result of the
1110 * search processing, potentially including the sets of matching
1111 * entries and/or search references.
1112 *
1113 * @throws LDAPException If a problem occurs while sending the request or
1114 * reading the response.
1115 */
1116 @Override()
1117 protected SearchResult process(final LDAPConnection connection,
1118 final int depth)
1119 throws LDAPException
1120 {
1121 if (connection.synchronousMode())
1122 {
1123 return processSync(connection, depth,
1124 connection.getConnectionOptions().autoReconnect());
1125 }
1126
1127 final long requestTime = System.nanoTime();
1128 processAsync(connection, null);
1129
1130 try
1131 {
1132 // Wait for and process the response.
1133 final ArrayList<SearchResultEntry> entryList;
1134 final ArrayList<SearchResultReference> referenceList;
1135 if (searchResultListener == null)
1136 {
1137 entryList = new ArrayList<SearchResultEntry>(5);
1138 referenceList = new ArrayList<SearchResultReference>(5);
1139 }
1140 else
1141 {
1142 entryList = null;
1143 referenceList = null;
1144 }
1145
1146 int numEntries = 0;
1147 int numReferences = 0;
1148 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1149 final long responseTimeout = getResponseTimeoutMillis(connection);
1150 while (true)
1151 {
1152 final LDAPResponse response;
1153 try
1154 {
1155 if (responseTimeout > 0)
1156 {
1157 response =
1158 responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
1159 }
1160 else
1161 {
1162 response = responseQueue.take();
1163 }
1164 }
1165 catch (InterruptedException ie)
1166 {
1167 debugException(ie);
1168 throw new LDAPException(ResultCode.LOCAL_ERROR,
1169 ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie);
1170 }
1171
1172 if (response == null)
1173 {
1174 if (connection.getConnectionOptions().abandonOnTimeout())
1175 {
1176 connection.abandon(messageID);
1177 }
1178
1179 final SearchResult searchResult =
1180 new SearchResult(messageID, ResultCode.TIMEOUT,
1181 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout,
1182 connection.getHostPort()),
1183 null, null, entryList, referenceList, numEntries,
1184 numReferences, null);
1185 throw new LDAPSearchException(searchResult);
1186 }
1187
1188 if (response instanceof ConnectionClosedResponse)
1189 {
1190 final ConnectionClosedResponse ccr =
1191 (ConnectionClosedResponse) response;
1192 final String message = ccr.getMessage();
1193 if (message == null)
1194 {
1195 // The connection was closed while waiting for the response.
1196 final SearchResult searchResult =
1197 new SearchResult(messageID, ccr.getResultCode(),
1198 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1199 connection.getHostPort(), toString()),
1200 null, null, entryList, referenceList, numEntries,
1201 numReferences, null);
1202 throw new LDAPSearchException(searchResult);
1203 }
1204 else
1205 {
1206 // The connection was closed while waiting for the response.
1207 final SearchResult searchResult =
1208 new SearchResult(messageID, ccr.getResultCode(),
1209 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1210 get(connection.getHostPort(), toString(), message),
1211 null, null, entryList, referenceList, numEntries,
1212 numReferences, null);
1213 throw new LDAPSearchException(searchResult);
1214 }
1215 }
1216 else if (response instanceof SearchResultEntry)
1217 {
1218 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1219 numEntries++;
1220 if (searchResultListener == null)
1221 {
1222 entryList.add(searchEntry);
1223 }
1224 else
1225 {
1226 searchResultListener.searchEntryReturned(searchEntry);
1227 }
1228 }
1229 else if (response instanceof SearchResultReference)
1230 {
1231 final SearchResultReference searchReference =
1232 (SearchResultReference) response;
1233 if (followReferrals(connection))
1234 {
1235 final LDAPResult result = followSearchReference(messageID,
1236 searchReference, connection, depth);
1237 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1238 {
1239 // We couldn't follow the reference. We don't want to fail the
1240 // entire search because of this right now, so treat it as if
1241 // referral following had not been enabled. Also, set the
1242 // intermediate result code to match that of the result.
1243 numReferences++;
1244 if (searchResultListener == null)
1245 {
1246 referenceList.add(searchReference);
1247 }
1248 else
1249 {
1250 searchResultListener.searchReferenceReturned(searchReference);
1251 }
1252
1253 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1254 {
1255 intermediateResultCode = result.getResultCode();
1256 }
1257 }
1258 else if (result instanceof SearchResult)
1259 {
1260 final SearchResult searchResult = (SearchResult) result;
1261 numEntries += searchResult.getEntryCount();
1262 if (searchResultListener == null)
1263 {
1264 entryList.addAll(searchResult.getSearchEntries());
1265 }
1266 }
1267 }
1268 else
1269 {
1270 numReferences++;
1271 if (searchResultListener == null)
1272 {
1273 referenceList.add(searchReference);
1274 }
1275 else
1276 {
1277 searchResultListener.searchReferenceReturned(searchReference);
1278 }
1279 }
1280 }
1281 else
1282 {
1283 connection.getConnectionStatistics().incrementNumSearchResponses(
1284 numEntries, numReferences,
1285 (System.nanoTime() - requestTime));
1286 SearchResult result = (SearchResult) response;
1287 result.setCounts(numEntries, entryList, numReferences, referenceList);
1288
1289 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1290 followReferrals(connection))
1291 {
1292 if (depth >=
1293 connection.getConnectionOptions().getReferralHopLimit())
1294 {
1295 return new SearchResult(messageID,
1296 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1297 ERR_TOO_MANY_REFERRALS.get(),
1298 result.getMatchedDN(),
1299 result.getReferralURLs(), entryList,
1300 referenceList, numEntries,
1301 numReferences,
1302 result.getResponseControls());
1303 }
1304
1305 result = followReferral(result, connection, depth);
1306 }
1307
1308 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1309 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1310 {
1311 return new SearchResult(messageID, intermediateResultCode,
1312 result.getDiagnosticMessage(),
1313 result.getMatchedDN(),
1314 result.getReferralURLs(),
1315 entryList, referenceList, numEntries,
1316 numReferences,
1317 result.getResponseControls());
1318 }
1319
1320 return result;
1321 }
1322 }
1323 }
1324 finally
1325 {
1326 connection.deregisterResponseAcceptor(messageID);
1327 }
1328 }
1329
1330
1331
1332 /**
1333 * Sends this search request to the directory server over the provided
1334 * connection and returns the message ID for the request.
1335 *
1336 * @param connection The connection to use to communicate with the
1337 * directory server.
1338 * @param resultListener The async result listener that is to be notified
1339 * when the response is received. It may be
1340 * {@code null} only if the result is to be processed
1341 * by this class.
1342 *
1343 * @return The async request ID created for the operation, or {@code null} if
1344 * the provided {@code resultListener} is {@code null} and the
1345 * operation will not actually be processed asynchronously.
1346 *
1347 * @throws LDAPException If a problem occurs while sending the request.
1348 */
1349 AsyncRequestID processAsync(final LDAPConnection connection,
1350 final AsyncSearchResultListener resultListener)
1351 throws LDAPException
1352 {
1353 // Create the LDAP message.
1354 messageID = connection.nextMessageID();
1355 final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
1356
1357
1358 // If the provided async result listener is {@code null}, then we'll use
1359 // this class as the message acceptor. Otherwise, create an async helper
1360 // and use it as the message acceptor.
1361 final AsyncRequestID asyncRequestID;
1362 if (resultListener == null)
1363 {
1364 asyncRequestID = null;
1365 connection.registerResponseAcceptor(messageID, this);
1366 }
1367 else
1368 {
1369 final AsyncSearchHelper helper = new AsyncSearchHelper(connection,
1370 messageID, resultListener, getIntermediateResponseListener());
1371 connection.registerResponseAcceptor(messageID, helper);
1372 asyncRequestID = helper.getAsyncRequestID();
1373
1374 final long timeout = getResponseTimeoutMillis(connection);
1375 if (timeout > 0L)
1376 {
1377 final Timer timer = connection.getTimer();
1378 final AsyncTimeoutTimerTask timerTask =
1379 new AsyncTimeoutTimerTask(helper);
1380 timer.schedule(timerTask, timeout);
1381 asyncRequestID.setTimerTask(timerTask);
1382 }
1383 }
1384
1385
1386 // Send the request to the server.
1387 try
1388 {
1389 debugLDAPRequest(this);
1390 connection.getConnectionStatistics().incrementNumSearchRequests();
1391 connection.sendMessage(message);
1392 return asyncRequestID;
1393 }
1394 catch (LDAPException le)
1395 {
1396 debugException(le);
1397
1398 connection.deregisterResponseAcceptor(messageID);
1399 throw le;
1400 }
1401 }
1402
1403
1404
1405 /**
1406 * Processes this search operation in synchronous mode, in which the same
1407 * thread will send the request and read the response.
1408 *
1409 * @param connection The connection to use to communicate with the directory
1410 * server.
1411 * @param depth The current referral depth for this request. It should
1412 * always be one for the initial request, and should only
1413 * be incremented when following referrals.
1414 * @param allowRetry Indicates whether the request may be re-tried on a
1415 * re-established connection if the initial attempt fails
1416 * in a way that indicates the connection is no longer
1417 * valid and autoReconnect is true.
1418 *
1419 * @return An LDAP result object that provides information about the result
1420 * of the search processing.
1421 *
1422 * @throws LDAPException If a problem occurs while sending the request or
1423 * reading the response.
1424 */
1425 private SearchResult processSync(final LDAPConnection connection,
1426 final int depth, final boolean allowRetry)
1427 throws LDAPException
1428 {
1429 // Create the LDAP message.
1430 messageID = connection.nextMessageID();
1431 final LDAPMessage message =
1432 new LDAPMessage(messageID, this, getControls());
1433
1434
1435 // Set the appropriate timeout on the socket.
1436 final long responseTimeout = getResponseTimeoutMillis(connection);
1437 try
1438 {
1439 connection.getConnectionInternals(true).getSocket().setSoTimeout(
1440 (int) responseTimeout);
1441 }
1442 catch (Exception e)
1443 {
1444 debugException(e);
1445 }
1446
1447
1448 // Send the request to the server.
1449 final long requestTime = System.nanoTime();
1450 debugLDAPRequest(this);
1451 connection.getConnectionStatistics().incrementNumSearchRequests();
1452 try
1453 {
1454 connection.sendMessage(message);
1455 }
1456 catch (final LDAPException le)
1457 {
1458 debugException(le);
1459
1460 if (allowRetry)
1461 {
1462 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1463 le.getResultCode(), 0, 0);
1464 if (retryResult != null)
1465 {
1466 return retryResult;
1467 }
1468 }
1469
1470 throw le;
1471 }
1472
1473 final ArrayList<SearchResultEntry> entryList;
1474 final ArrayList<SearchResultReference> referenceList;
1475 if (searchResultListener == null)
1476 {
1477 entryList = new ArrayList<SearchResultEntry>(5);
1478 referenceList = new ArrayList<SearchResultReference>(5);
1479 }
1480 else
1481 {
1482 entryList = null;
1483 referenceList = null;
1484 }
1485
1486 int numEntries = 0;
1487 int numReferences = 0;
1488 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1489 while (true)
1490 {
1491 final LDAPResponse response;
1492 try
1493 {
1494 response = connection.readResponse(messageID);
1495 }
1496 catch (final LDAPException le)
1497 {
1498 debugException(le);
1499
1500 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1501 connection.getConnectionOptions().abandonOnTimeout())
1502 {
1503 connection.abandon(messageID);
1504 }
1505
1506 if (allowRetry)
1507 {
1508 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1509 le.getResultCode(), numEntries, numReferences);
1510 if (retryResult != null)
1511 {
1512 return retryResult;
1513 }
1514 }
1515
1516 throw le;
1517 }
1518
1519 if (response == null)
1520 {
1521 if (connection.getConnectionOptions().abandonOnTimeout())
1522 {
1523 connection.abandon(messageID);
1524 }
1525
1526 throw new LDAPException(ResultCode.TIMEOUT,
1527 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout,
1528 connection.getHostPort()));
1529 }
1530 else if (response instanceof ConnectionClosedResponse)
1531 {
1532
1533 if (allowRetry)
1534 {
1535 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1536 ResultCode.SERVER_DOWN, numEntries, numReferences);
1537 if (retryResult != null)
1538 {
1539 return retryResult;
1540 }
1541 }
1542
1543 final ConnectionClosedResponse ccr =
1544 (ConnectionClosedResponse) response;
1545 final String msg = ccr.getMessage();
1546 if (msg == null)
1547 {
1548 // The connection was closed while waiting for the response.
1549 final SearchResult searchResult =
1550 new SearchResult(messageID, ccr.getResultCode(),
1551 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1552 connection.getHostPort(), toString()),
1553 null, null, entryList, referenceList, numEntries,
1554 numReferences, null);
1555 throw new LDAPSearchException(searchResult);
1556 }
1557 else
1558 {
1559 // The connection was closed while waiting for the response.
1560 final SearchResult searchResult =
1561 new SearchResult(messageID, ccr.getResultCode(),
1562 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1563 get(connection.getHostPort(), toString(), msg),
1564 null, null, entryList, referenceList, numEntries,
1565 numReferences, null);
1566 throw new LDAPSearchException(searchResult);
1567 }
1568 }
1569 else if (response instanceof IntermediateResponse)
1570 {
1571 final IntermediateResponseListener listener =
1572 getIntermediateResponseListener();
1573 if (listener != null)
1574 {
1575 listener.intermediateResponseReturned(
1576 (IntermediateResponse) response);
1577 }
1578 }
1579 else if (response instanceof SearchResultEntry)
1580 {
1581 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1582 numEntries++;
1583 if (searchResultListener == null)
1584 {
1585 entryList.add(searchEntry);
1586 }
1587 else
1588 {
1589 searchResultListener.searchEntryReturned(searchEntry);
1590 }
1591 }
1592 else if (response instanceof SearchResultReference)
1593 {
1594 final SearchResultReference searchReference =
1595 (SearchResultReference) response;
1596 if (followReferrals(connection))
1597 {
1598 final LDAPResult result = followSearchReference(messageID,
1599 searchReference, connection, depth);
1600 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1601 {
1602 // We couldn't follow the reference. We don't want to fail the
1603 // entire search because of this right now, so treat it as if
1604 // referral following had not been enabled. Also, set the
1605 // intermediate result code to match that of the result.
1606 numReferences++;
1607 if (searchResultListener == null)
1608 {
1609 referenceList.add(searchReference);
1610 }
1611 else
1612 {
1613 searchResultListener.searchReferenceReturned(searchReference);
1614 }
1615
1616 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1617 {
1618 intermediateResultCode = result.getResultCode();
1619 }
1620 }
1621 else if (result instanceof SearchResult)
1622 {
1623 final SearchResult searchResult = (SearchResult) result;
1624 numEntries += searchResult.getEntryCount();
1625 if (searchResultListener == null)
1626 {
1627 entryList.addAll(searchResult.getSearchEntries());
1628 }
1629 }
1630 }
1631 else
1632 {
1633 numReferences++;
1634 if (searchResultListener == null)
1635 {
1636 referenceList.add(searchReference);
1637 }
1638 else
1639 {
1640 searchResultListener.searchReferenceReturned(searchReference);
1641 }
1642 }
1643 }
1644 else
1645 {
1646 final SearchResult result = (SearchResult) response;
1647 if (allowRetry)
1648 {
1649 final SearchResult retryResult = reconnectAndRetry(connection,
1650 depth, result.getResultCode(), numEntries, numReferences);
1651 if (retryResult != null)
1652 {
1653 return retryResult;
1654 }
1655 }
1656
1657 return handleResponse(connection, response, requestTime, depth,
1658 numEntries, numReferences, entryList,
1659 referenceList, intermediateResultCode);
1660 }
1661 }
1662 }
1663
1664
1665
1666 /**
1667 * Attempts to re-establish the connection and retry processing this request
1668 * on it.
1669 *
1670 * @param connection The connection to be re-established.
1671 * @param depth The current referral depth for this request. It
1672 * should always be one for the initial request, and
1673 * should only be incremented when following referrals.
1674 * @param resultCode The result code for the previous operation attempt.
1675 * @param numEntries The number of search result entries already sent for
1676 * the search operation.
1677 * @param numReferences The number of search result references already sent
1678 * for the search operation.
1679 *
1680 * @return The result from re-trying the search, or {@code null} if it could
1681 * not be re-tried.
1682 */
1683 private SearchResult reconnectAndRetry(final LDAPConnection connection,
1684 final int depth,
1685 final ResultCode resultCode,
1686 final int numEntries,
1687 final int numReferences)
1688 {
1689 try
1690 {
1691 // We will only want to retry for certain result codes that indicate a
1692 // connection problem.
1693 switch (resultCode.intValue())
1694 {
1695 case ResultCode.SERVER_DOWN_INT_VALUE:
1696 case ResultCode.DECODING_ERROR_INT_VALUE:
1697 case ResultCode.CONNECT_ERROR_INT_VALUE:
1698 // We want to try to re-establish the connection no matter what, but
1699 // we only want to retry the search if we haven't yet sent any
1700 // results.
1701 connection.reconnect();
1702 if ((numEntries == 0) && (numReferences == 0))
1703 {
1704 return processSync(connection, depth, false);
1705 }
1706 break;
1707 }
1708 }
1709 catch (final Exception e)
1710 {
1711 debugException(e);
1712 }
1713
1714 return null;
1715 }
1716
1717
1718
1719 /**
1720 * Performs the necessary processing for handling a response.
1721 *
1722 * @param connection The connection used to read the response.
1723 * @param response The response to be processed.
1724 * @param requestTime The time the request was sent to the
1725 * server.
1726 * @param depth The current referral depth for this
1727 * request. It should always be one for the
1728 * initial request, and should only be
1729 * incremented when following referrals.
1730 * @param numEntries The number of entries received from the
1731 * server.
1732 * @param numReferences The number of references received from
1733 * the server.
1734 * @param entryList The list of search result entries received
1735 * from the server, if applicable.
1736 * @param referenceList The list of search result references
1737 * received from the server, if applicable.
1738 * @param intermediateResultCode The intermediate result code so far for the
1739 * search operation.
1740 *
1741 * @return The search result.
1742 *
1743 * @throws LDAPException If a problem occurs.
1744 */
1745 private SearchResult handleResponse(final LDAPConnection connection,
1746 final LDAPResponse response, final long requestTime,
1747 final int depth, final int numEntries, final int numReferences,
1748 final List<SearchResultEntry> entryList,
1749 final List<SearchResultReference> referenceList,
1750 final ResultCode intermediateResultCode)
1751 throws LDAPException
1752 {
1753 connection.getConnectionStatistics().incrementNumSearchResponses(
1754 numEntries, numReferences,
1755 (System.nanoTime() - requestTime));
1756 SearchResult result = (SearchResult) response;
1757 result.setCounts(numEntries, entryList, numReferences, referenceList);
1758
1759 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1760 followReferrals(connection))
1761 {
1762 if (depth >=
1763 connection.getConnectionOptions().getReferralHopLimit())
1764 {
1765 return new SearchResult(messageID,
1766 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1767 ERR_TOO_MANY_REFERRALS.get(),
1768 result.getMatchedDN(),
1769 result.getReferralURLs(), entryList,
1770 referenceList, numEntries,
1771 numReferences,
1772 result.getResponseControls());
1773 }
1774
1775 result = followReferral(result, connection, depth);
1776 }
1777
1778 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1779 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1780 {
1781 return new SearchResult(messageID, intermediateResultCode,
1782 result.getDiagnosticMessage(),
1783 result.getMatchedDN(),
1784 result.getReferralURLs(),
1785 entryList, referenceList, numEntries,
1786 numReferences,
1787 result.getResponseControls());
1788 }
1789
1790 return result;
1791 }
1792
1793
1794
1795 /**
1796 * Attempts to follow a search result reference to continue a search in a
1797 * remote server.
1798 *
1799 * @param messageID The message ID for the LDAP message that is
1800 * associated with this result.
1801 * @param searchReference The search result reference to follow.
1802 * @param connection The connection on which the reference was
1803 * received.
1804 * @param depth The number of referrals followed in the course of
1805 * processing this request.
1806 *
1807 * @return The result of attempting to follow the search result reference.
1808 *
1809 * @throws LDAPException If a problem occurs while attempting to establish
1810 * the referral connection, sending the request, or
1811 * reading the result.
1812 */
1813 private LDAPResult followSearchReference(final int messageID,
1814 final SearchResultReference searchReference,
1815 final LDAPConnection connection, final int depth)
1816 throws LDAPException
1817 {
1818 for (final String urlString : searchReference.getReferralURLs())
1819 {
1820 try
1821 {
1822 final LDAPURL referralURL = new LDAPURL(urlString);
1823 final String host = referralURL.getHost();
1824
1825 if (host == null)
1826 {
1827 // We can't handle a referral in which there is no host.
1828 continue;
1829 }
1830
1831 final String requestBaseDN;
1832 if (referralURL.baseDNProvided())
1833 {
1834 requestBaseDN = referralURL.getBaseDN().toString();
1835 }
1836 else
1837 {
1838 requestBaseDN = baseDN;
1839 }
1840
1841 final SearchScope requestScope;
1842 if (referralURL.scopeProvided())
1843 {
1844 requestScope = referralURL.getScope();
1845 }
1846 else
1847 {
1848 requestScope = scope;
1849 }
1850
1851 final Filter requestFilter;
1852 if (referralURL.filterProvided())
1853 {
1854 requestFilter = referralURL.getFilter();
1855 }
1856 else
1857 {
1858 requestFilter = filter;
1859 }
1860
1861
1862 final SearchRequest searchRequest =
1863 new SearchRequest(searchResultListener, getControls(),
1864 requestBaseDN, requestScope, derefPolicy,
1865 sizeLimit, timeLimit, typesOnly, requestFilter,
1866 attributes);
1867
1868 final LDAPConnection referralConn = connection.getReferralConnector().
1869 getReferralConnection(referralURL, connection);
1870
1871 try
1872 {
1873 return searchRequest.process(referralConn, depth+1);
1874 }
1875 finally
1876 {
1877 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1878 referralConn.close();
1879 }
1880 }
1881 catch (LDAPException le)
1882 {
1883 debugException(le);
1884
1885 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1886 {
1887 throw le;
1888 }
1889 }
1890 }
1891
1892 // If we've gotten here, then we could not follow any of the referral URLs,
1893 // so we'll create a failure result.
1894 return new SearchResult(messageID, ResultCode.REFERRAL, null, null,
1895 searchReference.getReferralURLs(), 0, 0, null);
1896 }
1897
1898
1899
1900 /**
1901 * Attempts to follow a referral to perform an add operation in the target
1902 * server.
1903 *
1904 * @param referralResult The LDAP result object containing information about
1905 * the referral to follow.
1906 * @param connection The connection on which the referral was received.
1907 * @param depth The number of referrals followed in the course of
1908 * processing this request.
1909 *
1910 * @return The result of attempting to process the add operation by following
1911 * the referral.
1912 *
1913 * @throws LDAPException If a problem occurs while attempting to establish
1914 * the referral connection, sending the request, or
1915 * reading the result.
1916 */
1917 private SearchResult followReferral(final SearchResult referralResult,
1918 final LDAPConnection connection,
1919 final int depth)
1920 throws LDAPException
1921 {
1922 for (final String urlString : referralResult.getReferralURLs())
1923 {
1924 try
1925 {
1926 final LDAPURL referralURL = new LDAPURL(urlString);
1927 final String host = referralURL.getHost();
1928
1929 if (host == null)
1930 {
1931 // We can't handle a referral in which there is no host.
1932 continue;
1933 }
1934
1935 final String requestBaseDN;
1936 if (referralURL.baseDNProvided())
1937 {
1938 requestBaseDN = referralURL.getBaseDN().toString();
1939 }
1940 else
1941 {
1942 requestBaseDN = baseDN;
1943 }
1944
1945 final SearchScope requestScope;
1946 if (referralURL.scopeProvided())
1947 {
1948 requestScope = referralURL.getScope();
1949 }
1950 else
1951 {
1952 requestScope = scope;
1953 }
1954
1955 final Filter requestFilter;
1956 if (referralURL.filterProvided())
1957 {
1958 requestFilter = referralURL.getFilter();
1959 }
1960 else
1961 {
1962 requestFilter = filter;
1963 }
1964
1965
1966 final SearchRequest searchRequest =
1967 new SearchRequest(searchResultListener, getControls(),
1968 requestBaseDN, requestScope, derefPolicy,
1969 sizeLimit, timeLimit, typesOnly, requestFilter,
1970 attributes);
1971
1972 final LDAPConnection referralConn = connection.getReferralConnector().
1973 getReferralConnection(referralURL, connection);
1974 try
1975 {
1976 return searchRequest.process(referralConn, depth+1);
1977 }
1978 finally
1979 {
1980 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1981 referralConn.close();
1982 }
1983 }
1984 catch (LDAPException le)
1985 {
1986 debugException(le);
1987
1988 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1989 {
1990 throw le;
1991 }
1992 }
1993 }
1994
1995 // If we've gotten here, then we could not follow any of the referral URLs,
1996 // so we'll just return the original referral result.
1997 return referralResult;
1998 }
1999
2000
2001
2002 /**
2003 * {@inheritDoc}
2004 */
2005 @InternalUseOnly()
2006 public void responseReceived(final LDAPResponse response)
2007 throws LDAPException
2008 {
2009 try
2010 {
2011 responseQueue.put(response);
2012 }
2013 catch (Exception e)
2014 {
2015 debugException(e);
2016 throw new LDAPException(ResultCode.LOCAL_ERROR,
2017 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
2018 }
2019 }
2020
2021
2022
2023 /**
2024 * {@inheritDoc}
2025 */
2026 @Override()
2027 public int getLastMessageID()
2028 {
2029 return messageID;
2030 }
2031
2032
2033
2034 /**
2035 * {@inheritDoc}
2036 */
2037 @Override()
2038 public OperationType getOperationType()
2039 {
2040 return OperationType.SEARCH;
2041 }
2042
2043
2044
2045 /**
2046 * {@inheritDoc}
2047 */
2048 public SearchRequest duplicate()
2049 {
2050 return duplicate(getControls());
2051 }
2052
2053
2054
2055 /**
2056 * {@inheritDoc}
2057 */
2058 public SearchRequest duplicate(final Control[] controls)
2059 {
2060 final SearchRequest r = new SearchRequest(searchResultListener, controls,
2061 baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter,
2062 attributes);
2063 if (followReferralsInternal() != null)
2064 {
2065 r.setFollowReferrals(followReferralsInternal());
2066 }
2067
2068 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
2069
2070 return r;
2071 }
2072
2073
2074
2075 /**
2076 * {@inheritDoc}
2077 */
2078 @Override()
2079 public void toString(final StringBuilder buffer)
2080 {
2081 buffer.append("SearchRequest(baseDN='");
2082 buffer.append(baseDN);
2083 buffer.append("', scope=");
2084 buffer.append(scope);
2085 buffer.append(", deref=");
2086 buffer.append(derefPolicy);
2087 buffer.append(", sizeLimit=");
2088 buffer.append(sizeLimit);
2089 buffer.append(", timeLimit=");
2090 buffer.append(timeLimit);
2091 buffer.append(", filter='");
2092 buffer.append(filter);
2093 buffer.append("', attrs={");
2094
2095 for (int i=0; i < attributes.length; i++)
2096 {
2097 if (i > 0)
2098 {
2099 buffer.append(", ");
2100 }
2101
2102 buffer.append(attributes[i]);
2103 }
2104 buffer.append('}');
2105
2106 final Control[] controls = getControls();
2107 if (controls.length > 0)
2108 {
2109 buffer.append(", controls={");
2110 for (int i=0; i < controls.length; i++)
2111 {
2112 if (i > 0)
2113 {
2114 buffer.append(", ");
2115 }
2116
2117 buffer.append(controls[i]);
2118 }
2119 buffer.append('}');
2120 }
2121
2122 buffer.append(')');
2123 }
2124 }