001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-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) 2008-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.examples; 037 038 039 040import java.io.OutputStream; 041import java.text.SimpleDateFormat; 042import java.util.Date; 043import java.util.LinkedHashMap; 044import java.util.List; 045 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.DereferencePolicy; 048import com.unboundid.ldap.sdk.Filter; 049import com.unboundid.ldap.sdk.LDAPConnection; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.ResultCode; 052import com.unboundid.ldap.sdk.SearchRequest; 053import com.unboundid.ldap.sdk.SearchResult; 054import com.unboundid.ldap.sdk.SearchResultEntry; 055import com.unboundid.ldap.sdk.SearchResultListener; 056import com.unboundid.ldap.sdk.SearchResultReference; 057import com.unboundid.ldap.sdk.SearchScope; 058import com.unboundid.ldap.sdk.Version; 059import com.unboundid.util.Debug; 060import com.unboundid.util.LDAPCommandLineTool; 061import com.unboundid.util.NotNull; 062import com.unboundid.util.Nullable; 063import com.unboundid.util.StaticUtils; 064import com.unboundid.util.ThreadSafety; 065import com.unboundid.util.ThreadSafetyLevel; 066import com.unboundid.util.WakeableSleeper; 067import com.unboundid.util.args.ArgumentException; 068import com.unboundid.util.args.ArgumentParser; 069import com.unboundid.util.args.BooleanArgument; 070import com.unboundid.util.args.ControlArgument; 071import com.unboundid.util.args.DNArgument; 072import com.unboundid.util.args.IntegerArgument; 073import com.unboundid.util.args.ScopeArgument; 074 075 076 077/** 078 * This class provides a simple tool that can be used to search an LDAP 079 * directory server. Some of the APIs demonstrated by this example include: 080 * <UL> 081 * <LI>Argument Parsing (from the {@code com.unboundid.util.args} 082 * package)</LI> 083 * <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util} 084 * package)</LI> 085 * <LI>LDAP Communication (from the {@code com.unboundid.ldap.sdk} 086 * package)</LI> 087 * </UL> 088 * <BR><BR> 089 * All of the necessary information is provided using 090 * command line arguments. Supported arguments include those allowed by the 091 * {@link LDAPCommandLineTool} class, as well as the following additional 092 * arguments: 093 * <UL> 094 * <LI>"-b {baseDN}" or "--baseDN {baseDN}" -- specifies the base DN to use 095 * for the search. This must be provided.</LI> 096 * <LI>"-s {scope}" or "--scope {scope}" -- specifies the scope to use for the 097 * search. The scope value should be one of "base", "one", "sub", or 098 * "subord". If this isn't specified, then a scope of "sub" will be 099 * used.</LI> 100 * <LI>"-R" or "--followReferrals" -- indicates that the tool should follow 101 * any referrals encountered while searching.</LI> 102 * <LI>"-t" or "--terse" -- indicates that the tool should generate minimal 103 * output beyond the search results.</LI> 104 * <LI>"-i {millis}" or "--repeatIntervalMillis {millis}" -- indicates that 105 * the search should be periodically repeated with the specified delay 106 * (in milliseconds) between requests.</LI> 107 * <LI>"-n {count}" or "--numSearches {count}" -- specifies the total number 108 * of times that the search should be performed. This may only be used in 109 * conjunction with the "--repeatIntervalMillis" argument. If 110 * "--repeatIntervalMillis" is used without "--numSearches", then the 111 * searches will continue to be repeated until the tool is 112 * interrupted.</LI> 113 * <LI>"--bindControl {control}" -- specifies a control that should be 114 * included in the bind request sent by this tool before performing any 115 * search operations.</LI> 116 * <LI>"-J {control}" or "--control {control}" -- specifies a control that 117 * should be included in the search request(s) sent by this tool.</LI> 118 * </UL> 119 * In addition, after the above named arguments are provided, a set of one or 120 * more unnamed trailing arguments must be given. The first argument should be 121 * the string representation of the filter to use for the search. If there are 122 * any additional trailing arguments, then they will be interpreted as the 123 * attributes to return in matching entries. If no attribute names are given, 124 * then the server should return all user attributes in matching entries. 125 * <BR><BR> 126 * Note that this class implements the SearchResultListener interface, which 127 * will be notified whenever a search result entry or reference is returned from 128 * the server. Whenever an entry is received, it will simply be printed 129 * displayed in LDIF. 130 * 131 * @see com.unboundid.ldap.sdk.unboundidds.tools.LDAPSearch 132 */ 133@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 134public final class LDAPSearch 135 extends LDAPCommandLineTool 136 implements SearchResultListener 137{ 138 /** 139 * The date formatter that should be used when writing timestamps. 140 */ 141 @NotNull private static final SimpleDateFormat DATE_FORMAT = 142 new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss.SSS"); 143 144 145 146 /** 147 * The serial version UID for this serializable class. 148 */ 149 private static final long serialVersionUID = 7465188734621412477L; 150 151 152 153 // The argument parser used by this program. 154 @Nullable private ArgumentParser parser; 155 156 // Indicates whether the search should be repeated. 157 private boolean repeat; 158 159 // The argument used to indicate whether to follow referrals. 160 @Nullable private BooleanArgument followReferrals; 161 162 // The argument used to indicate whether to use terse mode. 163 @Nullable private BooleanArgument terseMode; 164 165 // The argument used to specify any bind controls that should be used. 166 @Nullable private ControlArgument bindControls; 167 168 // The argument used to specify any search controls that should be used. 169 @Nullable private ControlArgument searchControls; 170 171 // The number of times to perform the search. 172 @Nullable private IntegerArgument numSearches; 173 174 // The interval in milliseconds between repeated searches. 175 @Nullable private IntegerArgument repeatIntervalMillis; 176 177 // The argument used to specify the base DN for the search. 178 @Nullable private DNArgument baseDN; 179 180 // The argument used to specify the scope for the search. 181 @Nullable private ScopeArgument scopeArg; 182 183 184 185 /** 186 * Parse the provided command line arguments and make the appropriate set of 187 * changes. 188 * 189 * @param args The command line arguments provided to this program. 190 */ 191 public static void main(@NotNull final String[] args) 192 { 193 final ResultCode resultCode = main(args, System.out, System.err); 194 if (resultCode != ResultCode.SUCCESS) 195 { 196 System.exit(resultCode.intValue()); 197 } 198 } 199 200 201 202 /** 203 * Parse the provided command line arguments and make the appropriate set of 204 * changes. 205 * 206 * @param args The command line arguments provided to this program. 207 * @param outStream The output stream to which standard out should be 208 * written. It may be {@code null} if output should be 209 * suppressed. 210 * @param errStream The output stream to which standard error should be 211 * written. It may be {@code null} if error messages 212 * should be suppressed. 213 * 214 * @return A result code indicating whether the processing was successful. 215 */ 216 @NotNull() 217 public static ResultCode main(@NotNull final String[] args, 218 @Nullable final OutputStream outStream, 219 @Nullable final OutputStream errStream) 220 { 221 final LDAPSearch ldapSearch = new LDAPSearch(outStream, errStream); 222 return ldapSearch.runTool(args); 223 } 224 225 226 227 /** 228 * Creates a new instance of this tool. 229 * 230 * @param outStream The output stream to which standard out should be 231 * written. It may be {@code null} if output should be 232 * suppressed. 233 * @param errStream The output stream to which standard error should be 234 * written. It may be {@code null} if error messages 235 * should be suppressed. 236 */ 237 public LDAPSearch(@Nullable final OutputStream outStream, 238 @Nullable final OutputStream errStream) 239 { 240 super(outStream, errStream); 241 } 242 243 244 245 /** 246 * Retrieves the name for this tool. 247 * 248 * @return The name for this tool. 249 */ 250 @Override() 251 @NotNull() 252 public String getToolName() 253 { 254 return "ldapsearch"; 255 } 256 257 258 259 /** 260 * Retrieves the description for this tool. 261 * 262 * @return The description for this tool. 263 */ 264 @Override() 265 @NotNull() 266 public String getToolDescription() 267 { 268 return "Search an LDAP directory server."; 269 } 270 271 272 273 /** 274 * Retrieves the version string for this tool. 275 * 276 * @return The version string for this tool. 277 */ 278 @Override() 279 @NotNull() 280 public String getToolVersion() 281 { 282 return Version.NUMERIC_VERSION_STRING; 283 } 284 285 286 287 /** 288 * Retrieves the minimum number of unnamed trailing arguments that are 289 * required. 290 * 291 * @return One, to indicate that at least one trailing argument (representing 292 * the search filter) must be provided. 293 */ 294 @Override() 295 public int getMinTrailingArguments() 296 { 297 return 1; 298 } 299 300 301 302 /** 303 * Retrieves the maximum number of unnamed trailing arguments that are 304 * allowed. 305 * 306 * @return A negative value to indicate that any number of trailing arguments 307 * may be provided. 308 */ 309 @Override() 310 public int getMaxTrailingArguments() 311 { 312 return -1; 313 } 314 315 316 317 /** 318 * Retrieves a placeholder string that may be used to indicate what kinds of 319 * trailing arguments are allowed. 320 * 321 * @return A placeholder string that may be used to indicate what kinds of 322 * trailing arguments are allowed. 323 */ 324 @Override() 325 @NotNull() 326 public String getTrailingArgumentsPlaceholder() 327 { 328 return "{filter} [attr1 [attr2 [...]]]"; 329 } 330 331 332 333 /** 334 * Indicates whether this tool should provide support for an interactive mode, 335 * in which the tool offers a mode in which the arguments can be provided in 336 * a text-driven menu rather than requiring them to be given on the command 337 * line. If interactive mode is supported, it may be invoked using the 338 * "--interactive" argument. Alternately, if interactive mode is supported 339 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then 340 * interactive mode may be invoked by simply launching the tool without any 341 * arguments. 342 * 343 * @return {@code true} if this tool supports interactive mode, or 344 * {@code false} if not. 345 */ 346 @Override() 347 public boolean supportsInteractiveMode() 348 { 349 return true; 350 } 351 352 353 354 /** 355 * Indicates whether this tool defaults to launching in interactive mode if 356 * the tool is invoked without any command-line arguments. This will only be 357 * used if {@link #supportsInteractiveMode()} returns {@code true}. 358 * 359 * @return {@code true} if this tool defaults to using interactive mode if 360 * launched without any command-line arguments, or {@code false} if 361 * not. 362 */ 363 @Override() 364 public boolean defaultsToInteractiveMode() 365 { 366 return true; 367 } 368 369 370 371 /** 372 * Indicates whether this tool should provide arguments for redirecting output 373 * to a file. If this method returns {@code true}, then the tool will offer 374 * an "--outputFile" argument that will specify the path to a file to which 375 * all standard output and standard error content will be written, and it will 376 * also offer a "--teeToStandardOut" argument that can only be used if the 377 * "--outputFile" argument is present and will cause all output to be written 378 * to both the specified output file and to standard output. 379 * 380 * @return {@code true} if this tool should provide arguments for redirecting 381 * output to a file, or {@code false} if not. 382 */ 383 @Override() 384 protected boolean supportsOutputFile() 385 { 386 return true; 387 } 388 389 390 391 /** 392 * Indicates whether this tool supports the use of a properties file for 393 * specifying default values for arguments that aren't specified on the 394 * command line. 395 * 396 * @return {@code true} if this tool supports the use of a properties file 397 * for specifying default values for arguments that aren't specified 398 * on the command line, or {@code false} if not. 399 */ 400 @Override() 401 public boolean supportsPropertiesFile() 402 { 403 return true; 404 } 405 406 407 408 /** 409 * Indicates whether this tool supports the ability to generate a debug log 410 * file. If this method returns {@code true}, then the tool will expose 411 * additional arguments that can control debug logging. 412 * 413 * @return {@code true} if this tool supports the ability to generate a debug 414 * log file, or {@code false} if not. 415 */ 416 @Override() 417 protected boolean supportsDebugLogging() 418 { 419 return true; 420 } 421 422 423 424 /** 425 * Indicates whether this tool should default to interactively prompting for 426 * the bind password if a password is required but no argument was provided 427 * to indicate how to get the password. 428 * 429 * @return {@code true} if this tool should default to interactively 430 * prompting for the bind password, or {@code false} if not. 431 */ 432 @Override() 433 protected boolean defaultToPromptForBindPassword() 434 { 435 return true; 436 } 437 438 439 440 /** 441 * Indicates whether the LDAP-specific arguments should include alternate 442 * versions of all long identifiers that consist of multiple words so that 443 * they are available in both camelCase and dash-separated versions. 444 * 445 * @return {@code true} if this tool should provide multiple versions of 446 * long identifiers for LDAP-specific arguments, or {@code false} if 447 * not. 448 */ 449 @Override() 450 protected boolean includeAlternateLongIdentifiers() 451 { 452 return true; 453 } 454 455 456 457 /** 458 * Indicates whether this tool should provide a command-line argument that 459 * allows for low-level SSL debugging. If this returns {@code true}, then an 460 * "--enableSSLDebugging}" argument will be added that sets the 461 * "javax.net.debug" system property to "all" before attempting any 462 * communication. 463 * 464 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 465 * argument, or {@code false} if not. 466 */ 467 @Override() 468 protected boolean supportsSSLDebugging() 469 { 470 return true; 471 } 472 473 474 475 /** 476 * Adds the arguments used by this program that aren't already provided by the 477 * generic {@code LDAPCommandLineTool} framework. 478 * 479 * @param parser The argument parser to which the arguments should be added. 480 * 481 * @throws ArgumentException If a problem occurs while adding the arguments. 482 */ 483 @Override() 484 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 485 throws ArgumentException 486 { 487 this.parser = parser; 488 489 String description = "The base DN to use for the search. This must be " + 490 "provided."; 491 baseDN = new DNArgument('b', "baseDN", true, 1, "{dn}", description); 492 baseDN.addLongIdentifier("base-dn", true); 493 parser.addArgument(baseDN); 494 495 496 description = "The scope to use for the search. It should be 'base', " + 497 "'one', 'sub', or 'subord'. If this is not provided, then " + 498 "a default scope of 'sub' will be used."; 499 scopeArg = new ScopeArgument('s', "scope", false, "{scope}", description, 500 SearchScope.SUB); 501 parser.addArgument(scopeArg); 502 503 504 description = "Follow any referrals encountered during processing."; 505 followReferrals = new BooleanArgument('R', "followReferrals", description); 506 followReferrals.addLongIdentifier("follow-referrals", true); 507 parser.addArgument(followReferrals); 508 509 510 description = "Information about a control to include in the bind request."; 511 bindControls = new ControlArgument(null, "bindControl", false, 0, null, 512 description); 513 bindControls.addLongIdentifier("bind-control", true); 514 parser.addArgument(bindControls); 515 516 517 description = "Information about a control to include in search requests."; 518 searchControls = new ControlArgument('J', "control", false, 0, null, 519 description); 520 parser.addArgument(searchControls); 521 522 523 description = "Generate terse output with minimal additional information."; 524 terseMode = new BooleanArgument('t', "terse", description); 525 parser.addArgument(terseMode); 526 527 528 description = "Specifies the length of time in milliseconds to sleep " + 529 "before repeating the same search. If this is not " + 530 "provided, then the search will only be performed once."; 531 repeatIntervalMillis = new IntegerArgument('i', "repeatIntervalMillis", 532 false, 1, "{millis}", 533 description, 0, 534 Integer.MAX_VALUE); 535 repeatIntervalMillis.addLongIdentifier("repeat-interval-millis", true); 536 parser.addArgument(repeatIntervalMillis); 537 538 539 description = "Specifies the number of times that the search should be " + 540 "performed. If this argument is present, then the " + 541 "--repeatIntervalMillis argument must also be provided to " + 542 "specify the length of time between searches. If " + 543 "--repeatIntervalMillis is used without --numSearches, " + 544 "then the search will be repeated until the tool is " + 545 "interrupted."; 546 numSearches = new IntegerArgument('n', "numSearches", false, 1, "{count}", 547 description, 1, Integer.MAX_VALUE); 548 numSearches.addLongIdentifier("num-searches", true); 549 parser.addArgument(numSearches); 550 parser.addDependentArgumentSet(numSearches, repeatIntervalMillis); 551 } 552 553 554 555 /** 556 * {@inheritDoc} 557 */ 558 @Override() 559 public void doExtendedNonLDAPArgumentValidation() 560 throws ArgumentException 561 { 562 // There must have been at least one trailing argument provided, and it must 563 // be parsable as a valid search filter. 564 if (parser.getTrailingArguments().isEmpty()) 565 { 566 throw new ArgumentException("At least one trailing argument must be " + 567 "provided to specify the search filter. Additional trailing " + 568 "arguments are allowed to specify the attributes to return in " + 569 "search result entries."); 570 } 571 572 try 573 { 574 Filter.create(parser.getTrailingArguments().get(0)); 575 } 576 catch (final Exception e) 577 { 578 Debug.debugException(e); 579 throw new ArgumentException( 580 "The first trailing argument value could not be parsed as a valid " + 581 "LDAP search filter.", 582 e); 583 } 584 } 585 586 587 588 /** 589 * {@inheritDoc} 590 */ 591 @Override() 592 @NotNull() 593 protected List<Control> getBindControls() 594 { 595 return bindControls.getValues(); 596 } 597 598 599 600 /** 601 * Performs the actual processing for this tool. In this case, it gets a 602 * connection to the directory server and uses it to perform the requested 603 * search. 604 * 605 * @return The result code for the processing that was performed. 606 */ 607 @Override() 608 @NotNull() 609 public ResultCode doToolProcessing() 610 { 611 // Make sure that at least one trailing argument was provided, which will be 612 // the filter. If there were any other arguments, then they will be the 613 // attributes to return. 614 final List<String> trailingArguments = parser.getTrailingArguments(); 615 if (trailingArguments.isEmpty()) 616 { 617 err("No search filter was provided."); 618 err(); 619 err(parser.getUsageString(StaticUtils.TERMINAL_WIDTH_COLUMNS - 1)); 620 return ResultCode.PARAM_ERROR; 621 } 622 623 final Filter filter; 624 try 625 { 626 filter = Filter.create(trailingArguments.get(0)); 627 } 628 catch (final LDAPException le) 629 { 630 err("Invalid search filter: ", le.getMessage()); 631 return le.getResultCode(); 632 } 633 634 final String[] attributesToReturn; 635 if (trailingArguments.size() > 1) 636 { 637 attributesToReturn = new String[trailingArguments.size() - 1]; 638 for (int i=1; i < trailingArguments.size(); i++) 639 { 640 attributesToReturn[i-1] = trailingArguments.get(i); 641 } 642 } 643 else 644 { 645 attributesToReturn = StaticUtils.NO_STRINGS; 646 } 647 648 649 // Get the connection to the directory server. 650 final LDAPConnection connection; 651 try 652 { 653 connection = getConnection(); 654 if (! terseMode.isPresent()) 655 { 656 out("# Connected to ", connection.getConnectedAddress(), ':', 657 connection.getConnectedPort()); 658 } 659 } 660 catch (final LDAPException le) 661 { 662 err("Error connecting to the directory server: ", le.getMessage()); 663 return le.getResultCode(); 664 } 665 666 667 // Create a search request with the appropriate information and process it 668 // in the server. Note that in this case, we're creating a search result 669 // listener to handle the results since there could potentially be a lot of 670 // them. 671 final SearchRequest searchRequest = 672 new SearchRequest(this, baseDN.getStringValue(), scopeArg.getValue(), 673 DereferencePolicy.NEVER, 0, 0, false, filter, 674 attributesToReturn); 675 searchRequest.setFollowReferrals(followReferrals.isPresent()); 676 677 final List<Control> controlList = searchControls.getValues(); 678 if (controlList != null) 679 { 680 searchRequest.setControls(controlList); 681 } 682 683 684 final boolean infinite; 685 final int numIterations; 686 if (repeatIntervalMillis.isPresent()) 687 { 688 repeat = true; 689 690 if (numSearches.isPresent()) 691 { 692 infinite = false; 693 numIterations = numSearches.getValue(); 694 } 695 else 696 { 697 infinite = true; 698 numIterations = Integer.MAX_VALUE; 699 } 700 } 701 else 702 { 703 infinite = false; 704 repeat = false; 705 numIterations = 1; 706 } 707 708 ResultCode resultCode = ResultCode.SUCCESS; 709 long lastSearchTime = System.currentTimeMillis(); 710 final WakeableSleeper sleeper = new WakeableSleeper(); 711 for (int i=0; (infinite || (i < numIterations)); i++) 712 { 713 if (repeat && (i > 0)) 714 { 715 final long sleepTime = 716 (lastSearchTime + repeatIntervalMillis.getValue()) - 717 System.currentTimeMillis(); 718 if (sleepTime > 0) 719 { 720 sleeper.sleep(sleepTime); 721 } 722 lastSearchTime = System.currentTimeMillis(); 723 } 724 725 try 726 { 727 final SearchResult searchResult = connection.search(searchRequest); 728 if ((! repeat) && (! terseMode.isPresent())) 729 { 730 out("# The search operation was processed successfully."); 731 out("# Entries returned: ", searchResult.getEntryCount()); 732 out("# References returned: ", searchResult.getReferenceCount()); 733 } 734 } 735 catch (final LDAPException le) 736 { 737 err("An error occurred while processing the search: ", 738 le.getMessage()); 739 err("Result Code: ", le.getResultCode().intValue(), " (", 740 le.getResultCode().getName(), ')'); 741 if (le.getMatchedDN() != null) 742 { 743 err("Matched DN: ", le.getMatchedDN()); 744 } 745 746 if (le.getReferralURLs() != null) 747 { 748 for (final String url : le.getReferralURLs()) 749 { 750 err("Referral URL: ", url); 751 } 752 } 753 754 if (resultCode == ResultCode.SUCCESS) 755 { 756 resultCode = le.getResultCode(); 757 } 758 759 if (! le.getResultCode().isConnectionUsable()) 760 { 761 break; 762 } 763 } 764 } 765 766 767 // Close the connection to the directory server and exit. 768 connection.close(); 769 if (! terseMode.isPresent()) 770 { 771 out(); 772 out("# Disconnected from the server"); 773 } 774 return resultCode; 775 } 776 777 778 779 /** 780 * Indicates that the provided search result entry was returned from the 781 * associated search operation. 782 * 783 * @param entry The entry that was returned from the search. 784 */ 785 @Override() 786 public void searchEntryReturned(@NotNull final SearchResultEntry entry) 787 { 788 if (repeat) 789 { 790 out("# ", DATE_FORMAT.format(new Date())); 791 } 792 793 out(entry.toLDIFString()); 794 } 795 796 797 798 /** 799 * Indicates that the provided search result reference was returned from the 800 * associated search operation. 801 * 802 * @param reference The reference that was returned from the search. 803 */ 804 @Override() 805 public void searchReferenceReturned( 806 @NotNull final SearchResultReference reference) 807 { 808 if (repeat) 809 { 810 out("# ", DATE_FORMAT.format(new Date())); 811 } 812 813 out(reference.toString()); 814 } 815 816 817 818 /** 819 * {@inheritDoc} 820 */ 821 @Override() 822 @NotNull() 823 public LinkedHashMap<String[],String> getExampleUsages() 824 { 825 final LinkedHashMap<String[],String> examples = 826 new LinkedHashMap<>(StaticUtils.computeMapCapacity(1)); 827 828 final String[] args = 829 { 830 "--hostname", "server.example.com", 831 "--port", "389", 832 "--bindDN", "uid=admin,dc=example,dc=com", 833 "--bindPassword", "password", 834 "--baseDN", "dc=example,dc=com", 835 "--scope", "sub", 836 "(uid=jdoe)", 837 "givenName", 838 "sn", 839 "mail" 840 }; 841 final String description = 842 "Perform a search in the directory server to find all entries " + 843 "matching the filter '(uid=jdoe)' anywhere below " + 844 "'dc=example,dc=com'. Include only the givenName, sn, and mail " + 845 "attributes in the entries that are returned."; 846 examples.put(args, description); 847 848 return examples; 849 } 850}