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