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