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