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