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