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