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    }