001/*
002 * Copyright 2007-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2022 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2007-2022 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.util.ArrayList;
041import java.util.Arrays;
042import java.util.Collections;
043import java.util.List;
044import java.util.Timer;
045import java.util.concurrent.LinkedBlockingQueue;
046import java.util.concurrent.TimeUnit;
047import java.util.logging.Level;
048
049import com.unboundid.asn1.ASN1Boolean;
050import com.unboundid.asn1.ASN1Buffer;
051import com.unboundid.asn1.ASN1BufferSequence;
052import com.unboundid.asn1.ASN1Element;
053import com.unboundid.asn1.ASN1Enumerated;
054import com.unboundid.asn1.ASN1Integer;
055import com.unboundid.asn1.ASN1OctetString;
056import com.unboundid.asn1.ASN1Sequence;
057import com.unboundid.ldap.protocol.LDAPMessage;
058import com.unboundid.ldap.protocol.LDAPResponse;
059import com.unboundid.ldap.protocol.ProtocolOp;
060import com.unboundid.util.Debug;
061import com.unboundid.util.InternalUseOnly;
062import com.unboundid.util.Mutable;
063import com.unboundid.util.NotNull;
064import com.unboundid.util.Nullable;
065import com.unboundid.util.StaticUtils;
066import com.unboundid.util.ThreadSafety;
067import com.unboundid.util.ThreadSafetyLevel;
068import com.unboundid.util.Validator;
069
070import static com.unboundid.ldap.sdk.LDAPMessages.*;
071
072
073
074/**
075 * This class implements the processing necessary to perform an LDAPv3 search
076 * operation, which can be used to retrieve entries that match a given set of
077 * criteria.  A search request may include the following elements:
078 * <UL>
079 *   <LI>Base DN -- Specifies the base DN for the search.  Only entries at or
080 *       below this location in the server (based on the scope) will be
081 *       considered potential matches.</LI>
082 *   <LI>Scope -- Specifies the range of entries relative to the base DN that
083 *       may be considered potential matches.</LI>
084 *   <LI>Dereference Policy -- Specifies the behavior that the server should
085 *       exhibit if any alias entries are encountered while processing the
086 *       search.  If no dereference policy is provided, then a default of
087 *       {@code DereferencePolicy.NEVER} will be used.</LI>
088 *   <LI>Size Limit -- Specifies the maximum number of entries that should be
089 *       returned from the search.  A value of zero indicates that there should
090 *       not be any limit enforced.  Note that the directory server may also
091 *       be configured with a server-side size limit which can also limit the
092 *       number of entries that may be returned to the client and in that case
093 *       the smaller of the client-side and server-side limits will be
094 *       used.  If no size limit is provided, then a default of zero (unlimited)
095 *       will be used.</LI>
096 *   <LI>Time Limit -- Specifies the maximum length of time in seconds that the
097 *       server should spend processing the search.  A value of zero indicates
098 *       that there should not be any limit enforced.  Note that the directory
099 *       server may also be configured with a server-side time limit which can
100 *       also limit the processing time, and in that case the smaller of the
101 *       client-side and server-side limits will be used.  If no time limit is
102 *       provided, then a default of zero (unlimited) will be used.</LI>
103 *   <LI>Types Only -- Indicates whether matching entries should include only
104 *       attribute names, or both attribute names and values.  If no value is
105 *       provided, then a default of {@code false} will be used.</LI>
106 *   <LI>Filter -- Specifies the criteria for determining which entries should
107 *       be returned.  See the {@link Filter} class for the types of filters
108 *       that may be used.
109 *       <BR><BR>
110 *       Note that filters can be specified using either their string
111 *       representations or as {@link Filter} objects.  As noted in the
112 *       documentation for the {@link Filter} class, using the string
113 *       representation may be somewhat dangerous if the data is not properly
114 *       sanitized because special characters contained in the filter may cause
115 *       it to be invalid or worse expose a vulnerability that could cause the
116 *       filter to request more information than was intended.  As a result, if
117 *       the filter may include special characters or user-provided strings,
118 *       then it is recommended that you use {@link Filter} objects created from
119 *       their individual components rather than their string representations.
120 * </LI>
121 *   <LI>Attributes -- Specifies the set of attributes that should be included
122 *       in matching entries.  If no attributes are provided, then the server
123 *       will default to returning all user attributes.  If a specified set of
124 *       attributes is given, then only those attributes will be included.
125 *       Values that may be included to indicate a special meaning include:
126 *       <UL>
127 *         <LI>{@code NO_ATTRIBUTES} -- Indicates that no attributes should be
128 *             returned.  That is, only the DNs of matching entries will be
129 *             returned.</LI>
130 *         <LI>{@code ALL_USER_ATTRIBUTES} -- Indicates that all user attributes
131 *             should be included in matching entries.  This is the default if
132 *             no attributes are provided, but this special value may be
133 *             included if a specific set of operational attributes should be
134 *             included along with all user attributes.</LI>
135 *         <LI>{@code ALL_OPERATIONAL_ATTRIBUTES} -- Indicates that all
136 *             operational attributes should be included in matching
137 *             entries.</LI>
138 *       </UL>
139 *       These special values may be used alone or in conjunction with each
140 *       other and/or any specific attribute names or OIDs.</LI>
141 *   <LI>An optional set of controls to include in the request to send to the
142 *       server.</LI>
143 *   <LI>An optional {@link SearchResultListener} which may be used to process
144 *       search result entries and search result references returned by the
145 *       server in the course of processing the request.  If this is
146 *       {@code null}, then the entries and references will be collected and
147 *       returned in the {@link SearchResult} object that is returned.</LI>
148 * </UL>
149 * When processing a search operation, there are three ways that the returned
150 * entries and references may be accessed:
151 * <UL>
152 *   <LI>If the {@link LDAPInterface#search(SearchRequest)} method is used and
153 *       the provided search request does not include a
154 *       {@link SearchResultListener} object, then the entries and references
155 *       will be collected internally and made available in the
156 *       {@link SearchResult} object that is returned.</LI>
157 *   <LI>If the {@link LDAPInterface#search(SearchRequest)} method is used and
158 *       the provided search request does include a {@link SearchResultListener}
159 *       object, then that listener will be used to provide access to the
160 *       entries and references, and they will not be present in the
161 *       {@link SearchResult} object (although the number of entries and
162 *       references returned will still be available).</LI>
163 *   <LI>The {@link LDAPEntrySource} object may be used to access the entries
164 *        and references returned from the search.  It uses an
165 *        {@code Iterator}-like API to provide access to the entries that are
166 *        returned, and any references returned will be included in the
167 *        {@link EntrySourceException} thrown on the appropriate call to
168 *        {@link LDAPEntrySource#nextEntry()}.</LI>
169 * </UL>
170 * <BR><BR>
171 * {@code SearchRequest} objects are mutable and therefore can be altered and
172 * re-used for multiple requests.  Note, however, that {@code SearchRequest}
173 * objects are not threadsafe and therefore a single {@code SearchRequest}
174 * object instance should not be used to process multiple requests at the same
175 * time.
176 * <BR><BR>
177 * <H2>Example</H2>
178 * The following example demonstrates a simple search operation in which the
179 * client performs a search to find all users in the "Sales" department and then
180 * retrieves the name and e-mail address for each matching user:
181 * <PRE>
182 * // Construct a filter that can be used to find everyone in the Sales
183 * // department, and then create a search request to find all such users
184 * // in the directory.
185 * Filter filter = Filter.createEqualityFilter("ou", "Sales");
186 * SearchRequest searchRequest =
187 *      new SearchRequest("dc=example,dc=com", SearchScope.SUB, filter,
188 *           "cn", "mail");
189 * SearchResult searchResult;
190 *
191 * try
192 * {
193 *   searchResult = connection.search(searchRequest);
194 *
195 *   for (SearchResultEntry entry : searchResult.getSearchEntries())
196 *   {
197 *     String name = entry.getAttributeValue("cn");
198 *     String mail = entry.getAttributeValue("mail");
199 *   }
200 * }
201 * catch (LDAPSearchException lse)
202 * {
203 *   // The search failed for some reason.
204 *   searchResult = lse.getSearchResult();
205 *   ResultCode resultCode = lse.getResultCode();
206 *   String errorMessageFromServer = lse.getDiagnosticMessage();
207 * }
208 * </PRE>
209 */
210@Mutable()
211@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
212public final class SearchRequest
213       extends UpdatableLDAPRequest
214       implements ReadOnlySearchRequest, ResponseAcceptor, ProtocolOp
215{
216  /**
217   * The special value "*" that can be included in the set of requested
218   * attributes to indicate that all user attributes should be returned.
219   */
220  @NotNull public static final String ALL_USER_ATTRIBUTES = "*";
221
222
223
224  /**
225   * The special value "+" that can be included in the set of requested
226   * attributes to indicate that all operational attributes should be returned.
227   */
228  @NotNull public static final String ALL_OPERATIONAL_ATTRIBUTES = "+";
229
230
231
232  /**
233   * The special value "1.1" that can be included in the set of requested
234   * attributes to indicate that no attributes should be returned, with the
235   * exception of any other attributes explicitly named in the set of requested
236   * attributes.
237   */
238  @NotNull public static final String NO_ATTRIBUTES = "1.1";
239
240
241
242  /**
243   * The default set of requested attributes that will be used, which will
244   * return all user attributes but no operational attributes.
245   */
246  @NotNull public static final String[] REQUEST_ATTRS_DEFAULT =
247       StaticUtils.NO_STRINGS;
248
249
250
251  /**
252   * The serial version UID for this serializable class.
253   */
254  private static final long serialVersionUID = 1500219434086474893L;
255
256
257
258  // The set of requested attributes.
259  @NotNull private String[] attributes;
260
261  // Indicates whether to retrieve attribute types only or both types and
262  // values.
263  private boolean typesOnly;
264
265  // The behavior to use when aliases are encountered.
266  @NotNull private DereferencePolicy derefPolicy;
267
268  // The message ID from the last LDAP message sent from this request.
269  private int messageID = -1;
270
271  // The size limit for this search request.
272  private int sizeLimit;
273
274  // The time limit for this search request.
275  private int timeLimit;
276
277  // The parsed filter for this search request.
278  @NotNull private Filter filter;
279
280  // The queue that will be used to receive response messages from the server.
281  @NotNull private final LinkedBlockingQueue<LDAPResponse> responseQueue =
282       new LinkedBlockingQueue<>(50);
283
284  // The search result listener that should be used to return results
285  // interactively to the requester.
286  @Nullable private final SearchResultListener searchResultListener;
287
288  // The scope for this search request.
289  @NotNull private SearchScope scope;
290
291  // The base DN for this search request.
292  @NotNull private String baseDN;
293
294
295
296  /**
297   * Creates a new search request with the provided information.  Search result
298   * entries and references will be collected internally and included in the
299   * {@code SearchResult} object returned when search processing is completed.
300   *
301   * @param  baseDN      The base DN for the search request.  It must not be
302   *                     {@code null}.
303   * @param  scope       The scope that specifies the range of entries that
304   *                     should be examined for the search.
305   * @param  filter      The string representation of the filter to use to
306   *                     identify matching entries.  It must not be
307   *                     {@code null}.
308   * @param  attributes  The set of attributes that should be returned in
309   *                     matching entries.  It may be {@code null} or empty if
310   *                     the default attribute set (all user attributes) is to
311   *                     be requested.
312   *
313   * @throws  LDAPException  If the provided filter string cannot be parsed as
314   *                         an LDAP filter.
315   */
316  public SearchRequest(@NotNull final String baseDN,
317                       @NotNull final SearchScope scope,
318                       @NotNull final String filter,
319                       @Nullable final String... attributes)
320         throws LDAPException
321  {
322    this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false,
323         Filter.create(filter), attributes);
324  }
325
326
327
328  /**
329   * Creates a new search request with the provided information.  Search result
330   * entries and references will be collected internally and included in the
331   * {@code SearchResult} object returned when search processing is completed.
332   *
333   * @param  baseDN      The base DN for the search request.  It must not be
334   *                     {@code null}.
335   * @param  scope       The scope that specifies the range of entries that
336   *                     should be examined for the search.
337   * @param  filter      The string representation of the filter to use to
338   *                     identify matching entries.  It must not be
339   *                     {@code null}.
340   * @param  attributes  The set of attributes that should be returned in
341   *                     matching entries.  It may be {@code null} or empty if
342   *                     the default attribute set (all user attributes) is to
343   *                     be requested.
344   */
345  public SearchRequest(@NotNull final String baseDN,
346                       @NotNull final SearchScope scope,
347                       @NotNull final Filter filter,
348                       @Nullable final String... attributes)
349  {
350    this(null, null, baseDN, scope, DereferencePolicy.NEVER, 0, 0, false,
351         filter, attributes);
352  }
353
354
355
356  /**
357   * Creates a new search request with the provided information.  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    if (connection.synchronousMode())
1395    {
1396      @SuppressWarnings("deprecation")
1397      final boolean autoReconnect =
1398           connection.getConnectionOptions().autoReconnect();
1399      return processSync(connection, depth, autoReconnect);
1400    }
1401
1402    final long requestTime = System.nanoTime();
1403    processAsync(connection, null);
1404
1405    try
1406    {
1407      // Wait for and process the response.
1408      final ArrayList<SearchResultEntry> entryList;
1409      final ArrayList<SearchResultReference> referenceList;
1410      if (searchResultListener == null)
1411      {
1412        entryList     = new ArrayList<>(5);
1413        referenceList = new ArrayList<>(5);
1414      }
1415      else
1416      {
1417        entryList     = null;
1418        referenceList = null;
1419      }
1420
1421      int numEntries    = 0;
1422      int numReferences = 0;
1423      ResultCode intermediateResultCode = ResultCode.SUCCESS;
1424      final long responseTimeout = getResponseTimeoutMillis(connection);
1425      while (true)
1426      {
1427        final LDAPResponse response;
1428        try
1429        {
1430          if (responseTimeout > 0)
1431          {
1432            response =
1433                 responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
1434          }
1435          else
1436          {
1437            response = responseQueue.take();
1438          }
1439        }
1440        catch (final InterruptedException ie)
1441        {
1442          Debug.debugException(ie);
1443          Thread.currentThread().interrupt();
1444          throw new LDAPException(ResultCode.LOCAL_ERROR,
1445               ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie);
1446        }
1447
1448        if (response == null)
1449        {
1450          if (connection.getConnectionOptions().abandonOnTimeout())
1451          {
1452            connection.abandon(messageID);
1453          }
1454
1455          final SearchResult searchResult =
1456               new SearchResult(messageID, ResultCode.TIMEOUT,
1457                    ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID,
1458                         baseDN, scope.getName(), filter.toString(),
1459                         connection.getHostPort()),
1460                    null, null, entryList, referenceList, numEntries,
1461                    numReferences, null);
1462          throw new LDAPSearchException(searchResult);
1463        }
1464
1465        if (response instanceof ConnectionClosedResponse)
1466        {
1467          final ConnectionClosedResponse ccr =
1468               (ConnectionClosedResponse) response;
1469          final String message = ccr.getMessage();
1470          if (message == null)
1471          {
1472            // The connection was closed while waiting for the response.
1473            final SearchResult searchResult =
1474                 new SearchResult(messageID, ccr.getResultCode(),
1475                      ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1476                           connection.getHostPort(), toString()),
1477                      null, null, entryList, referenceList, numEntries,
1478                      numReferences, null);
1479            throw new LDAPSearchException(searchResult);
1480          }
1481          else
1482          {
1483            // The connection was closed while waiting for the response.
1484            final SearchResult searchResult =
1485                 new SearchResult(messageID, ccr.getResultCode(),
1486                      ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1487                           get(connection.getHostPort(), toString(), message),
1488                      null, null, entryList, referenceList, numEntries,
1489                      numReferences, null);
1490            throw new LDAPSearchException(searchResult);
1491          }
1492        }
1493        else if (response instanceof SearchResultEntry)
1494        {
1495          final SearchResultEntry searchEntry = (SearchResultEntry) response;
1496          numEntries++;
1497          if (searchResultListener == null)
1498          {
1499            entryList.add(searchEntry);
1500          }
1501          else
1502          {
1503            searchResultListener.searchEntryReturned(searchEntry);
1504          }
1505        }
1506        else if (response instanceof SearchResultReference)
1507        {
1508          final SearchResultReference searchReference =
1509               (SearchResultReference) response;
1510          if (followReferrals(connection))
1511          {
1512            final LDAPResult result = followSearchReference(messageID,
1513                 searchReference, connection, depth);
1514            if (! result.getResultCode().equals(ResultCode.SUCCESS))
1515            {
1516              // We couldn't follow the reference.  We don't want to fail the
1517              // entire search because of this right now, so treat it as if
1518              // referral following had not been enabled.  Also, set the
1519              // intermediate result code to match that of the result.
1520              numReferences++;
1521              if (searchResultListener == null)
1522              {
1523                referenceList.add(searchReference);
1524              }
1525              else
1526              {
1527                searchResultListener.searchReferenceReturned(searchReference);
1528              }
1529
1530              if (intermediateResultCode.equals(ResultCode.SUCCESS) &&
1531                 (result.getResultCode() != ResultCode.REFERRAL))
1532              {
1533                intermediateResultCode = result.getResultCode();
1534              }
1535            }
1536            else if (result instanceof SearchResult)
1537            {
1538              final SearchResult searchResult = (SearchResult) result;
1539              numEntries += searchResult.getEntryCount();
1540              if (searchResultListener == null)
1541              {
1542                entryList.addAll(searchResult.getSearchEntries());
1543              }
1544            }
1545          }
1546          else
1547          {
1548            numReferences++;
1549            if (searchResultListener == null)
1550            {
1551              referenceList.add(searchReference);
1552            }
1553            else
1554            {
1555              searchResultListener.searchReferenceReturned(searchReference);
1556            }
1557          }
1558        }
1559        else
1560        {
1561          connection.getConnectionStatistics().incrementNumSearchResponses(
1562               numEntries, numReferences,
1563               (System.nanoTime() - requestTime));
1564          SearchResult result = (SearchResult) response;
1565          result.setCounts(numEntries, entryList, numReferences, referenceList);
1566
1567          if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
1568              followReferrals(connection))
1569          {
1570            if (depth >=
1571                connection.getConnectionOptions().getReferralHopLimit())
1572            {
1573              return new SearchResult(messageID,
1574                                      ResultCode.REFERRAL_LIMIT_EXCEEDED,
1575                                      ERR_TOO_MANY_REFERRALS.get(),
1576                                      result.getMatchedDN(),
1577                                      result.getReferralURLs(), entryList,
1578                                      referenceList, numEntries,
1579                                      numReferences,
1580                                      result.getResponseControls());
1581            }
1582
1583            result = followReferral(result, connection, depth);
1584          }
1585
1586          if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
1587              (! intermediateResultCode.equals(ResultCode.SUCCESS)))
1588          {
1589            return new SearchResult(messageID, intermediateResultCode,
1590                                    result.getDiagnosticMessage(),
1591                                    result.getMatchedDN(),
1592                                    result.getReferralURLs(),
1593                                    entryList, referenceList, numEntries,
1594                                    numReferences,
1595                                    result.getResponseControls());
1596          }
1597
1598          return result;
1599        }
1600      }
1601    }
1602    finally
1603    {
1604      connection.deregisterResponseAcceptor(messageID);
1605    }
1606  }
1607
1608
1609
1610  /**
1611   * Sends this search request to the directory server over the provided
1612   * connection and returns the message ID for the request.
1613   *
1614   * @param  connection      The connection to use to communicate with the
1615   *                         directory server.
1616   * @param  resultListener  The async result listener that is to be notified
1617   *                         when the response is received.  It may be
1618   *                         {@code null} only if the result is to be processed
1619   *                         by this class.
1620   *
1621   * @return  The async request ID created for the operation, or {@code null} if
1622   *          the provided {@code resultListener} is {@code null} and the
1623   *          operation will not actually be processed asynchronously.
1624   *
1625   * @throws  LDAPException  If a problem occurs while sending the request.
1626   */
1627  @Nullable()
1628  AsyncRequestID processAsync(@NotNull final LDAPConnection connection,
1629                      @Nullable final AsyncSearchResultListener resultListener)
1630                 throws LDAPException
1631  {
1632    // Create the LDAP message.
1633    messageID = connection.nextMessageID();
1634    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
1635
1636
1637    // If the provided async result listener is {@code null}, then we'll use
1638    // this class as the message acceptor.  Otherwise, create an async helper
1639    // and use it as the message acceptor.
1640    final AsyncRequestID asyncRequestID;
1641    final long timeout = getResponseTimeoutMillis(connection);
1642    if (resultListener == null)
1643    {
1644      asyncRequestID = null;
1645      connection.registerResponseAcceptor(messageID, this);
1646    }
1647    else
1648    {
1649      final AsyncSearchHelper helper = new AsyncSearchHelper(connection,
1650           messageID, resultListener, getIntermediateResponseListener());
1651      connection.registerResponseAcceptor(messageID, helper);
1652      asyncRequestID = helper.getAsyncRequestID();
1653
1654      if (timeout > 0L)
1655      {
1656        final Timer timer = connection.getTimer();
1657        final AsyncTimeoutTimerTask timerTask =
1658             new AsyncTimeoutTimerTask(helper);
1659        timer.schedule(timerTask, timeout);
1660        asyncRequestID.setTimerTask(timerTask);
1661      }
1662    }
1663
1664
1665    // Send the request to the server.
1666    try
1667    {
1668      Debug.debugLDAPRequest(Level.INFO, this, messageID, connection);
1669
1670      final LDAPConnectionLogger logger =
1671           connection.getConnectionOptions().getConnectionLogger();
1672      if (logger != null)
1673      {
1674        logger.logSearchRequest(connection, messageID, this);
1675      }
1676
1677      connection.getConnectionStatistics().incrementNumSearchRequests();
1678      connection.sendMessage(message, timeout);
1679      return asyncRequestID;
1680    }
1681    catch (final LDAPException le)
1682    {
1683      Debug.debugException(le);
1684
1685      connection.deregisterResponseAcceptor(messageID);
1686      throw le;
1687    }
1688  }
1689
1690
1691
1692  /**
1693   * Processes this search operation in synchronous mode, in which the same
1694   * thread will send the request and read the response.
1695   *
1696   * @param  connection  The connection to use to communicate with the directory
1697   *                     server.
1698   * @param  depth       The current referral depth for this request.  It should
1699   *                     always be one for the initial request, and should only
1700   *                     be incremented when following referrals.
1701   * @param  allowRetry  Indicates whether the request may be re-tried on a
1702   *                     re-established connection if the initial attempt fails
1703   *                     in a way that indicates the connection is no longer
1704   *                     valid and autoReconnect is true.
1705   *
1706   * @return  An LDAP result object that provides information about the result
1707   *          of the search processing.
1708   *
1709   * @throws  LDAPException  If a problem occurs while sending the request or
1710   *                         reading the response.
1711   */
1712  @NotNull()
1713  private SearchResult processSync(@NotNull final LDAPConnection connection,
1714                                   final int depth, final boolean allowRetry)
1715          throws LDAPException
1716  {
1717    // Create the LDAP message.
1718    messageID = connection.nextMessageID();
1719    final LDAPMessage message =
1720         new LDAPMessage(messageID,  this, getControls());
1721
1722
1723    // Send the request to the server.
1724    final long responseTimeout = getResponseTimeoutMillis(connection);
1725    final long requestTime = System.nanoTime();
1726    Debug.debugLDAPRequest(Level.INFO, this, messageID, connection);
1727
1728    final LDAPConnectionLogger logger =
1729         connection.getConnectionOptions().getConnectionLogger();
1730    if (logger != null)
1731    {
1732      logger.logSearchRequest(connection, messageID, this);
1733    }
1734
1735    connection.getConnectionStatistics().incrementNumSearchRequests();
1736    try
1737    {
1738      connection.sendMessage(message, responseTimeout);
1739    }
1740    catch (final LDAPException le)
1741    {
1742      Debug.debugException(le);
1743
1744      if (allowRetry)
1745      {
1746        final SearchResult retryResult = reconnectAndRetry(connection, depth,
1747             le.getResultCode(), 0, 0);
1748        if (retryResult != null)
1749        {
1750          return retryResult;
1751        }
1752      }
1753
1754      throw le;
1755    }
1756
1757    final ArrayList<SearchResultEntry> entryList;
1758    final ArrayList<SearchResultReference> referenceList;
1759    if (searchResultListener == null)
1760    {
1761      entryList     = new ArrayList<>(5);
1762      referenceList = new ArrayList<>(5);
1763    }
1764    else
1765    {
1766      entryList     = null;
1767      referenceList = null;
1768    }
1769
1770    int numEntries    = 0;
1771    int numReferences = 0;
1772    ResultCode intermediateResultCode = ResultCode.SUCCESS;
1773    while (true)
1774    {
1775      final LDAPResponse response;
1776      try
1777      {
1778        response = connection.readResponse(messageID);
1779      }
1780      catch (final LDAPException le)
1781      {
1782        Debug.debugException(le);
1783
1784        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
1785            connection.getConnectionOptions().abandonOnTimeout())
1786        {
1787          connection.abandon(messageID);
1788        }
1789
1790        if (allowRetry)
1791        {
1792          final SearchResult retryResult = reconnectAndRetry(connection, depth,
1793               le.getResultCode(), numEntries, numReferences);
1794          if (retryResult != null)
1795          {
1796            return retryResult;
1797          }
1798        }
1799
1800        throw le;
1801      }
1802
1803      if (response == null)
1804      {
1805        if (connection.getConnectionOptions().abandonOnTimeout())
1806        {
1807          connection.abandon(messageID);
1808        }
1809
1810        throw new LDAPException(ResultCode.TIMEOUT,
1811             ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID, baseDN,
1812                  scope.getName(), filter.toString(),
1813                  connection.getHostPort()));
1814      }
1815      else if (response instanceof ConnectionClosedResponse)
1816      {
1817
1818        if (allowRetry)
1819        {
1820          final SearchResult retryResult = reconnectAndRetry(connection, depth,
1821               ResultCode.SERVER_DOWN, numEntries, numReferences);
1822          if (retryResult != null)
1823          {
1824            return retryResult;
1825          }
1826        }
1827
1828        final ConnectionClosedResponse ccr =
1829             (ConnectionClosedResponse) response;
1830        final String msg = ccr.getMessage();
1831        if (msg == null)
1832        {
1833          // The connection was closed while waiting for the response.
1834          final SearchResult searchResult =
1835               new SearchResult(messageID, ccr.getResultCode(),
1836                    ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get(
1837                         connection.getHostPort(), toString()),
1838                    null, null, entryList, referenceList, numEntries,
1839                    numReferences, null);
1840          throw new LDAPSearchException(searchResult);
1841        }
1842        else
1843        {
1844          // The connection was closed while waiting for the response.
1845          final SearchResult searchResult =
1846               new SearchResult(messageID, ccr.getResultCode(),
1847                    ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE.
1848                         get(connection.getHostPort(), toString(), msg),
1849                    null, null, entryList, referenceList, numEntries,
1850                    numReferences, null);
1851          throw new LDAPSearchException(searchResult);
1852        }
1853      }
1854      else if (response instanceof IntermediateResponse)
1855      {
1856        final IntermediateResponseListener listener =
1857             getIntermediateResponseListener();
1858        if (listener != null)
1859        {
1860          listener.intermediateResponseReturned(
1861               (IntermediateResponse) response);
1862        }
1863      }
1864      else if (response instanceof SearchResultEntry)
1865      {
1866        final SearchResultEntry searchEntry = (SearchResultEntry) response;
1867        numEntries++;
1868        if (searchResultListener == null)
1869        {
1870          entryList.add(searchEntry);
1871        }
1872        else
1873        {
1874          searchResultListener.searchEntryReturned(searchEntry);
1875        }
1876      }
1877      else if (response instanceof SearchResultReference)
1878      {
1879        final SearchResultReference searchReference =
1880             (SearchResultReference) response;
1881        if (followReferrals(connection))
1882        {
1883          final LDAPResult result = followSearchReference(messageID,
1884               searchReference, connection, depth);
1885          if (! result.getResultCode().equals(ResultCode.SUCCESS))
1886          {
1887            // We couldn't follow the reference.  We don't want to fail the
1888            // entire search because of this right now, so treat it as if
1889            // referral following had not been enabled.  Also, set the
1890            // intermediate result code to match that of the result.
1891            numReferences++;
1892            if (searchResultListener == null)
1893            {
1894              referenceList.add(searchReference);
1895            }
1896            else
1897            {
1898              searchResultListener.searchReferenceReturned(searchReference);
1899            }
1900
1901            if (intermediateResultCode.equals(ResultCode.SUCCESS) &&
1902               (result.getResultCode() != ResultCode.REFERRAL))
1903            {
1904              intermediateResultCode = result.getResultCode();
1905            }
1906          }
1907          else if (result instanceof SearchResult)
1908          {
1909            final SearchResult searchResult = (SearchResult) result;
1910            numEntries += searchResult.getEntryCount();
1911            if (searchResultListener == null)
1912            {
1913              entryList.addAll(searchResult.getSearchEntries());
1914            }
1915          }
1916        }
1917        else
1918        {
1919          numReferences++;
1920          if (searchResultListener == null)
1921          {
1922            referenceList.add(searchReference);
1923          }
1924          else
1925          {
1926            searchResultListener.searchReferenceReturned(searchReference);
1927          }
1928        }
1929      }
1930      else
1931      {
1932        final SearchResult result = (SearchResult) response;
1933        if (allowRetry)
1934        {
1935          final SearchResult retryResult = reconnectAndRetry(connection,
1936               depth, result.getResultCode(), numEntries, numReferences);
1937          if (retryResult != null)
1938          {
1939            return retryResult;
1940          }
1941        }
1942
1943        return handleResponse(connection, response, requestTime, depth,
1944                              numEntries, numReferences, entryList,
1945                              referenceList, intermediateResultCode);
1946      }
1947    }
1948  }
1949
1950
1951
1952  /**
1953   * Attempts to re-establish the connection and retry processing this request
1954   * on it.
1955   *
1956   * @param  connection     The connection to be re-established.
1957   * @param  depth          The current referral depth for this request.  It
1958   *                        should always be one for the initial request, and
1959   *                        should only be incremented when following referrals.
1960   * @param  resultCode     The result code for the previous operation attempt.
1961   * @param  numEntries     The number of search result entries already sent for
1962   *                        the search operation.
1963   * @param  numReferences  The number of search result references already sent
1964   *                        for the search operation.
1965   *
1966   * @return  The result from re-trying the search, or {@code null} if it could
1967   *          not be re-tried.
1968   */
1969  @Nullable()
1970  private SearchResult reconnectAndRetry(
1971                            @NotNull final LDAPConnection connection,
1972                            final int depth,
1973                            @NotNull final ResultCode resultCode,
1974                            final int numEntries,
1975                            final int numReferences)
1976  {
1977    try
1978    {
1979      // We will only want to retry for certain result codes that indicate a
1980      // connection problem.
1981      switch (resultCode.intValue())
1982      {
1983        case ResultCode.SERVER_DOWN_INT_VALUE:
1984        case ResultCode.DECODING_ERROR_INT_VALUE:
1985        case ResultCode.CONNECT_ERROR_INT_VALUE:
1986          // We want to try to re-establish the connection no matter what, but
1987          // we only want to retry the search if we haven't yet sent any
1988          // results.
1989          connection.reconnect();
1990          if ((numEntries == 0) && (numReferences == 0))
1991          {
1992            return processSync(connection, depth, false);
1993          }
1994          break;
1995      }
1996    }
1997    catch (final Exception e)
1998    {
1999      Debug.debugException(e);
2000    }
2001
2002    return null;
2003  }
2004
2005
2006
2007  /**
2008   * Performs the necessary processing for handling a response.
2009   *
2010   * @param  connection              The connection used to read the response.
2011   * @param  response                The response to be processed.
2012   * @param  requestTime             The time the request was sent to the
2013   *                                 server.
2014   * @param  depth                   The current referral depth for this
2015   *                                 request.  It should always be one for the
2016   *                                 initial request, and should only be
2017   *                                 incremented when following referrals.
2018   * @param  numEntries              The number of entries received from the
2019   *                                 server.
2020   * @param  numReferences           The number of references received from
2021   *                                 the server.
2022   * @param  entryList               The list of search result entries received
2023   *                                 from the server, if applicable.
2024   * @param  referenceList           The list of search result references
2025   *                                 received from the server, if applicable.
2026   * @param  intermediateResultCode  The intermediate result code so far for the
2027   *                                 search operation.
2028   *
2029   * @return  The search result.
2030   *
2031   * @throws  LDAPException  If a problem occurs.
2032   */
2033  @NotNull()
2034  private SearchResult handleResponse(@NotNull final LDAPConnection connection,
2035               @NotNull final LDAPResponse response, final long requestTime,
2036               final int depth, final int numEntries, final int numReferences,
2037               @Nullable final List<SearchResultEntry> entryList,
2038               @Nullable final List<SearchResultReference> referenceList,
2039               @NotNull final ResultCode intermediateResultCode)
2040          throws LDAPException
2041  {
2042    connection.getConnectionStatistics().incrementNumSearchResponses(
2043         numEntries, numReferences,
2044         (System.nanoTime() - requestTime));
2045    SearchResult result = (SearchResult) response;
2046    result.setCounts(numEntries, entryList, numReferences, referenceList);
2047
2048    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
2049        followReferrals(connection))
2050    {
2051      if (depth >=
2052          connection.getConnectionOptions().getReferralHopLimit())
2053      {
2054        return new SearchResult(messageID,
2055                                ResultCode.REFERRAL_LIMIT_EXCEEDED,
2056                                ERR_TOO_MANY_REFERRALS.get(),
2057                                result.getMatchedDN(),
2058                                result.getReferralURLs(), entryList,
2059                                referenceList, numEntries,
2060                                numReferences,
2061                                result.getResponseControls());
2062      }
2063
2064      result = followReferral(result, connection, depth);
2065    }
2066
2067    if ((result.getResultCode().equals(ResultCode.SUCCESS)) &&
2068        (! intermediateResultCode.equals(ResultCode.SUCCESS)))
2069    {
2070      return new SearchResult(messageID, intermediateResultCode,
2071                              result.getDiagnosticMessage(),
2072                              result.getMatchedDN(),
2073                              result.getReferralURLs(),
2074                              entryList, referenceList, numEntries,
2075                              numReferences,
2076                              result.getResponseControls());
2077    }
2078
2079    return result;
2080  }
2081
2082
2083
2084  /**
2085   * Attempts to follow a search result reference to continue a search in a
2086   * remote server.
2087   *
2088   * @param  messageID        The message ID for the LDAP message that is
2089   *                          associated with this result.
2090   * @param  searchReference  The search result reference to follow.
2091   * @param  connection       The connection on which the reference was
2092   *                          received.
2093   * @param  depth            The number of referrals followed in the course of
2094   *                          processing this request.
2095   *
2096   * @return  The result of attempting to follow the search result reference.
2097   *
2098   * @throws  LDAPException  If a problem occurs while attempting to establish
2099   *                         the referral connection, sending the request, or
2100   *                         reading the result.
2101   */
2102  @NotNull()
2103  private LDAPResult followSearchReference(final int messageID,
2104                          @NotNull final SearchResultReference searchReference,
2105                          @NotNull final LDAPConnection connection,
2106                          final int depth)
2107          throws LDAPException
2108  {
2109    for (final String urlString : searchReference.getReferralURLs())
2110    {
2111      try
2112      {
2113        final LDAPURL referralURL = new LDAPURL(urlString);
2114        final String host = referralURL.getHost();
2115
2116        if (host == null)
2117        {
2118          // We can't handle a referral in which there is no host.
2119          continue;
2120        }
2121
2122        final String requestBaseDN;
2123        if (referralURL.baseDNProvided())
2124        {
2125          requestBaseDN = referralURL.getBaseDN().toString();
2126        }
2127        else
2128        {
2129          requestBaseDN = baseDN;
2130        }
2131
2132        final SearchScope requestScope;
2133        if (referralURL.scopeProvided())
2134        {
2135          requestScope = referralURL.getScope();
2136        }
2137        else
2138        {
2139          requestScope = scope;
2140        }
2141
2142        final Filter requestFilter;
2143        if (referralURL.filterProvided())
2144        {
2145          requestFilter = referralURL.getFilter();
2146        }
2147        else
2148        {
2149          requestFilter = filter;
2150        }
2151
2152
2153        final SearchRequest searchRequest =
2154             new SearchRequest(searchResultListener, getControls(),
2155                               requestBaseDN, requestScope, derefPolicy,
2156                               sizeLimit, timeLimit, typesOnly, requestFilter,
2157                               attributes);
2158
2159        final LDAPConnection referralConn = getReferralConnector(connection).
2160             getReferralConnection(referralURL, connection);
2161
2162        try
2163        {
2164          return searchRequest.process(referralConn, depth+1);
2165        }
2166        finally
2167        {
2168          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
2169          referralConn.close();
2170        }
2171      }
2172      catch (final LDAPException le)
2173      {
2174        Debug.debugException(le);
2175
2176        if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
2177        {
2178          throw le;
2179        }
2180      }
2181    }
2182
2183    // If we've gotten here, then we could not follow any of the referral URLs,
2184    // so we'll create a failure result.
2185    return new SearchResult(messageID, ResultCode.REFERRAL, null, null,
2186                            searchReference.getReferralURLs(), 0, 0, null);
2187  }
2188
2189
2190
2191  /**
2192   * Attempts to follow a referral to perform an add operation in the target
2193   * server.
2194   *
2195   * @param  referralResult  The LDAP result object containing information about
2196   *                         the referral to follow.
2197   * @param  connection      The connection on which the referral was received.
2198   * @param  depth           The number of referrals followed in the course of
2199   *                         processing this request.
2200   *
2201   * @return  The result of attempting to process the add operation by following
2202   *          the referral.
2203   *
2204   * @throws  LDAPException  If a problem occurs while attempting to establish
2205   *                         the referral connection, sending the request, or
2206   *                         reading the result.
2207   */
2208  @NotNull()
2209  private SearchResult followReferral(
2210                            @NotNull final SearchResult referralResult,
2211                            @NotNull final LDAPConnection connection,
2212                            final int depth)
2213          throws LDAPException
2214  {
2215    for (final String urlString : referralResult.getReferralURLs())
2216    {
2217      try
2218      {
2219        final LDAPURL referralURL = new LDAPURL(urlString);
2220        final String host = referralURL.getHost();
2221
2222        if (host == null)
2223        {
2224          // We can't handle a referral in which there is no host.
2225          continue;
2226        }
2227
2228        final String requestBaseDN;
2229        if (referralURL.baseDNProvided())
2230        {
2231          requestBaseDN = referralURL.getBaseDN().toString();
2232        }
2233        else
2234        {
2235          requestBaseDN = baseDN;
2236        }
2237
2238        final SearchScope requestScope;
2239        if (referralURL.scopeProvided())
2240        {
2241          requestScope = referralURL.getScope();
2242        }
2243        else
2244        {
2245          requestScope = scope;
2246        }
2247
2248        final Filter requestFilter;
2249        if (referralURL.filterProvided())
2250        {
2251          requestFilter = referralURL.getFilter();
2252        }
2253        else
2254        {
2255          requestFilter = filter;
2256        }
2257
2258
2259        final SearchRequest searchRequest =
2260             new SearchRequest(searchResultListener, getControls(),
2261                               requestBaseDN, requestScope, derefPolicy,
2262                               sizeLimit, timeLimit, typesOnly, requestFilter,
2263                               attributes);
2264
2265        final LDAPConnection referralConn = getReferralConnector(connection).
2266             getReferralConnection(referralURL, connection);
2267        try
2268        {
2269          return searchRequest.process(referralConn, depth+1);
2270        }
2271        finally
2272        {
2273          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
2274          referralConn.close();
2275        }
2276      }
2277      catch (final LDAPException le)
2278      {
2279        Debug.debugException(le);
2280
2281        if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED))
2282        {
2283          throw le;
2284        }
2285      }
2286    }
2287
2288    // If we've gotten here, then we could not follow any of the referral URLs,
2289    // so we'll just return the original referral result.
2290    return referralResult;
2291  }
2292
2293
2294
2295  /**
2296   * {@inheritDoc}
2297   */
2298  @InternalUseOnly()
2299  @Override()
2300  public void responseReceived(@NotNull final LDAPResponse response)
2301         throws LDAPException
2302  {
2303    try
2304    {
2305      responseQueue.put(response);
2306    }
2307    catch (final Exception e)
2308    {
2309      Debug.debugException(e);
2310
2311      if (e instanceof InterruptedException)
2312      {
2313        Thread.currentThread().interrupt();
2314      }
2315
2316      throw new LDAPException(ResultCode.LOCAL_ERROR,
2317           ERR_EXCEPTION_HANDLING_RESPONSE.get(
2318                StaticUtils.getExceptionMessage(e)),
2319           e);
2320    }
2321  }
2322
2323
2324
2325  /**
2326   * {@inheritDoc}
2327   */
2328  @Override()
2329  public int getLastMessageID()
2330  {
2331    return messageID;
2332  }
2333
2334
2335
2336  /**
2337   * {@inheritDoc}
2338   */
2339  @Override()
2340  @NotNull()
2341  public OperationType getOperationType()
2342  {
2343    return OperationType.SEARCH;
2344  }
2345
2346
2347
2348  /**
2349   * {@inheritDoc}
2350   */
2351  @Override()
2352  @NotNull()
2353  public SearchRequest duplicate()
2354  {
2355    return duplicate(getControls());
2356  }
2357
2358
2359
2360  /**
2361   * {@inheritDoc}
2362   */
2363  @Override()
2364  @NotNull()
2365  public SearchRequest duplicate(@Nullable final Control[] controls)
2366  {
2367    final SearchRequest r = new SearchRequest(searchResultListener, controls,
2368         baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter,
2369         attributes);
2370    if (followReferralsInternal() != null)
2371    {
2372      r.setFollowReferrals(followReferralsInternal());
2373    }
2374
2375    if (getReferralConnectorInternal() != null)
2376    {
2377      r.setReferralConnector(getReferralConnectorInternal());
2378    }
2379
2380    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
2381
2382    return r;
2383  }
2384
2385
2386
2387  /**
2388   * {@inheritDoc}
2389   */
2390  @Override()
2391  public void toString(@NotNull final StringBuilder buffer)
2392  {
2393    buffer.append("SearchRequest(baseDN='");
2394    buffer.append(baseDN);
2395    buffer.append("', scope=");
2396    buffer.append(scope);
2397    buffer.append(", deref=");
2398    buffer.append(derefPolicy);
2399    buffer.append(", sizeLimit=");
2400    buffer.append(sizeLimit);
2401    buffer.append(", timeLimit=");
2402    buffer.append(timeLimit);
2403    buffer.append(", filter='");
2404    buffer.append(filter);
2405    buffer.append("', attrs={");
2406
2407    for (int i=0; i < attributes.length; i++)
2408    {
2409      if (i > 0)
2410      {
2411        buffer.append(", ");
2412      }
2413
2414      buffer.append(attributes[i]);
2415    }
2416    buffer.append('}');
2417
2418    final Control[] controls = getControls();
2419    if (controls.length > 0)
2420    {
2421      buffer.append(", controls={");
2422      for (int i=0; i < controls.length; i++)
2423      {
2424        if (i > 0)
2425        {
2426          buffer.append(", ");
2427        }
2428
2429        buffer.append(controls[i]);
2430      }
2431      buffer.append('}');
2432    }
2433
2434    buffer.append(')');
2435  }
2436
2437
2438
2439  /**
2440   * {@inheritDoc}
2441   */
2442  @Override()
2443  public void toCode(@NotNull final List<String> lineList,
2444                     @NotNull final String requestID,
2445                     final int indentSpaces, final boolean includeProcessing)
2446  {
2447    // Create the request variable.
2448    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(10);
2449    constructorArgs.add(ToCodeArgHelper.createString(baseDN, "Base DN"));
2450    constructorArgs.add(ToCodeArgHelper.createScope(scope, "Scope"));
2451    constructorArgs.add(ToCodeArgHelper.createDerefPolicy(derefPolicy,
2452         "Alias Dereference Policy"));
2453    constructorArgs.add(ToCodeArgHelper.createInteger(sizeLimit, "Size Limit"));
2454    constructorArgs.add(ToCodeArgHelper.createInteger(timeLimit, "Time Limit"));
2455    constructorArgs.add(ToCodeArgHelper.createBoolean(typesOnly, "Types Only"));
2456    constructorArgs.add(ToCodeArgHelper.createFilter(filter, "Filter"));
2457
2458    String comment = "Requested Attributes";
2459    for (final String s : attributes)
2460    {
2461      constructorArgs.add(ToCodeArgHelper.createString(s, comment));
2462      comment = null;
2463    }
2464
2465    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "SearchRequest",
2466         requestID + "Request", "new SearchRequest", constructorArgs);
2467
2468
2469    // If there are any controls, then add them to the request.
2470    for (final Control c : getControls())
2471    {
2472      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
2473           requestID + "Request.addControl",
2474           ToCodeArgHelper.createControl(c, null));
2475    }
2476
2477
2478    // Add lines for processing the request and obtaining the result.
2479    if (includeProcessing)
2480    {
2481      // Generate a string with the appropriate indent.
2482      final StringBuilder buffer = new StringBuilder();
2483      for (int i=0; i < indentSpaces; i++)
2484      {
2485        buffer.append(' ');
2486      }
2487      final String indent = buffer.toString();
2488
2489      lineList.add("");
2490      lineList.add(indent + "SearchResult " + requestID + "Result;");
2491      lineList.add(indent + "try");
2492      lineList.add(indent + '{');
2493      lineList.add(indent + "  " + requestID + "Result = connection.search(" +
2494           requestID + "Request);");
2495      lineList.add(indent + "  // The search was processed successfully.");
2496      lineList.add(indent + '}');
2497      lineList.add(indent + "catch (LDAPSearchException e)");
2498      lineList.add(indent + '{');
2499      lineList.add(indent + "  // The search failed.  Maybe the following " +
2500           "will help explain why.");
2501      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
2502      lineList.add(indent + "  String message = e.getMessage();");
2503      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
2504      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
2505      lineList.add(indent + "  Control[] responseControls = " +
2506           "e.getResponseControls();");
2507      lineList.add("");
2508      lineList.add(indent + "  // Even though there was an error, we may " +
2509           "have gotten some results.");
2510      lineList.add(indent + "  " + requestID + "Result = e.getSearchResult();");
2511      lineList.add(indent + '}');
2512      lineList.add("");
2513      lineList.add(indent + "// If there were results, then process them.");
2514      lineList.add(indent + "for (SearchResultEntry e : " + requestID +
2515           "Result.getSearchEntries())");
2516      lineList.add(indent + '{');
2517      lineList.add(indent + "  // Do something with the entry.");
2518      lineList.add(indent + '}');
2519    }
2520  }
2521}