001 /* 002 * Copyright 2007-2015 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005 /* 006 * Copyright (C) 2008-2015 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 @SuppressWarnings("deprecation") 1124 final boolean autoReconnect = 1125 connection.getConnectionOptions().autoReconnect(); 1126 return processSync(connection, depth, autoReconnect); 1127 } 1128 1129 final long requestTime = System.nanoTime(); 1130 processAsync(connection, null); 1131 1132 try 1133 { 1134 // Wait for and process the response. 1135 final ArrayList<SearchResultEntry> entryList; 1136 final ArrayList<SearchResultReference> referenceList; 1137 if (searchResultListener == null) 1138 { 1139 entryList = new ArrayList<SearchResultEntry>(5); 1140 referenceList = new ArrayList<SearchResultReference>(5); 1141 } 1142 else 1143 { 1144 entryList = null; 1145 referenceList = null; 1146 } 1147 1148 int numEntries = 0; 1149 int numReferences = 0; 1150 ResultCode intermediateResultCode = ResultCode.SUCCESS; 1151 final long responseTimeout = getResponseTimeoutMillis(connection); 1152 while (true) 1153 { 1154 final LDAPResponse response; 1155 try 1156 { 1157 if (responseTimeout > 0) 1158 { 1159 response = 1160 responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 1161 } 1162 else 1163 { 1164 response = responseQueue.take(); 1165 } 1166 } 1167 catch (InterruptedException ie) 1168 { 1169 debugException(ie); 1170 throw new LDAPException(ResultCode.LOCAL_ERROR, 1171 ERR_SEARCH_INTERRUPTED.get(connection.getHostPort()), ie); 1172 } 1173 1174 if (response == null) 1175 { 1176 if (connection.getConnectionOptions().abandonOnTimeout()) 1177 { 1178 connection.abandon(messageID); 1179 } 1180 1181 final SearchResult searchResult = 1182 new SearchResult(messageID, ResultCode.TIMEOUT, 1183 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID, 1184 baseDN, scope.getName(), filter.toString(), 1185 connection.getHostPort()), 1186 null, null, entryList, referenceList, numEntries, 1187 numReferences, null); 1188 throw new LDAPSearchException(searchResult); 1189 } 1190 1191 if (response instanceof ConnectionClosedResponse) 1192 { 1193 final ConnectionClosedResponse ccr = 1194 (ConnectionClosedResponse) response; 1195 final String message = ccr.getMessage(); 1196 if (message == null) 1197 { 1198 // The connection was closed while waiting for the response. 1199 final SearchResult searchResult = 1200 new SearchResult(messageID, ccr.getResultCode(), 1201 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get( 1202 connection.getHostPort(), toString()), 1203 null, null, entryList, referenceList, numEntries, 1204 numReferences, null); 1205 throw new LDAPSearchException(searchResult); 1206 } 1207 else 1208 { 1209 // The connection was closed while waiting for the response. 1210 final SearchResult searchResult = 1211 new SearchResult(messageID, ccr.getResultCode(), 1212 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE. 1213 get(connection.getHostPort(), toString(), message), 1214 null, null, entryList, referenceList, numEntries, 1215 numReferences, null); 1216 throw new LDAPSearchException(searchResult); 1217 } 1218 } 1219 else if (response instanceof SearchResultEntry) 1220 { 1221 final SearchResultEntry searchEntry = (SearchResultEntry) response; 1222 numEntries++; 1223 if (searchResultListener == null) 1224 { 1225 entryList.add(searchEntry); 1226 } 1227 else 1228 { 1229 searchResultListener.searchEntryReturned(searchEntry); 1230 } 1231 } 1232 else if (response instanceof SearchResultReference) 1233 { 1234 final SearchResultReference searchReference = 1235 (SearchResultReference) response; 1236 if (followReferrals(connection)) 1237 { 1238 final LDAPResult result = followSearchReference(messageID, 1239 searchReference, connection, depth); 1240 if (! result.getResultCode().equals(ResultCode.SUCCESS)) 1241 { 1242 // We couldn't follow the reference. We don't want to fail the 1243 // entire search because of this right now, so treat it as if 1244 // referral following had not been enabled. Also, set the 1245 // intermediate result code to match that of the result. 1246 numReferences++; 1247 if (searchResultListener == null) 1248 { 1249 referenceList.add(searchReference); 1250 } 1251 else 1252 { 1253 searchResultListener.searchReferenceReturned(searchReference); 1254 } 1255 1256 if (intermediateResultCode.equals(ResultCode.SUCCESS)) 1257 { 1258 intermediateResultCode = result.getResultCode(); 1259 } 1260 } 1261 else if (result instanceof SearchResult) 1262 { 1263 final SearchResult searchResult = (SearchResult) result; 1264 numEntries += searchResult.getEntryCount(); 1265 if (searchResultListener == null) 1266 { 1267 entryList.addAll(searchResult.getSearchEntries()); 1268 } 1269 } 1270 } 1271 else 1272 { 1273 numReferences++; 1274 if (searchResultListener == null) 1275 { 1276 referenceList.add(searchReference); 1277 } 1278 else 1279 { 1280 searchResultListener.searchReferenceReturned(searchReference); 1281 } 1282 } 1283 } 1284 else 1285 { 1286 connection.getConnectionStatistics().incrementNumSearchResponses( 1287 numEntries, numReferences, 1288 (System.nanoTime() - requestTime)); 1289 SearchResult result = (SearchResult) response; 1290 result.setCounts(numEntries, entryList, numReferences, referenceList); 1291 1292 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 1293 followReferrals(connection)) 1294 { 1295 if (depth >= 1296 connection.getConnectionOptions().getReferralHopLimit()) 1297 { 1298 return new SearchResult(messageID, 1299 ResultCode.REFERRAL_LIMIT_EXCEEDED, 1300 ERR_TOO_MANY_REFERRALS.get(), 1301 result.getMatchedDN(), 1302 result.getReferralURLs(), entryList, 1303 referenceList, numEntries, 1304 numReferences, 1305 result.getResponseControls()); 1306 } 1307 1308 result = followReferral(result, connection, depth); 1309 } 1310 1311 if ((result.getResultCode().equals(ResultCode.SUCCESS)) && 1312 (! intermediateResultCode.equals(ResultCode.SUCCESS))) 1313 { 1314 return new SearchResult(messageID, intermediateResultCode, 1315 result.getDiagnosticMessage(), 1316 result.getMatchedDN(), 1317 result.getReferralURLs(), 1318 entryList, referenceList, numEntries, 1319 numReferences, 1320 result.getResponseControls()); 1321 } 1322 1323 return result; 1324 } 1325 } 1326 } 1327 finally 1328 { 1329 connection.deregisterResponseAcceptor(messageID); 1330 } 1331 } 1332 1333 1334 1335 /** 1336 * Sends this search request to the directory server over the provided 1337 * connection and returns the message ID for the request. 1338 * 1339 * @param connection The connection to use to communicate with the 1340 * directory server. 1341 * @param resultListener The async result listener that is to be notified 1342 * when the response is received. It may be 1343 * {@code null} only if the result is to be processed 1344 * by this class. 1345 * 1346 * @return The async request ID created for the operation, or {@code null} if 1347 * the provided {@code resultListener} is {@code null} and the 1348 * operation will not actually be processed asynchronously. 1349 * 1350 * @throws LDAPException If a problem occurs while sending the request. 1351 */ 1352 AsyncRequestID processAsync(final LDAPConnection connection, 1353 final AsyncSearchResultListener resultListener) 1354 throws LDAPException 1355 { 1356 // Create the LDAP message. 1357 messageID = connection.nextMessageID(); 1358 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 1359 1360 1361 // If the provided async result listener is {@code null}, then we'll use 1362 // this class as the message acceptor. Otherwise, create an async helper 1363 // and use it as the message acceptor. 1364 final AsyncRequestID asyncRequestID; 1365 if (resultListener == null) 1366 { 1367 asyncRequestID = null; 1368 connection.registerResponseAcceptor(messageID, this); 1369 } 1370 else 1371 { 1372 final AsyncSearchHelper helper = new AsyncSearchHelper(connection, 1373 messageID, resultListener, getIntermediateResponseListener()); 1374 connection.registerResponseAcceptor(messageID, helper); 1375 asyncRequestID = helper.getAsyncRequestID(); 1376 1377 final long timeout = getResponseTimeoutMillis(connection); 1378 if (timeout > 0L) 1379 { 1380 final Timer timer = connection.getTimer(); 1381 final AsyncTimeoutTimerTask timerTask = 1382 new AsyncTimeoutTimerTask(helper); 1383 timer.schedule(timerTask, timeout); 1384 asyncRequestID.setTimerTask(timerTask); 1385 } 1386 } 1387 1388 1389 // Send the request to the server. 1390 try 1391 { 1392 debugLDAPRequest(this); 1393 connection.getConnectionStatistics().incrementNumSearchRequests(); 1394 connection.sendMessage(message); 1395 return asyncRequestID; 1396 } 1397 catch (LDAPException le) 1398 { 1399 debugException(le); 1400 1401 connection.deregisterResponseAcceptor(messageID); 1402 throw le; 1403 } 1404 } 1405 1406 1407 1408 /** 1409 * Processes this search operation in synchronous mode, in which the same 1410 * thread will send the request and read the response. 1411 * 1412 * @param connection The connection to use to communicate with the directory 1413 * server. 1414 * @param depth The current referral depth for this request. It should 1415 * always be one for the initial request, and should only 1416 * be incremented when following referrals. 1417 * @param allowRetry Indicates whether the request may be re-tried on a 1418 * re-established connection if the initial attempt fails 1419 * in a way that indicates the connection is no longer 1420 * valid and autoReconnect is true. 1421 * 1422 * @return An LDAP result object that provides information about the result 1423 * of the search processing. 1424 * 1425 * @throws LDAPException If a problem occurs while sending the request or 1426 * reading the response. 1427 */ 1428 private SearchResult processSync(final LDAPConnection connection, 1429 final int depth, final boolean allowRetry) 1430 throws LDAPException 1431 { 1432 // Create the LDAP message. 1433 messageID = connection.nextMessageID(); 1434 final LDAPMessage message = 1435 new LDAPMessage(messageID, this, getControls()); 1436 1437 1438 // Set the appropriate timeout on the socket. 1439 final long responseTimeout = getResponseTimeoutMillis(connection); 1440 try 1441 { 1442 connection.getConnectionInternals(true).getSocket().setSoTimeout( 1443 (int) responseTimeout); 1444 } 1445 catch (Exception e) 1446 { 1447 debugException(e); 1448 } 1449 1450 1451 // Send the request to the server. 1452 final long requestTime = System.nanoTime(); 1453 debugLDAPRequest(this); 1454 connection.getConnectionStatistics().incrementNumSearchRequests(); 1455 try 1456 { 1457 connection.sendMessage(message); 1458 } 1459 catch (final LDAPException le) 1460 { 1461 debugException(le); 1462 1463 if (allowRetry) 1464 { 1465 final SearchResult retryResult = reconnectAndRetry(connection, depth, 1466 le.getResultCode(), 0, 0); 1467 if (retryResult != null) 1468 { 1469 return retryResult; 1470 } 1471 } 1472 1473 throw le; 1474 } 1475 1476 final ArrayList<SearchResultEntry> entryList; 1477 final ArrayList<SearchResultReference> referenceList; 1478 if (searchResultListener == null) 1479 { 1480 entryList = new ArrayList<SearchResultEntry>(5); 1481 referenceList = new ArrayList<SearchResultReference>(5); 1482 } 1483 else 1484 { 1485 entryList = null; 1486 referenceList = null; 1487 } 1488 1489 int numEntries = 0; 1490 int numReferences = 0; 1491 ResultCode intermediateResultCode = ResultCode.SUCCESS; 1492 while (true) 1493 { 1494 final LDAPResponse response; 1495 try 1496 { 1497 response = connection.readResponse(messageID); 1498 } 1499 catch (final LDAPException le) 1500 { 1501 debugException(le); 1502 1503 if ((le.getResultCode() == ResultCode.TIMEOUT) && 1504 connection.getConnectionOptions().abandonOnTimeout()) 1505 { 1506 connection.abandon(messageID); 1507 } 1508 1509 if (allowRetry) 1510 { 1511 final SearchResult retryResult = reconnectAndRetry(connection, depth, 1512 le.getResultCode(), numEntries, numReferences); 1513 if (retryResult != null) 1514 { 1515 return retryResult; 1516 } 1517 } 1518 1519 throw le; 1520 } 1521 1522 if (response == null) 1523 { 1524 if (connection.getConnectionOptions().abandonOnTimeout()) 1525 { 1526 connection.abandon(messageID); 1527 } 1528 1529 throw new LDAPException(ResultCode.TIMEOUT, 1530 ERR_SEARCH_CLIENT_TIMEOUT.get(responseTimeout, messageID, baseDN, 1531 scope.getName(), filter.toString(), 1532 connection.getHostPort())); 1533 } 1534 else if (response instanceof ConnectionClosedResponse) 1535 { 1536 1537 if (allowRetry) 1538 { 1539 final SearchResult retryResult = reconnectAndRetry(connection, depth, 1540 ResultCode.SERVER_DOWN, numEntries, numReferences); 1541 if (retryResult != null) 1542 { 1543 return retryResult; 1544 } 1545 } 1546 1547 final ConnectionClosedResponse ccr = 1548 (ConnectionClosedResponse) response; 1549 final String msg = ccr.getMessage(); 1550 if (msg == null) 1551 { 1552 // The connection was closed while waiting for the response. 1553 final SearchResult searchResult = 1554 new SearchResult(messageID, ccr.getResultCode(), 1555 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE.get( 1556 connection.getHostPort(), toString()), 1557 null, null, entryList, referenceList, numEntries, 1558 numReferences, null); 1559 throw new LDAPSearchException(searchResult); 1560 } 1561 else 1562 { 1563 // The connection was closed while waiting for the response. 1564 final SearchResult searchResult = 1565 new SearchResult(messageID, ccr.getResultCode(), 1566 ERR_CONN_CLOSED_WAITING_FOR_SEARCH_RESPONSE_WITH_MESSAGE. 1567 get(connection.getHostPort(), toString(), msg), 1568 null, null, entryList, referenceList, numEntries, 1569 numReferences, null); 1570 throw new LDAPSearchException(searchResult); 1571 } 1572 } 1573 else if (response instanceof IntermediateResponse) 1574 { 1575 final IntermediateResponseListener listener = 1576 getIntermediateResponseListener(); 1577 if (listener != null) 1578 { 1579 listener.intermediateResponseReturned( 1580 (IntermediateResponse) response); 1581 } 1582 } 1583 else if (response instanceof SearchResultEntry) 1584 { 1585 final SearchResultEntry searchEntry = (SearchResultEntry) response; 1586 numEntries++; 1587 if (searchResultListener == null) 1588 { 1589 entryList.add(searchEntry); 1590 } 1591 else 1592 { 1593 searchResultListener.searchEntryReturned(searchEntry); 1594 } 1595 } 1596 else if (response instanceof SearchResultReference) 1597 { 1598 final SearchResultReference searchReference = 1599 (SearchResultReference) response; 1600 if (followReferrals(connection)) 1601 { 1602 final LDAPResult result = followSearchReference(messageID, 1603 searchReference, connection, depth); 1604 if (! result.getResultCode().equals(ResultCode.SUCCESS)) 1605 { 1606 // We couldn't follow the reference. We don't want to fail the 1607 // entire search because of this right now, so treat it as if 1608 // referral following had not been enabled. Also, set the 1609 // intermediate result code to match that of the result. 1610 numReferences++; 1611 if (searchResultListener == null) 1612 { 1613 referenceList.add(searchReference); 1614 } 1615 else 1616 { 1617 searchResultListener.searchReferenceReturned(searchReference); 1618 } 1619 1620 if (intermediateResultCode.equals(ResultCode.SUCCESS)) 1621 { 1622 intermediateResultCode = result.getResultCode(); 1623 } 1624 } 1625 else if (result instanceof SearchResult) 1626 { 1627 final SearchResult searchResult = (SearchResult) result; 1628 numEntries += searchResult.getEntryCount(); 1629 if (searchResultListener == null) 1630 { 1631 entryList.addAll(searchResult.getSearchEntries()); 1632 } 1633 } 1634 } 1635 else 1636 { 1637 numReferences++; 1638 if (searchResultListener == null) 1639 { 1640 referenceList.add(searchReference); 1641 } 1642 else 1643 { 1644 searchResultListener.searchReferenceReturned(searchReference); 1645 } 1646 } 1647 } 1648 else 1649 { 1650 final SearchResult result = (SearchResult) response; 1651 if (allowRetry) 1652 { 1653 final SearchResult retryResult = reconnectAndRetry(connection, 1654 depth, result.getResultCode(), numEntries, numReferences); 1655 if (retryResult != null) 1656 { 1657 return retryResult; 1658 } 1659 } 1660 1661 return handleResponse(connection, response, requestTime, depth, 1662 numEntries, numReferences, entryList, 1663 referenceList, intermediateResultCode); 1664 } 1665 } 1666 } 1667 1668 1669 1670 /** 1671 * Attempts to re-establish the connection and retry processing this request 1672 * on it. 1673 * 1674 * @param connection The connection to be re-established. 1675 * @param depth The current referral depth for this request. It 1676 * should always be one for the initial request, and 1677 * should only be incremented when following referrals. 1678 * @param resultCode The result code for the previous operation attempt. 1679 * @param numEntries The number of search result entries already sent for 1680 * the search operation. 1681 * @param numReferences The number of search result references already sent 1682 * for the search operation. 1683 * 1684 * @return The result from re-trying the search, or {@code null} if it could 1685 * not be re-tried. 1686 */ 1687 private SearchResult reconnectAndRetry(final LDAPConnection connection, 1688 final int depth, 1689 final ResultCode resultCode, 1690 final int numEntries, 1691 final int numReferences) 1692 { 1693 try 1694 { 1695 // We will only want to retry for certain result codes that indicate a 1696 // connection problem. 1697 switch (resultCode.intValue()) 1698 { 1699 case ResultCode.SERVER_DOWN_INT_VALUE: 1700 case ResultCode.DECODING_ERROR_INT_VALUE: 1701 case ResultCode.CONNECT_ERROR_INT_VALUE: 1702 // We want to try to re-establish the connection no matter what, but 1703 // we only want to retry the search if we haven't yet sent any 1704 // results. 1705 connection.reconnect(); 1706 if ((numEntries == 0) && (numReferences == 0)) 1707 { 1708 return processSync(connection, depth, false); 1709 } 1710 break; 1711 } 1712 } 1713 catch (final Exception e) 1714 { 1715 debugException(e); 1716 } 1717 1718 return null; 1719 } 1720 1721 1722 1723 /** 1724 * Performs the necessary processing for handling a response. 1725 * 1726 * @param connection The connection used to read the response. 1727 * @param response The response to be processed. 1728 * @param requestTime The time the request was sent to the 1729 * server. 1730 * @param depth The current referral depth for this 1731 * request. It should always be one for the 1732 * initial request, and should only be 1733 * incremented when following referrals. 1734 * @param numEntries The number of entries received from the 1735 * server. 1736 * @param numReferences The number of references received from 1737 * the server. 1738 * @param entryList The list of search result entries received 1739 * from the server, if applicable. 1740 * @param referenceList The list of search result references 1741 * received from the server, if applicable. 1742 * @param intermediateResultCode The intermediate result code so far for the 1743 * search operation. 1744 * 1745 * @return The search result. 1746 * 1747 * @throws LDAPException If a problem occurs. 1748 */ 1749 private SearchResult handleResponse(final LDAPConnection connection, 1750 final LDAPResponse response, final long requestTime, 1751 final int depth, final int numEntries, final int numReferences, 1752 final List<SearchResultEntry> entryList, 1753 final List<SearchResultReference> referenceList, 1754 final ResultCode intermediateResultCode) 1755 throws LDAPException 1756 { 1757 connection.getConnectionStatistics().incrementNumSearchResponses( 1758 numEntries, numReferences, 1759 (System.nanoTime() - requestTime)); 1760 SearchResult result = (SearchResult) response; 1761 result.setCounts(numEntries, entryList, numReferences, referenceList); 1762 1763 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 1764 followReferrals(connection)) 1765 { 1766 if (depth >= 1767 connection.getConnectionOptions().getReferralHopLimit()) 1768 { 1769 return new SearchResult(messageID, 1770 ResultCode.REFERRAL_LIMIT_EXCEEDED, 1771 ERR_TOO_MANY_REFERRALS.get(), 1772 result.getMatchedDN(), 1773 result.getReferralURLs(), entryList, 1774 referenceList, numEntries, 1775 numReferences, 1776 result.getResponseControls()); 1777 } 1778 1779 result = followReferral(result, connection, depth); 1780 } 1781 1782 if ((result.getResultCode().equals(ResultCode.SUCCESS)) && 1783 (! intermediateResultCode.equals(ResultCode.SUCCESS))) 1784 { 1785 return new SearchResult(messageID, intermediateResultCode, 1786 result.getDiagnosticMessage(), 1787 result.getMatchedDN(), 1788 result.getReferralURLs(), 1789 entryList, referenceList, numEntries, 1790 numReferences, 1791 result.getResponseControls()); 1792 } 1793 1794 return result; 1795 } 1796 1797 1798 1799 /** 1800 * Attempts to follow a search result reference to continue a search in a 1801 * remote server. 1802 * 1803 * @param messageID The message ID for the LDAP message that is 1804 * associated with this result. 1805 * @param searchReference The search result reference to follow. 1806 * @param connection The connection on which the reference was 1807 * received. 1808 * @param depth The number of referrals followed in the course of 1809 * processing this request. 1810 * 1811 * @return The result of attempting to follow the search result reference. 1812 * 1813 * @throws LDAPException If a problem occurs while attempting to establish 1814 * the referral connection, sending the request, or 1815 * reading the result. 1816 */ 1817 private LDAPResult followSearchReference(final int messageID, 1818 final SearchResultReference searchReference, 1819 final LDAPConnection connection, final int depth) 1820 throws LDAPException 1821 { 1822 for (final String urlString : searchReference.getReferralURLs()) 1823 { 1824 try 1825 { 1826 final LDAPURL referralURL = new LDAPURL(urlString); 1827 final String host = referralURL.getHost(); 1828 1829 if (host == null) 1830 { 1831 // We can't handle a referral in which there is no host. 1832 continue; 1833 } 1834 1835 final String requestBaseDN; 1836 if (referralURL.baseDNProvided()) 1837 { 1838 requestBaseDN = referralURL.getBaseDN().toString(); 1839 } 1840 else 1841 { 1842 requestBaseDN = baseDN; 1843 } 1844 1845 final SearchScope requestScope; 1846 if (referralURL.scopeProvided()) 1847 { 1848 requestScope = referralURL.getScope(); 1849 } 1850 else 1851 { 1852 requestScope = scope; 1853 } 1854 1855 final Filter requestFilter; 1856 if (referralURL.filterProvided()) 1857 { 1858 requestFilter = referralURL.getFilter(); 1859 } 1860 else 1861 { 1862 requestFilter = filter; 1863 } 1864 1865 1866 final SearchRequest searchRequest = 1867 new SearchRequest(searchResultListener, getControls(), 1868 requestBaseDN, requestScope, derefPolicy, 1869 sizeLimit, timeLimit, typesOnly, requestFilter, 1870 attributes); 1871 1872 final LDAPConnection referralConn = connection.getReferralConnector(). 1873 getReferralConnection(referralURL, connection); 1874 1875 try 1876 { 1877 return searchRequest.process(referralConn, depth+1); 1878 } 1879 finally 1880 { 1881 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1882 referralConn.close(); 1883 } 1884 } 1885 catch (LDAPException le) 1886 { 1887 debugException(le); 1888 1889 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED)) 1890 { 1891 throw le; 1892 } 1893 } 1894 } 1895 1896 // If we've gotten here, then we could not follow any of the referral URLs, 1897 // so we'll create a failure result. 1898 return new SearchResult(messageID, ResultCode.REFERRAL, null, null, 1899 searchReference.getReferralURLs(), 0, 0, null); 1900 } 1901 1902 1903 1904 /** 1905 * Attempts to follow a referral to perform an add operation in the target 1906 * server. 1907 * 1908 * @param referralResult The LDAP result object containing information about 1909 * the referral to follow. 1910 * @param connection The connection on which the referral was received. 1911 * @param depth The number of referrals followed in the course of 1912 * processing this request. 1913 * 1914 * @return The result of attempting to process the add operation by following 1915 * the referral. 1916 * 1917 * @throws LDAPException If a problem occurs while attempting to establish 1918 * the referral connection, sending the request, or 1919 * reading the result. 1920 */ 1921 private SearchResult followReferral(final SearchResult referralResult, 1922 final LDAPConnection connection, 1923 final int depth) 1924 throws LDAPException 1925 { 1926 for (final String urlString : referralResult.getReferralURLs()) 1927 { 1928 try 1929 { 1930 final LDAPURL referralURL = new LDAPURL(urlString); 1931 final String host = referralURL.getHost(); 1932 1933 if (host == null) 1934 { 1935 // We can't handle a referral in which there is no host. 1936 continue; 1937 } 1938 1939 final String requestBaseDN; 1940 if (referralURL.baseDNProvided()) 1941 { 1942 requestBaseDN = referralURL.getBaseDN().toString(); 1943 } 1944 else 1945 { 1946 requestBaseDN = baseDN; 1947 } 1948 1949 final SearchScope requestScope; 1950 if (referralURL.scopeProvided()) 1951 { 1952 requestScope = referralURL.getScope(); 1953 } 1954 else 1955 { 1956 requestScope = scope; 1957 } 1958 1959 final Filter requestFilter; 1960 if (referralURL.filterProvided()) 1961 { 1962 requestFilter = referralURL.getFilter(); 1963 } 1964 else 1965 { 1966 requestFilter = filter; 1967 } 1968 1969 1970 final SearchRequest searchRequest = 1971 new SearchRequest(searchResultListener, getControls(), 1972 requestBaseDN, requestScope, derefPolicy, 1973 sizeLimit, timeLimit, typesOnly, requestFilter, 1974 attributes); 1975 1976 final LDAPConnection referralConn = connection.getReferralConnector(). 1977 getReferralConnection(referralURL, connection); 1978 try 1979 { 1980 return searchRequest.process(referralConn, depth+1); 1981 } 1982 finally 1983 { 1984 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1985 referralConn.close(); 1986 } 1987 } 1988 catch (LDAPException le) 1989 { 1990 debugException(le); 1991 1992 if (le.getResultCode().equals(ResultCode.REFERRAL_LIMIT_EXCEEDED)) 1993 { 1994 throw le; 1995 } 1996 } 1997 } 1998 1999 // If we've gotten here, then we could not follow any of the referral URLs, 2000 // so we'll just return the original referral result. 2001 return referralResult; 2002 } 2003 2004 2005 2006 /** 2007 * {@inheritDoc} 2008 */ 2009 @InternalUseOnly() 2010 public void responseReceived(final LDAPResponse response) 2011 throws LDAPException 2012 { 2013 try 2014 { 2015 responseQueue.put(response); 2016 } 2017 catch (Exception e) 2018 { 2019 debugException(e); 2020 throw new LDAPException(ResultCode.LOCAL_ERROR, 2021 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 2022 } 2023 } 2024 2025 2026 2027 /** 2028 * {@inheritDoc} 2029 */ 2030 @Override() 2031 public int getLastMessageID() 2032 { 2033 return messageID; 2034 } 2035 2036 2037 2038 /** 2039 * {@inheritDoc} 2040 */ 2041 @Override() 2042 public OperationType getOperationType() 2043 { 2044 return OperationType.SEARCH; 2045 } 2046 2047 2048 2049 /** 2050 * {@inheritDoc} 2051 */ 2052 public SearchRequest duplicate() 2053 { 2054 return duplicate(getControls()); 2055 } 2056 2057 2058 2059 /** 2060 * {@inheritDoc} 2061 */ 2062 public SearchRequest duplicate(final Control[] controls) 2063 { 2064 final SearchRequest r = new SearchRequest(searchResultListener, controls, 2065 baseDN, scope, derefPolicy, sizeLimit, timeLimit, typesOnly, filter, 2066 attributes); 2067 if (followReferralsInternal() != null) 2068 { 2069 r.setFollowReferrals(followReferralsInternal()); 2070 } 2071 2072 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 2073 2074 return r; 2075 } 2076 2077 2078 2079 /** 2080 * {@inheritDoc} 2081 */ 2082 @Override() 2083 public void toString(final StringBuilder buffer) 2084 { 2085 buffer.append("SearchRequest(baseDN='"); 2086 buffer.append(baseDN); 2087 buffer.append("', scope="); 2088 buffer.append(scope); 2089 buffer.append(", deref="); 2090 buffer.append(derefPolicy); 2091 buffer.append(", sizeLimit="); 2092 buffer.append(sizeLimit); 2093 buffer.append(", timeLimit="); 2094 buffer.append(timeLimit); 2095 buffer.append(", filter='"); 2096 buffer.append(filter); 2097 buffer.append("', attrs={"); 2098 2099 for (int i=0; i < attributes.length; i++) 2100 { 2101 if (i > 0) 2102 { 2103 buffer.append(", "); 2104 } 2105 2106 buffer.append(attributes[i]); 2107 } 2108 buffer.append('}'); 2109 2110 final Control[] controls = getControls(); 2111 if (controls.length > 0) 2112 { 2113 buffer.append(", controls={"); 2114 for (int i=0; i < controls.length; i++) 2115 { 2116 if (i > 0) 2117 { 2118 buffer.append(", "); 2119 } 2120 2121 buffer.append(controls[i]); 2122 } 2123 buffer.append('}'); 2124 } 2125 2126 buffer.append(')'); 2127 } 2128 2129 2130 2131 /** 2132 * {@inheritDoc} 2133 */ 2134 public void toCode(final List<String> lineList, final String requestID, 2135 final int indentSpaces, final boolean includeProcessing) 2136 { 2137 // Create the request variable. 2138 final ArrayList<ToCodeArgHelper> constructorArgs = 2139 new ArrayList<ToCodeArgHelper>(10); 2140 constructorArgs.add(ToCodeArgHelper.createString(baseDN, "Base DN")); 2141 constructorArgs.add(ToCodeArgHelper.createScope(scope, "Scope")); 2142 constructorArgs.add(ToCodeArgHelper.createDerefPolicy(derefPolicy, 2143 "Alias Dereference Policy")); 2144 constructorArgs.add(ToCodeArgHelper.createInteger(sizeLimit, "Size Limit")); 2145 constructorArgs.add(ToCodeArgHelper.createInteger(timeLimit, "Time Limit")); 2146 constructorArgs.add(ToCodeArgHelper.createBoolean(typesOnly, "Types Only")); 2147 constructorArgs.add(ToCodeArgHelper.createFilter(filter, "Filter")); 2148 2149 String comment = "Requested Attributes"; 2150 for (final String s : attributes) 2151 { 2152 constructorArgs.add(ToCodeArgHelper.createString(s, comment)); 2153 comment = null; 2154 } 2155 2156 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "SearchRequest", 2157 requestID + "Request", "new SearchRequest", constructorArgs); 2158 2159 2160 // If there are any controls, then add them to the request. 2161 for (final Control c : getControls()) 2162 { 2163 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2164 requestID + "Request.addControl", 2165 ToCodeArgHelper.createControl(c, null)); 2166 } 2167 2168 2169 // Add lines for processing the request and obtaining the result. 2170 if (includeProcessing) 2171 { 2172 // Generate a string with the appropriate indent. 2173 final StringBuilder buffer = new StringBuilder(); 2174 for (int i=0; i < indentSpaces; i++) 2175 { 2176 buffer.append(' '); 2177 } 2178 final String indent = buffer.toString(); 2179 2180 lineList.add(""); 2181 lineList.add(indent + "SearchResult " + requestID + "Result;"); 2182 lineList.add(indent + "try"); 2183 lineList.add(indent + '{'); 2184 lineList.add(indent + " " + requestID + "Result = connection.search(" + 2185 requestID + "Request);"); 2186 lineList.add(indent + " // The search was processed successfully."); 2187 lineList.add(indent + '}'); 2188 lineList.add(indent + "catch (LDAPSearchException e)"); 2189 lineList.add(indent + '{'); 2190 lineList.add(indent + " // The search failed. Maybe the following " + 2191 "will help explain why."); 2192 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 2193 lineList.add(indent + " String message = e.getMessage();"); 2194 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 2195 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 2196 lineList.add(indent + " Control[] responseControls = " + 2197 "e.getResponseControls();"); 2198 lineList.add(""); 2199 lineList.add(indent + " // Even though there was an error, we may " + 2200 "have gotten some results."); 2201 lineList.add(indent + " " + requestID + "Result = e.getSearchResult();"); 2202 lineList.add(indent + '}'); 2203 lineList.add(""); 2204 lineList.add(indent + "// If there were results, then process them."); 2205 lineList.add(indent + "for (SearchResultEntry e : " + requestID + 2206 "Result.getSearchEntries())"); 2207 lineList.add(indent + '{'); 2208 lineList.add(indent + " // Do something with the entry."); 2209 lineList.add(indent + '}'); 2210 } 2211 } 2212 }