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