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}