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