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