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