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