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
046 import static com.unboundid.ldap.sdk.LDAPMessages.*;
047 import static com.unboundid.util.Debug.*;
048 import static com.unboundid.util.StaticUtils.*;
049 import static com.unboundid.util.Validator.*;
050
051
052
053 /**
054 * This class implements the processing necessary to perform an LDAPv3 search
055 * operation, which can be used to retrieve entries that match a given set of
056 * criteria. A search request may include the following elements:
057 * <UL>
058 * <LI>Base DN -- Specifies the base DN for the search. Only entries at or
059 * below this location in the server (based on the scope) will be
060 * considered potential matches.</LI>
061 * <LI>Scope -- Specifies the range of entries relative to the base DN that
062 * may be considered potential matches.</LI>
063 * <LI>Dereference Policy -- Specifies the behavior that the server should
064 * exhibit if any alias entries are encountered while processing the
065 * search. If no dereference policy is provided, then a default of
066 * {@code DereferencePolicy.NEVER} will be used.</LI>
067 * <LI>Size Limit -- Specifies the maximum number of entries that should be
068 * returned from the search. A value of zero indicates that there should
069 * not be any limit enforced. Note that the directory server may also
070 * be configured with a server-side size limit which can also limit the
071 * number of entries that may be returned to the client and in that case
072 * the smaller of the client-side and server-side limits will be
073 * used. If no size limit is provided, then a default of zero (unlimited)
074 * will be used.</LI>
075 * <LI>Time Limit -- Specifies the maximum length of time in seconds that the
076 * server should spend processing the search. A value of zero indicates
077 * that there should not be any limit enforced. Note that the directory
078 * server may also be configured with a server-side time limit which can
079 * also limit the processing time, and in that case the smaller of the
080 * client-side and server-side limits will be used. If no time limit is
081 * provided, then a default of zero (unlimited) will be used.</LI>
082 * <LI>Types Only -- Indicates whether matching entries should include only
083 * attribute names, or both attribute names and values. If no value is
084 * provided, then a default of {@code false} will be used.</LI>
085 * <LI>Filter -- Specifies the criteria for determining which entries should
086 * be returned. See the {@code Filter} class for the types of filters
087 * that may be used.
088 * <BR><BR>
089 * Note that filters can be specified using either their string
090 * representations or as {@code Filter} objects. As noted in the
091 * documentation for the {@code Filter} class, using the string
092 * representation may be somewhat dangerous if the data is not properly
093 * sanitized because special characters contained in the filter may cause
094 * it to be invalid or worse expose a vulnerability that could cause the
095 * filter to request more information than was intended. As a result, if
096 * the filter may include special characters or user-provided strings,
097 * then it is recommended that you use {@code Filter} objects created from
098 * their individual components rather than their string representations.
099 * </LI>
100 * <LI>Attributes -- Specifies the set of attributes that should be included
101 * in matching entries. If no attributes are provided, then the server
102 * will default to returning all user attributes. If a specified set of
103 * attributes is given, then only those attributes will be included.
104 * Values that may be included to indicate a special meaning include:
105 * <UL>
106 * <LI>{@code NO_ATTRIBUTES} -- Indicates that no attributes should be
107 * returned. That is, only the DNs of matching entries will be
108 * returned.</LI>
109 * <LI>{@code ALL_USER_ATTRIBUTES} -- Indicates that all user attributes
110 * should be included in matching entries. This is the default if
111 * no attributes are provided, but this special value may be
112 * included if a specific set of operational attributes should be
113 * included along with all user attributes.</LI>
114 * <LI>{@code ALL_OPERATIONAL_ATTRIBUTES} -- Indicates that all
115 * operational attributes should be included in matching
116 * entries.</LI>
117 * </UL>
118 * These special values may be used alone or in conjunction with each
119 * other and/or any specific attribute names or OIDs.</LI>
120 * <LI>An optional set of controls to include in the request to send to the
121 * server.</LI>
122 * <LI>An optional {@code SearchResultListener} which may be used to process
123 * search result entries and search result references returned by the
124 * server in the course of processing the request. If this is
125 * {@code null}, then the entries and references will be collected and
126 * returned in the {@code SearchResult} object that is returned.</LI>
127 * </UL>
128 * When processing a search operation, there are three ways that the returned
129 * entries and references may be accessed:
130 * <UL>
131 * <LI>If the {@code LDAPInterface#search(SearchRequest)} method is used and
132 * the provided search request does not include a
133 * {@code SearchResultListener} object, then the entries and references
134 * will be collected internally and made available in the
135 * {@code SearchResult} object that is returned.</LI>
136 * <LI>If the {@code LDAPInterface#search(SearchRequest)} method is used and
137 * the provided search request does include a {@code SearchResultListener}
138 * object, then that listener will be used to provide access to the
139 * entries and references, and they will not be present in the
140 * {@code SearchResult} object (although the number of entries and
141 * references returned will still be available).</LI>
142 * <LI>The {@code LDAPEntrySource} object may be used to access the entries
143 * and references returned from the search. It uses an
144 * {@code Iterator}-like API to provide access to the entries that are
145 * returned, and any references returned will be included in the
146 * {@code EntrySourceException} thrown on the appropriate call to
147 * {@code LDAPEntrySource#nextEntry()}.</LI>
148 * </UL>
149 * <BR><BR>
150 * {@code SearchRequest} objects are mutable and therefore can be altered and
151 * re-used for multiple requests. Note, however, that {@code SearchRequest}
152 * objects are not threadsafe and therefore a single {@code SearchRequest}
153 * object instance should not be used to process multiple requests at the same
154 * time.
155 * <BR><BR>
156 * <H2>Example</H2>
157 * The following example demonstrates a simple search operation in which the
158 * client performs a search to find all users in the "Sales" department and then
159 * retrieves the name and e-mail address for each matching user:
160 * <PRE>
161 * // Construct a filter that can be used to find everyone in the Sales
162 * // department, and then create a search request to find all such users
163 * // in the directory.
164 * Filter filter = Filter.createEqualityFilter("ou", "Sales");
165 * SearchRequest searchRequest =
166 * new SearchRequest("dc=example,dc=com", SearchScope.SUB, filter,
167 * "cn", "mail");
168 * SearchResult searchResult;
169 *
170 * try
171 * {
172 * 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 * }
179 * }
180 * catch (LDAPSearchException lse)
181 * {
182 * // The search failed for some reason.
183 * searchResult = lse.getSearchResult();
184 * ResultCode resultCode = lse.getResultCode();
185 * String errorMessageFromServer = lse.getDiagnosticMessage();
186 * }
187 * </PRE>
188 */
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 {@code 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 {@code 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 @SuppressWarnings("deprecation")
1124 final boolean autoReconnect =
1125 connection.getConnectionOptions().autoReconnect();
1126 return processSync(connection, depth, autoReconnect);
1127 }
1128
1129 final long requestTime = System.nanoTime();
1130 processAsync(connection, null);
1131
1132 try
1133 {
1134 // Wait for and process the response.
1135 final ArrayList<SearchResultEntry> entryList;
1136 final ArrayList<SearchResultReference> referenceList;
1137 if (searchResultListener == null)
1138 {
1139 entryList = new ArrayList<SearchResultEntry>(5);
1140 referenceList = new ArrayList<SearchResultReference>(5);
1141 }
1142 else
1143 {
1144 entryList = null;
1145 referenceList = null;
1146 }
1147
1148 int numEntries = 0;
1149 int numReferences = 0;
1150 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1151 final long responseTimeout = getResponseTimeoutMillis(connection);
1152 while (true)
1153 {
1154 final LDAPResponse response;
1155 try
1156 {
1157 if (responseTimeout > 0)
1158 {
1159 response =
1160 responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
1161 }
1162 else
1163 {
1164 response = responseQueue.take();
1165 }
1166 }
1167 catch (InterruptedException ie)
1168 {
1169 debugException(ie);
1170 throw new LDAPException(ResultCode.LOCAL_ERROR,
1171 ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie);
1172 }
1173
1174 if (response == null)
1175 {
1176 if (connection.getConnectionOptions().abandonOnTimeout())
1177 {
1178 connection.abandon(messageID);
1179 }
1180
1181 final SearchResult searchResult =
1182 new SearchResult(messageID, ResultCode.TIMEOUT,
1183 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID,
1184 baseDN, scope.getName(), filter.toString(),
1185 connection.getHostPort()),
1186 null, null, entryList, referenceList, numEntries,
1187 numReferences, null);
1188 throw new LDAPSearchException(searchResult);
1189 }
1190
1191 if (response instanceof ConnectionClosedResponse)
1192 {
1193 final ConnectionClosedResponse ccr =
1194 (ConnectionClosedResponse) response;
1195 final String message = ccr.getMessage();
1196 if (message == null)
1197 {
1198 // The connection was closed while waiting for the response.
1199 final SearchResult searchResult =
1200 new SearchResult(messageID, ccr.getResultCode(),
1201 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1202 connection.getHostPort(), toString()),
1203 null, null, entryList, referenceList, numEntries,
1204 numReferences, null);
1205 throw new LDAPSearchException(searchResult);
1206 }
1207 else
1208 {
1209 // The connection was closed while waiting for the response.
1210 final SearchResult searchResult =
1211 new SearchResult(messageID, ccr.getResultCode(),
1212 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1213 get(connection.getHostPort(), toString(), message),
1214 null, null, entryList, referenceList, numEntries,
1215 numReferences, null);
1216 throw new LDAPSearchException(searchResult);
1217 }
1218 }
1219 else if (response instanceof SearchResultEntry)
1220 {
1221 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1222 numEntries++;
1223 if (searchResultListener == null)
1224 {
1225 entryList.add(searchEntry);
1226 }
1227 else
1228 {
1229 searchResultListener.searchEntryReturned(searchEntry);
1230 }
1231 }
1232 else if (response instanceof SearchResultReference)
1233 {
1234 final SearchResultReference searchReference =
1235 (SearchResultReference) response;
1236 if (followReferrals(connection))
1237 {
1238 final LDAPResult result = followSearchReference(messageID,
1239 searchReference, connection, depth);
1240 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1241 {
1242 // We couldn't follow the reference. We don't want to fail the
1243 // entire search because of this right now, so treat it as if
1244 // referral following had not been enabled. Also, set the
1245 // intermediate result code to match that of the result.
1246 numReferences++;
1247 if (searchResultListener == null)
1248 {
1249 referenceList.add(searchReference);
1250 }
1251 else
1252 {
1253 searchResultListener.searchReferenceReturned(searchReference);
1254 }
1255
1256 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1257 {
1258 intermediateResultCode = result.getResultCode();
1259 }
1260 }
1261 else if (result instanceof SearchResult)
1262 {
1263 final SearchResult searchResult = (SearchResult) result;
1264 numEntries += searchResult.getEntryCount();
1265 if (searchResultListener == null)
1266 {
1267 entryList.addAll(searchResult.getSearchEntries());
1268 }
1269 }
1270 }
1271 else
1272 {
1273 numReferences++;
1274 if (searchResultListener == null)
1275 {
1276 referenceList.add(searchReference);
1277 }
1278 else
1279 {
1280 searchResultListener.searchReferenceReturned(searchReference);
1281 }
1282 }
1283 }
1284 else
1285 {
1286 connection.getConnectionStatistics().incrementNumSearchResponses(
1287 numEntries, numReferences,
1288 (System.nanoTime() - requestTime));
1289 SearchResult result = (SearchResult) response;
1290 result.setCounts(numEntries, entryList, numReferences, referenceList);
1291
1292 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1293 followReferrals(connection))
1294 {
1295 if (depth >=
1296 connection.getConnectionOptions().getReferralHopLimit())
1297 {
1298 return new SearchResult(messageID,
1299 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1300 ERR_TOO_MANY_REFERRALS.get(),
1301 result.getMatchedDN(),
1302 result.getReferralURLs(), entryList,
1303 referenceList, numEntries,
1304 numReferences,
1305 result.getResponseControls());
1306 }
1307
1308 result = followReferral(result, connection, depth);
1309 }
1310
1311 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1312 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1313 {
1314 return new SearchResult(messageID, intermediateResultCode,
1315 result.getDiagnosticMessage(),
1316 result.getMatchedDN(),
1317 result.getReferralURLs(),
1318 entryList, referenceList, numEntries,
1319 numReferences,
1320 result.getResponseControls());
1321 }
1322
1323 return result;
1324 }
1325 }
1326 }
1327 finally
1328 {
1329 connection.deregisterResponseAcceptor(messageID);
1330 }
1331 }
1332
1333
1334
1335 /**
1336 * Sends this search request to the directory server over the provided
1337 * connection and returns the message ID for the request.
1338 *
1339 * @param connection The connection to use to communicate with the
1340 * directory server.
1341 * @param resultListener The async result listener that is to be notified
1342 * when the response is received. It may be
1343 * {@code null} only if the result is to be processed
1344 * by this class.
1345 *
1346 * @return The async request ID created for the operation, or {@code null} if
1347 * the provided {@code resultListener} is {@code null} and the
1348 * operation will not actually be processed asynchronously.
1349 *
1350 * @throws LDAPException If a problem occurs while sending the request.
1351 */
1352 AsyncRequestID processAsync(final LDAPConnection connection,
1353 final AsyncSearchResultListener resultListener)
1354 throws LDAPException
1355 {
1356 // Create the LDAP message.
1357 messageID = connection.nextMessageID();
1358 final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
1359
1360
1361 // If the provided async result listener is {@code null}, then we'll use
1362 // this class as the message acceptor. Otherwise, create an async helper
1363 // and use it as the message acceptor.
1364 final AsyncRequestID asyncRequestID;
1365 if (resultListener == null)
1366 {
1367 asyncRequestID = null;
1368 connection.registerResponseAcceptor(messageID, this);
1369 }
1370 else
1371 {
1372 final AsyncSearchHelper helper = new AsyncSearchHelper(connection,
1373 messageID, resultListener, getIntermediateResponseListener());
1374 connection.registerResponseAcceptor(messageID, helper);
1375 asyncRequestID = helper.getAsyncRequestID();
1376
1377 final long timeout = getResponseTimeoutMillis(connection);
1378 if (timeout > 0L)
1379 {
1380 final Timer timer = connection.getTimer();
1381 final AsyncTimeoutTimerTask timerTask =
1382 new AsyncTimeoutTimerTask(helper);
1383 timer.schedule(timerTask, timeout);
1384 asyncRequestID.setTimerTask(timerTask);
1385 }
1386 }
1387
1388
1389 // Send the request to the server.
1390 try
1391 {
1392 debugLDAPRequest(this);
1393 connection.getConnectionStatistics().incrementNumSearchRequests();
1394 connection.sendMessage(message);
1395 return asyncRequestID;
1396 }
1397 catch (LDAPException le)
1398 {
1399 debugException(le);
1400
1401 connection.deregisterResponseAcceptor(messageID);
1402 throw le;
1403 }
1404 }
1405
1406
1407
1408 /**
1409 * Processes this search operation in synchronous mode, in which the same
1410 * thread will send the request and read the response.
1411 *
1412 * @param connection The connection to use to communicate with the directory
1413 * server.
1414 * @param depth The current referral depth for this request. It should
1415 * always be one for the initial request, and should only
1416 * be incremented when following referrals.
1417 * @param allowRetry Indicates whether the request may be re-tried on a
1418 * re-established connection if the initial attempt fails
1419 * in a way that indicates the connection is no longer
1420 * valid and autoReconnect is true.
1421 *
1422 * @return An LDAP result object that provides information about the result
1423 * of the search processing.
1424 *
1425 * @throws LDAPException If a problem occurs while sending the request or
1426 * reading the response.
1427 */
1428 private SearchResult processSync(final LDAPConnection connection,
1429 final int depth, final boolean allowRetry)
1430 throws LDAPException
1431 {
1432 // Create the LDAP message.
1433 messageID = connection.nextMessageID();
1434 final LDAPMessage message =
1435 new LDAPMessage(messageID, this, getControls());
1436
1437
1438 // Set the appropriate timeout on the socket.
1439 final long responseTimeout = getResponseTimeoutMillis(connection);
1440 try
1441 {
1442 connection.getConnectionInternals(true).getSocket().setSoTimeout(
1443 (int) responseTimeout);
1444 }
1445 catch (Exception e)
1446 {
1447 debugException(e);
1448 }
1449
1450
1451 // Send the request to the server.
1452 final long requestTime = System.nanoTime();
1453 debugLDAPRequest(this);
1454 connection.getConnectionStatistics().incrementNumSearchRequests();
1455 try
1456 {
1457 connection.sendMessage(message);
1458 }
1459 catch (final LDAPException le)
1460 {
1461 debugException(le);
1462
1463 if (allowRetry)
1464 {
1465 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1466 le.getResultCode(), 0, 0);
1467 if (retryResult != null)
1468 {
1469 return retryResult;
1470 }
1471 }
1472
1473 throw le;
1474 }
1475
1476 final ArrayList<SearchResultEntry> entryList;
1477 final ArrayList<SearchResultReference> referenceList;
1478 if (searchResultListener == null)
1479 {
1480 entryList = new ArrayList<SearchResultEntry>(5);
1481 referenceList = new ArrayList<SearchResultReference>(5);
1482 }
1483 else
1484 {
1485 entryList = null;
1486 referenceList = null;
1487 }
1488
1489 int numEntries = 0;
1490 int numReferences = 0;
1491 ResultCode intermediateResultCode = ResultCode.SUCCESS;
1492 while (true)
1493 {
1494 final LDAPResponse response;
1495 try
1496 {
1497 response = connection.readResponse(messageID);
1498 }
1499 catch (final LDAPException le)
1500 {
1501 debugException(le);
1502
1503 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1504 connection.getConnectionOptions().abandonOnTimeout())
1505 {
1506 connection.abandon(messageID);
1507 }
1508
1509 if (allowRetry)
1510 {
1511 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1512 le.getResultCode(), numEntries, numReferences);
1513 if (retryResult != null)
1514 {
1515 return retryResult;
1516 }
1517 }
1518
1519 throw le;
1520 }
1521
1522 if (response == null)
1523 {
1524 if (connection.getConnectionOptions().abandonOnTimeout())
1525 {
1526 connection.abandon(messageID);
1527 }
1528
1529 throw new LDAPException(ResultCode.TIMEOUT,
1530 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID, baseDN,
1531 scope.getName(), filter.toString(),
1532 connection.getHostPort()));
1533 }
1534 else if (response instanceof ConnectionClosedResponse)
1535 {
1536
1537 if (allowRetry)
1538 {
1539 final SearchResult retryResult = reconnectAndRetry(connection, depth,
1540 ResultCode.SERVER_DOWN, numEntries, numReferences);
1541 if (retryResult != null)
1542 {
1543 return retryResult;
1544 }
1545 }
1546
1547 final ConnectionClosedResponse ccr =
1548 (ConnectionClosedResponse) response;
1549 final String msg = ccr.getMessage();
1550 if (msg == null)
1551 {
1552 // The connection was closed while waiting for the response.
1553 final SearchResult searchResult =
1554 new SearchResult(messageID, ccr.getResultCode(),
1555 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1556 connection.getHostPort(), toString()),
1557 null, null, entryList, referenceList, numEntries,
1558 numReferences, null);
1559 throw new LDAPSearchException(searchResult);
1560 }
1561 else
1562 {
1563 // The connection was closed while waiting for the response.
1564 final SearchResult searchResult =
1565 new SearchResult(messageID, ccr.getResultCode(),
1566 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1567 get(connection.getHostPort(), toString(), msg),
1568 null, null, entryList, referenceList, numEntries,
1569 numReferences, null);
1570 throw new LDAPSearchException(searchResult);
1571 }
1572 }
1573 else if (response instanceof IntermediateResponse)
1574 {
1575 final IntermediateResponseListener listener =
1576 getIntermediateResponseListener();
1577 if (listener != null)
1578 {
1579 listener.intermediateResponseReturned(
1580 (IntermediateResponse) response);
1581 }
1582 }
1583 else if (response instanceof SearchResultEntry)
1584 {
1585 final SearchResultEntry searchEntry = (SearchResultEntry) response;
1586 numEntries++;
1587 if (searchResultListener == null)
1588 {
1589 entryList.add(searchEntry);
1590 }
1591 else
1592 {
1593 searchResultListener.searchEntryReturned(searchEntry);
1594 }
1595 }
1596 else if (response instanceof SearchResultReference)
1597 {
1598 final SearchResultReference searchReference =
1599 (SearchResultReference) response;
1600 if (followReferrals(connection))
1601 {
1602 final LDAPResult result = followSearchReference(messageID,
1603 searchReference, connection, depth);
1604 if (! result.getResultCode().equals(ResultCode.SUCCESS))
1605 {
1606 // We couldn't follow the reference. We don't want to fail the
1607 // entire search because of this right now, so treat it as if
1608 // referral following had not been enabled. Also, set the
1609 // intermediate result code to match that of the result.
1610 numReferences++;
1611 if (searchResultListener == null)
1612 {
1613 referenceList.add(searchReference);
1614 }
1615 else
1616 {
1617 searchResultListener.searchReferenceReturned(searchReference);
1618 }
1619
1620 if (intermediateResultCode.equals(ResultCode.SUCCESS))
1621 {
1622 intermediateResultCode = result.getResultCode();
1623 }
1624 }
1625 else if (result instanceof SearchResult)
1626 {
1627 final SearchResult searchResult = (SearchResult) result;
1628 numEntries += searchResult.getEntryCount();
1629 if (searchResultListener == null)
1630 {
1631 entryList.addAll(searchResult.getSearchEntries());
1632 }
1633 }
1634 }
1635 else
1636 {
1637 numReferences++;
1638 if (searchResultListener == null)
1639 {
1640 referenceList.add(searchReference);
1641 }
1642 else
1643 {
1644 searchResultListener.searchReferenceReturned(searchReference);
1645 }
1646 }
1647 }
1648 else
1649 {
1650 final SearchResult result = (SearchResult) response;
1651 if (allowRetry)
1652 {
1653 final SearchResult retryResult = reconnectAndRetry(connection,
1654 depth, result.getResultCode(), numEntries, numReferences);
1655 if (retryResult != null)
1656 {
1657 return retryResult;
1658 }
1659 }
1660
1661 return handleResponse(connection, response, requestTime, depth,
1662 numEntries, numReferences, entryList,
1663 referenceList, intermediateResultCode);
1664 }
1665 }
1666 }
1667
1668
1669
1670 /**
1671 * Attempts to re-establish the connection and retry processing this request
1672 * on it.
1673 *
1674 * @param connection The connection to be re-established.
1675 * @param depth The current referral depth for this request. It
1676 * should always be one for the initial request, and
1677 * should only be incremented when following referrals.
1678 * @param resultCode The result code for the previous operation attempt.
1679 * @param numEntries The number of search result entries already sent for
1680 * the search operation.
1681 * @param numReferences The number of search result references already sent
1682 * for the search operation.
1683 *
1684 * @return The result from re-trying the search, or {@code null} if it could
1685 * not be re-tried.
1686 */
1687 private SearchResult reconnectAndRetry(final LDAPConnection connection,
1688 final int depth,
1689 final ResultCode resultCode,
1690 final int numEntries,
1691 final int numReferences)
1692 {
1693 try
1694 {
1695 // We will only want to retry for certain result codes that indicate a
1696 // connection problem.
1697 switch (resultCode.intValue())
1698 {
1699 case ResultCode.SERVER_DOWN_INT_VALUE:
1700 case ResultCode.DECODING_ERROR_INT_VALUE:
1701 case ResultCode.CONNECT_ERROR_INT_VALUE:
1702 // We want to try to re-establish the connection no matter what, but
1703 // we only want to retry the search if we haven't yet sent any
1704 // results.
1705 connection.reconnect();
1706 if ((numEntries == 0) && (numReferences == 0))
1707 {
1708 return processSync(connection, depth, false);
1709 }
1710 break;
1711 }
1712 }
1713 catch (final Exception e)
1714 {
1715 debugException(e);
1716 }
1717
1718 return null;
1719 }
1720
1721
1722
1723 /**
1724 * Performs the necessary processing for handling a response.
1725 *
1726 * @param connection The connection used to read the response.
1727 * @param response The response to be processed.
1728 * @param requestTime The time the request was sent to the
1729 * server.
1730 * @param depth The current referral depth for this
1731 * request. It should always be one for the
1732 * initial request, and should only be
1733 * incremented when following referrals.
1734 * @param numEntries The number of entries received from the
1735 * server.
1736 * @param numReferences The number of references received from
1737 * the server.
1738 * @param entryList The list of search result entries received
1739 * from the server, if applicable.
1740 * @param referenceList The list of search result references
1741 * received from the server, if applicable.
1742 * @param intermediateResultCode The intermediate result code so far for the
1743 * search operation.
1744 *
1745 * @return The search result.
1746 *
1747 * @throws LDAPException If a problem occurs.
1748 */
1749 private SearchResult handleResponse(final LDAPConnection connection,
1750 final LDAPResponse response, final long requestTime,
1751 final int depth, final int numEntries, final int numReferences,
1752 final List<SearchResultEntry> entryList,
1753 final List<SearchResultReference> referenceList,
1754 final ResultCode intermediateResultCode)
1755 throws LDAPException
1756 {
1757 connection.getConnectionStatistics().incrementNumSearchResponses(
1758 numEntries, numReferences,
1759 (System.nanoTime() - requestTime));
1760 SearchResult result = (SearchResult) response;
1761 result.setCounts(numEntries, entryList, numReferences, referenceList);
1762
1763 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1764 followReferrals(connection))
1765 {
1766 if (depth >=
1767 connection.getConnectionOptions().getReferralHopLimit())
1768 {
1769 return new SearchResult(messageID,
1770 ResultCode.REFERRAL_LIMIT_EXCEEDED,
1771 ERR_TOO_MANY_REFERRALS.get(),
1772 result.getMatchedDN(),
1773 result.getReferralURLs(), entryList,
1774 referenceList, numEntries,
1775 numReferences,
1776 result.getResponseControls());
1777 }
1778
1779 result = followReferral(result, connection, depth);
1780 }
1781
1782 if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1783 (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1784 {
1785 return new SearchResult(messageID, intermediateResultCode,
1786 result.getDiagnosticMessage(),
1787 result.getMatchedDN(),
1788 result.getReferralURLs(),
1789 entryList, referenceList, numEntries,
1790 numReferences,
1791 result.getResponseControls());
1792 }
1793
1794 return result;
1795 }
1796
1797
1798
1799 /**
1800 * Attempts to follow a search result reference to continue a search in a
1801 * remote server.
1802 *
1803 * @param messageID The message ID for the LDAP message that is
1804 * associated with this result.
1805 * @param searchReference The search result reference to follow.
1806 * @param connection The connection on which the reference was
1807 * received.
1808 * @param depth The number of referrals followed in the course of
1809 * processing this request.
1810 *
1811 * @return The result of attempting to follow the search result reference.
1812 *
1813 * @throws LDAPException If a problem occurs while attempting to establish
1814 * the referral connection, sending the request, or
1815 * reading the result.
1816 */
1817 private LDAPResult followSearchReference(final int messageID,
1818 final SearchResultReference searchReference,
1819 final LDAPConnection connection, final int depth)
1820 throws LDAPException
1821 {
1822 for (final String urlString : searchReference.getReferralURLs())
1823 {
1824 try
1825 {
1826 final LDAPURL referralURL = new LDAPURL(urlString);
1827 final String host = referralURL.getHost();
1828
1829 if (host == null)
1830 {
1831 // We can't handle a referral in which there is no host.
1832 continue;
1833 }
1834
1835 final String requestBaseDN;
1836 if (referralURL.baseDNProvided())
1837 {
1838 requestBaseDN = referralURL.getBaseDN().toString();
1839 }
1840 else
1841 {
1842 requestBaseDN = baseDN;
1843 }
1844
1845 final SearchScope requestScope;
1846 if (referralURL.scopeProvided())
1847 {
1848 requestScope = referralURL.getScope();
1849 }
1850 else
1851 {
1852 requestScope = scope;
1853 }
1854
1855 final Filter requestFilter;
1856 if (referralURL.filterProvided())
1857 {
1858 requestFilter = referralURL.getFilter();
1859 }
1860 else
1861 {
1862 requestFilter = filter;
1863 }
1864
1865
1866 final SearchRequest searchRequest =
1867 new SearchRequest(searchResultListener, getControls(),
1868 requestBaseDN, requestScope, derefPolicy,
1869 sizeLimit, timeLimit, typesOnly, requestFilter,
1870 attributes);
1871
1872 final LDAPConnection referralConn = connection.getReferralConnector().
1873 getReferralConnection(referralURL, connection);
1874
1875 try
1876 {
1877 return searchRequest.process(referralConn, depth+1);
1878 }
1879 finally
1880 {
1881 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1882 referralConn.close();
1883 }
1884 }
1885 catch (LDAPException le)
1886 {
1887 debugException(le);
1888
1889 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1890 {
1891 throw le;
1892 }
1893 }
1894 }
1895
1896 // If we've gotten here, then we could not follow any of the referral URLs,
1897 // so we'll create a failure result.
1898 return new SearchResult(messageID, ResultCode.REFERRAL, null, null,
1899 searchReference.getReferralURLs(), 0, 0, null);
1900 }
1901
1902
1903
1904 /**
1905 * Attempts to follow a referral to perform an add operation in the target
1906 * server.
1907 *
1908 * @param referralResult The LDAP result object containing information about
1909 * the referral to follow.
1910 * @param connection The connection on which the referral was received.
1911 * @param depth The number of referrals followed in the course of
1912 * processing this request.
1913 *
1914 * @return The result of attempting to process the add operation by following
1915 * the referral.
1916 *
1917 * @throws LDAPException If a problem occurs while attempting to establish
1918 * the referral connection, sending the request, or
1919 * reading the result.
1920 */
1921 private SearchResult followReferral(final SearchResult referralResult,
1922 final LDAPConnection connection,
1923 final int depth)
1924 throws LDAPException
1925 {
1926 for (final String urlString : referralResult.getReferralURLs())
1927 {
1928 try
1929 {
1930 final LDAPURL referralURL = new LDAPURL(urlString);
1931 final String host = referralURL.getHost();
1932
1933 if (host == null)
1934 {
1935 // We can't handle a referral in which there is no host.
1936 continue;
1937 }
1938
1939 final String requestBaseDN;
1940 if (referralURL.baseDNProvided())
1941 {
1942 requestBaseDN = referralURL.getBaseDN().toString();
1943 }
1944 else
1945 {
1946 requestBaseDN = baseDN;
1947 }
1948
1949 final SearchScope requestScope;
1950 if (referralURL.scopeProvided())
1951 {
1952 requestScope = referralURL.getScope();
1953 }
1954 else
1955 {
1956 requestScope = scope;
1957 }
1958
1959 final Filter requestFilter;
1960 if (referralURL.filterProvided())
1961 {
1962 requestFilter = referralURL.getFilter();
1963 }
1964 else
1965 {
1966 requestFilter = filter;
1967 }
1968
1969
1970 final SearchRequest searchRequest =
1971 new SearchRequest(searchResultListener, getControls(),
1972 requestBaseDN, requestScope, derefPolicy,
1973 sizeLimit, timeLimit, typesOnly, requestFilter,
1974 attributes);
1975
1976 final LDAPConnection referralConn = connection.getReferralConnector().
1977 getReferralConnection(referralURL, connection);
1978 try
1979 {
1980 return searchRequest.process(referralConn, depth+1);
1981 }
1982 finally
1983 {
1984 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1985 referralConn.close();
1986 }
1987 }
1988 catch (LDAPException le)
1989 {
1990 debugException(le);
1991
1992 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1993 {
1994 throw le;
1995 }
1996 }
1997 }
1998
1999 // If we've gotten here, then we could not follow any of the referral URLs,
2000 // so we'll just return the original referral result.
2001 return referralResult;
2002 }
2003
2004
2005
2006 /**
2007 * {@inheritDoc}
2008 */
2009 @InternalUseOnly()
2010 public void responseReceived(final LDAPResponse response)
2011 throws LDAPException
2012 {
2013 try
2014 {
2015 responseQueue.put(response);
2016 }
2017 catch (Exception e)
2018 {
2019 debugException(e);
2020 throw new LDAPException(ResultCode.LOCAL_ERROR,
2021 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
2022 }
2023 }
2024
2025
2026
2027 /**
2028 * {@inheritDoc}
2029 */
2030 @Override()
2031 public int getLastMessageID()
2032 {
2033 return messageID;
2034 }
2035
2036
2037
2038 /**
2039 * {@inheritDoc}
2040 */
2041 @Override()
2042 public OperationType getOperationType()
2043 {
2044 return OperationType.SEARCH;
2045 }
2046
2047
2048
2049 /**
2050 * {@inheritDoc}
2051 */
2052 public SearchRequest duplicate()
2053 {
2054 return duplicate(getControls());
2055 }
2056
2057
2058
2059 /**
2060 * {@inheritDoc}
2061 */
2062 public SearchRequest duplicate(final Control[] controls)
2063 {
2064 final SearchRequest r = new SearchRequest(searchResultListener, controls,
2065 baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter,
2066 attributes);
2067 if (followReferralsInternal() != null)
2068 {
2069 r.setFollowReferrals(followReferralsInternal());
2070 }
2071
2072 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
2073
2074 return r;
2075 }
2076
2077
2078
2079 /**
2080 * {@inheritDoc}
2081 */
2082 @Override()
2083 public void toString(final StringBuilder buffer)
2084 {
2085 buffer.append("SearchRequest(baseDN='");
2086 buffer.append(baseDN);
2087 buffer.append("', scope=");
2088 buffer.append(scope);
2089 buffer.append(", deref=");
2090 buffer.append(derefPolicy);
2091 buffer.append(", sizeLimit=");
2092 buffer.append(sizeLimit);
2093 buffer.append(", timeLimit=");
2094 buffer.append(timeLimit);
2095 buffer.append(", filter='");
2096 buffer.append(filter);
2097 buffer.append("', attrs={");
2098
2099 for (int i=0; i < attributes.length; i++)
2100 {
2101 if (i > 0)
2102 {
2103 buffer.append(", ");
2104 }
2105
2106 buffer.append(attributes[i]);
2107 }
2108 buffer.append('}');
2109
2110 final Control[] controls = getControls();
2111 if (controls.length > 0)
2112 {
2113 buffer.append(", controls={");
2114 for (int i=0; i < controls.length; i++)
2115 {
2116 if (i > 0)
2117 {
2118 buffer.append(", ");
2119 }
2120
2121 buffer.append(controls[i]);
2122 }
2123 buffer.append('}');
2124 }
2125
2126 buffer.append(')');
2127 }
2128
2129
2130
2131 /**
2132 * {@inheritDoc}
2133 */
2134 public void toCode(final List<String> lineList, final String requestID,
2135 final int indentSpaces, final boolean includeProcessing)
2136 {
2137 // Create the request variable.
2138 final ArrayList<ToCodeArgHelper> constructorArgs =
2139 new ArrayList<ToCodeArgHelper>(10);
2140 constructorArgs.add(ToCodeArgHelper.createString(baseDN, "Base DN"));
2141 constructorArgs.add(ToCodeArgHelper.createScope(scope, "Scope"));
2142 constructorArgs.add(ToCodeArgHelper.createDerefPolicy(derefPolicy,
2143 "Alias Dereference Policy"));
2144 constructorArgs.add(ToCodeArgHelper.createInteger(sizeLimit, "Size Limit"));
2145 constructorArgs.add(ToCodeArgHelper.createInteger(timeLimit, "Time Limit"));
2146 constructorArgs.add(ToCodeArgHelper.createBoolean(typesOnly, "Types Only"));
2147 constructorArgs.add(ToCodeArgHelper.createFilter(filter, "Filter"));
2148
2149 String comment = "Requested Attributes";
2150 for (final String s : attributes)
2151 {
2152 constructorArgs.add(ToCodeArgHelper.createString(s, comment));
2153 comment = null;
2154 }
2155
2156 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "SearchRequest",
2157 requestID + "Request", "new SearchRequest", constructorArgs);
2158
2159
2160 // If there are any controls, then add them to the request.
2161 for (final Control c : getControls())
2162 {
2163 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
2164 requestID + "Request.addControl",
2165 ToCodeArgHelper.createControl(c, null));
2166 }
2167
2168
2169 // Add lines for processing the request and obtaining the result.
2170 if (includeProcessing)
2171 {
2172 // Generate a string with the appropriate indent.
2173 final StringBuilder buffer = new StringBuilder();
2174 for (int i=0; i < indentSpaces; i++)
2175 {
2176 buffer.append(' ');
2177 }
2178 final String indent = buffer.toString();
2179
2180 lineList.add("");
2181 lineList.add(indent + "SearchResult " + requestID + "Result;");
2182 lineList.add(indent + "try");
2183 lineList.add(indent + '{');
2184 lineList.add(indent + " " + requestID + "Result = connection.search(" +
2185 requestID + "Request);");
2186 lineList.add(indent + " // The search was processed successfully.");
2187 lineList.add(indent + '}');
2188 lineList.add(indent + "catch (LDAPSearchException e)");
2189 lineList.add(indent + '{');
2190 lineList.add(indent + " // The search failed. Maybe the following " +
2191 "will help explain why.");
2192 lineList.add(indent + " ResultCode resultCode = e.getResultCode();");
2193 lineList.add(indent + " String message = e.getMessage();");
2194 lineList.add(indent + " String matchedDN = e.getMatchedDN();");
2195 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();");
2196 lineList.add(indent + " Control[] responseControls = " +
2197 "e.getResponseControls();");
2198 lineList.add("");
2199 lineList.add(indent + " // Even though there was an error, we may " +
2200 "have gotten some results.");
2201 lineList.add(indent + " " + requestID + "Result = e.getSearchResult();");
2202 lineList.add(indent + '}');
2203 lineList.add("");
2204 lineList.add(indent + "// If there were results, then process them.");
2205 lineList.add(indent + "for (SearchResultEntry e : " + requestID +
2206 "Result.getSearchEntries())");
2207 lineList.add(indent + '{');
2208 lineList.add(indent + " // Do something with the entry.");
2209 lineList.add(indent + '}');
2210 }
2211 }
2212 }