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