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