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