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