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