001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk;
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 * retrieves the name and e-mail address for each matching user:
163 * <PRE>
164 * // Construct a filter that can be used to find everyone in the Sales
165 * // department, and then create a search request to find all such users
166 * // in the directory.
167 * Filter filter = Filter.createEqualityFilter("ou", "Sales");
168 * SearchRequest searchRequest =
169 * new SearchRequest("dc=example,dc=com", SearchScope.SUB, filter,
170 * "cn", "mail");
171 * SearchResult searchResult;
172 *
173 * try
174 * {
175 * searchResult = connection.search(searchRequest);
176 *
177 * for (SearchResultEntry entry : searchResult.getSearchEntries())
178 * {
179 * String name = entry.getAttributeValue("cn");
180 * String mail = entry.getAttributeValue("mail");
181 * }
182 * }
183 * catch (LDAPSearchException lse)
184 * {
185 * // The search failed for some reason.
186 * searchResult = lse.getSearchResult();
187 * ResultCode resultCode = lse.getResultCode();
188 * String errorMessageFromServer = lse.getDiagnosticMessage();
189 * }
190 * </PRE>
191 */
192 @Mutable()
193 @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
194 public final class SearchRequest
195 extends UpdatableLDAPRequest
196 implements ReadOnlySearchRequest, ResponseAcceptor, ProtocolOp
197 {
198 /**
199 * The special value "*" that can be included in the set of requested
200 * attributes to indicate that all user attributes should be returned.
201 */
202 public static final String ALL_USER_ATTRIBUTES = "*";
203
204
205
206 /**
207 * The special value "+" that can be included in the set of requested
208 * attributes to indicate that all operational attributes should be returned.
209 */
210 public static final String ALL_OPERATIONAL_ATTRIBUTES = "+";
211
212
213
214 /**
215 * The special value "1.1" that can be included in the set of requested
216 * attributes to indicate that no attributes should be returned, with the
217 * exception of any other attributes explicitly named in the set of requested
218 * attributes.
219 */
220 public static final String NO_ATTRIBUTES = "1.1";
221
222
223
224 /**
225 * The default set of requested attributes that will be used, which will
226 * return all user attributes but no operational attributes.
227 */
228 public static final String[] REQUEST_ATTRS_DEFAULT = NO_STRINGS;
229
230
231
232 /**
233 * The serial version UID for this serializable class.
234 */
235 private static final long serialVersionUID = 1500219434086474893L;
236
237
238
239 // The set of requested attributes.
240 private String[] attributes;
241
242 // Indicates whether to retrieve attribute types only or both types and
243 // values.
244 private boolean typesOnly;
245
246 // The behavior to use when aliases are encountered.
247 private DereferencePolicy derefPolicy;
248
249 // The message ID from the last LDAP message sent from this request.
250 private int messageID = -1;
251
252 // The size limit for this search request.
253 private int sizeLimit;
254
255 // The time limit for this search request.
256 private int timeLimit;
257
258 // The parsed filter for this search request.
259 private Filter filter;
260
261 // The queue that will be used to receive response messages from the server.
262 private final LinkedBlockingQueue<LDAPResponse> responseQueue =
263 new LinkedBlockingQueue<LDAPResponse>(50);
264
265 // The search result listener that should be used to return results
266 // interactively to the requester.
267 private final SearchResultListener searchResultListener;
268
269 // The scope for this search request.
270 private SearchScope scope;
271
272 // The base DN for this search request.
273 private String baseDN;
274
275
276
277 /**
278 * Creates a new search request with the provided information. Search result
279 * entries and references will be collected internally and included in the
280 * {@code SearchResult} object returned when search processing is completed.
281 *
282 * @param baseDN The base DN for the search request. It must not be
283 * {@code null}.
284 * @param scope The scope that specifies the range of entries that
285 * should be examined for the search.
286 * @param filter The string representation of the filter to use to
287 * identify matching entries. It must not be
288 * {@code null}.
289 * @param attributes The set of attributes that should be returned in
290 * matching entries. It may be {@code null} or empty if
291 * the default attribute set (all user attributes) is to
292 * be requested.
293 *
294 * @throws LDAPException If the provided filter string cannot be parsed as
295 * an LDAP filter.
296 */
297 public SearchRequest(final String baseDN, final SearchScope scope,
298 final String filter, final String... attributes)
299 throws LDAPException
300 {
301 this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false,
302 Filter.create(filter), attributes);
303 }
304
305
306
307 /**
308 * Creates a new search request with the provided information. Search result
309 * entries and references will be collected internally and included in the
310 * {@code SearchResult} object returned when search processing is completed.
311 *
312 * @param baseDN The base DN for the search request. It must not be
313 * {@code null}.
314 * @param scope The scope that specifies the range of entries that
315 * should be examined for the search.
316 * @param filter The string representation of the filter to use to
317 * identify matching entries. It must not be
318 * {@code null}.
319 * @param attributes The set of attributes that should be returned in
320 * matching entries. It may be {@code null} or empty if
321 * the default attribute set (all user attributes) is to
322 * be requested.
323 */
324 public SearchRequest(final String baseDN, final SearchScope scope,
325 final Filter filter, final String... attributes)
326 {
327 this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false,
328 filter, attributes);
329 }
330
331
332
333 /**
334 * Creates a new search request with the provided information.
335 *
336 * @param searchResultListener The search result listener that should be
337 * used to return results to the client. It may
338 * be {@code null} if the search results should
339 * be collected internally and returned in the
340 * {@code SearchResult} object.
341 * @param baseDN The base DN for the search request. It must
342 * not be {@code null}.
343 * @param scope The scope that specifies the range of entries
344 * that should be examined for the search.
345 * @param filter The string representation of the filter to
346 * use to identify matching entries. It must
347 * not be {@code null}.
348 * @param attributes The set of attributes that should be returned
349 * in matching entries. It may be {@code null}
350 * or empty if the default attribute set (all
351 * user attributes) is to be requested.
352 *
353 * @throws LDAPException If the provided filter string cannot be parsed as
354 * an LDAP filter.
355 */
356 public SearchRequest(final SearchResultListener searchResultListener,
357 final String baseDN, final SearchScope scope,
358 final String filter, final String... attributes)
359 throws LDAPException
360 {
361 this(searchResultListener, null, baseDN, scope, DereferencePolicy.NEVER, 0,
362 0, false, Filter.create(filter), attributes);
363 }
364
365
366
367 /**
368 * Creates a new search request with the provided information.
369 *
370 * @param searchResultListener The search result listener that should be
371 * used to return results to the client. It may
372 * be {@code null} if the search results should
373 * be collected internally and returned in the
374 * {@code SearchResult} object.
375 * @param baseDN The base DN for the search request. It must
376 * not be {@code null}.
377 * @param scope The scope that specifies the range of entries
378 * that should be examined for the search.
379 * @param filter The string representation of the filter to
380 * use to identify matching entries. It must
381 * not be {@code null}.
382 * @param attributes The set of attributes that should be returned
383 * in matching entries. It may be {@code null}
384 * or empty if the default attribute set (all
385 * user attributes) is to be requested.
386 */
387 public SearchRequest(final SearchResultListener searchResultListener,
388 final String baseDN, final SearchScope scope,
389 final Filter filter, final String... attributes)
390 {
391 this(searchResultListener, null, baseDN, scope, DereferencePolicy.NEVER, 0,
392 0, false, filter, attributes);
393 }
394
395
396
397 /**
398 * Creates a new search request with the provided information. Search result
399 * entries and references will be collected internally and included in the
400 * {@code SearchResult} object returned when search processing is completed.
401 *
402 * @param baseDN The base DN for the search request. It must not be
403 * {@code null}.
404 * @param scope The scope that specifies the range of entries that
405 * should be examined for the search.
406 * @param derefPolicy The dereference policy the server should use for any
407 * aliases encountered while processing the search.
408 * @param sizeLimit The maximum number of entries that the server should
409 * return for the search. A value of zero indicates that
410 * there should be no limit.
411 * @param timeLimit The maximum length of time in seconds that the server
412 * should spend processing this search request. A value
413 * of zero indicates that there should be no limit.
414 * @param typesOnly Indicates whether to return only attribute names in
415 * matching entries, or both attribute names and values.
416 * @param filter The filter to use to identify matching entries. It
417 * must not be {@code null}.
418 * @param attributes The set of attributes that should be returned in
419 * matching entries. It may be {@code null} or empty if
420 * the default attribute set (all user attributes) is to
421 * be requested.
422 *
423 * @throws LDAPException If the provided filter string cannot be parsed as
424 * an LDAP filter.
425 */
426 public SearchRequest(final String baseDN, final SearchScope scope,
427 final DereferencePolicy derefPolicy, final int sizeLimit,
428 final int timeLimit, final boolean typesOnly,
429 final String filter, final String... attributes)
430 throws LDAPException
431 {
432 this(null, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit,
433 typesOnly, Filter.create(filter), attributes);
434 }
435
436
437
438 /**
439 * Creates a new search request with the provided information. Search result
440 * entries and references will be collected internally and included in the
441 * {@code SearchResult} object returned when search processing is completed.
442 *
443 * @param baseDN The base DN for the search request. It must not be
444 * {@code null}.
445 * @param scope The scope that specifies the range of entries that
446 * should be examined for the search.
447 * @param derefPolicy The dereference policy the server should use for any
448 * aliases encountered while processing the search.
449 * @param sizeLimit The maximum number of entries that the server should
450 * return for the search. A value of zero indicates that
451 * there should be no limit.
452 * @param timeLimit The maximum length of time in seconds that the server
453 * should spend processing this search request. A value
454 * of zero indicates that there should be no limit.
455 * @param typesOnly Indicates whether to return only attribute names in
456 * matching entries, or both attribute names and values.
457 * @param filter The filter to use to identify matching entries. It
458 * must not be {@code null}.
459 * @param attributes The set of attributes that should be returned in
460 * matching entries. It may be {@code null} or empty if
461 * the default attribute set (all user attributes) is to
462 * be requested.
463 */
464 public SearchRequest(final String baseDN, final SearchScope scope,
465 final DereferencePolicy derefPolicy, final int sizeLimit,
466 final int timeLimit, final boolean typesOnly,
467 final Filter filter, final String... attributes)
468 {
469 this(null, null, baseDN, scope, derefPolicy, sizeLimit, timeLimit,
470 typesOnly, filter, attributes);
471 }
472
473
474
475 /**
476 * Creates a new search request with the provided information.
477 *
478 * @param searchResultListener The search result listener that should be
479 * used to return results to the client. It may
480 * be {@code null} if the search results should
481 * be collected internally and returned in the
482 * {@code SearchResult} object.
483 * @param baseDN The base DN for the search request. It must
484 * not be {@code null}.
485 * @param scope The scope that specifies the range of entries
486 * that should be examined for the search.
487 * @param derefPolicy The dereference policy the server should use
488 * for any aliases encountered while processing
489 * the search.
490 * @param sizeLimit The maximum number of entries that the server
491 * should return for the search. A value of
492 * zero indicates that there should be no limit.
493 * @param timeLimit The maximum length of time in seconds that
494 * the server should spend processing this
495 * search request. A value of zero indicates
496 * that there should be no limit.
497 * @param typesOnly Indicates whether to return only attribute
498 * names in matching entries, or both attribute
499 * names and values.
500 * @param filter The filter to use to identify matching
501 * entries. It must not be {@code null}.
502 * @param attributes The set of attributes that should be returned
503 * in matching entries. It may be {@code null}
504 * or empty if the default attribute set (all
505 * user attributes) is to be requested.
506 *
507 * @throws LDAPException If the provided filter string cannot be parsed as
508 * an LDAP filter.
509 */
510 public SearchRequest(final SearchResultListener searchResultListener,
511 final String baseDN, final SearchScope scope,
512 final DereferencePolicy derefPolicy, final int sizeLimit,
513 final int timeLimit, final boolean typesOnly,
514 final String filter, final String... attributes)
515 throws LDAPException
516 {
517 this(searchResultListener, null, baseDN, scope, derefPolicy, sizeLimit,
518 timeLimit, typesOnly, Filter.create(filter), attributes);
519 }
520
521
522
523 /**
524 * Creates a new search request with the provided information.
525 *
526 * @param searchResultListener The search result listener that should be
527 * used to return results to the client. It may
528 * be {@code null} if the search results should
529 * be collected internally and returned in the
530 * {@code SearchResult} object.
531 * @param baseDN The base DN for the search request. It must
532 * not be {@code null}.
533 * @param scope The scope that specifies the range of entries
534 * that should be examined for the search.
535 * @param derefPolicy The dereference policy the server should use
536 * for any aliases encountered while processing
537 * the search.
538 * @param sizeLimit The maximum number of entries that the server
539 * should return for the search. A value of
540 * zero indicates that there should be no limit.
541 * @param timeLimit The maximum length of time in seconds that
542 * the server should spend processing this
543 * search request. A value of zero indicates
544 * that there should be no limit.
545 * @param typesOnly Indicates whether to return only attribute
546 * names in matching entries, or both attribute
547 * names and values.
548 * @param filter The filter to use to identify matching
549 * entries. It must not be {@code null}.
550 * @param attributes The set of attributes that should be returned
551 * in matching entries. It may be {@code null}
552 * or empty if the default attribute set (all
553 * user attributes) is to be requested.
554 */
555 public SearchRequest(final SearchResultListener searchResultListener,
556 final String baseDN, final SearchScope scope,
557 final DereferencePolicy derefPolicy, final int sizeLimit,
558 final int timeLimit, final boolean typesOnly,
559 final Filter filter, final String... attributes)
560 {
561 this(searchResultListener, null, baseDN, scope, derefPolicy, sizeLimit,
562 timeLimit, typesOnly, filter, attributes);
563 }
564
565
566
567 /**
568 * Creates a new search request with the provided information.
569 *
570 * @param searchResultListener The search result listener that should be
571 * used to return results to the client. It may
572 * be {@code null} if the search results should
573 * be collected internally and returned in the
574 * {@code SearchResult} object.
575 * @param controls The set of controls to include in the
576 * request. It may be {@code null} or empty if
577 * no controls should be included in the
578 * request.
579 * @param baseDN The base DN for the search request. It must
580 * not be {@code null}.
581 * @param scope The scope that specifies the range of entries
582 * that should be examined for the search.
583 * @param derefPolicy The dereference policy the server should use
584 * for any aliases encountered while processing
585 * the search.
586 * @param sizeLimit The maximum number of entries that the server
587 * should return for the search. A value of
588 * zero indicates that there should be no limit.
589 * @param timeLimit The maximum length of time in seconds that
590 * the server should spend processing this
591 * search request. A value of zero indicates
592 * that there should be no limit.
593 * @param typesOnly Indicates whether to return only attribute
594 * names in matching entries, or both attribute
595 * names and values.
596 * @param filter The filter to use to identify matching
597 * entries. It must not be {@code null}.
598 * @param attributes The set of attributes that should be returned
599 * in matching entries. It may be {@code null}
600 * or empty if the default attribute set (all
601 * user attributes) is to be requested.
602 *
603 * @throws LDAPException If the provided filter string cannot be parsed as
604 * an LDAP filter.
605 */
606 public SearchRequest(final SearchResultListener searchResultListener,
607 final Control[] controls, final String baseDN,
608 final SearchScope scope,
609 final DereferencePolicy derefPolicy, final int sizeLimit,
610 final int timeLimit, final boolean typesOnly,
611 final String filter, final String... attributes)
612 throws LDAPException
613 {
614 this(searchResultListener, controls, baseDN, scope, derefPolicy, sizeLimit,
615 timeLimit, typesOnly, Filter.create(filter), attributes);
616 }
617
618
619
620 /**
621 * Creates a new search request with the provided information.
622 *
623 * @param searchResultListener The search result listener that should be
624 * used to return results to the client. It may
625 * be {@code null} if the search results should
626 * be collected internally and returned in the
627 * {@code SearchResult} object.
628 * @param controls The set of controls to include in the
629 * request. It may be {@code null} or empty if
630 * no controls should be included in the
631 * request.
632 * @param baseDN The base DN for the search request. It must
633 * not be {@code null}.
634 * @param scope The scope that specifies the range of entries
635 * that should be examined for the search.
636 * @param derefPolicy The dereference policy the server should use
637 * for any aliases encountered while processing
638 * the search.
639 * @param sizeLimit The maximum number of entries that the server
640 * should return for the search. A value of
641 * zero indicates that there should be no limit.
642 * @param timeLimit The maximum length of time in seconds that
643 * the server should spend processing this
644 * search request. A value of zero indicates
645 * that there should be no limit.
646 * @param typesOnly Indicates whether to return only attribute
647 * names in matching entries, or both attribute
648 * names and values.
649 * @param filter The filter to use to identify matching
650 * entries. It must not be {@code null}.
651 * @param attributes The set of attributes that should be returned
652 * in matching entries. It may be {@code null}
653 * or empty if the default attribute set (all
654 * user attributes) is to be requested.
655 */
656 public SearchRequest(final SearchResultListener searchResultListener,
657 final Control[] controls, final String baseDN,
658 final SearchScope scope,
659 final DereferencePolicy derefPolicy, final int sizeLimit,
660 final int timeLimit, final boolean typesOnly,
661 final Filter filter, final String... attributes)
662 {
663 super(controls);
664
665 ensureNotNull(baseDN, filter);
666
667 this.baseDN = baseDN;
668 this.scope = scope;
669 this.derefPolicy = derefPolicy;
670 this.typesOnly = typesOnly;
671 this.filter = filter;
672 this.searchResultListener = searchResultListener;
673
674 if (sizeLimit < 0)
675 {
676 this.sizeLimit = 0;
677 }
678 else
679 {
680 this.sizeLimit = sizeLimit;
681 }
682
683 if (timeLimit < 0)
684 {
685 this.timeLimit = 0;
686 }
687 else
688 {
689 this.timeLimit = timeLimit;
690 }
691
692 if (attributes == null)
693 {
694 this.attributes = REQUEST_ATTRS_DEFAULT;
695 }
696 else
697 {
698 this.attributes = attributes;
699 }
700 }
701
702
703
704 /**
705 * {@inheritDoc}
706 */
707 public String getBaseDN()
708 {
709 return baseDN;
710 }
711
712
713
714 /**
715 * Specifies the base DN for this search request.
716 *
717 * @param baseDN The base DN for this search request. It must not be
718 * {@code null}.
719 */
720 public void setBaseDN(final String baseDN)
721 {
722 ensureNotNull(baseDN);
723
724 this.baseDN = baseDN;
725 }
726
727
728
729 /**
730 * Specifies the base DN for this search request.
731 *
732 * @param baseDN The base DN for this search request. It must not be
733 * {@code null}.
734 */
735 public void setBaseDN(final DN baseDN)
736 {
737 ensureNotNull(baseDN);
738
739 this.baseDN = baseDN.toString();
740 }
741
742
743
744 /**
745 * {@inheritDoc}
746 */
747 public SearchScope getScope()
748 {
749 return scope;
750 }
751
752
753
754 /**
755 * Specifies the scope for this search request.
756 *
757 * @param scope The scope for this search request.
758 */
759 public void setScope(final SearchScope scope)
760 {
761 this.scope = scope;
762 }
763
764
765
766 /**
767 * {@inheritDoc}
768 */
769 public DereferencePolicy getDereferencePolicy()
770 {
771 return derefPolicy;
772 }
773
774
775
776 /**
777 * Specifies the dereference policy that should be used by the server for any
778 * aliases encountered during search processing.
779 *
780 * @param derefPolicy The dereference policy that should be used by the
781 * server for any aliases encountered during search
782 * processing.
783 */
784 public void setDerefPolicy(final DereferencePolicy derefPolicy)
785 {
786 this.derefPolicy = derefPolicy;
787 }
788
789
790
791 /**
792 * {@inheritDoc}
793 */
794 public int getSizeLimit()
795 {
796 return sizeLimit;
797 }
798
799
800
801 /**
802 * Specifies the maximum number of entries that should be returned by the
803 * server when processing this search request. A value of zero indicates that
804 * there should be no limit.
805 * <BR><BR>
806 * Note that if an attempt to process a search operation fails because the
807 * size limit has been exceeded, an {@link LDAPSearchException} will be
808 * thrown. If one or more entries or references have already been returned
809 * for the search, then the {@code LDAPSearchException} methods like
810 * {@code getEntryCount}, {@code getSearchEntries}, {@code getReferenceCount},
811 * and {@code getSearchReferences} may be used to obtain information about
812 * those entries and references (although if a search result listener was
813 * provided, then it will have been used to make any entries and references
814 * available, and they will not be available through the
815 * {@code getSearchEntries} and {@code getSearchReferences} methods).
816 *
817 * @param sizeLimit The maximum number of entries that should be returned by
818 * the server when processing this search request.
819 */
820 public void setSizeLimit(final int sizeLimit)
821 {
822 if (sizeLimit < 0)
823 {
824 this.sizeLimit = 0;
825 }
826 else
827 {
828 this.sizeLimit = sizeLimit;
829 }
830 }
831
832
833
834 /**
835 * {@inheritDoc}
836 */
837 public int getTimeLimitSeconds()
838 {
839 return timeLimit;
840 }
841
842
843
844 /**
845 * Specifies the maximum length of time in seconds that the server should
846 * spend processing this search request. A value of zero indicates that there
847 * should be no limit.
848 * <BR><BR>
849 * Note that if an attempt to process a search operation fails because the
850 * time limit has been exceeded, an {@link LDAPSearchException} will be
851 * thrown. If one or more entries or references have already been returned
852 * for the search, then the {@code LDAPSearchException} methods like
853 * {@code getEntryCount}, {@code getSearchEntries}, {@code getReferenceCount},
854 * and {@code getSearchReferences} may be used to obtain information about
855 * those entries and references (although if a search result listener was
856 * provided, then it will have been used to make any entries and references
857 * available, and they will not be available through the
858 * {@code getSearchEntries} and {@code getSearchReferences} methods).
859 *
860 * @param timeLimit The maximum length of time in seconds that the server
861 * should spend processing this search request.
862 */
863 public void setTimeLimitSeconds(final int timeLimit)
864 {
865 if (timeLimit < 0)
866 {
867 this.timeLimit = 0;
868 }
869 else
870 {
871 this.timeLimit = timeLimit;
872 }
873 }
874
875
876
877 /**
878 * {@inheritDoc}
879 */
880 public boolean typesOnly()
881 {
882 return typesOnly;
883 }
884
885
886
887 /**
888 * Specifies whether the server should return only attribute names in matching
889 * entries, rather than both names and values.
890 *
891 * @param typesOnly Specifies whether the server should return only
892 * attribute names in matching entries, rather than both
893 * names and values.
894 */
895 public void setTypesOnly(final boolean typesOnly)
896 {
897 this.typesOnly = typesOnly;
898 }
899
900
901
902 /**
903 * {@inheritDoc}
904 */
905 public Filter getFilter()
906 {
907 return filter;
908 }
909
910
911
912 /**
913 * Specifies the filter that should be used to identify matching entries.
914 *
915 * @param filter The string representation for the filter that should be
916 * used to identify matching entries. It must not be
917 * {@code null}.
918 *
919 * @throws LDAPException If the provided filter string cannot be parsed as a
920 * search filter.
921 */
922 public void setFilter(final String filter)
923 throws LDAPException
924 {
925 ensureNotNull(filter);
926
927 this.filter = Filter.create(filter);
928 }
929
930
931
932 /**
933 * Specifies the filter that should be used to identify matching entries.
934 *
935 * @param filter The filter that should be used to identify matching
936 * entries. It must not be {@code null}.
937 */
938 public void setFilter(final Filter filter)
939 {
940 ensureNotNull(filter);
941
942 this.filter = filter;
943 }
944
945
946
947 /**
948 * Retrieves the set of requested attributes to include in matching entries.
949 * The caller must not attempt to alter the contents of the array.
950 *
951 * @return The set of requested attributes to include in matching entries, or
952 * an empty array if the default set of attributes (all user
953 * attributes but no operational attributes) should be requested.
954 */
955 public String[] getAttributes()
956 {
957 return attributes;
958 }
959
960
961
962 /**
963 * {@inheritDoc}
964 */
965 public List<String> getAttributeList()
966 {
967 return Collections.unmodifiableList(Arrays.asList(attributes));
968 }
969
970
971
972 /**
973 * Specifies the set of requested attributes to include in matching entries.
974 *
975 * @param attributes The set of requested attributes to include in matching
976 * entries. It may be {@code null} if the default set of
977 * attributes (all user attributes but no operational
978 * attributes) should be requested.
979 */
980 public void setAttributes(final String... attributes)
981 {
982 if (attributes == null)
983 {
984 this.attributes = REQUEST_ATTRS_DEFAULT;
985 }
986 else
987 {
988 this.attributes = attributes;
989 }
990 }
991
992
993
994 /**
995 * Specifies the set of requested attributes to include in matching entries.
996 *
997 * @param attributes The set of requested attributes to include in matching
998 * entries. It may be {@code null} if the default set of
999 * attributes (all user attributes but no operational
1000 * attributes) should be requested.
1001 */
1002 public void setAttributes(final List<String> attributes)
1003 {
1004 if (attributes == null)
1005 {
1006 this.attributes = REQUEST_ATTRS_DEFAULT;
1007 }
1008 else
1009 {
1010 this.attributes = new String[attributes.size()];
1011 for (int i=0; i < this.attributes.length; i++)
1012 {
1013 this.attributes[i] = attributes.get(i);
1014 }
1015 }
1016 }
1017
1018
1019
1020 /**
1021 * Retrieves the search result listener for this search request, if available.
1022 *
1023 * @return The search result listener for this search request, or
1024 * {@code null} if none has been configured.
1025 */
1026 public SearchResultListener getSearchResultListener()
1027 {
1028 return searchResultListener;
1029 }
1030
1031
1032
1033 /**
1034 * {@inheritDoc}
1035 */
1036 public byte getProtocolOpType()
1037 {
1038 return LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST;
1039 }
1040
1041
1042
1043 /**
1044 * {@inheritDoc}
1045 */
1046 public void writeTo(final ASN1Buffer writer)
1047 {
1048 final ASN1BufferSequence requestSequence =
1049 writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST);
1050 writer.addOctetString(baseDN);
1051 writer.addEnumerated(scope.intValue());
1052 writer.addEnumerated(derefPolicy.intValue());
1053 writer.addInteger(sizeLimit);
1054 writer.addInteger(timeLimit);
1055 writer.addBoolean(typesOnly);
1056 filter.writeTo(writer);
1057
1058 final ASN1BufferSequence attrSequence = writer.beginSequence();
1059 for (final String s : attributes)
1060 {
1061 writer.addOctetString(s);
1062 }
1063 attrSequence.end();
1064 requestSequence.end();
1065 }
1066
1067
1068
1069 /**
1070 * Encodes the search request protocol op to an ASN.1 element.
1071 *
1072 * @return The ASN.1 element with the encoded search request protocol op.
1073 */
1074 public ASN1Element encodeProtocolOp()
1075 {
1076 // Create the search request protocol op.
1077 final ASN1Element[] attrElements = new ASN1Element[attributes.length];
1078 for (int i=0; i < attrElements.length; i++)
1079 {
1080 attrElements[i] = new ASN1OctetString(attributes[i]);
1081 }
1082
1083 final ASN1Element[] protocolOpElements =
1084 {
1085 new ASN1OctetString(baseDN),
1086 new ASN1Enumerated(scope.intValue()),
1087 new ASN1Enumerated(derefPolicy.intValue()),
1088 new ASN1Integer(sizeLimit),
1089 new ASN1Integer(timeLimit),
1090 new ASN1Boolean(typesOnly),
1091 filter.encode(),
1092 new ASN1Sequence(attrElements)
1093 };
1094
1095 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST,
1096 protocolOpElements);
1097 }
1098
1099
1100
1101 /**
1102 * Sends this search request to the directory server over the provided
1103 * connection and returns the associated response. The search result entries
1104 * and references will either be collected and returned in the
1105 * {@code SearchResult} object that is returned, or will be interactively
1106 * returned via the {@code SearchResultListener} interface.
1107 *
1108 * @param connection The connection to use to communicate with the directory
1109 * server.
1110 * @param depth The current referral depth for this request. It should
1111 * always be one for the initial request, and should only
1112 * be incremented when following referrals.
1113 *
1114 * @return An object that provides information about the result of the
1115 * search processing, potentially including the sets of matching
1116 * entries and/or search references.
1117 *
1118 * @throws LDAPException If a problem occurs while sending the request or
1119 * reading the response.
1120 */
1121 @Override()
1122 protected SearchResult process(final LDAPConnection connection,
1123 final int depth)
1124 throws LDAPException
1125 {
1126 if (connection.synchronousMode())
1127 {
1128 @SuppressWarnings("deprecation")
1129 final boolean autoReconnect =
1130 connection.getConnectionOptions().autoReconnect();
1131 return processSync(connection, depth, autoReconnect);
1132 }
1133
1134 final long requestTime = System.nanoTime();
1135 processAsync(connection, null);
1136
1137 try
1138 {
1139 // Wait for and process the response.
1140 final ArrayList<SearchResultEntry> entryList;
1141 final ArrayList<SearchResultReference> referenceList;
1142 if (searchResultListener == null)
1143 {
1144 entryList = new ArrayList<SearchResultEntry>(5);
1145 referenceList = new ArrayList<SearchResultReference>(5);
1146 }
1147 else
1148 {
1149 entryList = null;
1150 referenceList = null;
1151 }
1152
1153 int numEntries = 0;
1154 int numReferences = 0;
1155 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1156 final long responseTimeout = getResponseTimeoutMillis(connection);
1157 while (true)
1158 {
1159 final LDAPResponse response;
1160 try
1161 {
1162 if (responseTimeout > 0)
1163 {
1164 response =
1165 responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
1166 }
1167 else
1168 {
1169 response = responseQueue.take();
1170 }
1171 }
1172 catch (InterruptedException ie)
1173 {
1174 debugException(ie);
1175 throw new LDAPException(ResultCode.LOCAL_ERROR,
1176 ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie);
1177 }
1178
1179 if (response == null)
1180 {
1181 if (connection.getConnectionOptions().abandonOnTimeout())
1182 {
1183 connection.abandon(messageID);
1184 }
1185
1186 final SearchResult searchResult =
1187 new SearchResult(messageID, ResultCode.TIMEOUT,
1188 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID,
1189 baseDN, scope.getName(), filter.toString(),
1190 connection.getHostPort()),
1191 null, null, entryList, referenceList, numEntries,
1192 numReferences, null);
1193 throw new LDAPSearchException(searchResult);
1194 }
1195
1196 if (response instanceof ConnectionClosedResponse)
1197 {
1198 final ConnectionClosedResponse ccr =
1199 (ConnectionClosedResponse) response;
1200 final String message = ccr.getMessage();
1201 if (message == null)
1202 {
1203 // The connection was closed while waiting for the response.
1204 final SearchResult searchResult =
1205 new SearchResult(messageID, ccr.getResultCode(),
1206 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1207 connection.getHostPort(), toString()),
1208 null, null, entryList, referenceList, numEntries,
1209 numReferences, null);
1210 throw new LDAPSearchException(searchResult);
1211 }
1212 else
1213 {
1214 // The connection was closed while waiting for the response.
1215 final SearchResult searchResult =
1216 new SearchResult(messageID, ccr.getResultCode(),
1217 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1218 get(connection.getHostPort(), toString(), message),
1219 null, null, entryList, referenceList, numEntries,
1220 numReferences, null);
1221 throw new LDAPSearchException(searchResult);
1222 }
1223 }
1224 else if (response instanceof SearchResultEntry)
1225 {
1226 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1227 numEntries++;
1228 if (searchResultListener == null)
1229 {
1230 entryList.add(searchEntry);
1231 }
1232 else
1233 {
1234 searchResultListener.searchEntryReturned(searchEntry);
1235 }
1236 }
1237 else if (response instanceof SearchResultReference)
1238 {
1239 final SearchResultReference searchReference =
1240 (SearchResultReference) response;
1241 if (followReferrals(connection))
1242 {
1243 final LDAPResult result = followSearchReference(messageID,
1244 searchReference, connection, depth);
1245 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1246 {
1247 // We couldn't follow the reference. We don't want to fail the
1248 // entire search because of this right now, so treat it as if
1249 // referral following had not been enabled. Also, set the
1250 // intermediate result code to match that of the result.
1251 numReferences++;
1252 if (searchResultListener == null)
1253 {
1254 referenceList.add(searchReference);
1255 }
1256 else
1257 {
1258 searchResultListener.searchReferenceReturned(searchReference);
1259 }
1260
1261 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1262 {
1263 intermediateResultCode = result.getResultCode();
1264 }
1265 }
1266 else if (result instanceof SearchResult)
1267 {
1268 final SearchResult searchResult = (SearchResult) result;
1269 numEntries += searchResult.getEntryCount();
1270 if (searchResultListener == null)
1271 {
1272 entryList.addAll(searchResult.getSearchEntries());
1273 }
1274 }
1275 }
1276 else
1277 {
1278 numReferences++;
1279 if (searchResultListener == null)
1280 {
1281 referenceList.add(searchReference);
1282 }
1283 else
1284 {
1285 searchResultListener.searchReferenceReturned(searchReference);
1286 }
1287 }
1288 }
1289 else
1290 {
1291 connection.getConnectionStatistics().incrementNumSearchResponses(
1292 numEntries, numReferences,
1293 (System.nanoTime() - requestTime));
1294 SearchResult result = (SearchResult) response;
1295 result.setCounts(numEntries, entryList, numReferences, referenceList);
1296
1297 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1298 followReferrals(connection))
1299 {
1300 if (depth >=
1301 connection.getConnectionOptions().getReferralHopLimit())
1302 {
1303 return new SearchResult(messageID,
1304 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1305 ERR_TOO_MANY_REFERRALS.get(),
1306 result.getMatchedDN(),
1307 result.getReferralURLs(), entryList,
1308 referenceList, numEntries,
1309 numReferences,
1310 result.getResponseControls());
1311 }
1312
1313 result = followReferral(result, connection, depth);
1314 }
1315
1316 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1317 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1318 {
1319 return new SearchResult(messageID, intermediateResultCode,
1320 result.getDiagnosticMessage(),
1321 result.getMatchedDN(),
1322 result.getReferralURLs(),
1323 entryList, referenceList, numEntries,
1324 numReferences,
1325 result.getResponseControls());
1326 }
1327
1328 return result;
1329 }
1330 }
1331 }
1332 finally
1333 {
1334 connection.deregisterResponseAcceptor(messageID);
1335 }
1336 }
1337
1338
1339
1340 /**
1341 * Sends this search request to the directory server over the provided
1342 * connection and returns the message ID for the request.
1343 *
1344 * @param connection The connection to use to communicate with the
1345 * directory server.
1346 * @param resultListener The async result listener that is to be notified
1347 * when the response is received. It may be
1348 * {@code null} only if the result is to be processed
1349 * by this class.
1350 *
1351 * @return The async request ID created for the operation, or {@code null} if
1352 * the provided {@code resultListener} is {@code null} and the
1353 * operation will not actually be processed asynchronously.
1354 *
1355 * @throws LDAPException If a problem occurs while sending the request.
1356 */
1357 AsyncRequestID processAsync(final LDAPConnection connection,
1358 final AsyncSearchResultListener resultListener)
1359 throws LDAPException
1360 {
1361 // Create the LDAP message.
1362 messageID = connection.nextMessageID();
1363 final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
1364
1365
1366 // If the provided async result listener is {@code null}, then we'll use
1367 // this class as the message acceptor. Otherwise, create an async helper
1368 // and use it as the message acceptor.
1369 final AsyncRequestID asyncRequestID;
1370 if (resultListener == null)
1371 {
1372 asyncRequestID = null;
1373 connection.registerResponseAcceptor(messageID, this);
1374 }
1375 else
1376 {
1377 final AsyncSearchHelper helper = new AsyncSearchHelper(connection,
1378 messageID, resultListener, getIntermediateResponseListener());
1379 connection.registerResponseAcceptor(messageID, helper);
1380 asyncRequestID = helper.getAsyncRequestID();
1381
1382 final long timeout = getResponseTimeoutMillis(connection);
1383 if (timeout > 0L)
1384 {
1385 final Timer timer = connection.getTimer();
1386 final AsyncTimeoutTimerTask timerTask =
1387 new AsyncTimeoutTimerTask(helper);
1388 timer.schedule(timerTask, timeout);
1389 asyncRequestID.setTimerTask(timerTask);
1390 }
1391 }
1392
1393
1394 // Send the request to the server.
1395 try
1396 {
1397 debugLDAPRequest(this);
1398 connection.getConnectionStatistics().incrementNumSearchRequests();
1399 connection.sendMessage(message);
1400 return asyncRequestID;
1401 }
1402 catch (LDAPException le)
1403 {
1404 debugException(le);
1405
1406 connection.deregisterResponseAcceptor(messageID);
1407 throw le;
1408 }
1409 }
1410
1411
1412
1413 /**
1414 * Processes this search operation in synchronous mode, in which the same
1415 * thread will send the request and read the response.
1416 *
1417 * @param connection The connection to use to communicate with the directory
1418 * server.
1419 * @param depth The current referral depth for this request. It should
1420 * always be one for the initial request, and should only
1421 * be incremented when following referrals.
1422 * @param allowRetry Indicates whether the request may be re-tried on a
1423 * re-established connection if the initial attempt fails
1424 * in a way that indicates the connection is no longer
1425 * valid and autoReconnect is true.
1426 *
1427 * @return An LDAP result object that provides information about the result
1428 * of the search processing.
1429 *
1430 * @throws LDAPException If a problem occurs while sending the request or
1431 * reading the response.
1432 */
1433 private SearchResult processSync(final LDAPConnection connection,
1434 final int depth, final boolean allowRetry)
1435 throws LDAPException
1436 {
1437 // Create the LDAP message.
1438 messageID = connection.nextMessageID();
1439 final LDAPMessage message =
1440 new LDAPMessage(messageID, this, getControls());
1441
1442
1443 // Set the appropriate timeout on the socket.
1444 final long responseTimeout = getResponseTimeoutMillis(connection);
1445 try
1446 {
1447 connection.getConnectionInternals(true).getSocket().setSoTimeout(
1448 (int) responseTimeout);
1449 }
1450 catch (Exception e)
1451 {
1452 debugException(e);
1453 }
1454
1455
1456 // Send the request to the server.
1457 final long requestTime = System.nanoTime();
1458 debugLDAPRequest(this);
1459 connection.getConnectionStatistics().incrementNumSearchRequests();
1460 try
1461 {
1462 connection.sendMessage(message);
1463 }
1464 catch (final LDAPException le)
1465 {
1466 debugException(le);
1467
1468 if (allowRetry)
1469 {
1470 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1471 le.getResultCode(), 0, 0);
1472 if (retryResult != null)
1473 {
1474 return retryResult;
1475 }
1476 }
1477
1478 throw le;
1479 }
1480
1481 final ArrayList<SearchResultEntry> entryList;
1482 final ArrayList<SearchResultReference> referenceList;
1483 if (searchResultListener == null)
1484 {
1485 entryList = new ArrayList<SearchResultEntry>(5);
1486 referenceList = new ArrayList<SearchResultReference>(5);
1487 }
1488 else
1489 {
1490 entryList = null;
1491 referenceList = null;
1492 }
1493
1494 int numEntries = 0;
1495 int numReferences = 0;
1496 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1497 while (true)
1498 {
1499 final LDAPResponse response;
1500 try
1501 {
1502 response = connection.readResponse(messageID);
1503 }
1504 catch (final LDAPException le)
1505 {
1506 debugException(le);
1507
1508 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1509 connection.getConnectionOptions().abandonOnTimeout())
1510 {
1511 connection.abandon(messageID);
1512 }
1513
1514 if (allowRetry)
1515 {
1516 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1517 le.getResultCode(), numEntries, numReferences);
1518 if (retryResult != null)
1519 {
1520 return retryResult;
1521 }
1522 }
1523
1524 throw le;
1525 }
1526
1527 if (response == null)
1528 {
1529 if (connection.getConnectionOptions().abandonOnTimeout())
1530 {
1531 connection.abandon(messageID);
1532 }
1533
1534 throw new LDAPException(ResultCode.TIMEOUT,
1535 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID, baseDN,
1536 scope.getName(), filter.toString(),
1537 connection.getHostPort()));
1538 }
1539 else if (response instanceof ConnectionClosedResponse)
1540 {
1541
1542 if (allowRetry)
1543 {
1544 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1545 ResultCode.SERVER_DOWN, numEntries, numReferences);
1546 if (retryResult != null)
1547 {
1548 return retryResult;
1549 }
1550 }
1551
1552 final ConnectionClosedResponse ccr =
1553 (ConnectionClosedResponse) response;
1554 final String msg = ccr.getMessage();
1555 if (msg == null)
1556 {
1557 // The connection was closed while waiting for the response.
1558 final SearchResult searchResult =
1559 new SearchResult(messageID, ccr.getResultCode(),
1560 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1561 connection.getHostPort(), toString()),
1562 null, null, entryList, referenceList, numEntries,
1563 numReferences, null);
1564 throw new LDAPSearchException(searchResult);
1565 }
1566 else
1567 {
1568 // The connection was closed while waiting for the response.
1569 final SearchResult searchResult =
1570 new SearchResult(messageID, ccr.getResultCode(),
1571 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1572 get(connection.getHostPort(), toString(), msg),
1573 null, null, entryList, referenceList, numEntries,
1574 numReferences, null);
1575 throw new LDAPSearchException(searchResult);
1576 }
1577 }
1578 else if (response instanceof IntermediateResponse)
1579 {
1580 final IntermediateResponseListener listener =
1581 getIntermediateResponseListener();
1582 if (listener != null)
1583 {
1584 listener.intermediateResponseReturned(
1585 (IntermediateResponse) response);
1586 }
1587 }
1588 else if (response instanceof SearchResultEntry)
1589 {
1590 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1591 numEntries++;
1592 if (searchResultListener == null)
1593 {
1594 entryList.add(searchEntry);
1595 }
1596 else
1597 {
1598 searchResultListener.searchEntryReturned(searchEntry);
1599 }
1600 }
1601 else if (response instanceof SearchResultReference)
1602 {
1603 final SearchResultReference searchReference =
1604 (SearchResultReference) response;
1605 if (followReferrals(connection))
1606 {
1607 final LDAPResult result = followSearchReference(messageID,
1608 searchReference, connection, depth);
1609 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1610 {
1611 // We couldn't follow the reference. We don't want to fail the
1612 // entire search because of this right now, so treat it as if
1613 // referral following had not been enabled. Also, set the
1614 // intermediate result code to match that of the result.
1615 numReferences++;
1616 if (searchResultListener == null)
1617 {
1618 referenceList.add(searchReference);
1619 }
1620 else
1621 {
1622 searchResultListener.searchReferenceReturned(searchReference);
1623 }
1624
1625 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1626 {
1627 intermediateResultCode = result.getResultCode();
1628 }
1629 }
1630 else if (result instanceof SearchResult)
1631 {
1632 final SearchResult searchResult = (SearchResult) result;
1633 numEntries += searchResult.getEntryCount();
1634 if (searchResultListener == null)
1635 {
1636 entryList.addAll(searchResult.getSearchEntries());
1637 }
1638 }
1639 }
1640 else
1641 {
1642 numReferences++;
1643 if (searchResultListener == null)
1644 {
1645 referenceList.add(searchReference);
1646 }
1647 else
1648 {
1649 searchResultListener.searchReferenceReturned(searchReference);
1650 }
1651 }
1652 }
1653 else
1654 {
1655 final SearchResult result = (SearchResult) response;
1656 if (allowRetry)
1657 {
1658 final SearchResult retryResult = reconnectAndRetry(connection,
1659 depth, result.getResultCode(), numEntries, numReferences);
1660 if (retryResult != null)
1661 {
1662 return retryResult;
1663 }
1664 }
1665
1666 return handleResponse(connection, response, requestTime, depth,
1667 numEntries, numReferences, entryList,
1668 referenceList, intermediateResultCode);
1669 }
1670 }
1671 }
1672
1673
1674
1675 /**
1676 * Attempts to re-establish the connection and retry processing this request
1677 * on it.
1678 *
1679 * @param connection The connection to be re-established.
1680 * @param depth The current referral depth for this request. It
1681 * should always be one for the initial request, and
1682 * should only be incremented when following referrals.
1683 * @param resultCode The result code for the previous operation attempt.
1684 * @param numEntries The number of search result entries already sent for
1685 * the search operation.
1686 * @param numReferences The number of search result references already sent
1687 * for the search operation.
1688 *
1689 * @return The result from re-trying the search, or {@code null} if it could
1690 * not be re-tried.
1691 */
1692 private SearchResult reconnectAndRetry(final LDAPConnection connection,
1693 final int depth,
1694 final ResultCode resultCode,
1695 final int numEntries,
1696 final int numReferences)
1697 {
1698 try
1699 {
1700 // We will only want to retry for certain result codes that indicate a
1701 // connection problem.
1702 switch (resultCode.intValue())
1703 {
1704 case ResultCode.SERVER_DOWN_INT_VALUE:
1705 case ResultCode.DECODING_ERROR_INT_VALUE:
1706 case ResultCode.CONNECT_ERROR_INT_VALUE:
1707 // We want to try to re-establish the connection no matter what, but
1708 // we only want to retry the search if we haven't yet sent any
1709 // results.
1710 connection.reconnect();
1711 if ((numEntries == 0) && (numReferences == 0))
1712 {
1713 return processSync(connection, depth, false);
1714 }
1715 break;
1716 }
1717 }
1718 catch (final Exception e)
1719 {
1720 debugException(e);
1721 }
1722
1723 return null;
1724 }
1725
1726
1727
1728 /**
1729 * Performs the necessary processing for handling a response.
1730 *
1731 * @param connection The connection used to read the response.
1732 * @param response The response to be processed.
1733 * @param requestTime The time the request was sent to the
1734 * server.
1735 * @param depth The current referral depth for this
1736 * request. It should always be one for the
1737 * initial request, and should only be
1738 * incremented when following referrals.
1739 * @param numEntries The number of entries received from the
1740 * server.
1741 * @param numReferences The number of references received from
1742 * the server.
1743 * @param entryList The list of search result entries received
1744 * from the server, if applicable.
1745 * @param referenceList The list of search result references
1746 * received from the server, if applicable.
1747 * @param intermediateResultCode The intermediate result code so far for the
1748 * search operation.
1749 *
1750 * @return The search result.
1751 *
1752 * @throws LDAPException If a problem occurs.
1753 */
1754 private SearchResult handleResponse(final LDAPConnection connection,
1755 final LDAPResponse response, final long requestTime,
1756 final int depth, final int numEntries, final int numReferences,
1757 final List<SearchResultEntry> entryList,
1758 final List<SearchResultReference> referenceList,
1759 final ResultCode intermediateResultCode)
1760 throws LDAPException
1761 {
1762 connection.getConnectionStatistics().incrementNumSearchResponses(
1763 numEntries, numReferences,
1764 (System.nanoTime() - requestTime));
1765 SearchResult result = (SearchResult) response;
1766 result.setCounts(numEntries, entryList, numReferences, referenceList);
1767
1768 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1769 followReferrals(connection))
1770 {
1771 if (depth >=
1772 connection.getConnectionOptions().getReferralHopLimit())
1773 {
1774 return new SearchResult(messageID,
1775 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1776 ERR_TOO_MANY_REFERRALS.get(),
1777 result.getMatchedDN(),
1778 result.getReferralURLs(), entryList,
1779 referenceList, numEntries,
1780 numReferences,
1781 result.getResponseControls());
1782 }
1783
1784 result = followReferral(result, connection, depth);
1785 }
1786
1787 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1788 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1789 {
1790 return new SearchResult(messageID, intermediateResultCode,
1791 result.getDiagnosticMessage(),
1792 result.getMatchedDN(),
1793 result.getReferralURLs(),
1794 entryList, referenceList, numEntries,
1795 numReferences,
1796 result.getResponseControls());
1797 }
1798
1799 return result;
1800 }
1801
1802
1803
1804 /**
1805 * Attempts to follow a search result reference to continue a search in a
1806 * remote server.
1807 *
1808 * @param messageID The message ID for the LDAP message that is
1809 * associated with this result.
1810 * @param searchReference The search result reference to follow.
1811 * @param connection The connection on which the reference was
1812 * received.
1813 * @param depth The number of referrals followed in the course of
1814 * processing this request.
1815 *
1816 * @return The result of attempting to follow the search result reference.
1817 *
1818 * @throws LDAPException If a problem occurs while attempting to establish
1819 * the referral connection, sending the request, or
1820 * reading the result.
1821 */
1822 private LDAPResult followSearchReference(final int messageID,
1823 final SearchResultReference searchReference,
1824 final LDAPConnection connection, final int depth)
1825 throws LDAPException
1826 {
1827 for (final String urlString : searchReference.getReferralURLs())
1828 {
1829 try
1830 {
1831 final LDAPURL referralURL = new LDAPURL(urlString);
1832 final String host = referralURL.getHost();
1833
1834 if (host == null)
1835 {
1836 // We can't handle a referral in which there is no host.
1837 continue;
1838 }
1839
1840 final String requestBaseDN;
1841 if (referralURL.baseDNProvided())
1842 {
1843 requestBaseDN = referralURL.getBaseDN().toString();
1844 }
1845 else
1846 {
1847 requestBaseDN = baseDN;
1848 }
1849
1850 final SearchScope requestScope;
1851 if (referralURL.scopeProvided())
1852 {
1853 requestScope = referralURL.getScope();
1854 }
1855 else
1856 {
1857 requestScope = scope;
1858 }
1859
1860 final Filter requestFilter;
1861 if (referralURL.filterProvided())
1862 {
1863 requestFilter = referralURL.getFilter();
1864 }
1865 else
1866 {
1867 requestFilter = filter;
1868 }
1869
1870
1871 final SearchRequest searchRequest =
1872 new SearchRequest(searchResultListener, getControls(),
1873 requestBaseDN, requestScope, derefPolicy,
1874 sizeLimit, timeLimit, typesOnly, requestFilter,
1875 attributes);
1876
1877 final LDAPConnection referralConn = connection.getReferralConnector().
1878 getReferralConnection(referralURL, connection);
1879
1880 try
1881 {
1882 return searchRequest.process(referralConn, depth+1);
1883 }
1884 finally
1885 {
1886 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1887 referralConn.close();
1888 }
1889 }
1890 catch (LDAPException le)
1891 {
1892 debugException(le);
1893
1894 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1895 {
1896 throw le;
1897 }
1898 }
1899 }
1900
1901 // If we've gotten here, then we could not follow any of the referral URLs,
1902 // so we'll create a failure result.
1903 return new SearchResult(messageID, ResultCode.REFERRAL, null, null,
1904 searchReference.getReferralURLs(), 0, 0, null);
1905 }
1906
1907
1908
1909 /**
1910 * Attempts to follow a referral to perform an add operation in the target
1911 * server.
1912 *
1913 * @param referralResult The LDAP result object containing information about
1914 * the referral to follow.
1915 * @param connection The connection on which the referral was received.
1916 * @param depth The number of referrals followed in the course of
1917 * processing this request.
1918 *
1919 * @return The result of attempting to process the add operation by following
1920 * the referral.
1921 *
1922 * @throws LDAPException If a problem occurs while attempting to establish
1923 * the referral connection, sending the request, or
1924 * reading the result.
1925 */
1926 private SearchResult followReferral(final SearchResult referralResult,
1927 final LDAPConnection connection,
1928 final int depth)
1929 throws LDAPException
1930 {
1931 for (final String urlString : referralResult.getReferralURLs())
1932 {
1933 try
1934 {
1935 final LDAPURL referralURL = new LDAPURL(urlString);
1936 final String host = referralURL.getHost();
1937
1938 if (host == null)
1939 {
1940 // We can't handle a referral in which there is no host.
1941 continue;
1942 }
1943
1944 final String requestBaseDN;
1945 if (referralURL.baseDNProvided())
1946 {
1947 requestBaseDN = referralURL.getBaseDN().toString();
1948 }
1949 else
1950 {
1951 requestBaseDN = baseDN;
1952 }
1953
1954 final SearchScope requestScope;
1955 if (referralURL.scopeProvided())
1956 {
1957 requestScope = referralURL.getScope();
1958 }
1959 else
1960 {
1961 requestScope = scope;
1962 }
1963
1964 final Filter requestFilter;
1965 if (referralURL.filterProvided())
1966 {
1967 requestFilter = referralURL.getFilter();
1968 }
1969 else
1970 {
1971 requestFilter = filter;
1972 }
1973
1974
1975 final SearchRequest searchRequest =
1976 new SearchRequest(searchResultListener, getControls(),
1977 requestBaseDN, requestScope, derefPolicy,
1978 sizeLimit, timeLimit, typesOnly, requestFilter,
1979 attributes);
1980
1981 final LDAPConnection referralConn = connection.getReferralConnector().
1982 getReferralConnection(referralURL, connection);
1983 try
1984 {
1985 return searchRequest.process(referralConn, depth+1);
1986 }
1987 finally
1988 {
1989 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1990 referralConn.close();
1991 }
1992 }
1993 catch (LDAPException le)
1994 {
1995 debugException(le);
1996
1997 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1998 {
1999 throw le;
2000 }
2001 }
2002 }
2003
2004 // If we've gotten here, then we could not follow any of the referral URLs,
2005 // so we'll just return the original referral result.
2006 return referralResult;
2007 }
2008
2009
2010
2011 /**
2012 * {@inheritDoc}
2013 */
2014 @InternalUseOnly()
2015 public void responseReceived(final LDAPResponse response)
2016 throws LDAPException
2017 {
2018 try
2019 {
2020 responseQueue.put(response);
2021 }
2022 catch (Exception e)
2023 {
2024 debugException(e);
2025 throw new LDAPException(ResultCode.LOCAL_ERROR,
2026 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
2027 }
2028 }
2029
2030
2031
2032 /**
2033 * {@inheritDoc}
2034 */
2035 @Override()
2036 public int getLastMessageID()
2037 {
2038 return messageID;
2039 }
2040
2041
2042
2043 /**
2044 * {@inheritDoc}
2045 */
2046 @Override()
2047 public OperationType getOperationType()
2048 {
2049 return OperationType.SEARCH;
2050 }
2051
2052
2053
2054 /**
2055 * {@inheritDoc}
2056 */
2057 public SearchRequest duplicate()
2058 {
2059 return duplicate(getControls());
2060 }
2061
2062
2063
2064 /**
2065 * {@inheritDoc}
2066 */
2067 public SearchRequest duplicate(final Control[] controls)
2068 {
2069 final SearchRequest r = new SearchRequest(searchResultListener, controls,
2070 baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter,
2071 attributes);
2072 if (followReferralsInternal() != null)
2073 {
2074 r.setFollowReferrals(followReferralsInternal());
2075 }
2076
2077 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
2078
2079 return r;
2080 }
2081
2082
2083
2084 /**
2085 * {@inheritDoc}
2086 */
2087 @Override()
2088 public void toString(final StringBuilder buffer)
2089 {
2090 buffer.append("SearchRequest(baseDN='");
2091 buffer.append(baseDN);
2092 buffer.append("', scope=");
2093 buffer.append(scope);
2094 buffer.append(", deref=");
2095 buffer.append(derefPolicy);
2096 buffer.append(", sizeLimit=");
2097 buffer.append(sizeLimit);
2098 buffer.append(", timeLimit=");
2099 buffer.append(timeLimit);
2100 buffer.append(", filter='");
2101 buffer.append(filter);
2102 buffer.append("', attrs={");
2103
2104 for (int i=0; i < attributes.length; i++)
2105 {
2106 if (i > 0)
2107 {
2108 buffer.append(", ");
2109 }
2110
2111 buffer.append(attributes[i]);
2112 }
2113 buffer.append('}');
2114
2115 final Control[] controls = getControls();
2116 if (controls.length > 0)
2117 {
2118 buffer.append(", controls={");
2119 for (int i=0; i < controls.length; i++)
2120 {
2121 if (i > 0)
2122 {
2123 buffer.append(", ");
2124 }
2125
2126 buffer.append(controls[i]);
2127 }
2128 buffer.append('}');
2129 }
2130
2131 buffer.append(')');
2132 }
2133
2134
2135
2136 /**
2137 * {@inheritDoc}
2138 */
2139 public void toCode(final List<String> lineList, final String requestID,
2140 final int indentSpaces, final boolean includeProcessing)
2141 {
2142 // Create the request variable.
2143 final ArrayList<ToCodeArgHelper> constructorArgs =
2144 new ArrayList<ToCodeArgHelper>(10);
2145 constructorArgs.add(ToCodeArgHelper.createString(baseDN, "Base DN"));
2146 constructorArgs.add(ToCodeArgHelper.createScope(scope, "Scope"));
2147 constructorArgs.add(ToCodeArgHelper.createDerefPolicy(derefPolicy,
2148 "Alias Dereference Policy"));
2149 constructorArgs.add(ToCodeArgHelper.createInteger(sizeLimit, "Size Limit"));
2150 constructorArgs.add(ToCodeArgHelper.createInteger(timeLimit, "Time Limit"));
2151 constructorArgs.add(ToCodeArgHelper.createBoolean(typesOnly, "Types Only"));
2152 constructorArgs.add(ToCodeArgHelper.createFilter(filter, "Filter"));
2153
2154 String comment = "Requested Attributes";
2155 for (final String s : attributes)
2156 {
2157 constructorArgs.add(ToCodeArgHelper.createString(s, comment));
2158 comment = null;
2159 }
2160
2161 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "SearchRequest",
2162 requestID + "Request", "new SearchRequest", constructorArgs);
2163
2164
2165 // If there are any controls, then add them to the request.
2166 for (final Control c : getControls())
2167 {
2168 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
2169 requestID + "Request.addControl",
2170 ToCodeArgHelper.createControl(c, null));
2171 }
2172
2173
2174 // Add lines for processing the request and obtaining the result.
2175 if (includeProcessing)
2176 {
2177 // Generate a string with the appropriate indent.
2178 final StringBuilder buffer = new StringBuilder();
2179 for (int i=0; i < indentSpaces; i++)
2180 {
2181 buffer.append(' ');
2182 }
2183 final String indent = buffer.toString();
2184
2185 lineList.add("");
2186 lineList.add(indent + "SearchResult " + requestID + "Result;");
2187 lineList.add(indent + "try");
2188 lineList.add(indent + '{');
2189 lineList.add(indent + " " + requestID + "Result = connection.search(" +
2190 requestID + "Request);");
2191 lineList.add(indent + " // The search was processed successfully.");
2192 lineList.add(indent + '}');
2193 lineList.add(indent + "catch (LDAPSearchException e)");
2194 lineList.add(indent + '{');
2195 lineList.add(indent + " // The search failed. Maybe the following " +
2196 "will help explain why.");
2197 lineList.add(indent + " ResultCode resultCode = e.getResultCode();");
2198 lineList.add(indent + " String message = e.getMessage();");
2199 lineList.add(indent + " String matchedDN = e.getMatchedDN();");
2200 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();");
2201 lineList.add(indent + " Control[] responseControls = " +
2202 "e.getResponseControls();");
2203 lineList.add("");
2204 lineList.add(indent + " // Even though there was an error, we may " +
2205 "have gotten some results.");
2206 lineList.add(indent + " " + requestID + "Result = e.getSearchResult();");
2207 lineList.add(indent + '}');
2208 lineList.add("");
2209 lineList.add(indent + "// If there were results, then process them.");
2210 lineList.add(indent + "for (SearchResultEntry e : " + requestID +
2211 "Result.getSearchEntries())");
2212 lineList.add(indent + '{');
2213 lineList.add(indent + " // Do something with the entry.");
2214 lineList.add(indent + '}');
2215 }
2216 }
2217 }