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