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