001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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 */
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  public String getBaseDN()
708  {
709    return baseDN;
710  }
711
712
713
714  /**
715   * Specifies the base DN for this search request.
716   *
717   * @param  baseDN  The base DN for this search request.  It must not be
718   *                 {@code null}.
719   */
720  public void setBaseDN(final String baseDN)
721  {
722    ensureNotNull(baseDN);
723
724    this.baseDN = baseDN;
725  }
726
727
728
729  /**
730   * Specifies the base DN for this search request.
731   *
732   * @param  baseDN  The base DN for this search request.  It must not be
733   *                 {@code null}.
734   */
735  public void setBaseDN(final DN baseDN)
736  {
737    ensureNotNull(baseDN);
738
739    this.baseDN = baseDN.toString();
740  }
741
742
743
744  /**
745   * {@inheritDoc}
746   */
747  public SearchScope getScope()
748  {
749    return scope;
750  }
751
752
753
754  /**
755   * Specifies the scope for this search request.
756   *
757   * @param  scope  The scope for this search request.
758   */
759  public void setScope(final SearchScope scope)
760  {
761    this.scope = scope;
762  }
763
764
765
766  /**
767   * {@inheritDoc}
768   */
769  public DereferencePolicy getDereferencePolicy()
770  {
771    return derefPolicy;
772  }
773
774
775
776  /**
777   * Specifies the dereference policy that should be used by the server for any
778   * aliases encountered during search processing.
779   *
780   * @param  derefPolicy  The dereference policy that should be used by the
781   *                      server for any aliases encountered during search
782   *                      processing.
783   */
784  public void setDerefPolicy(final DereferencePolicy derefPolicy)
785  {
786    this.derefPolicy = derefPolicy;
787  }
788
789
790
791  /**
792   * {@inheritDoc}
793   */
794  public int getSizeLimit()
795  {
796    return sizeLimit;
797  }
798
799
800
801  /**
802   * Specifies the maximum number of entries that should be returned by the
803   * server when processing this search request.  A value of zero indicates that
804   * there should be no limit.
805   * <BR><BR>
806   * Note that if an attempt to process a search operation fails because the
807   * size limit has been exceeded, an {@link LDAPSearchException} will be
808   * thrown.  If one or more entries or references have already been returned
809   * for the search, then the {@code LDAPSearchException} methods like
810   * {@code getEntryCount}, {@code getSearchEntries}, {@code getReferenceCount},
811   * and {@code getSearchReferences} may be used to obtain information about
812   * those entries and references (although if a search result listener was
813   * provided, then it will have been used to make any entries and references
814   * available, and they will not be available through the
815   * {@code getSearchEntries} and {@code getSearchReferences} methods).
816   *
817   * @param  sizeLimit  The maximum number of entries that should be returned by
818   *                    the server when processing this search request.
819   */
820  public void setSizeLimit(final int sizeLimit)
821  {
822    if (sizeLimit < 0)
823    {
824      this.sizeLimit = 0;
825    }
826    else
827    {
828      this.sizeLimit = sizeLimit;
829    }
830  }
831
832
833
834  /**
835   * {@inheritDoc}
836   */
837  public int getTimeLimitSeconds()
838  {
839    return timeLimit;
840  }
841
842
843
844  /**
845   * Specifies the maximum length of time in seconds that the server should
846   * spend processing this search request.  A value of zero indicates that there
847   * should be no limit.
848   * <BR><BR>
849   * Note that if an attempt to process a search operation fails because the
850   * time limit has been exceeded, an {@link LDAPSearchException} will be
851   * thrown.  If one or more entries or references have already been returned
852   * for the search, then the {@code LDAPSearchException} methods like
853   * {@code getEntryCount}, {@code getSearchEntries}, {@code getReferenceCount},
854   * and {@code getSearchReferences} may be used to obtain information about
855   * those entries and references (although if a search result listener was
856   * provided, then it will have been used to make any entries and references
857   * available, and they will not be available through the
858   * {@code getSearchEntries} and {@code getSearchReferences} methods).
859   *
860   * @param  timeLimit  The maximum length of time in seconds that the server
861   *                    should spend processing this search request.
862   */
863  public void setTimeLimitSeconds(final int timeLimit)
864  {
865    if (timeLimit < 0)
866    {
867      this.timeLimit = 0;
868    }
869    else
870    {
871      this.timeLimit = timeLimit;
872    }
873  }
874
875
876
877  /**
878   * {@inheritDoc}
879   */
880  public boolean typesOnly()
881  {
882    return typesOnly;
883  }
884
885
886
887  /**
888   * Specifies whether the server should return only attribute names in matching
889   * entries, rather than both names and values.
890   *
891   * @param  typesOnly  Specifies whether the server should return only
892   *                    attribute names in matching entries, rather than both
893   *                    names and values.
894   */
895  public void setTypesOnly(final boolean typesOnly)
896  {
897    this.typesOnly = typesOnly;
898  }
899
900
901
902  /**
903   * {@inheritDoc}
904   */
905  public Filter getFilter()
906  {
907    return filter;
908  }
909
910
911
912  /**
913   * Specifies the filter that should be used to identify matching entries.
914   *
915   * @param  filter  The string representation for the filter that should be
916   *                 used to identify matching entries.  It must not be
917   *                 {@code null}.
918   *
919   * @throws  LDAPException  If the provided filter string cannot be parsed as a
920   *                         search filter.
921   */
922  public void setFilter(final String filter)
923         throws LDAPException
924  {
925    ensureNotNull(filter);
926
927    this.filter = Filter.create(filter);
928  }
929
930
931
932  /**
933   * Specifies the filter that should be used to identify matching entries.
934   *
935   * @param  filter  The filter that should be used to identify matching
936   *                 entries.  It must not be {@code null}.
937   */
938  public void setFilter(final Filter filter)
939  {
940    ensureNotNull(filter);
941
942    this.filter = filter;
943  }
944
945
946
947  /**
948   * Retrieves the set of requested attributes to include in matching entries.
949   * The caller must not attempt to alter the contents of the array.
950   *
951   * @return  The set of requested attributes to include in matching entries, or
952   *          an empty array if the default set of attributes (all user
953   *          attributes but no operational attributes) should be requested.
954   */
955  public String[] getAttributes()
956  {
957    return attributes;
958  }
959
960
961
962  /**
963   * {@inheritDoc}
964   */
965  public List<String> getAttributeList()
966  {
967    return Collections.unmodifiableList(Arrays.asList(attributes));
968  }
969
970
971
972  /**
973   * Specifies the set of requested attributes to include in matching entries.
974   *
975   * @param  attributes  The set of requested attributes to include in matching
976   *                     entries.  It may be {@code null} if the default set of
977   *                     attributes (all user attributes but no operational
978   *                     attributes) should be requested.
979   */
980  public void setAttributes(final String... attributes)
981  {
982    if (attributes == null)
983    {
984      this.attributes = REQUEST_ATTRS_DEFAULT;
985    }
986    else
987    {
988      this.attributes = attributes;
989    }
990  }
991
992
993
994  /**
995   * Specifies the set of requested attributes to include in matching entries.
996   *
997   * @param  attributes  The set of requested attributes to include in matching
998   *                     entries.  It may be {@code null} if the default set of
999   *                     attributes (all user attributes but no operational
1000   *                     attributes) should be requested.
1001   */
1002  public void setAttributes(final List<String> attributes)
1003  {
1004    if (attributes == null)
1005    {
1006      this.attributes = REQUEST_ATTRS_DEFAULT;
1007    }
1008    else
1009    {
1010      this.attributes = new String[attributes.size()];
1011      for (int i=0; i < this.attributes.length; i++)
1012      {
1013        this.attributes[i] = attributes.get(i);
1014      }
1015    }
1016  }
1017
1018
1019
1020  /**
1021   * Retrieves the search result listener for this search request, if available.
1022   *
1023   * @return  The search result listener for this search request, or
1024   *          {@code null} if none has been configured.
1025   */
1026  public SearchResultListener getSearchResultListener()
1027  {
1028    return searchResultListener;
1029  }
1030
1031
1032
1033  /**
1034   * {@inheritDoc}
1035   */
1036  public byte getProtocolOpType()
1037  {
1038    return LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST;
1039  }
1040
1041
1042
1043  /**
1044   * {@inheritDoc}
1045   */
1046  public void writeTo(final ASN1Buffer writer)
1047  {
1048    final ASN1BufferSequence requestSequence =
1049         writer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST);
1050    writer.addOctetString(baseDN);
1051    writer.addEnumerated(scope.intValue());
1052    writer.addEnumerated(derefPolicy.intValue());
1053    writer.addInteger(sizeLimit);
1054    writer.addInteger(timeLimit);
1055    writer.addBoolean(typesOnly);
1056    filter.writeTo(writer);
1057
1058    final ASN1BufferSequence attrSequence = writer.beginSequence();
1059    for (final String s : attributes)
1060    {
1061      writer.addOctetString(s);
1062    }
1063    attrSequence.end();
1064    requestSequence.end();
1065  }
1066
1067
1068
1069  /**
1070   * Encodes the search request protocol op to an ASN.1 element.
1071   *
1072   * @return  The ASN.1 element with the encoded search request protocol op.
1073   */
1074  public ASN1Element encodeProtocolOp()
1075  {
1076    // Create the search request protocol op.
1077    final ASN1Element[] attrElements = new ASN1Element[attributes.length];
1078    for (int i=0; i < attrElements.length; i++)
1079    {
1080      attrElements[i] = new ASN1OctetString(attributes[i]);
1081    }
1082
1083    final ASN1Element[] protocolOpElements =
1084    {
1085      new ASN1OctetString(baseDN),
1086      new ASN1Enumerated(scope.intValue()),
1087      new ASN1Enumerated(derefPolicy.intValue()),
1088      new ASN1Integer(sizeLimit),
1089      new ASN1Integer(timeLimit),
1090      new ASN1Boolean(typesOnly),
1091      filter.encode(),
1092      new ASN1Sequence(attrElements)
1093    };
1094
1095    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST,
1096                            protocolOpElements);
1097  }
1098
1099
1100
1101  /**
1102   * Sends this search request to the directory server over the provided
1103   * connection and returns the associated response.  The search result entries
1104   * and references will either be collected and returned in the
1105   * {@code SearchResult} object that is returned, or will be interactively
1106   * returned via the {@code SearchResultListener} interface.
1107   *
1108   * @param  connection  The connection to use to communicate with the directory
1109   *                     server.
1110   * @param  depth       The current referral depth for this request.  It should
1111   *                     always be one for the initial request, and should only
1112   *                     be incremented when following referrals.
1113   *
1114   * @return  An object that provides information about the result of the
1115   *          search processing, potentially including the sets of matching
1116   *          entries and/or search references.
1117   *
1118   * @throws  LDAPException  If a problem occurs while sending the request or
1119   *                         reading the response.
1120   */
1121  @Override()
1122  protected SearchResult process(final LDAPConnection connection,
1123                                 final int depth)
1124            throws LDAPException
1125  {
1126    if (connection.synchronousMode())
1127    {
1128      @SuppressWarnings("deprecation")
1129      final boolean autoReconnect =
1130           connection.getConnectionOptions().autoReconnect();
1131      return processSync(connection, depth, autoReconnect);
1132    }
1133
1134    final long requestTime = System.nanoTime();
1135    processAsync(connection, null);
1136
1137    try
1138    {
1139      // Wait for and process the response.
1140      final ArrayList<SearchResultEntry> entryList;
1141      final ArrayList<SearchResultReference> referenceList;
1142      if (searchResultListener == null)
1143      {
1144        entryList     = new ArrayList<SearchResultEntry>(5);
1145        referenceList = new ArrayList<SearchResultReference>(5);
1146      }
1147      else
1148      {
1149        entryList     = null;
1150        referenceList = null;
1151      }
1152
1153      int numEntries    = 0;
1154      int numReferences = 0;
1155      ResultCode intermediateResultCode = ResultCode.SUCCESS;
1156      final long responseTimeout = getResponseTimeoutMillis(connection);
1157      while (true)
1158      {
1159        final LDAPResponse response;
1160        try
1161        {
1162          if (responseTimeout > 0)
1163          {
1164            response =
1165                 responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
1166          }
1167          else
1168          {
1169            response = responseQueue.take();
1170          }
1171        }
1172        catch (InterruptedException ie)
1173        {
1174          debugException(ie);
1175          Thread.currentThread().interrupt();
1176          throw new LDAPException(ResultCode.LOCAL_ERROR,
1177               ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie);
1178        }
1179
1180        if (response == null)
1181        {
1182          if (connection.getConnectionOptions().abandonOnTimeout())
1183          {
1184            connection.abandon(messageID);
1185          }
1186
1187          final SearchResult searchResult =
1188               new SearchResult(messageID, ResultCode.TIMEOUT,
1189                    ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID,
1190                         baseDN, scope.getName(), filter.toString(),
1191                         connection.getHostPort()),
1192                    null, null, entryList, referenceList, numEntries,
1193                    numReferences, null);
1194          throw new LDAPSearchException(searchResult);
1195        }
1196
1197        if (response instanceof ConnectionClosedResponse)
1198        {
1199          final ConnectionClosedResponse ccr =
1200               (ConnectionClosedResponse) response;
1201          final String message = ccr.getMessage();
1202          if (message == null)
1203          {
1204            // The connection was closed while waiting for the response.
1205            final SearchResult searchResult =
1206                 new SearchResult(messageID, ccr.getResultCode(),
1207                      ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1208                           connection.getHostPort(), toString()),
1209                      null, null, entryList, referenceList, numEntries,
1210                      numReferences, null);
1211            throw new LDAPSearchException(searchResult);
1212          }
1213          else
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_WITH_MESSAGE.
1219                           get(connection.getHostPort(), toString(), message),
1220                      null, null, entryList, referenceList, numEntries,
1221                      numReferences, null);
1222            throw new LDAPSearchException(searchResult);
1223          }
1224        }
1225        else if (response instanceof SearchResultEntry)
1226        {
1227          final SearchResultEntry searchEntry = (SearchResultEntry) response;
1228          numEntries++;
1229          if (searchResultListener == null)
1230          {
1231            entryList.add(searchEntry);
1232          }
1233          else
1234          {
1235            searchResultListener.searchEntryReturned(searchEntry);
1236          }
1237        }
1238        else if (response instanceof SearchResultReference)
1239        {
1240          final SearchResultReference searchReference =
1241               (SearchResultReference) response;
1242          if (followReferrals(connection))
1243          {
1244            final LDAPResult result = followSearchReference(messageID,
1245                 searchReference, connection, depth);
1246            if (! result.getResultCode().equals(ResultCode.SUCCESS))
1247            {
1248              // We couldn't follow the reference.  We don't want to fail the
1249              // entire search because of this right now, so treat it as if
1250              // referral following had not been enabled.  Also, set the
1251              // intermediate result code to match that of the result.
1252              numReferences++;
1253              if (searchResultListener == null)
1254              {
1255                referenceList.add(searchReference);
1256              }
1257              else
1258              {
1259                searchResultListener.searchReferenceReturned(searchReference);
1260              }
1261
1262              if (intermediateResultCode.equals(ResultCode.SUCCESS))
1263              {
1264                intermediateResultCode = result.getResultCode();
1265              }
1266            }
1267            else if (result instanceof SearchResult)
1268            {
1269              final SearchResult searchResult = (SearchResult) result;
1270              numEntries += searchResult.getEntryCount();
1271              if (searchResultListener == null)
1272              {
1273                entryList.addAll(searchResult.getSearchEntries());
1274              }
1275            }
1276          }
1277          else
1278          {
1279            numReferences++;
1280            if (searchResultListener == null)
1281            {
1282              referenceList.add(searchReference);
1283            }
1284            else
1285            {
1286              searchResultListener.searchReferenceReturned(searchReference);
1287            }
1288          }
1289        }
1290        else
1291        {
1292          connection.getConnectionStatistics().incrementNumSearchResponses(
1293               numEntries, numReferences,
1294               (System.nanoTime() - requestTime));
1295          SearchResult result = (SearchResult) response;
1296          result.setCounts(numEntries, entryList, numReferences, referenceList);
1297
1298          if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1299              followReferrals(connection))
1300          {
1301            if (depth >=
1302                connection.getConnectionOptions().getReferralHopLimit())
1303            {
1304              return new SearchResult(messageID,
1305                                      ResultCode.REFERRAL_LIMIT_EXCEEDED,
1306                                      ERR_TOO_MANY_REFERRALS.get(),
1307                                      result.getMatchedDN(),
1308                                      result.getReferralURLs(), entryList,
1309                                      referenceList, numEntries,
1310                                      numReferences,
1311                                      result.getResponseControls());
1312            }
1313
1314            result = followReferral(result, connection, depth);
1315          }
1316
1317          if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1318              (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1319          {
1320            return new SearchResult(messageID, intermediateResultCode,
1321                                    result.getDiagnosticMessage(),
1322                                    result.getMatchedDN(),
1323                                    result.getReferralURLs(),
1324                                    entryList, referenceList, numEntries,
1325                                    numReferences,
1326                                    result.getResponseControls());
1327          }
1328
1329          return result;
1330        }
1331      }
1332    }
1333    finally
1334    {
1335      connection.deregisterResponseAcceptor(messageID);
1336    }
1337  }
1338
1339
1340
1341  /**
1342   * Sends this search request to the directory server over the provided
1343   * connection and returns the message ID for the request.
1344   *
1345   * @param  connection      The connection to use to communicate with the
1346   *                         directory server.
1347   * @param  resultListener  The async result listener that is to be notified
1348   *                         when the response is received.  It may be
1349   *                         {@code null} only if the result is to be processed
1350   *                         by this class.
1351   *
1352   * @return  The async request ID created for the operation, or {@code null} if
1353   *          the provided {@code resultListener} is {@code null} and the
1354   *          operation will not actually be processed asynchronously.
1355   *
1356   * @throws  LDAPException  If a problem occurs while sending the request.
1357   */
1358  AsyncRequestID processAsync(final LDAPConnection connection,
1359                              final AsyncSearchResultListener resultListener)
1360                 throws LDAPException
1361  {
1362    // Create the LDAP message.
1363    messageID = connection.nextMessageID();
1364    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
1365
1366
1367    // If the provided async result listener is {@code null}, then we'll use
1368    // this class as the message acceptor.  Otherwise, create an async helper
1369    // and use it as the message acceptor.
1370    final AsyncRequestID asyncRequestID;
1371    if (resultListener == null)
1372    {
1373      asyncRequestID = null;
1374      connection.registerResponseAcceptor(messageID, this);
1375    }
1376    else
1377    {
1378      final AsyncSearchHelper helper = new AsyncSearchHelper(connection,
1379           messageID, resultListener, getIntermediateResponseListener());
1380      connection.registerResponseAcceptor(messageID, helper);
1381      asyncRequestID = helper.getAsyncRequestID();
1382
1383      final long timeout = getResponseTimeoutMillis(connection);
1384      if (timeout > 0L)
1385      {
1386        final Timer timer = connection.getTimer();
1387        final AsyncTimeoutTimerTask timerTask =
1388             new AsyncTimeoutTimerTask(helper);
1389        timer.schedule(timerTask, timeout);
1390        asyncRequestID.setTimerTask(timerTask);
1391      }
1392    }
1393
1394
1395    // Send the request to the server.
1396    try
1397    {
1398      debugLDAPRequest(this);
1399      connection.getConnectionStatistics().incrementNumSearchRequests();
1400      connection.sendMessage(message);
1401      return asyncRequestID;
1402    }
1403    catch (LDAPException le)
1404    {
1405      debugException(le);
1406
1407      connection.deregisterResponseAcceptor(messageID);
1408      throw le;
1409    }
1410  }
1411
1412
1413
1414  /**
1415   * Processes this search operation in synchronous mode, in which the same
1416   * thread will send the request and read the response.
1417   *
1418   * @param  connection  The connection to use to communicate with the directory
1419   *                     server.
1420   * @param  depth       The current referral depth for this request.  It should
1421   *                     always be one for the initial request, and should only
1422   *                     be incremented when following referrals.
1423   * @param  allowRetry  Indicates whether the request may be re-tried on a
1424   *                     re-established connection if the initial attempt fails
1425   *                     in a way that indicates the connection is no longer
1426   *                     valid and autoReconnect is true.
1427   *
1428   * @return  An LDAP result object that provides information about the result
1429   *          of the search processing.
1430   *
1431   * @throws  LDAPException  If a problem occurs while sending the request or
1432   *                         reading the response.
1433   */
1434  private SearchResult processSync(final LDAPConnection connection,
1435                                   final int depth, final boolean allowRetry)
1436          throws LDAPException
1437  {
1438    // Create the LDAP message.
1439    messageID = connection.nextMessageID();
1440    final LDAPMessage message =
1441         new LDAPMessage(messageID,  this, getControls());
1442
1443
1444    // Set the appropriate timeout on the socket.
1445    final long responseTimeout = getResponseTimeoutMillis(connection);
1446    try
1447    {
1448      connection.getConnectionInternals(true).getSocket().setSoTimeout(
1449           (int) responseTimeout);
1450    }
1451    catch (Exception e)
1452    {
1453      debugException(e);
1454    }
1455
1456
1457    // Send the request to the server.
1458    final long requestTime = System.nanoTime();
1459    debugLDAPRequest(this);
1460    connection.getConnectionStatistics().incrementNumSearchRequests();
1461    try
1462    {
1463      connection.sendMessage(message);
1464    }
1465    catch (final LDAPException le)
1466    {
1467      debugException(le);
1468
1469      if (allowRetry)
1470      {
1471        final SearchResult retryResult = reconnectAndRetry(connection, depth,
1472             le.getResultCode(), 0, 0);
1473        if (retryResult != null)
1474        {
1475          return retryResult;
1476        }
1477      }
1478
1479      throw le;
1480    }
1481
1482    final ArrayList<SearchResultEntry> entryList;
1483    final ArrayList<SearchResultReference> referenceList;
1484    if (searchResultListener == null)
1485    {
1486      entryList     = new ArrayList<SearchResultEntry>(5);
1487      referenceList = new ArrayList<SearchResultReference>(5);
1488    }
1489    else
1490    {
1491      entryList     = null;
1492      referenceList = null;
1493    }
1494
1495    int numEntries    = 0;
1496    int numReferences = 0;
1497    ResultCode intermediateResultCode = ResultCode.SUCCESS;
1498    while (true)
1499    {
1500      final LDAPResponse response;
1501      try
1502      {
1503        response = connection.readResponse(messageID);
1504      }
1505      catch (final LDAPException le)
1506      {
1507        debugException(le);
1508
1509        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1510            connection.getConnectionOptions().abandonOnTimeout())
1511        {
1512          connection.abandon(messageID);
1513        }
1514
1515        if (allowRetry)
1516        {
1517          final SearchResult retryResult = reconnectAndRetry(connection, depth,
1518               le.getResultCode(), numEntries, numReferences);
1519          if (retryResult != null)
1520          {
1521            return retryResult;
1522          }
1523        }
1524
1525        throw le;
1526      }
1527
1528      if (response == null)
1529      {
1530        if (connection.getConnectionOptions().abandonOnTimeout())
1531        {
1532          connection.abandon(messageID);
1533        }
1534
1535        throw new LDAPException(ResultCode.TIMEOUT,
1536             ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID, baseDN,
1537                  scope.getName(), filter.toString(),
1538                  connection.getHostPort()));
1539      }
1540      else if (response instanceof ConnectionClosedResponse)
1541      {
1542
1543        if (allowRetry)
1544        {
1545          final SearchResult retryResult = reconnectAndRetry(connection, depth,
1546               ResultCode.SERVER_DOWN, numEntries, numReferences);
1547          if (retryResult != null)
1548          {
1549            return retryResult;
1550          }
1551        }
1552
1553        final ConnectionClosedResponse ccr =
1554             (ConnectionClosedResponse) response;
1555        final String msg = ccr.getMessage();
1556        if (msg == null)
1557        {
1558          // The connection was closed while waiting for the response.
1559          final SearchResult searchResult =
1560               new SearchResult(messageID, ccr.getResultCode(),
1561                    ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1562                         connection.getHostPort(), toString()),
1563                    null, null, entryList, referenceList, numEntries,
1564                    numReferences, null);
1565          throw new LDAPSearchException(searchResult);
1566        }
1567        else
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_WITH_MESSAGE.
1573                         get(connection.getHostPort(), toString(), msg),
1574                    null, null, entryList, referenceList, numEntries,
1575                    numReferences, null);
1576          throw new LDAPSearchException(searchResult);
1577        }
1578      }
1579      else if (response instanceof IntermediateResponse)
1580      {
1581        final IntermediateResponseListener listener =
1582             getIntermediateResponseListener();
1583        if (listener != null)
1584        {
1585          listener.intermediateResponseReturned(
1586               (IntermediateResponse) response);
1587        }
1588      }
1589      else if (response instanceof SearchResultEntry)
1590      {
1591        final SearchResultEntry searchEntry = (SearchResultEntry) response;
1592        numEntries++;
1593        if (searchResultListener == null)
1594        {
1595          entryList.add(searchEntry);
1596        }
1597        else
1598        {
1599          searchResultListener.searchEntryReturned(searchEntry);
1600        }
1601      }
1602      else if (response instanceof SearchResultReference)
1603      {
1604        final SearchResultReference searchReference =
1605             (SearchResultReference) response;
1606        if (followReferrals(connection))
1607        {
1608          final LDAPResult result = followSearchReference(messageID,
1609               searchReference, connection, depth);
1610          if (! result.getResultCode().equals(ResultCode.SUCCESS))
1611          {
1612            // We couldn't follow the reference.  We don't want to fail the
1613            // entire search because of this right now, so treat it as if
1614            // referral following had not been enabled.  Also, set the
1615            // intermediate result code to match that of the result.
1616            numReferences++;
1617            if (searchResultListener == null)
1618            {
1619              referenceList.add(searchReference);
1620            }
1621            else
1622            {
1623              searchResultListener.searchReferenceReturned(searchReference);
1624            }
1625
1626            if (intermediateResultCode.equals(ResultCode.SUCCESS))
1627            {
1628              intermediateResultCode = result.getResultCode();
1629            }
1630          }
1631          else if (result instanceof SearchResult)
1632          {
1633            final SearchResult searchResult = (SearchResult) result;
1634            numEntries += searchResult.getEntryCount();
1635            if (searchResultListener == null)
1636            {
1637              entryList.addAll(searchResult.getSearchEntries());
1638            }
1639          }
1640        }
1641        else
1642        {
1643          numReferences++;
1644          if (searchResultListener == null)
1645          {
1646            referenceList.add(searchReference);
1647          }
1648          else
1649          {
1650            searchResultListener.searchReferenceReturned(searchReference);
1651          }
1652        }
1653      }
1654      else
1655      {
1656        final SearchResult result = (SearchResult) response;
1657        if (allowRetry)
1658        {
1659          final SearchResult retryResult = reconnectAndRetry(connection,
1660               depth, result.getResultCode(), numEntries, numReferences);
1661          if (retryResult != null)
1662          {
1663            return retryResult;
1664          }
1665        }
1666
1667        return handleResponse(connection, response, requestTime, depth,
1668                              numEntries, numReferences, entryList,
1669                              referenceList, intermediateResultCode);
1670      }
1671    }
1672  }
1673
1674
1675
1676  /**
1677   * Attempts to re-establish the connection and retry processing this request
1678   * on it.
1679   *
1680   * @param  connection     The connection to be re-established.
1681   * @param  depth          The current referral depth for this request.  It
1682   *                        should always be one for the initial request, and
1683   *                        should only be incremented when following referrals.
1684   * @param  resultCode     The result code for the previous operation attempt.
1685   * @param  numEntries     The number of search result entries already sent for
1686   *                        the search operation.
1687   * @param  numReferences  The number of search result references already sent
1688   *                        for the search operation.
1689   *
1690   * @return  The result from re-trying the search, or {@code null} if it could
1691   *          not be re-tried.
1692   */
1693  private SearchResult reconnectAndRetry(final LDAPConnection connection,
1694                                         final int depth,
1695                                         final ResultCode resultCode,
1696                                         final int numEntries,
1697                                         final int numReferences)
1698  {
1699    try
1700    {
1701      // We will only want to retry for certain result codes that indicate a
1702      // connection problem.
1703      switch (resultCode.intValue())
1704      {
1705        case ResultCode.SERVER_DOWN_INT_VALUE:
1706        case ResultCode.DECODING_ERROR_INT_VALUE:
1707        case ResultCode.CONNECT_ERROR_INT_VALUE:
1708          // We want to try to re-establish the connection no matter what, but
1709          // we only want to retry the search if we haven't yet sent any
1710          // results.
1711          connection.reconnect();
1712          if ((numEntries == 0) && (numReferences == 0))
1713          {
1714            return processSync(connection, depth, false);
1715          }
1716          break;
1717      }
1718    }
1719    catch (final Exception e)
1720    {
1721      debugException(e);
1722    }
1723
1724    return null;
1725  }
1726
1727
1728
1729  /**
1730   * Performs the necessary processing for handling a response.
1731   *
1732   * @param  connection              The connection used to read the response.
1733   * @param  response                The response to be processed.
1734   * @param  requestTime             The time the request was sent to the
1735   *                                 server.
1736   * @param  depth                   The current referral depth for this
1737   *                                 request.  It should always be one for the
1738   *                                 initial request, and should only be
1739   *                                 incremented when following referrals.
1740   * @param  numEntries              The number of entries received from the
1741   *                                 server.
1742   * @param  numReferences           The number of references received from
1743   *                                 the server.
1744   * @param  entryList               The list of search result entries received
1745   *                                 from the server, if applicable.
1746   * @param  referenceList           The list of search result references
1747   *                                 received from the server, if applicable.
1748   * @param  intermediateResultCode  The intermediate result code so far for the
1749   *                                 search operation.
1750   *
1751   * @return  The search result.
1752   *
1753   * @throws  LDAPException  If a problem occurs.
1754   */
1755  private SearchResult handleResponse(final LDAPConnection connection,
1756               final LDAPResponse response, final long requestTime,
1757               final int depth, final int numEntries, final int numReferences,
1758               final List<SearchResultEntry> entryList,
1759               final List<SearchResultReference> referenceList,
1760               final ResultCode intermediateResultCode)
1761          throws LDAPException
1762  {
1763    connection.getConnectionStatistics().incrementNumSearchResponses(
1764         numEntries, numReferences,
1765         (System.nanoTime() - requestTime));
1766    SearchResult result = (SearchResult) response;
1767    result.setCounts(numEntries, entryList, numReferences, referenceList);
1768
1769    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1770        followReferrals(connection))
1771    {
1772      if (depth >=
1773          connection.getConnectionOptions().getReferralHopLimit())
1774      {
1775        return new SearchResult(messageID,
1776                                ResultCode.REFERRAL_LIMIT_EXCEEDED,
1777                                ERR_TOO_MANY_REFERRALS.get(),
1778                                result.getMatchedDN(),
1779                                result.getReferralURLs(), entryList,
1780                                referenceList, numEntries,
1781                                numReferences,
1782                                result.getResponseControls());
1783      }
1784
1785      result = followReferral(result, connection, depth);
1786    }
1787
1788    if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1789        (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1790    {
1791      return new SearchResult(messageID, intermediateResultCode,
1792                              result.getDiagnosticMessage(),
1793                              result.getMatchedDN(),
1794                              result.getReferralURLs(),
1795                              entryList, referenceList, numEntries,
1796                              numReferences,
1797                              result.getResponseControls());
1798    }
1799
1800    return result;
1801  }
1802
1803
1804
1805  /**
1806   * Attempts to follow a search result reference to continue a search in a
1807   * remote server.
1808   *
1809   * @param  messageID        The message ID for the LDAP message that is
1810   *                          associated with this result.
1811   * @param  searchReference  The search result reference to follow.
1812   * @param  connection       The connection on which the reference was
1813   *                          received.
1814   * @param  depth            The number of referrals followed in the course of
1815   *                          processing this request.
1816   *
1817   * @return  The result of attempting to follow the search result reference.
1818   *
1819   * @throws  LDAPException  If a problem occurs while attempting to establish
1820   *                         the referral connection, sending the request, or
1821   *                         reading the result.
1822   */
1823  private LDAPResult followSearchReference(final int messageID,
1824                          final SearchResultReference searchReference,
1825                          final LDAPConnection connection, final int depth)
1826          throws LDAPException
1827  {
1828    for (final String urlString : searchReference.getReferralURLs())
1829    {
1830      try
1831      {
1832        final LDAPURL referralURL = new LDAPURL(urlString);
1833        final String host = referralURL.getHost();
1834
1835        if (host == null)
1836        {
1837          // We can't handle a referral in which there is no host.
1838          continue;
1839        }
1840
1841        final String requestBaseDN;
1842        if (referralURL.baseDNProvided())
1843        {
1844          requestBaseDN = referralURL.getBaseDN().toString();
1845        }
1846        else
1847        {
1848          requestBaseDN = baseDN;
1849        }
1850
1851        final SearchScope requestScope;
1852        if (referralURL.scopeProvided())
1853        {
1854          requestScope = referralURL.getScope();
1855        }
1856        else
1857        {
1858          requestScope = scope;
1859        }
1860
1861        final Filter requestFilter;
1862        if (referralURL.filterProvided())
1863        {
1864          requestFilter = referralURL.getFilter();
1865        }
1866        else
1867        {
1868          requestFilter = filter;
1869        }
1870
1871
1872        final SearchRequest searchRequest =
1873             new SearchRequest(searchResultListener, getControls(),
1874                               requestBaseDN, requestScope, derefPolicy,
1875                               sizeLimit, timeLimit, typesOnly, requestFilter,
1876                               attributes);
1877
1878        final LDAPConnection referralConn = connection.getReferralConnector().
1879             getReferralConnection(referralURL, connection);
1880
1881        try
1882        {
1883          return searchRequest.process(referralConn, depth+1);
1884        }
1885        finally
1886        {
1887          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1888          referralConn.close();
1889        }
1890      }
1891      catch (LDAPException le)
1892      {
1893        debugException(le);
1894
1895        if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1896        {
1897          throw le;
1898        }
1899      }
1900    }
1901
1902    // If we've gotten here, then we could not follow any of the referral URLs,
1903    // so we'll create a failure result.
1904    return new SearchResult(messageID, ResultCode.REFERRAL, null, null,
1905                            searchReference.getReferralURLs(), 0, 0, null);
1906  }
1907
1908
1909
1910  /**
1911   * Attempts to follow a referral to perform an add operation in the target
1912   * server.
1913   *
1914   * @param  referralResult  The LDAP result object containing information about
1915   *                         the referral to follow.
1916   * @param  connection      The connection on which the referral was received.
1917   * @param  depth           The number of referrals followed in the course of
1918   *                         processing this request.
1919   *
1920   * @return  The result of attempting to process the add operation by following
1921   *          the referral.
1922   *
1923   * @throws  LDAPException  If a problem occurs while attempting to establish
1924   *                         the referral connection, sending the request, or
1925   *                         reading the result.
1926   */
1927  private SearchResult followReferral(final SearchResult referralResult,
1928                                      final LDAPConnection connection,
1929                                      final int depth)
1930          throws LDAPException
1931  {
1932    for (final String urlString : referralResult.getReferralURLs())
1933    {
1934      try
1935      {
1936        final LDAPURL referralURL = new LDAPURL(urlString);
1937        final String host = referralURL.getHost();
1938
1939        if (host == null)
1940        {
1941          // We can't handle a referral in which there is no host.
1942          continue;
1943        }
1944
1945        final String requestBaseDN;
1946        if (referralURL.baseDNProvided())
1947        {
1948          requestBaseDN = referralURL.getBaseDN().toString();
1949        }
1950        else
1951        {
1952          requestBaseDN = baseDN;
1953        }
1954
1955        final SearchScope requestScope;
1956        if (referralURL.scopeProvided())
1957        {
1958          requestScope = referralURL.getScope();
1959        }
1960        else
1961        {
1962          requestScope = scope;
1963        }
1964
1965        final Filter requestFilter;
1966        if (referralURL.filterProvided())
1967        {
1968          requestFilter = referralURL.getFilter();
1969        }
1970        else
1971        {
1972          requestFilter = filter;
1973        }
1974
1975
1976        final SearchRequest searchRequest =
1977             new SearchRequest(searchResultListener, getControls(),
1978                               requestBaseDN, requestScope, derefPolicy,
1979                               sizeLimit, timeLimit, typesOnly, requestFilter,
1980                               attributes);
1981
1982        final LDAPConnection referralConn = connection.getReferralConnector().
1983             getReferralConnection(referralURL, connection);
1984        try
1985        {
1986          return searchRequest.process(referralConn, depth+1);
1987        }
1988        finally
1989        {
1990          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1991          referralConn.close();
1992        }
1993      }
1994      catch (LDAPException le)
1995      {
1996        debugException(le);
1997
1998        if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
1999        {
2000          throw le;
2001        }
2002      }
2003    }
2004
2005    // If we've gotten here, then we could not follow any of the referral URLs,
2006    // so we'll just return the original referral result.
2007    return referralResult;
2008  }
2009
2010
2011
2012  /**
2013   * {@inheritDoc}
2014   */
2015  @InternalUseOnly()
2016  public void responseReceived(final LDAPResponse response)
2017         throws LDAPException
2018  {
2019    try
2020    {
2021      responseQueue.put(response);
2022    }
2023    catch (Exception e)
2024    {
2025      debugException(e);
2026
2027      if (e instanceof InterruptedException)
2028      {
2029        Thread.currentThread().interrupt();
2030      }
2031
2032      throw new LDAPException(ResultCode.LOCAL_ERROR,
2033           ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
2034    }
2035  }
2036
2037
2038
2039  /**
2040   * {@inheritDoc}
2041   */
2042  @Override()
2043  public int getLastMessageID()
2044  {
2045    return messageID;
2046  }
2047
2048
2049
2050  /**
2051   * {@inheritDoc}
2052   */
2053  @Override()
2054  public OperationType getOperationType()
2055  {
2056    return OperationType.SEARCH;
2057  }
2058
2059
2060
2061  /**
2062   * {@inheritDoc}
2063   */
2064  public SearchRequest duplicate()
2065  {
2066    return duplicate(getControls());
2067  }
2068
2069
2070
2071  /**
2072   * {@inheritDoc}
2073   */
2074  public SearchRequest duplicate(final Control[] controls)
2075  {
2076    final SearchRequest r = new SearchRequest(searchResultListener, controls,
2077         baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter,
2078         attributes);
2079    if (followReferralsInternal() != null)
2080    {
2081      r.setFollowReferrals(followReferralsInternal());
2082    }
2083
2084    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
2085
2086    return r;
2087  }
2088
2089
2090
2091  /**
2092   * {@inheritDoc}
2093   */
2094  @Override()
2095  public void toString(final StringBuilder buffer)
2096  {
2097    buffer.append("SearchRequest(baseDN='");
2098    buffer.append(baseDN);
2099    buffer.append("', scope=");
2100    buffer.append(scope);
2101    buffer.append(", deref=");
2102    buffer.append(derefPolicy);
2103    buffer.append(", sizeLimit=");
2104    buffer.append(sizeLimit);
2105    buffer.append(", timeLimit=");
2106    buffer.append(timeLimit);
2107    buffer.append(", filter='");
2108    buffer.append(filter);
2109    buffer.append("', attrs={");
2110
2111    for (int i=0; i < attributes.length; i++)
2112    {
2113      if (i > 0)
2114      {
2115        buffer.append(", ");
2116      }
2117
2118      buffer.append(attributes[i]);
2119    }
2120    buffer.append('}');
2121
2122    final Control[] controls = getControls();
2123    if (controls.length > 0)
2124    {
2125      buffer.append(", controls={");
2126      for (int i=0; i < controls.length; i++)
2127      {
2128        if (i > 0)
2129        {
2130          buffer.append(", ");
2131        }
2132
2133        buffer.append(controls[i]);
2134      }
2135      buffer.append('}');
2136    }
2137
2138    buffer.append(')');
2139  }
2140
2141
2142
2143  /**
2144   * {@inheritDoc}
2145   */
2146  public void toCode(final List<String> lineList, final String requestID,
2147                     final int indentSpaces, final boolean includeProcessing)
2148  {
2149    // Create the request variable.
2150    final ArrayList<ToCodeArgHelper> constructorArgs =
2151         new ArrayList<ToCodeArgHelper>(10);
2152    constructorArgs.add(ToCodeArgHelper.createString(baseDN, "Base DN"));
2153    constructorArgs.add(ToCodeArgHelper.createScope(scope, "Scope"));
2154    constructorArgs.add(ToCodeArgHelper.createDerefPolicy(derefPolicy,
2155         "Alias Dereference Policy"));
2156    constructorArgs.add(ToCodeArgHelper.createInteger(sizeLimit, "Size Limit"));
2157    constructorArgs.add(ToCodeArgHelper.createInteger(timeLimit, "Time Limit"));
2158    constructorArgs.add(ToCodeArgHelper.createBoolean(typesOnly, "Types Only"));
2159    constructorArgs.add(ToCodeArgHelper.createFilter(filter, "Filter"));
2160
2161    String comment = "Requested Attributes";
2162    for (final String s : attributes)
2163    {
2164      constructorArgs.add(ToCodeArgHelper.createString(s, comment));
2165      comment = null;
2166    }
2167
2168    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "SearchRequest",
2169         requestID + "Request", "new SearchRequest", constructorArgs);
2170
2171
2172    // If there are any controls, then add them to the request.
2173    for (final Control c : getControls())
2174    {
2175      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
2176           requestID + "Request.addControl",
2177           ToCodeArgHelper.createControl(c, null));
2178    }
2179
2180
2181    // Add lines for processing the request and obtaining the result.
2182    if (includeProcessing)
2183    {
2184      // Generate a string with the appropriate indent.
2185      final StringBuilder buffer = new StringBuilder();
2186      for (int i=0; i < indentSpaces; i++)
2187      {
2188        buffer.append(' ');
2189      }
2190      final String indent = buffer.toString();
2191
2192      lineList.add("");
2193      lineList.add(indent + "SearchResult " + requestID + "Result;");
2194      lineList.add(indent + "try");
2195      lineList.add(indent + '{');
2196      lineList.add(indent + "  " + requestID + "Result = connection.search(" +
2197           requestID + "Request);");
2198      lineList.add(indent + "  // The search was processed successfully.");
2199      lineList.add(indent + '}');
2200      lineList.add(indent + "catch (LDAPSearchException e)");
2201      lineList.add(indent + '{');
2202      lineList.add(indent + "  // The search failed.  Maybe the following " +
2203           "will help explain why.");
2204      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
2205      lineList.add(indent + "  String message = e.getMessage();");
2206      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
2207      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
2208      lineList.add(indent + "  Control[] responseControls = " +
2209           "e.getResponseControls();");
2210      lineList.add("");
2211      lineList.add(indent + "  // Even though there was an error, we may " +
2212           "have gotten some results.");
2213      lineList.add(indent + "  " + requestID + "Result = e.getSearchResult();");
2214      lineList.add(indent + '}');
2215      lineList.add("");
2216      lineList.add(indent + "// If there were results, then process them.");
2217      lineList.add(indent + "for (SearchResultEntry e : " + requestID +
2218           "Result.getSearchEntries())");
2219      lineList.add(indent + '{');
2220      lineList.add(indent + "  // Do something with the entry.");
2221      lineList.add(indent + '}');
2222    }
2223  }
2224}