001/*
002 * Copyright 2017-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2022 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) 2017-2022 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.unboundidds.tools;
037
038
039
040import java.io.BufferedReader;
041import java.io.File;
042import java.io.FileOutputStream;
043import java.io.FileReader;
044import java.io.IOException;
045import java.io.OutputStream;
046import java.io.PrintStream;
047import java.util.ArrayList;
048import java.util.Arrays;
049import java.util.Collections;
050import java.util.EnumSet;
051import java.util.Iterator;
052import java.util.LinkedHashMap;
053import java.util.List;
054import java.util.Map;
055import java.util.Set;
056import java.util.StringTokenizer;
057import java.util.concurrent.atomic.AtomicLong;
058import java.util.zip.GZIPOutputStream;
059
060import com.unboundid.asn1.ASN1OctetString;
061import com.unboundid.ldap.sdk.Control;
062import com.unboundid.ldap.sdk.DN;
063import com.unboundid.ldap.sdk.DereferencePolicy;
064import com.unboundid.ldap.sdk.ExtendedResult;
065import com.unboundid.ldap.sdk.Filter;
066import com.unboundid.ldap.sdk.LDAPConnectionOptions;
067import com.unboundid.ldap.sdk.LDAPConnection;
068import com.unboundid.ldap.sdk.LDAPConnectionPool;
069import com.unboundid.ldap.sdk.LDAPException;
070import com.unboundid.ldap.sdk.LDAPResult;
071import com.unboundid.ldap.sdk.LDAPSearchException;
072import com.unboundid.ldap.sdk.LDAPURL;
073import com.unboundid.ldap.sdk.ResultCode;
074import com.unboundid.ldap.sdk.SearchRequest;
075import com.unboundid.ldap.sdk.SearchResult;
076import com.unboundid.ldap.sdk.SearchScope;
077import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler;
078import com.unboundid.ldap.sdk.Version;
079import com.unboundid.ldap.sdk.controls.AssertionRequestControl;
080import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
081import com.unboundid.ldap.sdk.controls.DraftLDUPSubentriesRequestControl;
082import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl;
083import com.unboundid.ldap.sdk.controls.MatchedValuesFilter;
084import com.unboundid.ldap.sdk.controls.MatchedValuesRequestControl;
085import com.unboundid.ldap.sdk.controls.PersistentSearchChangeType;
086import com.unboundid.ldap.sdk.controls.PersistentSearchRequestControl;
087import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl;
088import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl;
089import com.unboundid.ldap.sdk.controls.RFC3672SubentriesRequestControl;
090import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl;
091import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
092import com.unboundid.ldap.sdk.controls.SortKey;
093import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl;
094import com.unboundid.ldap.sdk.persist.PersistUtils;
095import com.unboundid.ldap.sdk.transformations.EntryTransformation;
096import com.unboundid.ldap.sdk.transformations.ExcludeAttributeTransformation;
097import com.unboundid.ldap.sdk.transformations.MoveSubtreeTransformation;
098import com.unboundid.ldap.sdk.transformations.RedactAttributeTransformation;
099import com.unboundid.ldap.sdk.transformations.RenameAttributeTransformation;
100import com.unboundid.ldap.sdk.transformations.ScrambleAttributeTransformation;
101import com.unboundid.ldap.sdk.unboundidds.controls.AccountUsableRequestControl;
102import com.unboundid.ldap.sdk.unboundidds.controls.ExcludeBranchRequestControl;
103import com.unboundid.ldap.sdk.unboundidds.controls.
104            GetAuthorizationEntryRequestControl;
105import com.unboundid.ldap.sdk.unboundidds.controls.
106            GetBackendSetIDRequestControl;
107import com.unboundid.ldap.sdk.unboundidds.controls.
108            GetEffectiveRightsRequestControl;
109import com.unboundid.ldap.sdk.unboundidds.controls.
110            GetRecentLoginHistoryRequestControl;
111import com.unboundid.ldap.sdk.unboundidds.controls.GetServerIDRequestControl;
112import com.unboundid.ldap.sdk.unboundidds.controls.
113            GetUserResourceLimitsRequestControl;
114import com.unboundid.ldap.sdk.unboundidds.controls.
115            JSONFormattedControlDecodeBehavior;
116import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedRequestControl;
117import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedResponseControl;
118import com.unboundid.ldap.sdk.unboundidds.controls.JoinBaseDN;
119import com.unboundid.ldap.sdk.unboundidds.controls.JoinRequestControl;
120import com.unboundid.ldap.sdk.unboundidds.controls.JoinRequestValue;
121import com.unboundid.ldap.sdk.unboundidds.controls.JoinRule;
122import com.unboundid.ldap.sdk.unboundidds.controls.
123            MatchingEntryCountRequestControl;
124import com.unboundid.ldap.sdk.unboundidds.controls.
125            MatchingEntryCountRequestControlProperties;
126import com.unboundid.ldap.sdk.unboundidds.controls.
127            OperationPurposeRequestControl;
128import com.unboundid.ldap.sdk.unboundidds.controls.
129            OverrideSearchLimitsRequestControl;
130import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl;
131import com.unboundid.ldap.sdk.unboundidds.controls.
132            PermitUnindexedSearchRequestControl;
133import com.unboundid.ldap.sdk.unboundidds.controls.
134            RealAttributesOnlyRequestControl;
135import com.unboundid.ldap.sdk.unboundidds.controls.
136            RejectUnindexedSearchRequestControl;
137import com.unboundid.ldap.sdk.unboundidds.controls.
138            ReturnConflictEntriesRequestControl;
139import com.unboundid.ldap.sdk.unboundidds.controls.
140            RouteToBackendSetRequestControl;
141import com.unboundid.ldap.sdk.unboundidds.controls.RouteToServerRequestControl;
142import com.unboundid.ldap.sdk.unboundidds.controls.
143            SoftDeletedEntryAccessRequestControl;
144import com.unboundid.ldap.sdk.unboundidds.controls.
145            SuppressOperationalAttributeUpdateRequestControl;
146import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType;
147import com.unboundid.ldap.sdk.unboundidds.controls.
148            VirtualAttributesOnlyRequestControl;
149import com.unboundid.ldap.sdk.unboundidds.extensions.
150            StartAdministrativeSessionExtendedRequest;
151import com.unboundid.ldap.sdk.unboundidds.extensions.
152            StartAdministrativeSessionPostConnectProcessor;
153import com.unboundid.ldif.LDIFWriter;
154import com.unboundid.util.Debug;
155import com.unboundid.util.FilterFileReader;
156import com.unboundid.util.FixedRateBarrier;
157import com.unboundid.util.LDAPCommandLineTool;
158import com.unboundid.util.NotNull;
159import com.unboundid.util.Nullable;
160import com.unboundid.util.OutputFormat;
161import com.unboundid.util.PassphraseEncryptedOutputStream;
162import com.unboundid.util.StaticUtils;
163import com.unboundid.util.TeeOutputStream;
164import com.unboundid.util.ThreadSafety;
165import com.unboundid.util.ThreadSafetyLevel;
166import com.unboundid.util.args.ArgumentException;
167import com.unboundid.util.args.ArgumentParser;
168import com.unboundid.util.args.BooleanArgument;
169import com.unboundid.util.args.BooleanValueArgument;
170import com.unboundid.util.args.ControlArgument;
171import com.unboundid.util.args.DNArgument;
172import com.unboundid.util.args.FileArgument;
173import com.unboundid.util.args.FilterArgument;
174import com.unboundid.util.args.IntegerArgument;
175import com.unboundid.util.args.ScopeArgument;
176import com.unboundid.util.args.StringArgument;
177
178import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
179
180
181
182/**
183 * This class provides an implementation of an LDAP command-line tool that may
184 * be used to issue searches to a directory server.  Matching entries will be
185 * output in the LDAP data interchange format (LDIF), to standard output and/or
186 * to a specified file.  This is a much more full-featured tool than the
187 * {@link com.unboundid.ldap.sdk.examples.LDAPSearch} tool, and includes a
188 * number of features only intended for use with Ping Identity, UnboundID, and
189 * Nokia/Alcatel-Lucent 8661 server products.
190 * <BR>
191 * <BLOCKQUOTE>
192 *   <B>NOTE:</B>  This class, and other classes within the
193 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
194 *   supported for use against Ping Identity, UnboundID, and
195 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
196 *   for proprietary functionality or for external specifications that are not
197 *   considered stable or mature enough to be guaranteed to work in an
198 *   interoperable way with other types of LDAP servers.
199 * </BLOCKQUOTE>
200 */
201@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
202public final class LDAPSearch
203       extends LDAPCommandLineTool
204       implements UnsolicitedNotificationHandler
205{
206  /**
207   * The column at which to wrap long lines.
208   */
209  private static int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
210
211
212
213  // The set of arguments supported by this program.
214  @Nullable private BooleanArgument accountUsable = null;
215  @Nullable private BooleanArgument authorizationIdentity = null;
216  @Nullable private BooleanArgument compressOutput = null;
217  @Nullable private BooleanArgument continueOnError = null;
218  @Nullable private BooleanArgument countEntries = null;
219  @Nullable private BooleanArgument dontWrap = null;
220  @Nullable private BooleanArgument draftLDUPSubentries = null;
221  @Nullable private BooleanArgument dryRun = null;
222  @Nullable private BooleanArgument encryptOutput = null;
223  @Nullable private BooleanArgument followReferrals = null;
224  @Nullable private BooleanArgument getBackendSetID = null;
225  @Nullable private BooleanArgument getServerID = null;
226  @Nullable private BooleanArgument getRecentLoginHistory = null;
227  @Nullable private BooleanArgument hideRedactedValueCount = null;
228  @Nullable private BooleanArgument getUserResourceLimits = null;
229  @Nullable private BooleanArgument includeReplicationConflictEntries = null;
230  @Nullable private BooleanArgument joinRequireMatch = null;
231  @Nullable private BooleanArgument manageDsaIT = null;
232  @Nullable private BooleanArgument permitUnindexedSearch = null;
233  @Nullable private BooleanArgument realAttributesOnly = null;
234  @Nullable private BooleanArgument rejectUnindexedSearch = null;
235  @Nullable private BooleanArgument requireMatch = null;
236  @Nullable private BooleanArgument retryFailedOperations = null;
237  @Nullable private BooleanArgument separateOutputFilePerSearch = null;
238  @Nullable private BooleanArgument suppressBase64EncodedValueComments = null;
239  @Nullable private BooleanArgument teeResultsToStandardOut = null;
240  @Nullable private BooleanArgument useAdministrativeSession = null;
241  @Nullable private BooleanArgument useJSONFormattedRequestControls = null;
242  @Nullable private BooleanArgument usePasswordPolicyControl = null;
243  @Nullable private BooleanArgument terse = null;
244  @Nullable private BooleanArgument typesOnly = null;
245  @Nullable private BooleanArgument verbose = null;
246  @Nullable private BooleanArgument virtualAttributesOnly = null;
247  @Nullable private BooleanValueArgument rfc3672Subentries = null;
248  @Nullable private ControlArgument bindControl = null;
249  @Nullable private ControlArgument searchControl = null;
250  @Nullable private DNArgument baseDN = null;
251  @Nullable private DNArgument excludeBranch = null;
252  @Nullable private DNArgument moveSubtreeFrom = null;
253  @Nullable private DNArgument moveSubtreeTo = null;
254  @Nullable private DNArgument proxyV1As = null;
255  @Nullable private FileArgument encryptionPassphraseFile = null;
256  @Nullable private FileArgument filterFile = null;
257  @Nullable private FileArgument ldapURLFile = null;
258  @Nullable private FileArgument outputFile = null;
259  @Nullable private FilterArgument assertionFilter = null;
260  @Nullable private FilterArgument filter = null;
261  @Nullable private FilterArgument joinFilter = null;
262  @Nullable private FilterArgument matchedValuesFilter = null;
263  @Nullable private IntegerArgument joinSizeLimit = null;
264  @Nullable private IntegerArgument ratePerSecond = null;
265  @Nullable private IntegerArgument scrambleRandomSeed = null;
266  @Nullable private IntegerArgument simplePageSize = null;
267  @Nullable private IntegerArgument sizeLimit = null;
268  @Nullable private IntegerArgument timeLimitSeconds = null;
269  @Nullable private IntegerArgument wrapColumn = null;
270  @Nullable private ScopeArgument joinScope = null;
271  @Nullable private ScopeArgument scope = null;
272  @Nullable private StringArgument dereferencePolicy = null;
273  @Nullable private StringArgument excludeAttribute = null;
274  @Nullable private StringArgument getAuthorizationEntryAttribute = null;
275  @Nullable private StringArgument getEffectiveRightsAttribute = null;
276  @Nullable private StringArgument getEffectiveRightsAuthzID = null;
277  @Nullable private StringArgument includeSoftDeletedEntries = null;
278  @Nullable private StringArgument joinBaseDN = null;
279  @Nullable private StringArgument joinRequestedAttribute = null;
280  @Nullable private StringArgument joinRule = null;
281  @Nullable private StringArgument matchingEntryCountControl = null;
282  @Nullable private StringArgument operationPurpose = null;
283  @Nullable private StringArgument outputFormat = null;
284  @Nullable private StringArgument overrideSearchLimit = null;
285  @Nullable private StringArgument persistentSearch = null;
286  @Nullable private StringArgument proxyAs = null;
287  @Nullable private StringArgument redactAttribute = null;
288  @Nullable private StringArgument renameAttributeFrom = null;
289  @Nullable private StringArgument renameAttributeTo = null;
290  @Nullable private StringArgument requestedAttribute = null;
291  @Nullable private StringArgument routeToBackendSet = null;
292  @Nullable private StringArgument routeToServer = null;
293  @Nullable private StringArgument scrambleAttribute = null;
294  @Nullable private StringArgument scrambleJSONField = null;
295  @Nullable private StringArgument sortOrder = null;
296  @Nullable private StringArgument suppressOperationalAttributeUpdates = null;
297  @Nullable private StringArgument virtualListView = null;
298
299  // The argument parser used by this tool.
300  @Nullable private volatile ArgumentParser parser = null;
301
302  // Controls that should be sent to the server but need special validation.
303  @Nullable private volatile JoinRequestControl joinRequestControl = null;
304  @NotNull private final List<RouteToBackendSetRequestControl>
305       routeToBackendSetRequestControls = new ArrayList<>(10);
306  @Nullable private volatile MatchedValuesRequestControl
307       matchedValuesRequestControl = null;
308  @Nullable private volatile MatchingEntryCountRequestControl
309       matchingEntryCountRequestControl = null;
310  @Nullable private volatile OverrideSearchLimitsRequestControl
311       overrideSearchLimitsRequestControl = null;
312  @Nullable private volatile PersistentSearchRequestControl
313       persistentSearchRequestControl = null;
314  @Nullable private volatile ServerSideSortRequestControl sortRequestControl =
315       null;
316  @Nullable private volatile VirtualListViewRequestControl vlvRequestControl =
317       null;
318
319  // Other values decoded from arguments.
320  @Nullable private volatile DereferencePolicy derefPolicy = null;
321
322  // The print streams used for standard output and error.
323  @NotNull private final AtomicLong outputFileCounter = new AtomicLong(1);
324  @Nullable private volatile PrintStream errStream = null;
325  @Nullable private volatile PrintStream outStream = null;
326
327  // The LDAP result writer for this tool.
328  @NotNull private volatile LDAPResultWriter resultWriter;
329
330  // The list of entry transformations to apply.
331  @Nullable private volatile List<EntryTransformation> entryTransformations =
332       null;
333
334  // The encryption passphrase to use if the output is to be encrypted.
335  @Nullable private String encryptionPassphrase = null;
336
337
338
339  /**
340   * Runs this tool with the provided command-line arguments.  It will use the
341   * JVM-default streams for standard input, output, and error.
342   *
343   * @param  args  The command-line arguments to provide to this program.
344   */
345  public static void main(@NotNull final String... args)
346  {
347    final ResultCode resultCode = main(System.out, System.err, args);
348    if (resultCode != ResultCode.SUCCESS)
349    {
350      System.exit(Math.min(resultCode.intValue(), 255));
351    }
352  }
353
354
355
356  /**
357   * Runs this tool with the provided streams and command-line arguments.
358   *
359   * @param  out   The output stream to use for standard output.  If this is
360   *               {@code null}, then standard output will be suppressed.
361   * @param  err   The output stream to use for standard error.  If this is
362   *               {@code null}, then standard error will be suppressed.
363   * @param  args  The command-line arguments provided to this program.
364   *
365   * @return  The result code obtained when running the tool.  Any result code
366   *          other than {@link ResultCode#SUCCESS} indicates an error.
367   */
368  @NotNull()
369  public static ResultCode main(@Nullable final OutputStream out,
370                                @Nullable final OutputStream err,
371                                @NotNull final String... args)
372  {
373    final LDAPSearch tool = new LDAPSearch(out, err);
374    return tool.runTool(args);
375  }
376
377
378
379  /**
380   * Creates a new instance of this tool with the provided streams.
381   *
382   * @param  out  The output stream to use for standard output.  If this is
383   *              {@code null}, then standard output will be suppressed.
384   * @param  err  The output stream to use for standard error.  If this is
385   *              {@code null}, then standard error will be suppressed.
386   */
387  public LDAPSearch(@Nullable final OutputStream out,
388                    @Nullable final OutputStream err)
389  {
390    super(out, err);
391
392    resultWriter = new LDIFLDAPResultWriter(getOut(), WRAP_COLUMN);
393  }
394
395
396
397  /**
398   * {@inheritDoc}
399   */
400  @Override()
401  @NotNull()
402  public String getToolName()
403  {
404    return "ldapsearch";
405  }
406
407
408
409  /**
410   * {@inheritDoc}
411   */
412  @Override()
413  @NotNull()
414  public String getToolDescription()
415  {
416    return INFO_LDAPSEARCH_TOOL_DESCRIPTION.get();
417  }
418
419
420
421  /**
422   * {@inheritDoc}
423   */
424  @Override()
425  @NotNull()
426  public List<String> getAdditionalDescriptionParagraphs()
427  {
428    return Arrays.asList(
429         INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_1.get(),
430         INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_2.get());
431  }
432
433
434
435  /**
436   * {@inheritDoc}
437   */
438  @Override()
439  @NotNull()
440  public String getToolVersion()
441  {
442    return Version.NUMERIC_VERSION_STRING;
443  }
444
445
446
447  /**
448   * {@inheritDoc}
449   */
450  @Override()
451  public int getMinTrailingArguments()
452  {
453    return 0;
454  }
455
456
457
458  /**
459   * {@inheritDoc}
460   */
461  @Override()
462  public int getMaxTrailingArguments()
463  {
464    return -1;
465  }
466
467
468
469  /**
470   * {@inheritDoc}
471   */
472  @Override()
473  @NotNull()
474  public String getTrailingArgumentsPlaceholder()
475  {
476    return INFO_LDAPSEARCH_TRAILING_ARGS_PLACEHOLDER.get();
477  }
478
479
480
481  /**
482   * {@inheritDoc}
483   */
484  @Override()
485  public boolean supportsInteractiveMode()
486  {
487    return true;
488  }
489
490
491
492  /**
493   * {@inheritDoc}
494   */
495  @Override()
496  public boolean defaultsToInteractiveMode()
497  {
498    return true;
499  }
500
501
502
503  /**
504   * {@inheritDoc}
505   */
506  @Override()
507  public boolean supportsPropertiesFile()
508  {
509    return true;
510  }
511
512
513
514  /**
515   * {@inheritDoc}
516   */
517  @Override()
518  protected boolean defaultToPromptForBindPassword()
519  {
520    return true;
521  }
522
523
524
525  /**
526   * {@inheritDoc}
527   */
528  @Override()
529  protected boolean includeAlternateLongIdentifiers()
530  {
531    return true;
532  }
533
534
535
536  /**
537   * {@inheritDoc}
538   */
539  @Override()
540  protected boolean supportsSSLDebugging()
541  {
542    return true;
543  }
544
545
546
547  /**
548   * {@inheritDoc}
549   */
550  @Override()
551  @NotNull()
552  protected Set<Character> getSuppressedShortIdentifiers()
553  {
554    return Collections.singleton('T');
555  }
556
557
558
559  /**
560   * {@inheritDoc}
561   */
562  @Override()
563  public void addNonLDAPArguments(@NotNull final ArgumentParser parser)
564         throws ArgumentException
565  {
566    this.parser = parser;
567
568    baseDN = new DNArgument('b', "baseDN", false, 1, null,
569         INFO_LDAPSEARCH_ARG_DESCRIPTION_BASE_DN.get());
570    baseDN.addLongIdentifier("base-dn", true);
571    baseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
572    parser.addArgument(baseDN);
573
574    scope = new ScopeArgument('s', "scope", false, null,
575         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCOPE.get(), SearchScope.SUB);
576    scope.addLongIdentifier("searchScope", true);
577    scope.addLongIdentifier("search-scope", true);
578    scope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
579    parser.addArgument(scope);
580
581    sizeLimit = new IntegerArgument('z', "sizeLimit", false, 1, null,
582         INFO_LDAPSEARCH_ARG_DESCRIPTION_SIZE_LIMIT.get(), 0,
583         Integer.MAX_VALUE, 0);
584    sizeLimit.addLongIdentifier("size-limit", true);
585    sizeLimit.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
586    parser.addArgument(sizeLimit);
587
588    timeLimitSeconds = new IntegerArgument('l', "timeLimitSeconds", false, 1,
589         null, INFO_LDAPSEARCH_ARG_DESCRIPTION_TIME_LIMIT.get(), 0,
590         Integer.MAX_VALUE, 0);
591    timeLimitSeconds.addLongIdentifier("timeLimit", true);
592    timeLimitSeconds.addLongIdentifier("time-limit-seconds", true);
593    timeLimitSeconds.addLongIdentifier("time-limit", true);
594    timeLimitSeconds.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
595    parser.addArgument(timeLimitSeconds);
596
597    final Set<String> derefAllowedValues =
598         StaticUtils.setOf("never", "always", "search", "find");
599    dereferencePolicy = new StringArgument('a', "dereferencePolicy", false, 1,
600         "{never|always|search|find}",
601         INFO_LDAPSEARCH_ARG_DESCRIPTION_DEREFERENCE_POLICY.get(),
602         derefAllowedValues, "never");
603    dereferencePolicy.addLongIdentifier("dereference-policy", true);
604    dereferencePolicy.setArgumentGroupName(
605         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
606    parser.addArgument(dereferencePolicy);
607
608    typesOnly = new BooleanArgument('A', "typesOnly", 1,
609         INFO_LDAPSEARCH_ARG_DESCRIPTION_TYPES_ONLY.get());
610    typesOnly.addLongIdentifier("types-only", true);
611    typesOnly.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
612    parser.addArgument(typesOnly);
613
614    requestedAttribute = new StringArgument(null, "requestedAttribute", false,
615         0, INFO_PLACEHOLDER_ATTR.get(),
616         INFO_LDAPSEARCH_ARG_DESCRIPTION_REQUESTED_ATTR.get());
617    requestedAttribute.addLongIdentifier("requested-attribute", true);
618    requestedAttribute.setArgumentGroupName(
619         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
620    parser.addArgument(requestedAttribute);
621
622    filter = new FilterArgument(null, "filter", false, 0,
623         INFO_PLACEHOLDER_FILTER.get(),
624         INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER.get());
625    filter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
626    parser.addArgument(filter);
627
628    filterFile = new FileArgument('f', "filterFile", false, 0, null,
629         INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER_FILE.get(), true, true,
630         true, false);
631    filterFile.addLongIdentifier("filename", true);
632    filterFile.addLongIdentifier("filter-file", true);
633    filterFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
634    parser.addArgument(filterFile);
635
636    ldapURLFile = new FileArgument(null, "ldapURLFile", false, 0, null,
637         INFO_LDAPSEARCH_ARG_DESCRIPTION_LDAP_URL_FILE.get(), true, true,
638         true, false);
639    ldapURLFile.addLongIdentifier("ldap-url-file", true);
640    ldapURLFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
641    parser.addArgument(ldapURLFile);
642
643    followReferrals = new BooleanArgument(null, "followReferrals", 1,
644         INFO_LDAPSEARCH_ARG_DESCRIPTION_FOLLOW_REFERRALS.get());
645    followReferrals.addLongIdentifier("follow-referrals", true);
646    followReferrals.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
647    parser.addArgument(followReferrals);
648
649    retryFailedOperations = new BooleanArgument(null, "retryFailedOperations",
650         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_RETRY_FAILED_OPERATIONS.get());
651    retryFailedOperations.addLongIdentifier("retry-failed-operations", true);
652    retryFailedOperations.setArgumentGroupName(
653         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
654    parser.addArgument(retryFailedOperations);
655
656    continueOnError = new BooleanArgument('c', "continueOnError", 1,
657         INFO_LDAPSEARCH_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get());
658    continueOnError.addLongIdentifier("continue-on-error", true);
659    continueOnError.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
660    parser.addArgument(continueOnError);
661
662    ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1,
663         INFO_PLACEHOLDER_NUM.get(),
664         INFO_LDAPSEARCH_ARG_DESCRIPTION_RATE_PER_SECOND.get(), 1,
665         Integer.MAX_VALUE);
666    ratePerSecond.addLongIdentifier("rate-per-second", true);
667    ratePerSecond.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
668    parser.addArgument(ratePerSecond);
669
670    useAdministrativeSession = new BooleanArgument(null,
671         "useAdministrativeSession", 1,
672         INFO_LDAPSEARCH_ARG_DESCRIPTION_USE_ADMIN_SESSION.get());
673    useAdministrativeSession.addLongIdentifier("use-administrative-session",
674         true);
675    useAdministrativeSession.setArgumentGroupName(
676         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
677    parser.addArgument(useAdministrativeSession);
678
679    dryRun = new BooleanArgument('n', "dryRun", 1,
680         INFO_LDAPSEARCH_ARG_DESCRIPTION_DRY_RUN.get());
681    dryRun.addLongIdentifier("dry-run", true);
682    dryRun.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
683    parser.addArgument(dryRun);
684
685    wrapColumn = new IntegerArgument(null, "wrapColumn", false, 1, null,
686         INFO_LDAPSEARCH_ARG_DESCRIPTION_WRAP_COLUMN.get(), 0,
687         Integer.MAX_VALUE);
688    wrapColumn.addLongIdentifier("wrap-column", true);
689    wrapColumn.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
690    parser.addArgument(wrapColumn);
691
692    dontWrap = new BooleanArgument('T', "dontWrap", 1,
693         INFO_LDAPSEARCH_ARG_DESCRIPTION_DONT_WRAP.get());
694    dontWrap.addLongIdentifier("doNotWrap", true);
695    dontWrap.addLongIdentifier("dont-wrap", true);
696    dontWrap.addLongIdentifier("do-not-wrap", true);
697    dontWrap.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
698    parser.addArgument(dontWrap);
699
700    suppressBase64EncodedValueComments = new BooleanArgument(null,
701         "suppressBase64EncodedValueComments", 1,
702         INFO_LDAPSEARCH_ARG_DESCRIPTION_SUPPRESS_BASE64_COMMENTS.get());
703    suppressBase64EncodedValueComments.addLongIdentifier(
704         "suppress-base64-encoded-value-comments", true);
705    suppressBase64EncodedValueComments.setArgumentGroupName(
706         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
707    parser.addArgument(suppressBase64EncodedValueComments);
708
709    countEntries = new BooleanArgument(null, "countEntries", 1,
710         INFO_LDAPSEARCH_ARG_DESCRIPTION_COUNT_ENTRIES.get());
711    countEntries.addLongIdentifier("count-entries", true);
712    countEntries.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
713    countEntries.setHidden(true);
714    parser.addArgument(countEntries);
715
716    outputFile = new FileArgument(null, "outputFile", false, 1, null,
717         INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FILE.get(), false, true, true,
718         false);
719    outputFile.addLongIdentifier("output-file", true);
720    outputFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
721    parser.addArgument(outputFile);
722
723    compressOutput = new BooleanArgument(null, "compressOutput", 1,
724         INFO_LDAPSEARCH_ARG_DESCRIPTION_COMPRESS_OUTPUT.get());
725    compressOutput.addLongIdentifier("compress-output", true);
726    compressOutput.addLongIdentifier("compress", true);
727    compressOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
728    parser.addArgument(compressOutput);
729
730    encryptOutput = new BooleanArgument(null, "encryptOutput", 1,
731         INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPT_OUTPUT.get());
732    encryptOutput.addLongIdentifier("encrypt-output", true);
733    encryptOutput.addLongIdentifier("encrypt", true);
734    encryptOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
735    parser.addArgument(encryptOutput);
736
737    encryptionPassphraseFile = new FileArgument(null,
738         "encryptionPassphraseFile", false, 1, null,
739         INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPTION_PW_FILE.get(), true, true,
740         true, false);
741    encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file",
742         true);
743    encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true);
744    encryptionPassphraseFile.addLongIdentifier("encryption-password-file",
745         true);
746    encryptionPassphraseFile.setArgumentGroupName(
747         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
748    parser.addArgument(encryptionPassphraseFile);
749
750    separateOutputFilePerSearch = new BooleanArgument(null,
751         "separateOutputFilePerSearch", 1,
752         INFO_LDAPSEARCH_ARG_DESCRIPTION_SEPARATE_OUTPUT_FILES.get());
753    separateOutputFilePerSearch.addLongIdentifier(
754         "separate-output-file-per-search", true);
755    separateOutputFilePerSearch.setArgumentGroupName(
756         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
757    parser.addArgument(separateOutputFilePerSearch);
758
759    teeResultsToStandardOut = new BooleanArgument(null,
760         "teeResultsToStandardOut", 1,
761         INFO_LDAPSEARCH_ARG_DESCRIPTION_TEE.get("outputFile"));
762    teeResultsToStandardOut.addLongIdentifier(
763         "tee-results-to-standard-out", true);
764    teeResultsToStandardOut.setArgumentGroupName(
765         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
766    parser.addArgument(teeResultsToStandardOut);
767
768    final Set<String> outputFormatAllowedValues = StaticUtils.setOf("ldif",
769         "json", "csv", "multi-valued-csv", "tab-delimited",
770         "multi-valued-tab-delimited", "dns-only", "values-only");
771    outputFormat = new StringArgument(null, "outputFormat", false, 1,
772         "{ldif|json|csv|multi-valued-csv|tab-delimited|" +
773              "multi-valued-tab-delimited|dns-only|values-only}",
774         INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FORMAT.get(
775              requestedAttribute.getIdentifierString(),
776              ldapURLFile.getIdentifierString()),
777         outputFormatAllowedValues, "ldif");
778    outputFormat.addLongIdentifier("output-format", true);
779    outputFormat.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
780    parser.addArgument(outputFormat);
781
782    requireMatch = new BooleanArgument(null, "requireMatch", 1,
783         INFO_LDAPSEARCH_ARG_DESCRIPTION_REQUIRE_MATCH.get(
784              getToolName(),
785              String.valueOf(ResultCode.NO_RESULTS_RETURNED)));
786    requireMatch.addLongIdentifier("require-match", true);
787    requireMatch.addLongIdentifier("requireMatchingEntry", true);
788    requireMatch.addLongIdentifier("require-matching-entry", true);
789    requireMatch.addLongIdentifier("requireMatchingEntries", true);
790    requireMatch.addLongIdentifier("require-matching-entries", true);
791    requireMatch.addLongIdentifier("requireEntry", true);
792    requireMatch.addLongIdentifier("require-entry", true);
793    requireMatch.addLongIdentifier("requireEntries", true);
794    requireMatch.addLongIdentifier("require-entries", true);
795    requireMatch.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
796    parser.addArgument(requireMatch);
797
798    terse = new BooleanArgument(null, "terse", 1,
799         INFO_LDAPSEARCH_ARG_DESCRIPTION_TERSE.get());
800    terse.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
801    parser.addArgument(terse);
802
803    verbose = new BooleanArgument('v', "verbose", 1,
804         INFO_LDAPSEARCH_ARG_DESCRIPTION_VERBOSE.get());
805    verbose.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
806    parser.addArgument(verbose);
807
808    bindControl = new ControlArgument(null, "bindControl", false, 0, null,
809         INFO_LDAPSEARCH_ARG_DESCRIPTION_BIND_CONTROL.get());
810    bindControl.addLongIdentifier("bind-control", true);
811    bindControl.setArgumentGroupName(
812         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
813    parser.addArgument(bindControl);
814
815    searchControl = new ControlArgument('J', "control", false, 0, null,
816         INFO_LDAPSEARCH_ARG_DESCRIPTION_SEARCH_CONTROL.get());
817    searchControl.addLongIdentifier("searchControl", true);
818    searchControl.addLongIdentifier("search-control", true);
819    searchControl.setArgumentGroupName(
820         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
821    parser.addArgument(searchControl);
822
823    authorizationIdentity = new BooleanArgument('E', "authorizationIdentity",
824         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_AUTHZ_IDENTITY.get());
825    authorizationIdentity.addLongIdentifier("reportAuthzID", true);
826    authorizationIdentity.addLongIdentifier("authorization-identity", true);
827    authorizationIdentity.addLongIdentifier("report-authzid", true);
828    authorizationIdentity.setArgumentGroupName(
829         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
830    parser.addArgument(authorizationIdentity);
831
832    assertionFilter = new FilterArgument(null, "assertionFilter", false, 1,
833         INFO_PLACEHOLDER_FILTER.get(),
834         INFO_LDAPSEARCH_ARG_DESCRIPTION_ASSERTION_FILTER.get());
835    assertionFilter.addLongIdentifier("assertion-filter", true);
836    assertionFilter.setArgumentGroupName(
837         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
838    parser.addArgument(assertionFilter);
839
840    accountUsable = new BooleanArgument(null, "accountUsable", 1,
841         INFO_LDAPSEARCH_ARG_DESCRIPTION_ACCOUNT_USABLE.get());
842    accountUsable.addLongIdentifier("account-usable", true);
843    accountUsable.setArgumentGroupName(
844         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
845    parser.addArgument(accountUsable);
846
847    excludeBranch = new DNArgument(null, "excludeBranch", false, 0, null,
848         INFO_LDAPSEARCH_ARG_DESCRIPTION_EXCLUDE_BRANCH.get());
849    excludeBranch.addLongIdentifier("exclude-branch", true);
850    excludeBranch.setArgumentGroupName(
851         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
852    parser.addArgument(excludeBranch);
853
854    getAuthorizationEntryAttribute = new StringArgument(null,
855         "getAuthorizationEntryAttribute", false, 0,
856         INFO_PLACEHOLDER_ATTR.get(),
857         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get());
858    getAuthorizationEntryAttribute.addLongIdentifier(
859         "get-authorization-entry-attribute", true);
860    getAuthorizationEntryAttribute.setArgumentGroupName(
861         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
862    parser.addArgument(getAuthorizationEntryAttribute);
863
864    getBackendSetID = new BooleanArgument(null, "getBackendSetID",
865         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_BACKEND_SET_ID.get());
866    getBackendSetID.addLongIdentifier("get-backend-set-id", true);
867    getBackendSetID.setArgumentGroupName(
868         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
869    parser.addArgument(getBackendSetID);
870
871    getEffectiveRightsAuthzID = new StringArgument('g',
872         "getEffectiveRightsAuthzID", false, 1,
873         INFO_PLACEHOLDER_AUTHZID.get(),
874         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_AUTHZID.get(
875              "getEffectiveRightsAttribute"));
876    getEffectiveRightsAuthzID.addLongIdentifier(
877         "get-effective-rights-authzid", true);
878    getEffectiveRightsAuthzID.setArgumentGroupName(
879         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
880    parser.addArgument(getEffectiveRightsAuthzID);
881
882    getEffectiveRightsAttribute = new StringArgument('e',
883         "getEffectiveRightsAttribute", false, 0,
884         INFO_PLACEHOLDER_ATTR.get(),
885         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_ATTR.get());
886    getEffectiveRightsAttribute.addLongIdentifier(
887         "get-effective-rights-attribute", true);
888    getEffectiveRightsAttribute.setArgumentGroupName(
889         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
890    parser.addArgument(getEffectiveRightsAttribute);
891
892    getRecentLoginHistory = new BooleanArgument(null, "getRecentLoginHistory",
893         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_RECENT_LOGIN_HISTORY.get());
894    getRecentLoginHistory.addLongIdentifier("get-recent-login-history", true);
895    getRecentLoginHistory.setArgumentGroupName(
896         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
897    parser.addArgument(getRecentLoginHistory);
898
899    getServerID = new BooleanArgument(null, "getServerID",
900         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_SERVER_ID.get());
901    getServerID.addLongIdentifier("get-server-id", true);
902    getServerID.setArgumentGroupName(
903         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
904    parser.addArgument(getServerID);
905
906    getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits",
907         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_USER_RESOURCE_LIMITS.get());
908    getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true);
909    getUserResourceLimits.setArgumentGroupName(
910         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
911    parser.addArgument(getUserResourceLimits);
912
913    includeReplicationConflictEntries = new BooleanArgument(null,
914         "includeReplicationConflictEntries", 1,
915         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_REPL_CONFLICTS.get());
916    includeReplicationConflictEntries.addLongIdentifier(
917         "include-replication-conflict-entries", true);
918    includeReplicationConflictEntries.setArgumentGroupName(
919         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
920    parser.addArgument(includeReplicationConflictEntries);
921
922    final Set<String> softDeleteAllowedValues = StaticUtils.setOf(
923         "with-non-deleted-entries", "without-non-deleted-entries",
924         "deleted-entries-in-undeleted-form");
925    includeSoftDeletedEntries = new StringArgument(null,
926         "includeSoftDeletedEntries", false, 1,
927         "{with-non-deleted-entries|without-non-deleted-entries|" +
928              "deleted-entries-in-undeleted-form}",
929         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_SOFT_DELETED.get(),
930         softDeleteAllowedValues);
931    includeSoftDeletedEntries.addLongIdentifier(
932         "include-soft-deleted-entries", true);
933    includeSoftDeletedEntries.setArgumentGroupName(
934         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
935    parser.addArgument(includeSoftDeletedEntries);
936
937    draftLDUPSubentries = new BooleanArgument(null, "draftLDUPSubentries", 1,
938         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_DRAFT_LDUP_SUBENTRIES.get());
939    draftLDUPSubentries.addLongIdentifier("draftIETFLDUPSubentries", true);
940    draftLDUPSubentries.addLongIdentifier("includeSubentries", true);
941    draftLDUPSubentries.addLongIdentifier("includeLDAPSubentries", true);
942    draftLDUPSubentries.addLongIdentifier("draft-ldup-subentries", true);
943    draftLDUPSubentries.addLongIdentifier("draft-ietf-ldup-subentries", true);
944    draftLDUPSubentries.addLongIdentifier("include-subentries", true);
945    draftLDUPSubentries.addLongIdentifier("include-ldap-subentries", true);
946    draftLDUPSubentries.setArgumentGroupName(
947         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
948    parser.addArgument(draftLDUPSubentries);
949
950    rfc3672Subentries = new BooleanValueArgument(null, "rfc3672Subentries",
951         false,
952         INFO_LDAPSEARCH_ARG_PLACEHOLDER_INCLUDE_RFC_3672_SUBENTRIES.get(),
953         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_RFC_3672_SUBENTRIES.get());
954    rfc3672Subentries.addLongIdentifier("rfc-3672-subentries", true);
955    rfc3672Subentries.addLongIdentifier("rfc3672-subentries", true);
956    rfc3672Subentries.setArgumentGroupName(
957         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
958    parser.addArgument(rfc3672Subentries);
959
960    joinRule = new StringArgument(null, "joinRule", false, 1,
961         "{dn:sourceAttr|reverse-dn:targetAttr|equals:sourceAttr:targetAttr|" +
962              "contains:sourceAttr:targetAttr }",
963         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_RULE.get());
964    joinRule.addLongIdentifier("join-rule", true);
965    joinRule.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
966    parser.addArgument(joinRule);
967
968    joinBaseDN = new StringArgument(null, "joinBaseDN", false, 1,
969         "{search-base|source-entry-dn|{dn}}",
970         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_BASE_DN.get());
971    joinBaseDN.addLongIdentifier("join-base-dn", true);
972    joinBaseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
973    parser.addArgument(joinBaseDN);
974
975    joinScope = new ScopeArgument(null, "joinScope", false, null,
976         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SCOPE.get());
977    joinScope.addLongIdentifier("join-scope", true);
978    joinScope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
979    parser.addArgument(joinScope);
980
981    joinSizeLimit = new IntegerArgument(null, "joinSizeLimit", false, 1,
982         INFO_PLACEHOLDER_NUM.get(),
983         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SIZE_LIMIT.get(), 0,
984         Integer.MAX_VALUE);
985    joinSizeLimit.addLongIdentifier("join-size-limit", true);
986    joinSizeLimit.setArgumentGroupName(
987         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
988    parser.addArgument(joinSizeLimit);
989
990    joinFilter = new FilterArgument(null, "joinFilter", false, 1, null,
991         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_FILTER.get());
992    joinFilter.addLongIdentifier("join-filter", true);
993    joinFilter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
994    parser.addArgument(joinFilter);
995
996    joinRequestedAttribute = new StringArgument(null, "joinRequestedAttribute",
997         false, 0, INFO_PLACEHOLDER_ATTR.get(),
998         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_ATTR.get());
999    joinRequestedAttribute.addLongIdentifier("join-requested-attribute", true);
1000    joinRequestedAttribute.setArgumentGroupName(
1001         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1002    parser.addArgument(joinRequestedAttribute);
1003
1004    joinRequireMatch = new BooleanArgument(null, "joinRequireMatch", 1,
1005         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_REQUIRE_MATCH.get());
1006    joinRequireMatch.addLongIdentifier("join-require-match", true);
1007    joinRequireMatch.setArgumentGroupName(
1008         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1009    parser.addArgument(joinRequireMatch);
1010
1011    manageDsaIT = new BooleanArgument(null, "manageDsaIT", 1,
1012         INFO_LDAPSEARCH_ARG_DESCRIPTION_MANAGE_DSA_IT.get());
1013    manageDsaIT.addLongIdentifier("manage-dsa-it", true);
1014    manageDsaIT.setArgumentGroupName(
1015         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1016    parser.addArgument(manageDsaIT);
1017
1018    matchedValuesFilter = new FilterArgument(null, "matchedValuesFilter",
1019         false, 0, INFO_PLACEHOLDER_FILTER.get(),
1020         INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHED_VALUES_FILTER.get());
1021    matchedValuesFilter.addLongIdentifier("matched-values-filter", true);
1022    matchedValuesFilter.setArgumentGroupName(
1023         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1024    parser.addArgument(matchedValuesFilter);
1025
1026    matchingEntryCountControl = new StringArgument(null,
1027         "matchingEntryCountControl", false, 1,
1028         "{examineCount=NNN[:alwaysExamine][:allowUnindexed]" +
1029              "[:skipResolvingExplodedIndexes]" +
1030              "[:fastShortCircuitThreshold=NNN]" +
1031              "[:slowShortCircuitThreshold=NNN][:extendedResponseData]" +
1032              "[:debug]}",
1033         INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHING_ENTRY_COUNT_CONTROL.get());
1034    matchingEntryCountControl.addLongIdentifier("matchingEntryCount", true);
1035    matchingEntryCountControl.addLongIdentifier(
1036         "matching-entry-count-control", true);
1037    matchingEntryCountControl.addLongIdentifier("matching-entry-count", true);
1038    matchingEntryCountControl.setArgumentGroupName(
1039         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1040    parser.addArgument(matchingEntryCountControl);
1041
1042    operationPurpose = new StringArgument(null, "operationPurpose", false, 1,
1043         INFO_PLACEHOLDER_PURPOSE.get(),
1044         INFO_LDAPSEARCH_ARG_DESCRIPTION_OPERATION_PURPOSE.get());
1045    operationPurpose.addLongIdentifier("operation-purpose", true);
1046    operationPurpose.setArgumentGroupName(
1047         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1048    parser.addArgument(operationPurpose);
1049
1050    overrideSearchLimit = new StringArgument(null, "overrideSearchLimit",
1051         false, 0, INFO_LDAPSEARCH_NAME_VALUE_PLACEHOLDER.get(),
1052         INFO_LDAPSEARCH_ARG_DESCRIPTION_OVERRIDE_SEARCH_LIMIT.get());
1053    overrideSearchLimit.addLongIdentifier("overrideSearchLimits", true);
1054    overrideSearchLimit.addLongIdentifier("override-search-limit", true);
1055    overrideSearchLimit.addLongIdentifier("override-search-limits", true);
1056    overrideSearchLimit.setArgumentGroupName(
1057         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1058    parser.addArgument(overrideSearchLimit);
1059
1060    persistentSearch = new StringArgument('C', "persistentSearch", false, 1,
1061         "ps[:changetype[:changesonly[:entrychgcontrols]]]",
1062         INFO_LDAPSEARCH_ARG_DESCRIPTION_PERSISTENT_SEARCH.get());
1063    persistentSearch.addLongIdentifier("persistent-search", true);
1064    persistentSearch.setArgumentGroupName(
1065         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1066    parser.addArgument(persistentSearch);
1067
1068    permitUnindexedSearch = new BooleanArgument(null, "permitUnindexedSearch",
1069         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_PERMIT_UNINDEXED_SEARCH.get());
1070    permitUnindexedSearch.addLongIdentifier("permitUnindexedSearches", true);
1071    permitUnindexedSearch.addLongIdentifier("permitUnindexed", true);
1072    permitUnindexedSearch.addLongIdentifier("permitIfUnindexed", true);
1073    permitUnindexedSearch.addLongIdentifier("permit-unindexed-search", true);
1074    permitUnindexedSearch.addLongIdentifier("permit-unindexed-searches", true);
1075    permitUnindexedSearch.addLongIdentifier("permit-unindexed", true);
1076    permitUnindexedSearch.addLongIdentifier("permit-if-unindexed", true);
1077    permitUnindexedSearch.setArgumentGroupName(
1078         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1079    parser.addArgument(permitUnindexedSearch);
1080
1081    proxyAs = new StringArgument('Y', "proxyAs", false, 1,
1082         INFO_PLACEHOLDER_AUTHZID.get(),
1083         INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_AS.get());
1084    proxyAs.addLongIdentifier("proxy-as", true);
1085    proxyAs.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1086    parser.addArgument(proxyAs);
1087
1088    proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null,
1089         INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_V1_AS.get());
1090    proxyV1As.addLongIdentifier("proxy-v1-as", true);
1091    proxyV1As.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1092    parser.addArgument(proxyV1As);
1093
1094    rejectUnindexedSearch = new BooleanArgument(null, "rejectUnindexedSearch",
1095         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_REJECT_UNINDEXED_SEARCH.get());
1096    rejectUnindexedSearch.addLongIdentifier("rejectUnindexedSearches", true);
1097    rejectUnindexedSearch.addLongIdentifier("rejectUnindexed", true);
1098    rejectUnindexedSearch.addLongIdentifier("rejectIfUnindexed", true);
1099    rejectUnindexedSearch.addLongIdentifier("reject-unindexed-search", true);
1100    rejectUnindexedSearch.addLongIdentifier("reject-unindexed-searches", true);
1101    rejectUnindexedSearch.addLongIdentifier("reject-unindexed", true);
1102    rejectUnindexedSearch.addLongIdentifier("reject-if-unindexed", true);
1103    rejectUnindexedSearch.setArgumentGroupName(
1104         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1105    parser.addArgument(rejectUnindexedSearch);
1106
1107    routeToBackendSet = new StringArgument(null, "routeToBackendSet",
1108         false, 0,
1109         INFO_LDAPSEARCH_ARG_PLACEHOLDER_ROUTE_TO_BACKEND_SET.get(),
1110         INFO_LDAPSEARCH_ARG_DESCRIPTION_ROUTE_TO_BACKEND_SET.get());
1111    routeToBackendSet.addLongIdentifier("route-to-backend-set", true);
1112    routeToBackendSet.setArgumentGroupName(
1113         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1114    parser.addArgument(routeToBackendSet);
1115
1116    routeToServer = new StringArgument(null, "routeToServer", false, 1,
1117         INFO_LDAPSEARCH_ARG_PLACEHOLDER_ROUTE_TO_SERVER.get(),
1118         INFO_LDAPSEARCH_ARG_DESCRIPTION_ROUTE_TO_SERVER.get());
1119    routeToServer.addLongIdentifier("route-to-server", true);
1120    routeToServer.setArgumentGroupName(
1121         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1122    parser.addArgument(routeToServer);
1123
1124    final Set<String> suppressOperationalAttributeUpdatesAllowedValues =
1125         StaticUtils.setOf("last-access-time", "last-login-time",
1126              "last-login-ip", "lastmod");
1127    suppressOperationalAttributeUpdates = new StringArgument(null,
1128         "suppressOperationalAttributeUpdates", false, -1,
1129         INFO_PLACEHOLDER_ATTR.get(),
1130         INFO_LDAPSEARCH_ARG_DESCRIPTION_SUPPRESS_OP_ATTR_UPDATES.get(),
1131         suppressOperationalAttributeUpdatesAllowedValues);
1132    suppressOperationalAttributeUpdates.addLongIdentifier(
1133         "suppress-operational-attribute-updates", true);
1134    suppressOperationalAttributeUpdates.setArgumentGroupName(
1135         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1136    parser.addArgument(suppressOperationalAttributeUpdates);
1137
1138    usePasswordPolicyControl = new BooleanArgument(null,
1139         "usePasswordPolicyControl", 1,
1140         INFO_LDAPSEARCH_ARG_DESCRIPTION_PASSWORD_POLICY.get());
1141    usePasswordPolicyControl.addLongIdentifier("use-password-policy-control",
1142         true);
1143    usePasswordPolicyControl.setArgumentGroupName(
1144         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1145    parser.addArgument(usePasswordPolicyControl);
1146
1147    realAttributesOnly = new BooleanArgument(null, "realAttributesOnly", 1,
1148         INFO_LDAPSEARCH_ARG_DESCRIPTION_REAL_ATTRS_ONLY.get());
1149    realAttributesOnly.addLongIdentifier("real-attributes-only", true);
1150    realAttributesOnly.setArgumentGroupName(
1151         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1152    parser.addArgument(realAttributesOnly);
1153
1154    sortOrder = new StringArgument('S', "sortOrder", false, 1, null,
1155         INFO_LDAPSEARCH_ARG_DESCRIPTION_SORT_ORDER.get());
1156    sortOrder.addLongIdentifier("sort-order", true);
1157    sortOrder.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1158    parser.addArgument(sortOrder);
1159
1160    simplePageSize = new IntegerArgument(null, "simplePageSize", false, 1,
1161         null, INFO_LDAPSEARCH_ARG_DESCRIPTION_PAGE_SIZE.get(), 1,
1162         Integer.MAX_VALUE);
1163    simplePageSize.addLongIdentifier("simple-page-size", true);
1164    simplePageSize.setArgumentGroupName(
1165         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1166    parser.addArgument(simplePageSize);
1167
1168    virtualAttributesOnly = new BooleanArgument(null,
1169         "virtualAttributesOnly", 1,
1170         INFO_LDAPSEARCH_ARG_DESCRIPTION_VIRTUAL_ATTRS_ONLY.get());
1171    virtualAttributesOnly.addLongIdentifier("virtual-attributes-only", true);
1172    virtualAttributesOnly.setArgumentGroupName(
1173         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1174    parser.addArgument(virtualAttributesOnly);
1175
1176    virtualListView = new StringArgument('G', "virtualListView", false, 1,
1177         "{before:after:index:count | before:after:value}",
1178         INFO_LDAPSEARCH_ARG_DESCRIPTION_VLV.get("sortOrder"));
1179    virtualListView.addLongIdentifier("vlv", true);
1180    virtualListView.addLongIdentifier("virtual-list-view", true);
1181    virtualListView.setArgumentGroupName(
1182         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1183    parser.addArgument(virtualListView);
1184
1185    useJSONFormattedRequestControls = new BooleanArgument(null,
1186         "useJSONFormattedRequestControls", 1,
1187         INFO_LDAPSEARCH_ARG_DESCRIPTION_USE_JSON_FORMATTED_CONTROLS.get());
1188    useJSONFormattedRequestControls.addLongIdentifier(
1189         "use-json-formatted-request-controls", true);
1190    useJSONFormattedRequestControls.addLongIdentifier(
1191         "useJSONFormattedControls", true);
1192    useJSONFormattedRequestControls.addLongIdentifier(
1193         "use-json-formatted-controls", true);
1194    useJSONFormattedRequestControls.setArgumentGroupName(
1195         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
1196    parser.addArgument(useJSONFormattedRequestControls);
1197
1198    excludeAttribute = new StringArgument(null, "excludeAttribute", false, 0,
1199         INFO_PLACEHOLDER_ATTR.get(),
1200         INFO_LDAPSEARCH_ARG_DESCRIPTION_EXCLUDE_ATTRIBUTE.get());
1201    excludeAttribute.addLongIdentifier("exclude-attribute", true);
1202    excludeAttribute.setArgumentGroupName(
1203         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1204    parser.addArgument(excludeAttribute);
1205
1206    redactAttribute = new StringArgument(null, "redactAttribute", false, 0,
1207         INFO_PLACEHOLDER_ATTR.get(),
1208         INFO_LDAPSEARCH_ARG_DESCRIPTION_REDACT_ATTRIBUTE.get());
1209    redactAttribute.addLongIdentifier("redact-attribute", true);
1210    redactAttribute.setArgumentGroupName(
1211         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1212    parser.addArgument(redactAttribute);
1213
1214    hideRedactedValueCount = new BooleanArgument(null, "hideRedactedValueCount",
1215         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_HIDE_REDACTED_VALUE_COUNT.get());
1216    hideRedactedValueCount.addLongIdentifier("hide-redacted-value-count", true);
1217    hideRedactedValueCount.setArgumentGroupName(
1218         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1219    parser.addArgument(hideRedactedValueCount);
1220
1221    scrambleAttribute = new StringArgument(null, "scrambleAttribute", false, 0,
1222         INFO_PLACEHOLDER_ATTR.get(),
1223         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_ATTRIBUTE.get());
1224    scrambleAttribute.addLongIdentifier("scramble-attribute", true);
1225    scrambleAttribute.setArgumentGroupName(
1226         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1227    parser.addArgument(scrambleAttribute);
1228
1229    scrambleJSONField = new StringArgument(null, "scrambleJSONField", false, 0,
1230         INFO_PLACEHOLDER_FIELD_NAME.get(),
1231         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_JSON_FIELD.get());
1232    scrambleJSONField.addLongIdentifier("scramble-json-field", true);
1233    scrambleJSONField.setArgumentGroupName(
1234         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1235    parser.addArgument(scrambleJSONField);
1236
1237    scrambleRandomSeed = new IntegerArgument(null, "scrambleRandomSeed", false,
1238         1, null, INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_RANDOM_SEED.get());
1239    scrambleRandomSeed.addLongIdentifier("scramble-random-seed", true);
1240    scrambleRandomSeed.setArgumentGroupName(
1241         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1242    parser.addArgument(scrambleRandomSeed);
1243
1244    renameAttributeFrom = new StringArgument(null, "renameAttributeFrom", false,
1245         0, INFO_PLACEHOLDER_ATTR.get(),
1246         INFO_LDAPSEARCH_ARG_DESCRIPTION_RENAME_ATTRIBUTE_FROM.get());
1247    renameAttributeFrom.addLongIdentifier("rename-attribute-from", true);
1248    renameAttributeFrom.setArgumentGroupName(
1249         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1250    parser.addArgument(renameAttributeFrom);
1251
1252    renameAttributeTo = new StringArgument(null, "renameAttributeTo", false,
1253         0, INFO_PLACEHOLDER_ATTR.get(),
1254         INFO_LDAPSEARCH_ARG_DESCRIPTION_RENAME_ATTRIBUTE_TO.get());
1255    renameAttributeTo.addLongIdentifier("rename-attribute-to", true);
1256    renameAttributeTo.setArgumentGroupName(
1257         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1258    parser.addArgument(renameAttributeTo);
1259
1260    moveSubtreeFrom = new DNArgument(null, "moveSubtreeFrom", false, 0,
1261         INFO_PLACEHOLDER_ATTR.get(),
1262         INFO_LDAPSEARCH_ARG_DESCRIPTION_MOVE_SUBTREE_FROM.get());
1263    moveSubtreeFrom.addLongIdentifier("move-subtree-from", true);
1264    moveSubtreeFrom.setArgumentGroupName(
1265         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1266    parser.addArgument(moveSubtreeFrom);
1267
1268    moveSubtreeTo = new DNArgument(null, "moveSubtreeTo", false, 0,
1269         INFO_PLACEHOLDER_ATTR.get(),
1270         INFO_LDAPSEARCH_ARG_DESCRIPTION_MOVE_SUBTREE_TO.get());
1271    moveSubtreeTo.addLongIdentifier("move-subtree-to", true);
1272    moveSubtreeTo.setArgumentGroupName(
1273         INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get());
1274    parser.addArgument(moveSubtreeTo);
1275
1276
1277    // The "--scriptFriendly" argument is provided for compatibility with legacy
1278    // ldapsearch tools, but is not actually used by this tool.
1279    final BooleanArgument scriptFriendly = new BooleanArgument(null,
1280         "scriptFriendly", 1,
1281         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRIPT_FRIENDLY.get());
1282    scriptFriendly.addLongIdentifier("script-friendly", true);
1283    scriptFriendly.setHidden(true);
1284    parser.addArgument(scriptFriendly);
1285
1286
1287    // The "-V" / "--ldapVersion" argument is provided for compatibility with
1288    // legacy ldapsearch tools, but is not actually used by this tool.
1289    final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion",
1290         false, 1, null, INFO_LDAPSEARCH_ARG_DESCRIPTION_LDAP_VERSION.get());
1291    ldapVersion.addLongIdentifier("ldap-version", true);
1292    ldapVersion.setHidden(true);
1293    parser.addArgument(ldapVersion);
1294
1295
1296    // The baseDN and ldapURLFile arguments can't be used together.
1297    parser.addExclusiveArgumentSet(baseDN, ldapURLFile);
1298
1299    // The scope and ldapURLFile arguments can't be used together.
1300    parser.addExclusiveArgumentSet(scope, ldapURLFile);
1301
1302    // The requestedAttribute and ldapURLFile arguments can't be used together.
1303    parser.addExclusiveArgumentSet(requestedAttribute, ldapURLFile);
1304
1305    // The filter and ldapURLFile arguments can't be used together.
1306    parser.addExclusiveArgumentSet(filter, ldapURLFile);
1307
1308    // The filterFile and ldapURLFile arguments can't be used together.
1309    parser.addExclusiveArgumentSet(filterFile, ldapURLFile);
1310
1311    // The followReferrals and manageDsaIT arguments can't be used together.
1312    parser.addExclusiveArgumentSet(followReferrals, manageDsaIT);
1313
1314    // The persistent search argument can't be used with either the filterFile
1315    // or ldapURLFile arguments.
1316    parser.addExclusiveArgumentSet(persistentSearch, filterFile);
1317    parser.addExclusiveArgumentSet(persistentSearch, ldapURLFile);
1318
1319    // The draft-ietf-ldup-subentry and RFC 3672 subentries controls cannot be
1320    // used together.
1321    parser.addExclusiveArgumentSet(draftLDUPSubentries, rfc3672Subentries);
1322
1323    // The realAttributesOnly and virtualAttributesOnly arguments can't be used
1324    // together.
1325    parser.addExclusiveArgumentSet(realAttributesOnly, virtualAttributesOnly);
1326
1327    // The simplePageSize and virtualListView arguments can't be used together.
1328    parser.addExclusiveArgumentSet(simplePageSize, virtualListView);
1329
1330    // The terse and verbose arguments can't be used together.
1331    parser.addExclusiveArgumentSet(terse, verbose);
1332
1333    // The getEffectiveRightsAttribute argument requires the
1334    // getEffectiveRightsAuthzID argument.
1335    parser.addDependentArgumentSet(getEffectiveRightsAttribute,
1336         getEffectiveRightsAuthzID);
1337
1338    // The virtualListView argument requires the sortOrder argument.
1339    parser.addDependentArgumentSet(virtualListView, sortOrder);
1340
1341    // The rejectUnindexedSearch and permitUnindexedSearch arguments can't be
1342    // used together.
1343    parser.addExclusiveArgumentSet(rejectUnindexedSearch,
1344         permitUnindexedSearch);
1345
1346    // The separateOutputFilePerSearch argument requires the outputFile
1347    // argument.  It also requires either the filter, filterFile or ldapURLFile
1348    // argument.
1349    parser.addDependentArgumentSet(separateOutputFilePerSearch, outputFile);
1350    parser.addDependentArgumentSet(separateOutputFilePerSearch, filter,
1351         filterFile, ldapURLFile);
1352
1353    // The teeResultsToStandardOut argument requires the outputFile argument.
1354    parser.addDependentArgumentSet(teeResultsToStandardOut, outputFile);
1355
1356    // The wrapColumn and dontWrap arguments must not be used together.
1357    parser.addExclusiveArgumentSet(wrapColumn, dontWrap);
1358
1359    // All arguments that specifically pertain to join processing can only be
1360    // used if the joinRule argument is provided.
1361    parser.addDependentArgumentSet(joinBaseDN, joinRule);
1362    parser.addDependentArgumentSet(joinScope, joinRule);
1363    parser.addDependentArgumentSet(joinSizeLimit, joinRule);
1364    parser.addDependentArgumentSet(joinFilter, joinRule);
1365    parser.addDependentArgumentSet(joinRequestedAttribute, joinRule);
1366    parser.addDependentArgumentSet(joinRequireMatch, joinRule);
1367
1368    // The countEntries argument must not be used in conjunction with the
1369    // filter, filterFile, LDAPURLFile, or persistentSearch arguments.
1370    parser.addExclusiveArgumentSet(countEntries, filter);
1371    parser.addExclusiveArgumentSet(countEntries, filterFile);
1372    parser.addExclusiveArgumentSet(countEntries, ldapURLFile);
1373    parser.addExclusiveArgumentSet(countEntries, persistentSearch);
1374
1375
1376    // The hideRedactedValueCount argument requires the redactAttribute
1377    // argument.
1378    parser.addDependentArgumentSet(hideRedactedValueCount, redactAttribute);
1379
1380    // The scrambleJSONField and scrambleRandomSeed arguments require the
1381    // scrambleAttribute argument.
1382    parser.addDependentArgumentSet(scrambleJSONField, scrambleAttribute);
1383    parser.addDependentArgumentSet(scrambleRandomSeed, scrambleAttribute);
1384
1385    // The renameAttributeFrom and renameAttributeTo arguments must be provided
1386    // together.
1387    parser.addDependentArgumentSet(renameAttributeFrom, renameAttributeTo);
1388    parser.addDependentArgumentSet(renameAttributeTo, renameAttributeFrom);
1389
1390    // The moveSubtreeFrom and moveSubtreeTo arguments must be provided
1391    // together.
1392    parser.addDependentArgumentSet(moveSubtreeFrom, moveSubtreeTo);
1393    parser.addDependentArgumentSet(moveSubtreeTo, moveSubtreeFrom);
1394
1395
1396    // The compressOutput argument can only be used if an output file is
1397    // specified and results aren't going to be teed.
1398    parser.addDependentArgumentSet(compressOutput, outputFile);
1399    parser.addExclusiveArgumentSet(compressOutput, teeResultsToStandardOut);
1400
1401
1402    // The encryptOutput argument can only be used if an output file is
1403    // specified and results aren't going to be teed.
1404    parser.addDependentArgumentSet(encryptOutput, outputFile);
1405    parser.addExclusiveArgumentSet(encryptOutput, teeResultsToStandardOut);
1406
1407
1408    // The encryptionPassphraseFile argument can only be used if the
1409    // encryptOutput argument is also provided.
1410    parser.addDependentArgumentSet(encryptionPassphraseFile, encryptOutput);
1411  }
1412
1413
1414
1415  /**
1416   * {@inheritDoc}
1417   */
1418  @Override()
1419  @NotNull()
1420  protected List<Control> getBindControls()
1421  {
1422    final ArrayList<Control> bindControls = new ArrayList<>(10);
1423
1424    if (bindControl.isPresent())
1425    {
1426      bindControls.addAll(bindControl.getValues());
1427    }
1428
1429    if (authorizationIdentity.isPresent())
1430    {
1431      bindControls.add(new AuthorizationIdentityRequestControl(false));
1432    }
1433
1434    if (getAuthorizationEntryAttribute.isPresent())
1435    {
1436      bindControls.add(new GetAuthorizationEntryRequestControl(true, true,
1437           getAuthorizationEntryAttribute.getValues()));
1438    }
1439
1440    if (getRecentLoginHistory.isPresent())
1441    {
1442      bindControls.add(new GetRecentLoginHistoryRequestControl());
1443    }
1444
1445    if (getUserResourceLimits.isPresent())
1446    {
1447      bindControls.add(new GetUserResourceLimitsRequestControl());
1448    }
1449
1450    if (usePasswordPolicyControl.isPresent())
1451    {
1452      bindControls.add(new PasswordPolicyRequestControl());
1453    }
1454
1455    if (suppressOperationalAttributeUpdates.isPresent())
1456    {
1457      final EnumSet<SuppressType> suppressTypes =
1458           EnumSet.noneOf(SuppressType.class);
1459      for (final String s : suppressOperationalAttributeUpdates.getValues())
1460      {
1461        if (s.equalsIgnoreCase("last-access-time"))
1462        {
1463          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
1464        }
1465        else if (s.equalsIgnoreCase("last-login-time"))
1466        {
1467          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
1468        }
1469        else if (s.equalsIgnoreCase("last-login-ip"))
1470        {
1471          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
1472        }
1473      }
1474
1475      bindControls.add(new SuppressOperationalAttributeUpdateRequestControl(
1476           suppressTypes));
1477    }
1478
1479    if (useJSONFormattedRequestControls.isPresent())
1480    {
1481      final JSONFormattedRequestControl jsonFormattedRequestControl =
1482           JSONFormattedRequestControl.createWithControls(true, bindControls);
1483      bindControls.clear();
1484      bindControls.add(jsonFormattedRequestControl);
1485    }
1486
1487    return bindControls;
1488  }
1489
1490
1491
1492  /**
1493   * {@inheritDoc}
1494   */
1495  @Override()
1496  protected boolean supportsMultipleServers()
1497  {
1498    // We will support providing information about multiple servers.  This tool
1499    // will not communicate with multiple servers concurrently, but it can
1500    // accept information about multiple servers in the event that multiple
1501    // searches are to be performed and a server goes down in the middle of
1502    // those searches.  In this case, we can resume processing on a
1503    // newly-created connection, possibly to a different server.
1504    return true;
1505  }
1506
1507
1508
1509  /**
1510   * {@inheritDoc}
1511   */
1512  @Override()
1513  public void doExtendedNonLDAPArgumentValidation()
1514         throws ArgumentException
1515  {
1516    // If wrapColumn was provided, then use its value.  Otherwise, if dontWrap
1517    // was provided, then use that.
1518    if (wrapColumn.isPresent())
1519    {
1520      final int wc = wrapColumn.getValue();
1521      if (wc <= 0)
1522      {
1523        WRAP_COLUMN = Integer.MAX_VALUE;
1524      }
1525      else
1526      {
1527        WRAP_COLUMN = wc;
1528      }
1529    }
1530    else if (dontWrap.isPresent())
1531    {
1532      WRAP_COLUMN = Integer.MAX_VALUE;
1533    }
1534
1535
1536    // If the ldapURLFile argument was provided, then there must not be any
1537    // trailing arguments.
1538    final List<String> trailingArgs = parser.getTrailingArguments();
1539    if (ldapURLFile.isPresent())
1540    {
1541      if (! trailingArgs.isEmpty())
1542      {
1543        throw new ArgumentException(
1544             ERR_LDAPSEARCH_TRAILING_ARGS_WITH_URL_FILE.get(
1545                  ldapURLFile.getIdentifierString()));
1546      }
1547    }
1548
1549
1550    // If the filter or filterFile argument was provided, then there may
1551    // optionally be trailing arguments, but the first trailing argument must
1552    // not be a filter.
1553    if (filter.isPresent() || filterFile.isPresent())
1554    {
1555      if (! trailingArgs.isEmpty())
1556      {
1557        try
1558        {
1559          Filter.create(trailingArgs.get(0));
1560          throw new ArgumentException(
1561               ERR_LDAPSEARCH_TRAILING_FILTER_WITH_FILTER_FILE.get(
1562                    filterFile.getIdentifierString()));
1563        }
1564        catch (final LDAPException le)
1565        {
1566          // This is the normal condition.  Not even worth debugging the
1567          // exception.
1568        }
1569      }
1570    }
1571
1572
1573    // If none of the ldapURLFile, filter, or filterFile arguments was provided,
1574    // then there must be at least one trailing argument, and the first trailing
1575    // argument must be a valid search filter.
1576    if (! (ldapURLFile.isPresent() || filter.isPresent() ||
1577           filterFile.isPresent()))
1578    {
1579      if (trailingArgs.isEmpty())
1580      {
1581        throw new ArgumentException(ERR_LDAPSEARCH_NO_TRAILING_ARGS.get(
1582             filterFile.getIdentifierString(),
1583             ldapURLFile.getIdentifierString()));
1584      }
1585
1586      try
1587      {
1588        Filter.create(trailingArgs.get(0));
1589      }
1590      catch (final Exception e)
1591      {
1592        Debug.debugException(e);
1593        throw new ArgumentException(
1594             ERR_LDAPSEARCH_FIRST_TRAILING_ARG_NOT_FILTER.get(
1595                  trailingArgs.get(0)),
1596             e);
1597      }
1598    }
1599
1600
1601    // There should never be a case in which a trailing argument starts with a
1602    // dash, and it's probably an attempt to use a named argument but that was
1603    // inadvertently put after the filter.  Warn about the problem, but don't
1604    // fail.
1605    for (final String s : trailingArgs)
1606    {
1607      if (s.startsWith("-"))
1608      {
1609        commentToErr(WARN_LDAPSEARCH_TRAILING_ARG_STARTS_WITH_DASH.get(s));
1610        break;
1611      }
1612    }
1613
1614
1615    // If any matched values filters are specified, then validate them and
1616    // pre-create the matched values request control.
1617    if (matchedValuesFilter.isPresent())
1618    {
1619      final List<Filter> filterList = matchedValuesFilter.getValues();
1620      final MatchedValuesFilter[] matchedValuesFilters =
1621           new MatchedValuesFilter[filterList.size()];
1622      for (int i=0; i < matchedValuesFilters.length; i++)
1623      {
1624        try
1625        {
1626          matchedValuesFilters[i] =
1627               MatchedValuesFilter.create(filterList.get(i));
1628        }
1629        catch (final Exception e)
1630        {
1631          Debug.debugException(e);
1632          throw new ArgumentException(
1633               ERR_LDAPSEARCH_INVALID_MATCHED_VALUES_FILTER.get(
1634                    filterList.get(i).toString()),
1635               e);
1636        }
1637      }
1638
1639      matchedValuesRequestControl =
1640           new MatchedValuesRequestControl(true, matchedValuesFilters);
1641    }
1642
1643
1644    // If we should use the matching entry count request control, then validate
1645    // the argument value and pre-create the control.
1646    if (matchingEntryCountControl.isPresent())
1647    {
1648      final MatchingEntryCountRequestControlProperties properties =
1649           new MatchingEntryCountRequestControlProperties();
1650
1651      Integer examineCount                 = null;
1652      try
1653      {
1654        for (final String element :
1655             matchingEntryCountControl.getValue().toLowerCase().split(":"))
1656        {
1657          if (element.startsWith("examinecount="))
1658          {
1659            examineCount = Integer.parseInt(element.substring(13));
1660          }
1661          else if (element.equals("allowunindexed"))
1662          {
1663            properties.setProcessSearchIfUnindexed(true);
1664          }
1665          else if (element.equals("alwaysexamine"))
1666          {
1667            properties.setAlwaysExamineCandidates(true);
1668          }
1669          else if (element.equals("skipresolvingexplodedindexes"))
1670          {
1671            properties.setSkipResolvingExplodedIndexes(true);
1672          }
1673          else if (element.startsWith("fastshortcircuitthreshold="))
1674          {
1675            properties.setFastShortCircuitThreshold(
1676                 Long.parseLong(element.substring(26)));
1677          }
1678          else if (element.startsWith("slowshortcircuitthreshold="))
1679          {
1680            properties.setSlowShortCircuitThreshold(
1681                 Long.parseLong(element.substring(26)));
1682          }
1683          else if (element.equals("extendedresponsedata"))
1684          {
1685            properties.setIncludeExtendedResponseData(true);
1686          }
1687          else if (element.equals("debug"))
1688          {
1689            properties.setIncludeDebugInfo(true);
1690          }
1691          else
1692          {
1693            throw new ArgumentException(
1694                 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get(
1695                      matchingEntryCountControl.getIdentifierString()));
1696          }
1697        }
1698      }
1699      catch (final ArgumentException ae)
1700      {
1701        Debug.debugException(ae);
1702        throw ae;
1703      }
1704      catch (final Exception e)
1705      {
1706        Debug.debugException(e);
1707        throw new ArgumentException(
1708             ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get(
1709                  matchingEntryCountControl.getIdentifierString()),
1710             e);
1711      }
1712
1713      if (examineCount == null)
1714      {
1715        throw new ArgumentException(
1716             ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get(
1717                  matchingEntryCountControl.getIdentifierString()));
1718      }
1719      else
1720      {
1721        properties.setMaxCandidatesToExamine(examineCount);
1722      }
1723
1724      matchingEntryCountRequestControl =
1725           new MatchingEntryCountRequestControl(true, properties);
1726    }
1727
1728
1729    // If we should include the override search limits request control, then
1730    // validate the provided values.
1731    if (overrideSearchLimit.isPresent())
1732    {
1733      final LinkedHashMap<String,String> properties =
1734           new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1735      for (final String value : overrideSearchLimit.getValues())
1736      {
1737        final int equalPos = value.indexOf('=');
1738        if (equalPos < 0)
1739        {
1740          throw new ArgumentException(
1741               ERR_LDAPSEARCH_OVERRIDE_LIMIT_NO_EQUAL.get(
1742                    overrideSearchLimit.getIdentifierString()));
1743        }
1744        else if (equalPos == 0)
1745        {
1746          throw new ArgumentException(
1747               ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_NAME.get(
1748                    overrideSearchLimit.getIdentifierString()));
1749        }
1750
1751        final String propertyName = value.substring(0, equalPos);
1752        if (properties.containsKey(propertyName))
1753        {
1754          throw new ArgumentException(
1755               ERR_LDAPSEARCH_OVERRIDE_LIMIT_DUPLICATE_PROPERTY_NAME.get(
1756                    overrideSearchLimit.getIdentifierString(), propertyName));
1757        }
1758
1759        if (equalPos == (value.length() - 1))
1760        {
1761          throw new ArgumentException(
1762               ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_VALUE.get(
1763                    overrideSearchLimit.getIdentifierString(), propertyName));
1764        }
1765
1766        properties.put(propertyName, value.substring(equalPos+1));
1767      }
1768
1769      overrideSearchLimitsRequestControl =
1770           new OverrideSearchLimitsRequestControl(properties, false);
1771    }
1772
1773
1774    // If we should use the persistent search request control, then validate
1775    // the argument value and pre-create the control.
1776    if (persistentSearch.isPresent())
1777    {
1778      boolean changesOnly = true;
1779      boolean returnECs   = true;
1780      EnumSet<PersistentSearchChangeType> changeTypes =
1781           EnumSet.allOf(PersistentSearchChangeType.class);
1782      try
1783      {
1784        final String[] elements =
1785             persistentSearch.getValue().toLowerCase().split(":");
1786        if (elements.length == 0)
1787        {
1788          throw new ArgumentException(
1789               ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1790                    persistentSearch.getIdentifierString()));
1791        }
1792
1793        final String header = StaticUtils.toLowerCase(elements[0]);
1794        if (! (header.equals("ps") || header.equals("persist") ||
1795             header.equals("persistent") || header.equals("psearch") ||
1796             header.equals("persistentsearch")))
1797        {
1798          throw new ArgumentException(
1799               ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1800                    persistentSearch.getIdentifierString()));
1801        }
1802
1803        if (elements.length > 1)
1804        {
1805          final String ctString = StaticUtils.toLowerCase(elements[1]);
1806          if (ctString.equals("any"))
1807          {
1808            changeTypes = EnumSet.allOf(PersistentSearchChangeType.class);
1809          }
1810          else
1811          {
1812            changeTypes.clear();
1813            for (final String t : ctString.split(","))
1814            {
1815              if (t.equals("add"))
1816              {
1817                changeTypes.add(PersistentSearchChangeType.ADD);
1818              }
1819              else if (t.equals("del") || t.equals("delete"))
1820              {
1821                changeTypes.add(PersistentSearchChangeType.DELETE);
1822              }
1823              else if (t.equals("mod") || t.equals("modify"))
1824              {
1825                changeTypes.add(PersistentSearchChangeType.MODIFY);
1826              }
1827              else if (t.equals("moddn") || t.equals("modrdn") ||
1828                   t.equals("modifydn") || t.equals("modifyrdn"))
1829              {
1830                changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
1831              }
1832              else
1833              {
1834                throw new ArgumentException(
1835                     ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1836                          persistentSearch.getIdentifierString()));
1837              }
1838            }
1839          }
1840        }
1841
1842        if (elements.length > 2)
1843        {
1844          if (elements[2].equalsIgnoreCase("true") || elements[2].equals("1"))
1845          {
1846            changesOnly = true;
1847          }
1848          else if (elements[2].equalsIgnoreCase("false") ||
1849               elements[2].equals("0"))
1850          {
1851            changesOnly = false;
1852          }
1853          else
1854          {
1855            throw new ArgumentException(
1856                 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1857                      persistentSearch.getIdentifierString()));
1858          }
1859        }
1860
1861        if (elements.length > 3)
1862        {
1863          if (elements[3].equalsIgnoreCase("true") || elements[3].equals("1"))
1864          {
1865            returnECs = true;
1866          }
1867          else if (elements[3].equalsIgnoreCase("false") ||
1868               elements[3].equals("0"))
1869          {
1870            returnECs = false;
1871          }
1872          else
1873          {
1874            throw new ArgumentException(
1875                 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1876                      persistentSearch.getIdentifierString()));
1877          }
1878        }
1879      }
1880      catch (final ArgumentException ae)
1881      {
1882        Debug.debugException(ae);
1883        throw ae;
1884      }
1885      catch (final Exception e)
1886      {
1887        Debug.debugException(e);
1888        throw new ArgumentException(
1889             ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1890                  persistentSearch.getIdentifierString()),
1891             e);
1892      }
1893
1894      persistentSearchRequestControl = new PersistentSearchRequestControl(
1895           changeTypes, changesOnly, returnECs, true);
1896    }
1897
1898
1899    // If we should use the server-side sort request control, then validate the
1900    // sort order and pre-create the control.
1901    if (sortOrder.isPresent())
1902    {
1903      final ArrayList<SortKey> sortKeyList = new ArrayList<>(5);
1904      final StringTokenizer tokenizer =
1905           new StringTokenizer(sortOrder.getValue(), ", ");
1906      while (tokenizer.hasMoreTokens())
1907      {
1908        final String token = tokenizer.nextToken();
1909
1910        final boolean ascending;
1911        String attributeName;
1912        if (token.startsWith("-"))
1913        {
1914          ascending = false;
1915          attributeName = token.substring(1);
1916        }
1917        else if (token.startsWith("+"))
1918        {
1919          ascending = true;
1920          attributeName = token.substring(1);
1921        }
1922        else
1923        {
1924          ascending = true;
1925          attributeName = token;
1926        }
1927
1928        final String matchingRuleID;
1929        final int colonPos = attributeName.indexOf(':');
1930        if (colonPos >= 0)
1931        {
1932          matchingRuleID = attributeName.substring(colonPos+1);
1933          attributeName = attributeName.substring(0, colonPos);
1934        }
1935        else
1936        {
1937          matchingRuleID = null;
1938        }
1939
1940        final StringBuilder invalidReason = new StringBuilder();
1941        if (! PersistUtils.isValidLDAPName(attributeName, false, invalidReason))
1942        {
1943          throw new ArgumentException(
1944               ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get(
1945                    sortOrder.getIdentifierString()));
1946        }
1947
1948        sortKeyList.add(
1949             new SortKey(attributeName, matchingRuleID, (! ascending)));
1950      }
1951
1952      if (sortKeyList.isEmpty())
1953      {
1954        throw new ArgumentException(
1955             ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get(
1956                  sortOrder.getIdentifierString()));
1957      }
1958
1959      final SortKey[] sortKeyArray = new SortKey[sortKeyList.size()];
1960      sortKeyList.toArray(sortKeyArray);
1961
1962      sortRequestControl = new ServerSideSortRequestControl(sortKeyArray);
1963    }
1964
1965
1966    // If we should use the virtual list view request control, then validate the
1967    // argument value and pre-create the control.
1968    if (virtualListView.isPresent())
1969    {
1970      try
1971      {
1972        final String[] elements = virtualListView.getValue().split(":");
1973        if (elements.length == 4)
1974        {
1975          vlvRequestControl = new VirtualListViewRequestControl(
1976               Integer.parseInt(elements[2]), Integer.parseInt(elements[0]),
1977               Integer.parseInt(elements[1]), Integer.parseInt(elements[3]),
1978               null);
1979        }
1980        else if (elements.length == 3)
1981        {
1982          vlvRequestControl = new VirtualListViewRequestControl(elements[2],
1983               Integer.parseInt(elements[0]), Integer.parseInt(elements[1]),
1984               null);
1985        }
1986        else
1987        {
1988          throw new ArgumentException(
1989               ERR_LDAPSEARCH_VLV_INVALID_VALUE.get(
1990                    virtualListView.getIdentifierString()));
1991        }
1992      }
1993      catch (final ArgumentException ae)
1994      {
1995        Debug.debugException(ae);
1996        throw ae;
1997      }
1998      catch (final Exception e)
1999      {
2000        Debug.debugException(e);
2001        throw new ArgumentException(
2002             ERR_LDAPSEARCH_VLV_INVALID_VALUE.get(
2003                  virtualListView.getIdentifierString()),
2004             e);
2005      }
2006    }
2007
2008
2009    // If we should use the LDAP join request control, then validate and
2010    // pre-create that control.
2011    if (joinRule.isPresent())
2012    {
2013      final JoinRule rule;
2014      try
2015      {
2016        final String[] elements = joinRule.getValue().toLowerCase().split(":");
2017        final String ruleName = StaticUtils.toLowerCase(elements[0]);
2018        if (ruleName.equals("dn"))
2019        {
2020          rule = JoinRule.createDNJoin(elements[1]);
2021        }
2022        else if (ruleName.equals("reverse-dn") || ruleName.equals("reversedn"))
2023        {
2024          rule = JoinRule.createReverseDNJoin(elements[1]);
2025        }
2026        else if (ruleName.equals("equals") || ruleName.equals("equality"))
2027        {
2028          rule = JoinRule.createEqualityJoin(elements[1], elements[2], false);
2029        }
2030        else if (ruleName.equals("contains") || ruleName.equals("substring"))
2031        {
2032          rule = JoinRule.createContainsJoin(elements[1], elements[2], false);
2033        }
2034        else
2035        {
2036          throw new ArgumentException(
2037               ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get(
2038                    joinRule.getIdentifierString()));
2039        }
2040      }
2041      catch (final ArgumentException ae)
2042      {
2043        Debug.debugException(ae);
2044        throw ae;
2045      }
2046      catch (final Exception e)
2047      {
2048        Debug.debugException(e);
2049        throw new ArgumentException(
2050             ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get(
2051                  joinRule.getIdentifierString()),
2052             e);
2053      }
2054
2055      final JoinBaseDN joinBase;
2056      if (joinBaseDN.isPresent())
2057      {
2058        final String s = StaticUtils.toLowerCase(joinBaseDN.getValue());
2059        if (s.equals("search-base") || s.equals("search-base-dn"))
2060        {
2061          joinBase = JoinBaseDN.createUseSearchBaseDN();
2062        }
2063        else if (s.equals("source-entry-dn") || s.equals("source-dn"))
2064        {
2065          joinBase = JoinBaseDN.createUseSourceEntryDN();
2066        }
2067        else
2068        {
2069          try
2070          {
2071            final DN dn = new DN(joinBaseDN.getValue());
2072            joinBase = JoinBaseDN.createUseCustomBaseDN(joinBaseDN.getValue());
2073          }
2074          catch (final Exception e)
2075          {
2076            Debug.debugException(e);
2077            throw new ArgumentException(
2078                 ERR_LDAPSEARCH_JOIN_BASE_DN_INVALID_VALUE.get(
2079                      joinBaseDN.getIdentifierString()),
2080                 e);
2081          }
2082        }
2083      }
2084      else
2085      {
2086        joinBase = JoinBaseDN.createUseSearchBaseDN();
2087      }
2088
2089      final String[] joinAttrs;
2090      if (joinRequestedAttribute.isPresent())
2091      {
2092        final List<String> valueList = joinRequestedAttribute.getValues();
2093        joinAttrs = new String[valueList.size()];
2094        valueList.toArray(joinAttrs);
2095      }
2096      else
2097      {
2098        joinAttrs = null;
2099      }
2100
2101      joinRequestControl = new JoinRequestControl(new JoinRequestValue(rule,
2102           joinBase, joinScope.getValue(), DereferencePolicy.NEVER,
2103           joinSizeLimit.getValue(), joinFilter.getValue(), joinAttrs,
2104           joinRequireMatch.isPresent(), null));
2105    }
2106
2107
2108    // If we should use the route to backend set request control, then validate
2109    // and pre-create those controls.
2110    if (routeToBackendSet.isPresent())
2111    {
2112      final List<String> values = routeToBackendSet.getValues();
2113      final Map<String,List<String>> idsByRP = new LinkedHashMap<>(
2114           StaticUtils.computeMapCapacity(values.size()));
2115      for (final String value : values)
2116      {
2117        final int colonPos = value.indexOf(':');
2118        if (colonPos <= 0)
2119        {
2120          throw new ArgumentException(
2121               ERR_LDAPSEARCH_ROUTE_TO_BACKEND_SET_INVALID_FORMAT.get(value,
2122                    routeToBackendSet.getIdentifierString()));
2123        }
2124
2125        final String rpID = value.substring(0, colonPos);
2126        final String bsID = value.substring(colonPos+1);
2127
2128        List<String> idsForRP = idsByRP.get(rpID);
2129        if (idsForRP == null)
2130        {
2131          idsForRP = new ArrayList<>(values.size());
2132          idsByRP.put(rpID, idsForRP);
2133        }
2134        idsForRP.add(bsID);
2135      }
2136
2137      for (final Map.Entry<String,List<String>> e : idsByRP.entrySet())
2138      {
2139        final String rpID = e.getKey();
2140        final List<String> bsIDs = e.getValue();
2141        routeToBackendSetRequestControls.add(
2142             RouteToBackendSetRequestControl.createAbsoluteRoutingRequest(true,
2143                  rpID, bsIDs));
2144      }
2145    }
2146
2147
2148    // Parse the dereference policy.
2149    final String derefStr =
2150         StaticUtils.toLowerCase(dereferencePolicy.getValue());
2151    if (derefStr.equals("always"))
2152    {
2153      derefPolicy = DereferencePolicy.ALWAYS;
2154    }
2155    else if (derefStr.equals("search"))
2156    {
2157      derefPolicy = DereferencePolicy.SEARCHING;
2158    }
2159    else if (derefStr.equals("find"))
2160    {
2161      derefPolicy = DereferencePolicy.FINDING;
2162    }
2163    else
2164    {
2165      derefPolicy = DereferencePolicy.NEVER;
2166    }
2167
2168
2169    // See if any entry transformations need to be applied.
2170    final ArrayList<EntryTransformation> transformations = new ArrayList<>(5);
2171    if (excludeAttribute.isPresent())
2172    {
2173      transformations.add(new ExcludeAttributeTransformation(null,
2174           excludeAttribute.getValues()));
2175    }
2176
2177    if (redactAttribute.isPresent())
2178    {
2179      transformations.add(new RedactAttributeTransformation(null, true,
2180           (! hideRedactedValueCount.isPresent()),
2181           redactAttribute.getValues()));
2182    }
2183
2184    if (scrambleAttribute.isPresent())
2185    {
2186      final Long randomSeed;
2187      if (scrambleRandomSeed.isPresent())
2188      {
2189        randomSeed = scrambleRandomSeed.getValue().longValue();
2190      }
2191      else
2192      {
2193        randomSeed = null;
2194      }
2195
2196      transformations.add(new ScrambleAttributeTransformation(null, randomSeed,
2197           true, scrambleAttribute.getValues(), scrambleJSONField.getValues()));
2198    }
2199
2200    if (renameAttributeFrom.isPresent())
2201    {
2202      if (renameAttributeFrom.getNumOccurrences() !=
2203          renameAttributeTo.getNumOccurrences())
2204      {
2205        throw new ArgumentException(
2206             ERR_LDAPSEARCH_RENAME_ATTRIBUTE_MISMATCH.get());
2207      }
2208
2209      final Iterator<String> sourceIterator =
2210           renameAttributeFrom.getValues().iterator();
2211      final Iterator<String> targetIterator =
2212           renameAttributeTo.getValues().iterator();
2213      while (sourceIterator.hasNext())
2214      {
2215        transformations.add(new RenameAttributeTransformation(null,
2216             sourceIterator.next(), targetIterator.next(), true));
2217      }
2218    }
2219
2220    if (moveSubtreeFrom.isPresent())
2221    {
2222      if (moveSubtreeFrom.getNumOccurrences() !=
2223          moveSubtreeTo.getNumOccurrences())
2224      {
2225        throw new ArgumentException(ERR_LDAPSEARCH_MOVE_SUBTREE_MISMATCH.get());
2226      }
2227
2228      final Iterator<DN> sourceIterator =
2229           moveSubtreeFrom.getValues().iterator();
2230      final Iterator<DN> targetIterator = moveSubtreeTo.getValues().iterator();
2231      while (sourceIterator.hasNext())
2232      {
2233        transformations.add(new MoveSubtreeTransformation(sourceIterator.next(),
2234             targetIterator.next()));
2235      }
2236    }
2237
2238    if (! transformations.isEmpty())
2239    {
2240      entryTransformations = transformations;
2241    }
2242
2243
2244    // Create the result writer.
2245    final String outputFormatStr =
2246         StaticUtils.toLowerCase(outputFormat.getValue());
2247    if (outputFormatStr.equals("json"))
2248    {
2249      resultWriter = new JSONLDAPResultWriter(getOutStream());
2250    }
2251    else if (outputFormatStr.equals("csv") ||
2252             outputFormatStr.equals("multi-valued-csv") ||
2253             outputFormatStr.equals("tab-delimited") ||
2254             outputFormatStr.equals("multi-valued-tab-delimited"))
2255    {
2256      // These output formats cannot be used with the --ldapURLFile argument.
2257      if (ldapURLFile.isPresent())
2258      {
2259        throw new ArgumentException(
2260             ERR_LDAPSEARCH_OUTPUT_FORMAT_NOT_SUPPORTED_WITH_URLS.get(
2261                  outputFormat.getValue(), ldapURLFile.getIdentifierString()));
2262      }
2263
2264      // These output formats require the requested attributes to be specified
2265      // via the --requestedAttribute argument rather than as unnamed trailing
2266      // arguments.
2267      final List<String> requestedAttributes = requestedAttribute.getValues();
2268      if ((requestedAttributes == null) || requestedAttributes.isEmpty())
2269      {
2270        throw new ArgumentException(
2271             ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2272                  outputFormat.getValue(),
2273                  requestedAttribute.getIdentifierString()));
2274      }
2275
2276      switch (trailingArgs.size())
2277      {
2278        case 0:
2279          // This is fine.
2280          break;
2281
2282        case 1:
2283          // Make sure that the trailing argument is a filter rather than a
2284          // requested attribute.  It's sufficient to ensure that neither the
2285          // filter nor filterFile argument was provided.
2286          if (filter.isPresent() || filterFile.isPresent())
2287          {
2288            throw new ArgumentException(
2289                 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2290                      outputFormat.getValue(),
2291                      requestedAttribute.getIdentifierString()));
2292          }
2293          break;
2294
2295        default:
2296          throw new ArgumentException(
2297               ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2298                    outputFormat.getValue(),
2299                    requestedAttribute.getIdentifierString()));
2300      }
2301
2302      final OutputFormat format;
2303      final boolean includeAllValues;
2304      switch (outputFormatStr)
2305      {
2306        case "multi-valued-csv":
2307          format = OutputFormat.CSV;
2308          includeAllValues = true;
2309          break;
2310        case "tab-delimited":
2311          format = OutputFormat.TAB_DELIMITED_TEXT;
2312          includeAllValues = false;
2313          break;
2314        case "multi-valued-tab-delimited":
2315          format = OutputFormat.TAB_DELIMITED_TEXT;
2316          includeAllValues = true;
2317          break;
2318        case "csv":
2319        default:
2320          format = OutputFormat.CSV;
2321          includeAllValues = false;
2322          break;
2323      }
2324
2325
2326      resultWriter = new ColumnBasedLDAPResultWriter(getOutStream(),
2327           format, requestedAttributes, WRAP_COLUMN, includeAllValues);
2328    }
2329    else if (outputFormatStr.equals("dns-only"))
2330    {
2331      resultWriter = new DNsOnlyLDAPResultWriter(getOutStream());
2332    }
2333    else if (outputFormatStr.equals("values-only"))
2334    {
2335      resultWriter = new ValuesOnlyLDAPResultWriter(getOutStream());
2336    }
2337    else
2338    {
2339      resultWriter = new LDIFLDAPResultWriter(getOutStream(), WRAP_COLUMN);
2340    }
2341  }
2342
2343
2344
2345  /**
2346   * {@inheritDoc}
2347   */
2348  @Override()
2349  @NotNull()
2350  public LDAPConnectionOptions getConnectionOptions()
2351  {
2352    final LDAPConnectionOptions options = new LDAPConnectionOptions();
2353
2354    options.setUseSynchronousMode(true);
2355    options.setFollowReferrals(followReferrals.isPresent());
2356    options.setUnsolicitedNotificationHandler(this);
2357    options.setResponseTimeoutMillis(0L);
2358
2359    return options;
2360  }
2361
2362
2363
2364  /**
2365   * {@inheritDoc}
2366   */
2367  @Override()
2368  @NotNull()
2369  public ResultCode doToolProcessing()
2370  {
2371    // If we should encrypt the output, then get the encryption passphrase.
2372    if (encryptOutput.isPresent())
2373    {
2374      if (encryptionPassphraseFile.isPresent())
2375      {
2376        try
2377        {
2378          encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile(
2379               encryptionPassphraseFile.getValue());
2380        }
2381        catch (final LDAPException e)
2382        {
2383          Debug.debugException(e);
2384          wrapErr(0, WRAP_COLUMN, e.getMessage());
2385          return e.getResultCode();
2386        }
2387      }
2388      else
2389      {
2390        try
2391        {
2392          encryptionPassphrase = ToolUtils.promptForEncryptionPassphrase(false,
2393               true, getOut(), getErr());
2394        }
2395        catch (final LDAPException e)
2396        {
2397          Debug.debugException(e);
2398          wrapErr(0, WRAP_COLUMN, e.getMessage());
2399          return e.getResultCode();
2400        }
2401      }
2402    }
2403
2404
2405    // If we should use an output file, then set that up now.  Otherwise, write
2406    // the header to standard output.
2407    if (outputFile.isPresent())
2408    {
2409      if (! separateOutputFilePerSearch.isPresent())
2410      {
2411        try
2412        {
2413          OutputStream s = new FileOutputStream(outputFile.getValue());
2414
2415          if (encryptOutput.isPresent())
2416          {
2417            s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s);
2418          }
2419
2420          if (compressOutput.isPresent())
2421          {
2422            s = new GZIPOutputStream(s);
2423          }
2424
2425          if (teeResultsToStandardOut.isPresent())
2426          {
2427            outStream = new PrintStream(new TeeOutputStream(s, getOut()));
2428          }
2429          else
2430          {
2431            outStream = new PrintStream(s);
2432          }
2433          resultWriter.updateOutputStream(outStream);
2434          errStream = outStream;
2435        }
2436        catch (final Exception e)
2437        {
2438          Debug.debugException(e);
2439          wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(
2440               outputFile.getValue().getAbsolutePath(),
2441               StaticUtils.getExceptionMessage(e)));
2442          return ResultCode.LOCAL_ERROR;
2443        }
2444
2445        resultWriter.writeHeader();
2446      }
2447    }
2448    else
2449    {
2450      resultWriter.writeHeader();
2451    }
2452
2453
2454    // Examine the arguments to determine the sets of controls to use for each
2455    // type of request.
2456    final List<Control> searchControls = getSearchControls();
2457
2458
2459    // If appropriate, ensure that any search result entries that include
2460    // base64-encoded attribute values will also include comments that attempt
2461    // to provide a human-readable representation of that value.
2462    final boolean originalCommentAboutBase64EncodedValues =
2463         LDIFWriter.commentAboutBase64EncodedValues();
2464    LDIFWriter.setCommentAboutBase64EncodedValues(
2465         ! suppressBase64EncodedValueComments.isPresent());
2466
2467
2468    LDAPConnectionPool pool = null;
2469    try
2470    {
2471      // Create a connection pool that will be used to communicate with the
2472      // directory server.
2473      if (! dryRun.isPresent())
2474      {
2475        try
2476        {
2477          final StartAdministrativeSessionPostConnectProcessor p;
2478          if (useAdministrativeSession.isPresent())
2479          {
2480            p = new StartAdministrativeSessionPostConnectProcessor(
2481                 new StartAdministrativeSessionExtendedRequest(getToolName(),
2482                      true));
2483          }
2484          else
2485          {
2486            p = null;
2487          }
2488
2489          pool = getConnectionPool(1, 1, 0, p, null, true,
2490               new ReportBindResultLDAPConnectionPoolHealthCheck(this, true,
2491                    false));
2492        }
2493        catch (final LDAPException le)
2494        {
2495          // This shouldn't happen since the pool won't throw an exception if an
2496          // attempt to create an initial connection fails.
2497          Debug.debugException(le);
2498          commentToErr(ERR_LDAPSEARCH_CANNOT_CREATE_CONNECTION_POOL.get(
2499               StaticUtils.getExceptionMessage(le)));
2500          return le.getResultCode();
2501        }
2502
2503        if (retryFailedOperations.isPresent())
2504        {
2505          pool.setRetryFailedOperationsDueToInvalidConnections(true);
2506        }
2507      }
2508
2509
2510      // If appropriate, create a rate limiter.
2511      final FixedRateBarrier rateLimiter;
2512      if (ratePerSecond.isPresent())
2513      {
2514        rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue());
2515      }
2516      else
2517      {
2518        rateLimiter = null;
2519      }
2520
2521
2522      // If one or more LDAP URL files are provided, then construct search
2523      // requests from those URLs.
2524      if (ldapURLFile.isPresent())
2525      {
2526        return searchWithLDAPURLs(pool, rateLimiter, searchControls);
2527      }
2528
2529
2530      // Get the set of requested attributes, as a combination of the
2531      // requestedAttribute argument values and any trailing arguments.
2532      final ArrayList<String> attrList = new ArrayList<>(10);
2533      if (requestedAttribute.isPresent())
2534      {
2535        attrList.addAll(requestedAttribute.getValues());
2536      }
2537
2538      final List<String> trailingArgs = parser.getTrailingArguments();
2539      if (! trailingArgs.isEmpty())
2540      {
2541        final Iterator<String> trailingArgIterator = trailingArgs.iterator();
2542        if (! (filter.isPresent() || filterFile.isPresent()))
2543        {
2544          trailingArgIterator.next();
2545        }
2546
2547        while (trailingArgIterator.hasNext())
2548        {
2549          attrList.add(trailingArgIterator.next());
2550        }
2551      }
2552
2553      final String[] attributes = new String[attrList.size()];
2554      attrList.toArray(attributes);
2555
2556
2557      // If either or both the filter or filterFile arguments are provided, then
2558      // use them to get the filters to process.  Otherwise, the first trailing
2559      // argument should be a filter.
2560      ResultCode resultCode = ResultCode.SUCCESS;
2561      if (filter.isPresent() || filterFile.isPresent())
2562      {
2563        if (filter.isPresent())
2564        {
2565          for (final Filter f : filter.getValues())
2566          {
2567            final ResultCode rc = searchWithFilter(pool, f, attributes,
2568                 rateLimiter, searchControls);
2569            if (rc != ResultCode.SUCCESS)
2570            {
2571              if (resultCode == ResultCode.SUCCESS)
2572              {
2573                resultCode = rc;
2574              }
2575
2576              if (! continueOnError.isPresent())
2577              {
2578                return resultCode;
2579              }
2580            }
2581          }
2582        }
2583
2584        if (filterFile.isPresent())
2585        {
2586          final ResultCode rc = searchWithFilterFile(pool, attributes,
2587               rateLimiter, searchControls);
2588          if (rc != ResultCode.SUCCESS)
2589          {
2590            if (resultCode == ResultCode.SUCCESS)
2591            {
2592              resultCode = rc;
2593            }
2594
2595            if (! continueOnError.isPresent())
2596            {
2597              return resultCode;
2598            }
2599          }
2600        }
2601      }
2602      else
2603      {
2604        final Filter f;
2605        try
2606        {
2607          final String filterStr =
2608               parser.getTrailingArguments().iterator().next();
2609          f = Filter.create(filterStr);
2610        }
2611        catch (final LDAPException le)
2612        {
2613          // This should never happen.
2614          Debug.debugException(le);
2615          displayResult(le.toLDAPResult());
2616          return le.getResultCode();
2617        }
2618
2619        resultCode =
2620             searchWithFilter(pool, f, attributes, rateLimiter, searchControls);
2621      }
2622
2623      return resultCode;
2624    }
2625    finally
2626    {
2627      if (pool != null)
2628      {
2629        try
2630        {
2631          pool.close();
2632        }
2633        catch (final Exception e)
2634        {
2635          Debug.debugException(e);
2636        }
2637      }
2638
2639      if (outStream != null)
2640      {
2641        try
2642        {
2643          outStream.close();
2644          outStream = null;
2645        }
2646        catch (final Exception e)
2647        {
2648          Debug.debugException(e);
2649        }
2650      }
2651
2652      if (errStream != null)
2653      {
2654        try
2655        {
2656          errStream.close();
2657          errStream = null;
2658        }
2659        catch (final Exception e)
2660        {
2661          Debug.debugException(e);
2662        }
2663      }
2664
2665      LDIFWriter.setCommentAboutBase64EncodedValues(
2666           originalCommentAboutBase64EncodedValues);
2667    }
2668  }
2669
2670
2671
2672  /**
2673   * Processes a set of searches using LDAP URLs read from one or more files.
2674   *
2675   * @param  pool            The connection pool to use to communicate with the
2676   *                         directory server.
2677   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2678   *                         request rate limiting.
2679   * @param  searchControls  The set of controls to include in search requests.
2680   *
2681   * @return  A result code indicating the result of the processing.
2682   */
2683  @NotNull()
2684  private ResultCode searchWithLDAPURLs(@NotNull final LDAPConnectionPool pool,
2685               @Nullable final FixedRateBarrier rateLimiter,
2686               @NotNull final List<Control> searchControls)
2687  {
2688    ResultCode resultCode = ResultCode.SUCCESS;
2689    for (final File f : ldapURLFile.getValues())
2690    {
2691      BufferedReader reader = null;
2692
2693      try
2694      {
2695        reader = new BufferedReader(new FileReader(f));
2696        while (true)
2697        {
2698          final String line = reader.readLine();
2699          if (line == null)
2700          {
2701            break;
2702          }
2703
2704          if ((line.length() == 0) || line.startsWith("#"))
2705          {
2706            continue;
2707          }
2708
2709          final LDAPURL url;
2710          try
2711          {
2712            url = new LDAPURL(line);
2713          }
2714          catch (final LDAPException le)
2715          {
2716            Debug.debugException(le);
2717
2718            commentToErr(ERR_LDAPSEARCH_MALFORMED_LDAP_URL.get(
2719                 f.getAbsolutePath(), line));
2720            if (resultCode == ResultCode.SUCCESS)
2721            {
2722              resultCode = le.getResultCode();
2723            }
2724
2725            if (continueOnError.isPresent())
2726            {
2727              continue;
2728            }
2729            else
2730            {
2731              return resultCode;
2732            }
2733          }
2734
2735          final SearchRequest searchRequest = new SearchRequest(
2736               new LDAPSearchListener(resultWriter, entryTransformations),
2737               url.getBaseDN().toString(), url.getScope(), derefPolicy,
2738               sizeLimit.getValue(), timeLimitSeconds.getValue(),
2739               typesOnly.isPresent(), url.getFilter(), url.getAttributes());
2740          final ResultCode rc =
2741               doSearch(pool, searchRequest, rateLimiter, searchControls);
2742          if (rc != ResultCode.SUCCESS)
2743          {
2744            if (resultCode == ResultCode.SUCCESS)
2745            {
2746              resultCode = rc;
2747            }
2748
2749            if (! continueOnError.isPresent())
2750            {
2751              return resultCode;
2752            }
2753          }
2754        }
2755      }
2756      catch (final IOException ioe)
2757      {
2758        commentToErr(ERR_LDAPSEARCH_CANNOT_READ_LDAP_URL_FILE.get(
2759             f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe)));
2760        return ResultCode.LOCAL_ERROR;
2761      }
2762      finally
2763      {
2764        if (reader != null)
2765        {
2766          try
2767          {
2768            reader.close();
2769          }
2770          catch (final Exception e)
2771          {
2772            Debug.debugException(e);
2773          }
2774        }
2775      }
2776    }
2777
2778    return resultCode;
2779  }
2780
2781
2782
2783  /**
2784   * Processes a set of searches using filters read from one or more files.
2785   *
2786   * @param  pool            The connection pool to use to communicate with the
2787   *                         directory server.
2788   * @param  attributes      The set of attributes to request that the server
2789   *                         include in matching entries.
2790   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2791   *                         request rate limiting.
2792   * @param  searchControls  The set of controls to include in search requests.
2793   *
2794   * @return  A result code indicating the result of the processing.
2795   */
2796  @NotNull()
2797  private ResultCode searchWithFilterFile(
2798               @NotNull final LDAPConnectionPool pool,
2799               @NotNull final String[] attributes,
2800               @Nullable final FixedRateBarrier rateLimiter,
2801               @NotNull final List<Control> searchControls)
2802  {
2803    ResultCode resultCode = ResultCode.SUCCESS;
2804    for (final File f : filterFile.getValues())
2805    {
2806      FilterFileReader reader = null;
2807
2808      try
2809      {
2810        reader = new FilterFileReader(f);
2811        while (true)
2812        {
2813          final Filter searchFilter;
2814          try
2815          {
2816            searchFilter = reader.readFilter();
2817          }
2818          catch (final LDAPException le)
2819          {
2820            Debug.debugException(le);
2821            commentToErr(ERR_LDAPSEARCH_MALFORMED_FILTER.get(
2822                 f.getAbsolutePath(), le.getMessage()));
2823            if (resultCode == ResultCode.SUCCESS)
2824            {
2825              resultCode = le.getResultCode();
2826            }
2827
2828            if (continueOnError.isPresent())
2829            {
2830              continue;
2831            }
2832            else
2833            {
2834              return resultCode;
2835            }
2836          }
2837
2838          if (searchFilter == null)
2839          {
2840            break;
2841          }
2842
2843          final ResultCode rc = searchWithFilter(pool, searchFilter, attributes,
2844               rateLimiter, searchControls);
2845          if (rc != ResultCode.SUCCESS)
2846          {
2847            if (resultCode == ResultCode.SUCCESS)
2848            {
2849              resultCode = rc;
2850            }
2851
2852            if (! continueOnError.isPresent())
2853            {
2854              return resultCode;
2855            }
2856          }
2857        }
2858      }
2859      catch (final IOException ioe)
2860      {
2861        Debug.debugException(ioe);
2862        commentToErr(ERR_LDAPSEARCH_CANNOT_READ_FILTER_FILE.get(
2863             f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe)));
2864        return ResultCode.LOCAL_ERROR;
2865      }
2866      finally
2867      {
2868        if (reader != null)
2869        {
2870          try
2871          {
2872            reader.close();
2873          }
2874          catch (final Exception e)
2875          {
2876            Debug.debugException(e);
2877          }
2878        }
2879      }
2880    }
2881
2882    return resultCode;
2883  }
2884
2885
2886
2887  /**
2888   * Processes a search using the provided filter.
2889   *
2890   * @param  pool            The connection pool to use to communicate with the
2891   *                         directory server.
2892   * @param  filter          The filter to use for the search.
2893   * @param  attributes      The set of attributes to request that the server
2894   *                         include in matching entries.
2895   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2896   *                         request rate limiting.
2897   * @param  searchControls  The set of controls to include in search requests.
2898   *
2899   * @return  A result code indicating the result of the processing.
2900   */
2901  @NotNull()
2902  private ResultCode searchWithFilter(@NotNull final LDAPConnectionPool pool,
2903               @NotNull final Filter filter,
2904               @NotNull final String[] attributes,
2905               @Nullable final FixedRateBarrier rateLimiter,
2906               @NotNull final List<Control> searchControls)
2907  {
2908    final String baseDNString;
2909    if (baseDN.isPresent())
2910    {
2911      baseDNString = baseDN.getStringValue();
2912    }
2913    else
2914    {
2915      baseDNString = "";
2916    }
2917
2918    final SearchRequest searchRequest = new SearchRequest(
2919         new LDAPSearchListener(resultWriter, entryTransformations),
2920         baseDNString, scope.getValue(), derefPolicy, sizeLimit.getValue(),
2921         timeLimitSeconds.getValue(), typesOnly.isPresent(), filter,
2922         attributes);
2923    return doSearch(pool, searchRequest, rateLimiter, searchControls);
2924  }
2925
2926
2927
2928  /**
2929   * Processes a search with the provided information.
2930   *
2931   * @param  pool            The connection pool to use to communicate with the
2932   *                         directory server.
2933   * @param  searchRequest   The search request to process.
2934   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2935   *                         request rate limiting.
2936   * @param  searchControls  The set of controls to include in search requests.
2937   *
2938   * @return  A result code indicating the result of the processing.
2939   */
2940  @NotNull()
2941  private ResultCode doSearch(@NotNull final LDAPConnectionPool pool,
2942                              @NotNull final SearchRequest searchRequest,
2943                              @Nullable final FixedRateBarrier rateLimiter,
2944                              @NotNull final List<Control> searchControls)
2945  {
2946    if (separateOutputFilePerSearch.isPresent())
2947    {
2948      try
2949      {
2950        final String path = outputFile.getValue().getAbsolutePath() + '.' +
2951             outputFileCounter.getAndIncrement();
2952
2953        OutputStream s = new FileOutputStream(path);
2954
2955        if (encryptOutput.isPresent())
2956        {
2957          s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s);
2958        }
2959
2960        if (compressOutput.isPresent())
2961        {
2962          s = new GZIPOutputStream(s);
2963        }
2964
2965        if (teeResultsToStandardOut.isPresent())
2966        {
2967          outStream = new PrintStream(new TeeOutputStream(s, getOut()));
2968        }
2969        else
2970        {
2971          outStream = new PrintStream(s);
2972        }
2973        resultWriter.updateOutputStream(outStream);
2974        errStream = outStream;
2975      }
2976      catch (final Exception e)
2977      {
2978        Debug.debugException(e);
2979        wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(
2980             outputFile.getValue().getAbsolutePath(),
2981             StaticUtils.getExceptionMessage(e)));
2982        return ResultCode.LOCAL_ERROR;
2983      }
2984
2985      resultWriter.writeHeader();
2986    }
2987
2988    try
2989    {
2990      if (rateLimiter != null)
2991      {
2992        rateLimiter.await();
2993      }
2994
2995
2996      ASN1OctetString pagedResultsCookie = null;
2997      boolean multiplePages = false;
2998      long totalEntries = 0;
2999      long totalReferences = 0;
3000
3001      SearchResult searchResult;
3002      try
3003      {
3004        while (true)
3005        {
3006          searchRequest.setControls(searchControls);
3007          if (simplePageSize.isPresent())
3008          {
3009            searchRequest.addControl(new SimplePagedResultsControl(
3010                 simplePageSize.getValue(), pagedResultsCookie));
3011          }
3012
3013          if (dryRun.isPresent())
3014          {
3015            searchResult = new SearchResult(-1, ResultCode.SUCCESS,
3016                 INFO_LDAPSEARCH_DRY_RUN_REQUEST_NOT_SENT.get(
3017                      dryRun.getIdentifierString(),
3018                      String.valueOf(searchRequest)),
3019                 null, null, 0, 0, null);
3020            break;
3021          }
3022          else
3023          {
3024            if (! terse.isPresent())
3025            {
3026              if (verbose.isPresent() || persistentSearch.isPresent() ||
3027                  filterFile.isPresent() || ldapURLFile.isPresent() ||
3028                  (filter.isPresent() && (filter.getNumOccurrences() > 1)))
3029              {
3030                commentToOut(INFO_LDAPSEARCH_SENDING_SEARCH_REQUEST.get(
3031                     String.valueOf(searchRequest)));
3032              }
3033            }
3034            searchResult = pool.search(searchRequest);
3035          }
3036
3037          searchResult = handleJSONEncodedResponseControls(searchResult);
3038
3039          if (searchResult.getEntryCount() > 0)
3040          {
3041            totalEntries += searchResult.getEntryCount();
3042          }
3043
3044          if (searchResult.getReferenceCount() > 0)
3045          {
3046            totalReferences += searchResult.getReferenceCount();
3047          }
3048
3049          if (simplePageSize.isPresent())
3050          {
3051            final SimplePagedResultsControl pagedResultsControl;
3052            try
3053            {
3054              pagedResultsControl = SimplePagedResultsControl.get(searchResult);
3055              if (pagedResultsControl == null)
3056              {
3057                throw new LDAPSearchException(new SearchResult(
3058                     searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND,
3059                     ERR_LDAPSEARCH_MISSING_PAGED_RESULTS_RESPONSE_CONTROL.
3060                          get(),
3061                     searchResult.getMatchedDN(),
3062                     searchResult.getReferralURLs(),
3063                     searchResult.getSearchEntries(),
3064                     searchResult.getSearchReferences(),
3065                     searchResult.getEntryCount(),
3066                     searchResult.getReferenceCount(),
3067                     searchResult.getResponseControls()));
3068              }
3069
3070              if (pagedResultsControl.moreResultsToReturn())
3071              {
3072                if (verbose.isPresent())
3073                {
3074                  commentToOut(
3075                       INFO_LDAPSEARCH_INTERMEDIATE_PAGED_SEARCH_RESULT.get());
3076                  displayResult(searchResult);
3077                }
3078
3079                multiplePages = true;
3080                pagedResultsCookie = pagedResultsControl.getCookie();
3081              }
3082              else
3083              {
3084                break;
3085              }
3086            }
3087            catch (final LDAPException le)
3088            {
3089              Debug.debugException(le);
3090              throw new LDAPSearchException(new SearchResult(
3091                   searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND,
3092                   ERR_LDAPSEARCH_CANNOT_DECODE_PAGED_RESULTS_RESPONSE_CONTROL.
3093                        get(StaticUtils.getExceptionMessage(le)),
3094                   searchResult.getMatchedDN(), searchResult.getReferralURLs(),
3095                   searchResult.getSearchEntries(),
3096                   searchResult.getSearchReferences(),
3097                   searchResult.getEntryCount(),
3098                   searchResult.getReferenceCount(),
3099                   searchResult.getResponseControls()));
3100            }
3101          }
3102          else
3103          {
3104            break;
3105          }
3106        }
3107      }
3108      catch (final LDAPSearchException lse)
3109      {
3110        Debug.debugException(lse);
3111        searchResult = lse.toLDAPResult();
3112
3113        if (searchResult.getEntryCount() > 0)
3114        {
3115          totalEntries += searchResult.getEntryCount();
3116        }
3117
3118        if (searchResult.getReferenceCount() > 0)
3119        {
3120          totalReferences += searchResult.getReferenceCount();
3121        }
3122      }
3123
3124      if ((searchResult.getResultCode() != ResultCode.SUCCESS) ||
3125          (searchResult.getDiagnosticMessage() != null) ||
3126          (! terse.isPresent()))
3127      {
3128        displayResult(searchResult);
3129      }
3130
3131      if (multiplePages && (! terse.isPresent()))
3132      {
3133        commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_ENTRIES.get(totalEntries));
3134
3135        if (totalReferences > 0)
3136        {
3137          commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_REFERENCES.get(
3138               totalReferences));
3139        }
3140      }
3141
3142      if (countEntries.isPresent())
3143      {
3144        return ResultCode.valueOf((int) Math.min(totalEntries, 255));
3145      }
3146      else if (requireMatch.isPresent() && (totalEntries == 0))
3147      {
3148        return ResultCode.NO_RESULTS_RETURNED;
3149      }
3150      else
3151      {
3152        return searchResult.getResultCode();
3153      }
3154    }
3155    finally
3156    {
3157      if (separateOutputFilePerSearch.isPresent())
3158      {
3159        try
3160        {
3161          outStream.close();
3162        }
3163        catch (final Exception e)
3164        {
3165          Debug.debugException(e);
3166        }
3167
3168        outStream = null;
3169        errStream = null;
3170      }
3171    }
3172  }
3173
3174
3175
3176  /**
3177   * Retrieves a list of the controls that should be used when processing search
3178   * operations.
3179   *
3180   * @return  A list of the controls that should be used when processing search
3181   *          operations.
3182   *
3183   * @throws  LDAPException  If a problem is encountered while generating the
3184   *                         controls for a search request.
3185   */
3186  @NotNull()
3187  private List<Control> getSearchControls()
3188  {
3189    final ArrayList<Control> controls = new ArrayList<>(10);
3190
3191    if (searchControl.isPresent())
3192    {
3193      controls.addAll(searchControl.getValues());
3194    }
3195
3196    if (joinRequestControl != null)
3197    {
3198      controls.add(joinRequestControl);
3199    }
3200
3201    if (matchedValuesRequestControl != null)
3202    {
3203      controls.add(matchedValuesRequestControl);
3204    }
3205
3206    if (matchingEntryCountRequestControl != null)
3207    {
3208      controls.add(matchingEntryCountRequestControl);
3209    }
3210
3211    if (overrideSearchLimitsRequestControl != null)
3212    {
3213      controls.add(overrideSearchLimitsRequestControl);
3214    }
3215
3216    if (persistentSearchRequestControl != null)
3217    {
3218      controls.add(persistentSearchRequestControl);
3219    }
3220
3221    if (sortRequestControl != null)
3222    {
3223      controls.add(sortRequestControl);
3224    }
3225
3226    if (vlvRequestControl != null)
3227    {
3228      controls.add(vlvRequestControl);
3229    }
3230
3231    controls.addAll(routeToBackendSetRequestControls);
3232
3233    if (accountUsable.isPresent())
3234    {
3235      controls.add(new AccountUsableRequestControl(true));
3236    }
3237
3238    if (getBackendSetID.isPresent())
3239    {
3240      controls.add(new GetBackendSetIDRequestControl(false));
3241    }
3242
3243    if (getServerID.isPresent())
3244    {
3245      controls.add(new GetServerIDRequestControl(false));
3246    }
3247
3248    if (includeReplicationConflictEntries.isPresent())
3249    {
3250      controls.add(new ReturnConflictEntriesRequestControl(true));
3251    }
3252
3253    if (includeSoftDeletedEntries.isPresent())
3254    {
3255      final String valueStr =
3256           StaticUtils.toLowerCase(includeSoftDeletedEntries.getValue());
3257      if (valueStr.equals("with-non-deleted-entries"))
3258      {
3259        controls.add(new SoftDeletedEntryAccessRequestControl(true, true,
3260             false));
3261      }
3262      else if (valueStr.equals("without-non-deleted-entries"))
3263      {
3264        controls.add(new SoftDeletedEntryAccessRequestControl(true, false,
3265             false));
3266      }
3267      else
3268      {
3269        controls.add(new SoftDeletedEntryAccessRequestControl(true, false,
3270             true));
3271      }
3272    }
3273
3274    if (draftLDUPSubentries.isPresent())
3275    {
3276      controls.add(new DraftLDUPSubentriesRequestControl(true));
3277    }
3278
3279    if (rfc3672Subentries.isPresent())
3280    {
3281      controls.add(new RFC3672SubentriesRequestControl(
3282           rfc3672Subentries.getValue()));
3283    }
3284
3285    if (manageDsaIT.isPresent())
3286    {
3287      controls.add(new ManageDsaITRequestControl(true));
3288    }
3289
3290    if (realAttributesOnly.isPresent())
3291    {
3292      controls.add(new RealAttributesOnlyRequestControl(true));
3293    }
3294
3295    if (routeToServer.isPresent())
3296    {
3297      controls.add(new RouteToServerRequestControl(false,
3298           routeToServer.getValue(), false, false, false));
3299    }
3300
3301    if (virtualAttributesOnly.isPresent())
3302    {
3303      controls.add(new VirtualAttributesOnlyRequestControl(true));
3304    }
3305
3306    if (excludeBranch.isPresent())
3307    {
3308      final ArrayList<String> dns =
3309           new ArrayList<>(excludeBranch.getValues().size());
3310      for (final DN dn : excludeBranch.getValues())
3311      {
3312        dns.add(dn.toString());
3313      }
3314      controls.add(new ExcludeBranchRequestControl(true, dns));
3315    }
3316
3317    if (assertionFilter.isPresent())
3318    {
3319      controls.add(new AssertionRequestControl(
3320           assertionFilter.getValue(), true));
3321    }
3322
3323    if (getEffectiveRightsAuthzID.isPresent())
3324    {
3325      final String[] attributes;
3326      if (getEffectiveRightsAttribute.isPresent())
3327      {
3328        attributes = new String[getEffectiveRightsAttribute.getValues().size()];
3329        for (int i=0; i < attributes.length; i++)
3330        {
3331          attributes[i] = getEffectiveRightsAttribute.getValues().get(i);
3332        }
3333      }
3334      else
3335      {
3336        attributes = StaticUtils.NO_STRINGS;
3337      }
3338
3339      controls.add(new GetEffectiveRightsRequestControl(true,
3340           getEffectiveRightsAuthzID.getValue(), attributes));
3341    }
3342
3343    if (operationPurpose.isPresent())
3344    {
3345      controls.add(new OperationPurposeRequestControl(true, "ldapsearch",
3346           Version.NUMERIC_VERSION_STRING, "LDAPSearch.getSearchControls",
3347           operationPurpose.getValue()));
3348    }
3349
3350    if (proxyAs.isPresent())
3351    {
3352      controls.add(new ProxiedAuthorizationV2RequestControl(
3353           proxyAs.getValue()));
3354    }
3355
3356    if (proxyV1As.isPresent())
3357    {
3358      controls.add(new ProxiedAuthorizationV1RequestControl(
3359           proxyV1As.getValue()));
3360    }
3361
3362    if (suppressOperationalAttributeUpdates.isPresent())
3363    {
3364      final EnumSet<SuppressType> suppressTypes =
3365           EnumSet.noneOf(SuppressType.class);
3366      for (final String s : suppressOperationalAttributeUpdates.getValues())
3367      {
3368        if (s.equalsIgnoreCase("last-access-time"))
3369        {
3370          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
3371        }
3372        else if (s.equalsIgnoreCase("last-login-time"))
3373        {
3374          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
3375        }
3376        else if (s.equalsIgnoreCase("last-login-ip"))
3377        {
3378          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
3379        }
3380      }
3381
3382      controls.add(new SuppressOperationalAttributeUpdateRequestControl(
3383           suppressTypes));
3384    }
3385
3386    if (rejectUnindexedSearch.isPresent())
3387    {
3388      controls.add(new RejectUnindexedSearchRequestControl());
3389    }
3390
3391    if (permitUnindexedSearch.isPresent())
3392    {
3393      controls.add(new PermitUnindexedSearchRequestControl());
3394    }
3395
3396    if (useJSONFormattedRequestControls.isPresent())
3397    {
3398      final JSONFormattedRequestControl jsonFormattedRequestControl =
3399           JSONFormattedRequestControl.createWithControls(true, controls);
3400      controls.clear();
3401      controls.add(jsonFormattedRequestControl);
3402    }
3403
3404    return controls;
3405  }
3406
3407
3408
3409  /**
3410   * Displays information about the provided result, including special
3411   * processing for a number of supported response controls.
3412   *
3413   * @param  result  The result to examine.
3414   */
3415  private void displayResult(@NotNull final LDAPResult result)
3416  {
3417    resultWriter.writeResult(result);
3418  }
3419
3420
3421
3422  /**
3423   * Writes the provided message to the output stream.
3424   *
3425   * @param  message  The message to be written.
3426   */
3427  void writeOut(@NotNull final String message)
3428  {
3429    if (outStream == null)
3430    {
3431      out(message);
3432    }
3433    else
3434    {
3435      outStream.println(message);
3436    }
3437  }
3438
3439
3440
3441  /**
3442   * Writes the provided message to the error stream.
3443   *
3444   * @param  message  The message to be written.
3445   */
3446  private void writeErr(@NotNull final String message)
3447  {
3448    if (errStream == null)
3449    {
3450      err(message);
3451    }
3452    else
3453    {
3454      errStream.println(message);
3455    }
3456  }
3457
3458
3459
3460  /**
3461   * Writes a line-wrapped, commented version of the provided message to
3462   * standard output.
3463   *
3464   * @param  message  The message to be written.
3465   */
3466  private void commentToOut(@NotNull final String message)
3467  {
3468    if (terse.isPresent())
3469    {
3470      return;
3471    }
3472
3473    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
3474    {
3475      writeOut("# " + line);
3476    }
3477  }
3478
3479
3480
3481  /**
3482   * Writes a line-wrapped, commented version of the provided message to
3483   * standard error.
3484   *
3485   * @param  message  The message to be written.
3486   */
3487  private void commentToErr(@NotNull final String message)
3488  {
3489    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
3490    {
3491      writeErr("# " + line);
3492    }
3493  }
3494
3495
3496
3497  /**
3498   * Retrieves the tool's output stream.
3499   *
3500   * @return  The tool's output stream.
3501   */
3502  @NotNull()
3503  PrintStream getOutStream()
3504  {
3505    if (outStream == null)
3506    {
3507      return getOut();
3508    }
3509    else
3510    {
3511      return outStream;
3512    }
3513  }
3514
3515
3516
3517  /**
3518   * Retrieves the tool's error stream.
3519   *
3520   * @return  The tool's error stream.
3521   */
3522  @NotNull()
3523  PrintStream getErrStream()
3524  {
3525    if (errStream == null)
3526    {
3527      return getErr();
3528    }
3529    else
3530    {
3531      return errStream;
3532    }
3533  }
3534
3535
3536
3537  /**
3538   * Sets the output handler that should be used by this tool  This is primarily
3539   * intended for testing purposes.
3540   *
3541   * @param  resultWriter  The result writer that should be used by this tool.
3542   */
3543  void setResultWriter(@NotNull final LDAPResultWriter resultWriter)
3544  {
3545    this.resultWriter = resultWriter;
3546  }
3547
3548
3549
3550  /**
3551   * {@inheritDoc}
3552   */
3553  @Override()
3554  public void handleUnsolicitedNotification(
3555                   @NotNull final LDAPConnection connection,
3556                   @NotNull final ExtendedResult notification)
3557  {
3558    resultWriter.writeUnsolicitedNotification(connection, notification);
3559  }
3560
3561
3562
3563  /**
3564   * Examines the provided search result to see if it includes a JSONf-formatted
3565   * response control.  If so, then its embedded controls will be extracted and
3566   * a new search result will be returned with those extracted controls instead
3567   * of the JSON-formatted response control.  Otherwise, the provided search
3568   * result will be returned.
3569   *
3570   * @param  searchResult  The search result to be handled.  It must not be
3571   *                       {@code null}.
3572   *
3573   * @return  A new search result with the controls extracted from a
3574   *          JSON-formatted response control, or the original search result if
3575   *          it did not include a JSON-formatted response control.
3576   */
3577  @NotNull()
3578  static SearchResult handleJSONEncodedResponseControls(
3579              @NotNull final SearchResult searchResult)
3580  {
3581    try
3582    {
3583      final JSONFormattedResponseControl jsonFormattedResponseControl =
3584           JSONFormattedResponseControl.get(searchResult);
3585      if (jsonFormattedResponseControl == null)
3586      {
3587        return searchResult;
3588      }
3589
3590      final JSONFormattedControlDecodeBehavior decodeBehavior =
3591           new JSONFormattedControlDecodeBehavior();
3592      decodeBehavior.setThrowOnUnparsableObject(false);
3593      decodeBehavior.setThrowOnInvalidCriticalControl(false);
3594      decodeBehavior.setThrowOnInvalidNonCriticalControl(false);
3595      decodeBehavior.setThrowOnInvalidNonCriticalControl(false);
3596      decodeBehavior.setAllowEmbeddedJSONFormattedControl(true);
3597      decodeBehavior.setStrict(false);
3598
3599      final List<Control> decodedControls =
3600           jsonFormattedResponseControl.decodeEmbeddedControls(
3601                decodeBehavior, null);
3602
3603      return new SearchResult(searchResult.getMessageID(),
3604           searchResult.getResultCode(),
3605           searchResult.getDiagnosticMessage(),
3606           searchResult.getMatchedDN(),
3607           searchResult.getReferralURLs(),
3608           searchResult.getSearchEntries(),
3609           searchResult.getSearchReferences(),
3610           searchResult.getEntryCount(),
3611           searchResult.getReferenceCount(),
3612           StaticUtils.toArray(decodedControls, Control.class));
3613    }
3614    catch (final LDAPException e)
3615    {
3616      Debug.debugException(e);
3617      return searchResult;
3618    }
3619  }
3620
3621
3622
3623  /**
3624   * {@inheritDoc}
3625   */
3626  @Override()
3627  @NotNull()
3628  public LinkedHashMap<String[],String> getExampleUsages()
3629  {
3630    final LinkedHashMap<String[],String> examples =
3631         new LinkedHashMap<>(StaticUtils.computeMapCapacity(5));
3632
3633    String[] args =
3634    {
3635      "--hostname", "directory.example.com",
3636      "--port", "389",
3637      "--bindDN", "uid=jdoe,ou=People,dc=example,dc=com",
3638      "--bindPassword", "password",
3639      "--baseDN", "ou=People,dc=example,dc=com",
3640      "--scope", "sub",
3641      "(uid=jqpublic)",
3642      "givenName",
3643      "sn",
3644      "mail"
3645    };
3646    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_1.get());
3647
3648
3649    args = new String[]
3650    {
3651      "--hostname", "directory.example.com",
3652      "--port", "636",
3653      "--useSSL",
3654      "--saslOption", "mech=PLAIN",
3655      "--saslOption", "authID=u:jdoe",
3656      "--bindPasswordFile", "/path/to/password/file",
3657      "--baseDN", "ou=People,dc=example,dc=com",
3658      "--scope", "sub",
3659      "--filterFile", "/path/to/filter/file",
3660      "--outputFile", "/path/to/base/output/file",
3661      "--separateOutputFilePerSearch",
3662      "--requestedAttribute", "*",
3663      "--requestedAttribute", "+"
3664    };
3665    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_2.get());
3666
3667
3668    args = new String[]
3669    {
3670      "--hostname", "directory.example.com",
3671      "--port", "389",
3672      "--useStartTLS",
3673      "--trustStorePath", "/path/to/truststore/file",
3674      "--baseDN", "",
3675      "--scope", "base",
3676      "--outputFile", "/path/to/output/file",
3677      "--teeResultsToStandardOut",
3678      "(objectClass=*)",
3679      "*",
3680      "+"
3681    };
3682    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_3.get());
3683
3684
3685    args = new String[]
3686    {
3687      "--hostname", "directory.example.com",
3688      "--port", "389",
3689      "--bindDN", "uid=admin,dc=example,dc=com",
3690      "--baseDN", "dc=example,dc=com",
3691      "--scope", "sub",
3692      "--outputFile", "/path/to/output/file",
3693      "--simplePageSize", "100",
3694      "(objectClass=*)",
3695      "*",
3696      "+"
3697    };
3698    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_4.get());
3699
3700
3701    args = new String[]
3702    {
3703      "--hostname", "directory.example.com",
3704      "--port", "389",
3705      "--bindDN", "uid=admin,dc=example,dc=com",
3706      "--baseDN", "dc=example,dc=com",
3707      "--scope", "sub",
3708      "(&(givenName=John)(sn=Doe))",
3709      "debugsearchindex"
3710    };
3711    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_5.get());
3712
3713    return examples;
3714  }
3715}