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.Arrays;
034import java.util.Collections;
035import java.util.EnumSet;
036import java.util.Iterator;
037import java.util.LinkedHashMap;
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 List<String> getAdditionalDescriptionParagraphs()
372  {
373    return Arrays.asList(
374         INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_1.get(),
375         INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_2.get());
376  }
377
378
379
380  /**
381   * {@inheritDoc}
382   */
383  @Override()
384  public String getToolVersion()
385  {
386    return Version.NUMERIC_VERSION_STRING;
387  }
388
389
390
391  /**
392   * {@inheritDoc}
393   */
394  @Override()
395  public int getMinTrailingArguments()
396  {
397    return 0;
398  }
399
400
401
402  /**
403   * {@inheritDoc}
404   */
405  @Override()
406  public int getMaxTrailingArguments()
407  {
408    return -1;
409  }
410
411
412
413  /**
414   * {@inheritDoc}
415   */
416  @Override()
417  public String getTrailingArgumentsPlaceholder()
418  {
419    return INFO_LDAPSEARCH_TRAILING_ARGS_PLACEHOLDER.get();
420  }
421
422
423
424  /**
425   * {@inheritDoc}
426   */
427  @Override()
428  public boolean supportsInteractiveMode()
429  {
430    return true;
431  }
432
433
434
435  /**
436   * {@inheritDoc}
437   */
438  @Override()
439  public boolean defaultsToInteractiveMode()
440  {
441    return true;
442  }
443
444
445
446  /**
447   * {@inheritDoc}
448   */
449  @Override()
450  public boolean supportsPropertiesFile()
451  {
452    return true;
453  }
454
455
456
457  /**
458   * {@inheritDoc}
459   */
460  @Override()
461  protected boolean defaultToPromptForBindPassword()
462  {
463    return true;
464  }
465
466
467
468  /**
469   * {@inheritDoc}
470   */
471  @Override()
472  protected boolean includeAlternateLongIdentifiers()
473  {
474    return true;
475  }
476
477
478
479  /**
480   * {@inheritDoc}
481   */
482  @Override()
483  protected Set<Character> getSuppressedShortIdentifiers()
484  {
485    return Collections.singleton('T');
486  }
487
488
489
490  /**
491   * {@inheritDoc}
492   */
493  @Override()
494  public void addNonLDAPArguments(final ArgumentParser parser)
495         throws ArgumentException
496  {
497    this.parser = parser;
498
499    baseDN = new DNArgument('b', "baseDN", false, 1, null,
500         INFO_LDAPSEARCH_ARG_DESCRIPTION_BASE_DN.get());
501    baseDN.addLongIdentifier("base-dn", true);
502    baseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
503    parser.addArgument(baseDN);
504
505    scope = new ScopeArgument('s', "scope", false, null,
506         INFO_LDAPSEARCH_ARG_DESCRIPTION_SCOPE.get(), SearchScope.SUB);
507    scope.addLongIdentifier("searchScope", true);
508    scope.addLongIdentifier("search-scope", true);
509    scope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
510    parser.addArgument(scope);
511
512    sizeLimit = new IntegerArgument('z', "sizeLimit", false, 1, null,
513         INFO_LDAPSEARCH_ARG_DESCRIPTION_SIZE_LIMIT.get(), 0,
514         Integer.MAX_VALUE, 0);
515    sizeLimit.addLongIdentifier("size-limit", true);
516    sizeLimit.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
517    parser.addArgument(sizeLimit);
518
519    timeLimitSeconds = new IntegerArgument('l', "timeLimitSeconds", false, 1,
520         null, INFO_LDAPSEARCH_ARG_DESCRIPTION_TIME_LIMIT.get(), 0,
521         Integer.MAX_VALUE, 0);
522    timeLimitSeconds.addLongIdentifier("timeLimit", true);
523    timeLimitSeconds.addLongIdentifier("time-limit-seconds", true);
524    timeLimitSeconds.addLongIdentifier("time-limit", true);
525    timeLimitSeconds.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
526    parser.addArgument(timeLimitSeconds);
527
528    final Set<String> derefAllowedValues =
529         StaticUtils.setOf("never", "always", "search", "find");
530    dereferencePolicy = new StringArgument('a', "dereferencePolicy", false, 1,
531         "{never|always|search|find}",
532         INFO_LDAPSEARCH_ARG_DESCRIPTION_DEREFERENCE_POLICY.get(),
533         derefAllowedValues, "never");
534    dereferencePolicy.addLongIdentifier("dereference-policy", true);
535    dereferencePolicy.setArgumentGroupName(
536         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
537    parser.addArgument(dereferencePolicy);
538
539    typesOnly = new BooleanArgument('A', "typesOnly", 1,
540         INFO_LDAPSEARCH_ARG_DESCRIPTION_TYPES_ONLY.get());
541    typesOnly.addLongIdentifier("types-only", true);
542    typesOnly.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
543    parser.addArgument(typesOnly);
544
545    requestedAttribute = new StringArgument(null, "requestedAttribute", false,
546         0, INFO_PLACEHOLDER_ATTR.get(),
547         INFO_LDAPSEARCH_ARG_DESCRIPTION_REQUESTED_ATTR.get());
548    requestedAttribute.addLongIdentifier("requested-attribute", true);
549    requestedAttribute.setArgumentGroupName(
550         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
551    parser.addArgument(requestedAttribute);
552
553    filter = new FilterArgument(null, "filter", false, 0,
554         INFO_PLACEHOLDER_FILTER.get(),
555         INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER.get());
556    filter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
557    parser.addArgument(filter);
558
559    filterFile = new FileArgument('f', "filterFile", false, 0, null,
560         INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER_FILE.get(), true, true,
561         true, false);
562    filterFile.addLongIdentifier("filename", true);
563    filterFile.addLongIdentifier("filter-file", true);
564    filterFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
565    parser.addArgument(filterFile);
566
567    ldapURLFile = new FileArgument(null, "ldapURLFile", false, 0, null,
568         INFO_LDAPSEARCH_ARG_DESCRIPTION_LDAP_URL_FILE.get(), true, true,
569         true, false);
570    ldapURLFile.addLongIdentifier("ldap-url-file", true);
571    ldapURLFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
572    parser.addArgument(ldapURLFile);
573
574    followReferrals = new BooleanArgument(null, "followReferrals", 1,
575         INFO_LDAPSEARCH_ARG_DESCRIPTION_FOLLOW_REFERRALS.get());
576    followReferrals.addLongIdentifier("follow-referrals", true);
577    followReferrals.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
578    parser.addArgument(followReferrals);
579
580    retryFailedOperations = new BooleanArgument(null, "retryFailedOperations",
581         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_RETRY_FAILED_OPERATIONS.get());
582    retryFailedOperations.addLongIdentifier("retry-failed-operations", true);
583    retryFailedOperations.setArgumentGroupName(
584         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
585    parser.addArgument(retryFailedOperations);
586
587    continueOnError = new BooleanArgument('c', "continueOnError", 1,
588         INFO_LDAPSEARCH_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get());
589    continueOnError.addLongIdentifier("continue-on-error", true);
590    continueOnError.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
591    parser.addArgument(continueOnError);
592
593    ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1,
594         INFO_PLACEHOLDER_NUM.get(),
595         INFO_LDAPSEARCH_ARG_DESCRIPTION_RATE_PER_SECOND.get(), 1,
596         Integer.MAX_VALUE);
597    ratePerSecond.addLongIdentifier("rate-per-second", true);
598    ratePerSecond.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
599    parser.addArgument(ratePerSecond);
600
601    useAdministrativeSession = new BooleanArgument(null,
602         "useAdministrativeSession", 1,
603         INFO_LDAPSEARCH_ARG_DESCRIPTION_USE_ADMIN_SESSION.get());
604    useAdministrativeSession.addLongIdentifier("use-administrative-session",
605         true);
606    useAdministrativeSession.setArgumentGroupName(
607         INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
608    parser.addArgument(useAdministrativeSession);
609
610    dryRun = new BooleanArgument('n', "dryRun", 1,
611         INFO_LDAPSEARCH_ARG_DESCRIPTION_DRY_RUN.get());
612    dryRun.addLongIdentifier("dry-run", true);
613    dryRun.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
614    parser.addArgument(dryRun);
615
616    wrapColumn = new IntegerArgument(null, "wrapColumn", false, 1, null,
617         INFO_LDAPSEARCH_ARG_DESCRIPTION_WRAP_COLUMN.get(), 0,
618         Integer.MAX_VALUE);
619    wrapColumn.addLongIdentifier("wrap-column", true);
620    wrapColumn.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
621    parser.addArgument(wrapColumn);
622
623    dontWrap = new BooleanArgument('T', "dontWrap", 1,
624         INFO_LDAPSEARCH_ARG_DESCRIPTION_DONT_WRAP.get());
625    dontWrap.addLongIdentifier("doNotWrap", true);
626    dontWrap.addLongIdentifier("dont-wrap", true);
627    dontWrap.addLongIdentifier("do-not-wrap", true);
628    dontWrap.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
629    parser.addArgument(dontWrap);
630
631    suppressBase64EncodedValueComments = new BooleanArgument(null,
632         "suppressBase64EncodedValueComments", 1,
633         INFO_LDAPSEARCH_ARG_DESCRIPTION_SUPPRESS_BASE64_COMMENTS.get());
634    suppressBase64EncodedValueComments.addLongIdentifier(
635         "suppress-base64-encoded-value-comments", true);
636    suppressBase64EncodedValueComments.setArgumentGroupName(
637         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
638    parser.addArgument(suppressBase64EncodedValueComments);
639
640    countEntries = new BooleanArgument(null, "countEntries", 1,
641         INFO_LDAPSEARCH_ARG_DESCRIPTION_COUNT_ENTRIES.get());
642    countEntries.addLongIdentifier("count-entries", true);
643    countEntries.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get());
644    countEntries.setHidden(true);
645    parser.addArgument(countEntries);
646
647    outputFile = new FileArgument(null, "outputFile", false, 1, null,
648         INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FILE.get(), false, true, true,
649         false);
650    outputFile.addLongIdentifier("output-file", true);
651    outputFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
652    parser.addArgument(outputFile);
653
654    compressOutput = new BooleanArgument(null, "compressOutput", 1,
655         INFO_LDAPSEARCH_ARG_DESCRIPTION_COMPRESS_OUTPUT.get());
656    compressOutput.addLongIdentifier("compress-output", true);
657    compressOutput.addLongIdentifier("compress", true);
658    compressOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
659    parser.addArgument(compressOutput);
660
661    encryptOutput = new BooleanArgument(null, "encryptOutput", 1,
662         INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPT_OUTPUT.get());
663    encryptOutput.addLongIdentifier("encrypt-output", true);
664    encryptOutput.addLongIdentifier("encrypt", true);
665    encryptOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
666    parser.addArgument(encryptOutput);
667
668    encryptionPassphraseFile = new FileArgument(null,
669         "encryptionPassphraseFile", false, 1, null,
670         INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPTION_PW_FILE.get(), true, true,
671         true, false);
672    encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file",
673         true);
674    encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true);
675    encryptionPassphraseFile.addLongIdentifier("encryption-password-file",
676         true);
677    encryptionPassphraseFile.setArgumentGroupName(
678         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
679    parser.addArgument(encryptionPassphraseFile);
680
681    separateOutputFilePerSearch = new BooleanArgument(null,
682         "separateOutputFilePerSearch", 1,
683         INFO_LDAPSEARCH_ARG_DESCRIPTION_SEPARATE_OUTPUT_FILES.get());
684    separateOutputFilePerSearch.addLongIdentifier(
685         "separate-output-file-per-search", true);
686    separateOutputFilePerSearch.setArgumentGroupName(
687         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
688    parser.addArgument(separateOutputFilePerSearch);
689
690    teeResultsToStandardOut = new BooleanArgument(null,
691         "teeResultsToStandardOut", 1,
692         INFO_LDAPSEARCH_ARG_DESCRIPTION_TEE.get("outputFile"));
693    teeResultsToStandardOut.addLongIdentifier(
694         "tee-results-to-standard-out", true);
695    teeResultsToStandardOut.setArgumentGroupName(
696         INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
697    parser.addArgument(teeResultsToStandardOut);
698
699    final Set<String> outputFormatAllowedValues =
700         StaticUtils.setOf("ldif", "json", "csv", "tab-delimited");
701    outputFormat = new StringArgument(null, "outputFormat", false, 1,
702         "{ldif|json|csv|tab-delimited}",
703         INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FORMAT.get(
704              requestedAttribute.getIdentifierString(),
705              ldapURLFile.getIdentifierString()),
706         outputFormatAllowedValues, "ldif");
707    outputFormat.addLongIdentifier("output-format", true);
708    outputFormat.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
709    parser.addArgument(outputFormat);
710
711    terse = new BooleanArgument(null, "terse", 1,
712         INFO_LDAPSEARCH_ARG_DESCRIPTION_TERSE.get());
713    terse.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
714    parser.addArgument(terse);
715
716    verbose = new BooleanArgument('v', "verbose", 1,
717         INFO_LDAPSEARCH_ARG_DESCRIPTION_VERBOSE.get());
718    verbose.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get());
719    parser.addArgument(verbose);
720
721    bindControl = new ControlArgument(null, "bindControl", false, 0, null,
722         INFO_LDAPSEARCH_ARG_DESCRIPTION_BIND_CONTROL.get());
723    bindControl.addLongIdentifier("bind-control", true);
724    bindControl.setArgumentGroupName(
725         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
726    parser.addArgument(bindControl);
727
728    searchControl = new ControlArgument('J', "control", false, 0, null,
729         INFO_LDAPSEARCH_ARG_DESCRIPTION_SEARCH_CONTROL.get());
730    searchControl.addLongIdentifier("searchControl", true);
731    searchControl.addLongIdentifier("search-control", true);
732    searchControl.setArgumentGroupName(
733         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
734    parser.addArgument(searchControl);
735
736    authorizationIdentity = new BooleanArgument('E', "authorizationIdentity",
737         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_AUTHZ_IDENTITY.get());
738    authorizationIdentity.addLongIdentifier("reportAuthzID", true);
739    authorizationIdentity.addLongIdentifier("authorization-identity", true);
740    authorizationIdentity.addLongIdentifier("report-authzid", true);
741    authorizationIdentity.setArgumentGroupName(
742         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
743    parser.addArgument(authorizationIdentity);
744
745    assertionFilter = new FilterArgument(null, "assertionFilter", false, 1,
746         INFO_PLACEHOLDER_FILTER.get(),
747         INFO_LDAPSEARCH_ARG_DESCRIPTION_ASSERTION_FILTER.get());
748    assertionFilter.addLongIdentifier("assertion-filter", true);
749    assertionFilter.setArgumentGroupName(
750         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
751    parser.addArgument(assertionFilter);
752
753    getAuthorizationEntryAttribute = new StringArgument(null,
754         "getAuthorizationEntryAttribute", false, 0,
755         INFO_PLACEHOLDER_ATTR.get(),
756         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get());
757    getAuthorizationEntryAttribute.addLongIdentifier(
758         "get-authorization-entry-attribute", true);
759    getAuthorizationEntryAttribute.setArgumentGroupName(
760         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
761    parser.addArgument(getAuthorizationEntryAttribute);
762
763    getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits",
764         1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_USER_RESOURCE_LIMITS.get());
765    getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true);
766    getUserResourceLimits.setArgumentGroupName(
767         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
768    parser.addArgument(getUserResourceLimits);
769
770    accountUsable = new BooleanArgument(null, "accountUsable", 1,
771         INFO_LDAPSEARCH_ARG_DESCRIPTION_ACCOUNT_USABLE.get());
772    accountUsable.addLongIdentifier("account-usable", true);
773    accountUsable.setArgumentGroupName(
774         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
775    parser.addArgument(accountUsable);
776
777    excludeBranch = new DNArgument(null, "excludeBranch", false, 0, null,
778         INFO_LDAPSEARCH_ARG_DESCRIPTION_EXCLUDE_BRANCH.get());
779    excludeBranch.addLongIdentifier("exclude-branch", true);
780    excludeBranch.setArgumentGroupName(
781         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
782    parser.addArgument(excludeBranch);
783
784    getEffectiveRightsAuthzID = new StringArgument('g',
785         "getEffectiveRightsAuthzID", false, 1,
786         INFO_PLACEHOLDER_AUTHZID.get(),
787         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_AUTHZID.get(
788              "getEffectiveRightsAttribute"));
789    getEffectiveRightsAuthzID.addLongIdentifier(
790         "get-effective-rights-authzid", true);
791    getEffectiveRightsAuthzID.setArgumentGroupName(
792         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
793    parser.addArgument(getEffectiveRightsAuthzID);
794
795    getEffectiveRightsAttribute = new StringArgument('e',
796         "getEffectiveRightsAttribute", false, 0,
797         INFO_PLACEHOLDER_ATTR.get(),
798         INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_ATTR.get());
799    getEffectiveRightsAttribute.addLongIdentifier(
800         "get-effective-rights-attribute", true);
801    getEffectiveRightsAttribute.setArgumentGroupName(
802         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
803    parser.addArgument(getEffectiveRightsAttribute);
804
805    includeReplicationConflictEntries = new BooleanArgument(null,
806         "includeReplicationConflictEntries", 1,
807         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_REPL_CONFLICTS.get());
808    includeReplicationConflictEntries.addLongIdentifier(
809         "include-replication-conflict-entries", true);
810    includeReplicationConflictEntries.setArgumentGroupName(
811         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
812    parser.addArgument(includeReplicationConflictEntries);
813
814    final Set<String> softDeleteAllowedValues = StaticUtils.setOf(
815         "with-non-deleted-entries", "without-non-deleted-entries",
816         "deleted-entries-in-undeleted-form");
817    includeSoftDeletedEntries = new StringArgument(null,
818         "includeSoftDeletedEntries", false, 1,
819         "{with-non-deleted-entries|without-non-deleted-entries|" +
820              "deleted-entries-in-undeleted-form}",
821         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_SOFT_DELETED.get(),
822         softDeleteAllowedValues);
823    includeSoftDeletedEntries.addLongIdentifier(
824         "include-soft-deleted-entries", true);
825    includeSoftDeletedEntries.setArgumentGroupName(
826         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
827    parser.addArgument(includeSoftDeletedEntries);
828
829    includeSubentries = new BooleanArgument(null, "includeSubentries", 1,
830         INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_SUBENTRIES.get());
831    includeSubentries.addLongIdentifier("includeLDAPSubentries", true);
832    includeSubentries.addLongIdentifier("include-subentries", true);
833    includeSubentries.addLongIdentifier("include-ldap-subentries", true);
834    includeSubentries.setArgumentGroupName(
835         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
836    parser.addArgument(includeSubentries);
837
838    joinRule = new StringArgument(null, "joinRule", false, 1,
839         "{dn:sourceAttr|reverse-dn:targetAttr|equals:sourceAttr:targetAttr|" +
840              "contains:sourceAttr:targetAttr }",
841         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_RULE.get());
842    joinRule.addLongIdentifier("join-rule", true);
843    joinRule.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
844    parser.addArgument(joinRule);
845
846    joinBaseDN = new StringArgument(null, "joinBaseDN", false, 1,
847         "{search-base|source-entry-dn|{dn}}",
848         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_BASE_DN.get());
849    joinBaseDN.addLongIdentifier("join-base-dn", true);
850    joinBaseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
851    parser.addArgument(joinBaseDN);
852
853    joinScope = new ScopeArgument(null, "joinScope", false, null,
854         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SCOPE.get());
855    joinScope.addLongIdentifier("join-scope", true);
856    joinScope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
857    parser.addArgument(joinScope);
858
859    joinSizeLimit = new IntegerArgument(null, "joinSizeLimit", false, 1,
860         INFO_PLACEHOLDER_NUM.get(),
861         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SIZE_LIMIT.get(), 0,
862         Integer.MAX_VALUE);
863    joinSizeLimit.addLongIdentifier("join-size-limit", true);
864    joinSizeLimit.setArgumentGroupName(
865         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
866    parser.addArgument(joinSizeLimit);
867
868    joinFilter = new FilterArgument(null, "joinFilter", false, 1, null,
869         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_FILTER.get());
870    joinFilter.addLongIdentifier("join-filter", true);
871    joinFilter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
872    parser.addArgument(joinFilter);
873
874    joinRequestedAttribute = new StringArgument(null, "joinRequestedAttribute",
875         false, 0, INFO_PLACEHOLDER_ATTR.get(),
876         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_ATTR.get());
877    joinRequestedAttribute.addLongIdentifier("join-requested-attribute", true);
878    joinRequestedAttribute.setArgumentGroupName(
879         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
880    parser.addArgument(joinRequestedAttribute);
881
882    joinRequireMatch = new BooleanArgument(null, "joinRequireMatch", 1,
883         INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_REQUIRE_MATCH.get());
884    joinRequireMatch.addLongIdentifier("join-require-match", true);
885    joinRequireMatch.setArgumentGroupName(
886         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
887    parser.addArgument(joinRequireMatch);
888
889    manageDsaIT = new BooleanArgument(null, "manageDsaIT", 1,
890         INFO_LDAPSEARCH_ARG_DESCRIPTION_MANAGE_DSA_IT.get());
891    manageDsaIT.addLongIdentifier("manage-dsa-it", true);
892    manageDsaIT.setArgumentGroupName(
893         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
894    parser.addArgument(manageDsaIT);
895
896    matchedValuesFilter = new FilterArgument(null, "matchedValuesFilter",
897         false, 0, INFO_PLACEHOLDER_FILTER.get(),
898         INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHED_VALUES_FILTER.get());
899    matchedValuesFilter.addLongIdentifier("matched-values-filter", true);
900    matchedValuesFilter.setArgumentGroupName(
901         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
902    parser.addArgument(matchedValuesFilter);
903
904    matchingEntryCountControl = new StringArgument(null,
905         "matchingEntryCountControl", false, 1,
906         "{examineCount=NNN[:alwaysExamine][:allowUnindexed]" +
907              "[:skipResolvingExplodedIndexes]" +
908              "[:fastShortCircuitThreshold=NNN]" +
909              "[:slowShortCircuitThreshold=NNN][:debug]}",
910         INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHING_ENTRY_COUNT_CONTROL.get());
911    matchingEntryCountControl.addLongIdentifier("matchingEntryCount", true);
912    matchingEntryCountControl.addLongIdentifier(
913         "matching-entry-count-control", true);
914    matchingEntryCountControl.addLongIdentifier("matching-entry-count", true);
915    matchingEntryCountControl.setArgumentGroupName(
916         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
917    parser.addArgument(matchingEntryCountControl);
918
919    operationPurpose = new StringArgument(null, "operationPurpose", false, 1,
920         INFO_PLACEHOLDER_PURPOSE.get(),
921         INFO_LDAPSEARCH_ARG_DESCRIPTION_OPERATION_PURPOSE.get());
922    operationPurpose.addLongIdentifier("operation-purpose", true);
923    operationPurpose.setArgumentGroupName(
924         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
925    parser.addArgument(operationPurpose);
926
927    overrideSearchLimit = new StringArgument(null, "overrideSearchLimit",
928         false, 0, INFO_LDAPSEARCH_NAME_VALUE_PLACEHOLDER.get(),
929         INFO_LDAPSEARCH_ARG_DESCRIPTION_OVERRIDE_SEARCH_LIMIT.get());
930    overrideSearchLimit.addLongIdentifier("overrideSearchLimits", true);
931    overrideSearchLimit.addLongIdentifier("override-search-limit", true);
932    overrideSearchLimit.addLongIdentifier("override-search-limits", true);
933    overrideSearchLimit.setArgumentGroupName(
934         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
935    parser.addArgument(overrideSearchLimit);
936
937    persistentSearch = new StringArgument('C', "persistentSearch", false, 1,
938         "ps[:changetype[:changesonly[:entrychgcontrols]]]",
939         INFO_LDAPSEARCH_ARG_DESCRIPTION_PERSISTENT_SEARCH.get());
940    persistentSearch.addLongIdentifier("persistent-search", true);
941    persistentSearch.setArgumentGroupName(
942         INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
943    parser.addArgument(persistentSearch);
944
945    proxyAs = new StringArgument('Y', "proxyAs", false, 1,
946         INFO_PLACEHOLDER_AUTHZID.get(),
947         INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_AS.get());
948    proxyAs.addLongIdentifier("proxy-as", true);
949    proxyAs.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
950    parser.addArgument(proxyAs);
951
952    proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null,
953         INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_V1_AS.get());
954    proxyV1As.addLongIdentifier("proxy-v1-as", true);
955    proxyV1As.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get());
956    parser.addArgument(proxyV1As);
957
958    final Set<String> suppressOperationalAttributeUpdatesAllowedValues =
959         StaticUtils.setOf("last-access-time", "last-login-time",
960              "last-login-ip", "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 =
1559           new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1560      for (final String value : overrideSearchLimit.getValues())
1561      {
1562        final int equalPos = value.indexOf('=');
1563        if (equalPos < 0)
1564        {
1565          throw new ArgumentException(
1566               ERR_LDAPSEARCH_OVERRIDE_LIMIT_NO_EQUAL.get(
1567                    overrideSearchLimit.getIdentifierString()));
1568        }
1569        else if (equalPos == 0)
1570        {
1571          throw new ArgumentException(
1572               ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_NAME.get(
1573                    overrideSearchLimit.getIdentifierString()));
1574        }
1575
1576        final String propertyName = value.substring(0, equalPos);
1577        if (properties.containsKey(propertyName))
1578        {
1579          throw new ArgumentException(
1580               ERR_LDAPSEARCH_OVERRIDE_LIMIT_DUPLICATE_PROPERTY_NAME.get(
1581                    overrideSearchLimit.getIdentifierString(), propertyName));
1582        }
1583
1584        if (equalPos == (value.length() - 1))
1585        {
1586          throw new ArgumentException(
1587               ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_VALUE.get(
1588                    overrideSearchLimit.getIdentifierString(), propertyName));
1589        }
1590
1591        properties.put(propertyName, value.substring(equalPos+1));
1592      }
1593
1594      overrideSearchLimitsRequestControl =
1595           new OverrideSearchLimitsRequestControl(properties, false);
1596    }
1597
1598
1599    // If we should use the persistent search request control, then validate
1600    // the argument value and pre-create the control.
1601    if (persistentSearch.isPresent())
1602    {
1603      boolean changesOnly = true;
1604      boolean returnECs   = true;
1605      EnumSet<PersistentSearchChangeType> changeTypes =
1606           EnumSet.allOf(PersistentSearchChangeType.class);
1607      try
1608      {
1609        final String[] elements =
1610             persistentSearch.getValue().toLowerCase().split(":");
1611        if (elements.length == 0)
1612        {
1613          throw new ArgumentException(
1614               ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1615                    persistentSearch.getIdentifierString()));
1616        }
1617
1618        final String header = StaticUtils.toLowerCase(elements[0]);
1619        if (! (header.equals("ps") || header.equals("persist") ||
1620             header.equals("persistent") || header.equals("psearch") ||
1621             header.equals("persistentsearch")))
1622        {
1623          throw new ArgumentException(
1624               ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1625                    persistentSearch.getIdentifierString()));
1626        }
1627
1628        if (elements.length > 1)
1629        {
1630          final String ctString = StaticUtils.toLowerCase(elements[1]);
1631          if (ctString.equals("any"))
1632          {
1633            changeTypes = EnumSet.allOf(PersistentSearchChangeType.class);
1634          }
1635          else
1636          {
1637            changeTypes.clear();
1638            for (final String t : ctString.split(","))
1639            {
1640              if (t.equals("add"))
1641              {
1642                changeTypes.add(PersistentSearchChangeType.ADD);
1643              }
1644              else if (t.equals("del") || t.equals("delete"))
1645              {
1646                changeTypes.add(PersistentSearchChangeType.DELETE);
1647              }
1648              else if (t.equals("mod") || t.equals("modify"))
1649              {
1650                changeTypes.add(PersistentSearchChangeType.MODIFY);
1651              }
1652              else if (t.equals("moddn") || t.equals("modrdn") ||
1653                   t.equals("modifydn") || t.equals("modifyrdn"))
1654              {
1655                changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
1656              }
1657              else
1658              {
1659                throw new ArgumentException(
1660                     ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1661                          persistentSearch.getIdentifierString()));
1662              }
1663            }
1664          }
1665        }
1666
1667        if (elements.length > 2)
1668        {
1669          if (elements[2].equalsIgnoreCase("true") || elements[2].equals("1"))
1670          {
1671            changesOnly = true;
1672          }
1673          else if (elements[2].equalsIgnoreCase("false") ||
1674               elements[2].equals("0"))
1675          {
1676            changesOnly = false;
1677          }
1678          else
1679          {
1680            throw new ArgumentException(
1681                 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1682                      persistentSearch.getIdentifierString()));
1683          }
1684        }
1685
1686        if (elements.length > 3)
1687        {
1688          if (elements[3].equalsIgnoreCase("true") || elements[3].equals("1"))
1689          {
1690            returnECs = true;
1691          }
1692          else if (elements[3].equalsIgnoreCase("false") ||
1693               elements[3].equals("0"))
1694          {
1695            returnECs = false;
1696          }
1697          else
1698          {
1699            throw new ArgumentException(
1700                 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1701                      persistentSearch.getIdentifierString()));
1702          }
1703        }
1704      }
1705      catch (final ArgumentException ae)
1706      {
1707        Debug.debugException(ae);
1708        throw ae;
1709      }
1710      catch (final Exception e)
1711      {
1712        Debug.debugException(e);
1713        throw new ArgumentException(
1714             ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get(
1715                  persistentSearch.getIdentifierString()),
1716             e);
1717      }
1718
1719      persistentSearchRequestControl = new PersistentSearchRequestControl(
1720           changeTypes, changesOnly, returnECs, true);
1721    }
1722
1723
1724    // If we should use the server-side sort request control, then validate the
1725    // sort order and pre-create the control.
1726    if (sortOrder.isPresent())
1727    {
1728      final ArrayList<SortKey> sortKeyList = new ArrayList<>(5);
1729      final StringTokenizer tokenizer =
1730           new StringTokenizer(sortOrder.getValue(), ", ");
1731      while (tokenizer.hasMoreTokens())
1732      {
1733        final String token = tokenizer.nextToken();
1734
1735        final boolean ascending;
1736        String attributeName;
1737        if (token.startsWith("-"))
1738        {
1739          ascending = false;
1740          attributeName = token.substring(1);
1741        }
1742        else if (token.startsWith("+"))
1743        {
1744          ascending = true;
1745          attributeName = token.substring(1);
1746        }
1747        else
1748        {
1749          ascending = true;
1750          attributeName = token;
1751        }
1752
1753        final String matchingRuleID;
1754        final int colonPos = attributeName.indexOf(':');
1755        if (colonPos >= 0)
1756        {
1757          matchingRuleID = attributeName.substring(colonPos+1);
1758          attributeName = attributeName.substring(0, colonPos);
1759        }
1760        else
1761        {
1762          matchingRuleID = null;
1763        }
1764
1765        final StringBuilder invalidReason = new StringBuilder();
1766        if (! PersistUtils.isValidLDAPName(attributeName, false, invalidReason))
1767        {
1768          throw new ArgumentException(
1769               ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get(
1770                    sortOrder.getIdentifierString()));
1771        }
1772
1773        sortKeyList.add(
1774             new SortKey(attributeName, matchingRuleID, (! ascending)));
1775      }
1776
1777      if (sortKeyList.isEmpty())
1778      {
1779        throw new ArgumentException(
1780             ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get(
1781                  sortOrder.getIdentifierString()));
1782      }
1783
1784      final SortKey[] sortKeyArray = new SortKey[sortKeyList.size()];
1785      sortKeyList.toArray(sortKeyArray);
1786
1787      sortRequestControl = new ServerSideSortRequestControl(sortKeyArray);
1788    }
1789
1790
1791    // If we should use the virtual list view request control, then validate the
1792    // argument value and pre-create the control.
1793    if (virtualListView.isPresent())
1794    {
1795      try
1796      {
1797        final String[] elements = virtualListView.getValue().split(":");
1798        if (elements.length == 4)
1799        {
1800          vlvRequestControl = new VirtualListViewRequestControl(
1801               Integer.parseInt(elements[2]), Integer.parseInt(elements[0]),
1802               Integer.parseInt(elements[1]), Integer.parseInt(elements[3]),
1803               null);
1804        }
1805        else if (elements.length == 3)
1806        {
1807          vlvRequestControl = new VirtualListViewRequestControl(elements[2],
1808               Integer.parseInt(elements[0]), Integer.parseInt(elements[1]),
1809               null);
1810        }
1811        else
1812        {
1813          throw new ArgumentException(
1814               ERR_LDAPSEARCH_VLV_INVALID_VALUE.get(
1815                    virtualListView.getIdentifierString()));
1816        }
1817      }
1818      catch (final ArgumentException ae)
1819      {
1820        Debug.debugException(ae);
1821        throw ae;
1822      }
1823      catch (final Exception e)
1824      {
1825        Debug.debugException(e);
1826        throw new ArgumentException(
1827             ERR_LDAPSEARCH_VLV_INVALID_VALUE.get(
1828                  virtualListView.getIdentifierString()),
1829             e);
1830      }
1831    }
1832
1833
1834    if (joinRule.isPresent())
1835    {
1836      final JoinRule rule;
1837      try
1838      {
1839        final String[] elements = joinRule.getValue().toLowerCase().split(":");
1840        final String ruleName = StaticUtils.toLowerCase(elements[0]);
1841        if (ruleName.equals("dn"))
1842        {
1843          rule = JoinRule.createDNJoin(elements[1]);
1844        }
1845        else if (ruleName.equals("reverse-dn") || ruleName.equals("reversedn"))
1846        {
1847          rule = JoinRule.createReverseDNJoin(elements[1]);
1848        }
1849        else if (ruleName.equals("equals") || ruleName.equals("equality"))
1850        {
1851          rule = JoinRule.createEqualityJoin(elements[1], elements[2], false);
1852        }
1853        else if (ruleName.equals("contains") || ruleName.equals("substring"))
1854        {
1855          rule = JoinRule.createContainsJoin(elements[1], elements[2], false);
1856        }
1857        else
1858        {
1859          throw new ArgumentException(
1860               ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get(
1861                    joinRule.getIdentifierString()));
1862        }
1863      }
1864      catch (final ArgumentException ae)
1865      {
1866        Debug.debugException(ae);
1867        throw ae;
1868      }
1869      catch (final Exception e)
1870      {
1871        Debug.debugException(e);
1872        throw new ArgumentException(
1873             ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get(
1874                  joinRule.getIdentifierString()),
1875             e);
1876      }
1877
1878      final JoinBaseDN joinBase;
1879      if (joinBaseDN.isPresent())
1880      {
1881        final String s = StaticUtils.toLowerCase(joinBaseDN.getValue());
1882        if (s.equals("search-base") || s.equals("search-base-dn"))
1883        {
1884          joinBase = JoinBaseDN.createUseSearchBaseDN();
1885        }
1886        else if (s.equals("source-entry-dn") || s.equals("source-dn"))
1887        {
1888          joinBase = JoinBaseDN.createUseSourceEntryDN();
1889        }
1890        else
1891        {
1892          try
1893          {
1894            final DN dn = new DN(joinBaseDN.getValue());
1895            joinBase = JoinBaseDN.createUseCustomBaseDN(joinBaseDN.getValue());
1896          }
1897          catch (final Exception e)
1898          {
1899            Debug.debugException(e);
1900            throw new ArgumentException(
1901                 ERR_LDAPSEARCH_JOIN_BASE_DN_INVALID_VALUE.get(
1902                      joinBaseDN.getIdentifierString()),
1903                 e);
1904          }
1905        }
1906      }
1907      else
1908      {
1909        joinBase = JoinBaseDN.createUseSearchBaseDN();
1910      }
1911
1912      final String[] joinAttrs;
1913      if (joinRequestedAttribute.isPresent())
1914      {
1915        final List<String> valueList = joinRequestedAttribute.getValues();
1916        joinAttrs = new String[valueList.size()];
1917        valueList.toArray(joinAttrs);
1918      }
1919      else
1920      {
1921        joinAttrs = null;
1922      }
1923
1924      joinRequestControl = new JoinRequestControl(new JoinRequestValue(rule,
1925           joinBase, joinScope.getValue(), DereferencePolicy.NEVER,
1926           joinSizeLimit.getValue(), joinFilter.getValue(), joinAttrs,
1927           joinRequireMatch.isPresent(), null));
1928    }
1929
1930
1931    // Parse the dereference policy.
1932    final String derefStr =
1933         StaticUtils.toLowerCase(dereferencePolicy.getValue());
1934    if (derefStr.equals("always"))
1935    {
1936      derefPolicy = DereferencePolicy.ALWAYS;
1937    }
1938    else if (derefStr.equals("search"))
1939    {
1940      derefPolicy = DereferencePolicy.SEARCHING;
1941    }
1942    else if (derefStr.equals("find"))
1943    {
1944      derefPolicy = DereferencePolicy.FINDING;
1945    }
1946    else
1947    {
1948      derefPolicy = DereferencePolicy.NEVER;
1949    }
1950
1951
1952    // See if any entry transformations need to be applied.
1953    final ArrayList<EntryTransformation> transformations = new ArrayList<>(5);
1954    if (excludeAttribute.isPresent())
1955    {
1956      transformations.add(new ExcludeAttributeTransformation(null,
1957           excludeAttribute.getValues()));
1958    }
1959
1960    if (redactAttribute.isPresent())
1961    {
1962      transformations.add(new RedactAttributeTransformation(null, true,
1963           (! hideRedactedValueCount.isPresent()),
1964           redactAttribute.getValues()));
1965    }
1966
1967    if (scrambleAttribute.isPresent())
1968    {
1969      final Long randomSeed;
1970      if (scrambleRandomSeed.isPresent())
1971      {
1972        randomSeed = scrambleRandomSeed.getValue().longValue();
1973      }
1974      else
1975      {
1976        randomSeed = null;
1977      }
1978
1979      transformations.add(new ScrambleAttributeTransformation(null, randomSeed,
1980           true, scrambleAttribute.getValues(), scrambleJSONField.getValues()));
1981    }
1982
1983    if (renameAttributeFrom.isPresent())
1984    {
1985      if (renameAttributeFrom.getNumOccurrences() !=
1986          renameAttributeTo.getNumOccurrences())
1987      {
1988        throw new ArgumentException(
1989             ERR_LDAPSEARCH_RENAME_ATTRIBUTE_MISMATCH.get());
1990      }
1991
1992      final Iterator<String> sourceIterator =
1993           renameAttributeFrom.getValues().iterator();
1994      final Iterator<String> targetIterator =
1995           renameAttributeTo.getValues().iterator();
1996      while (sourceIterator.hasNext())
1997      {
1998        transformations.add(new RenameAttributeTransformation(null,
1999             sourceIterator.next(), targetIterator.next(), true));
2000      }
2001    }
2002
2003    if (moveSubtreeFrom.isPresent())
2004    {
2005      if (moveSubtreeFrom.getNumOccurrences() !=
2006          moveSubtreeTo.getNumOccurrences())
2007      {
2008        throw new ArgumentException(ERR_LDAPSEARCH_MOVE_SUBTREE_MISMATCH.get());
2009      }
2010
2011      final Iterator<DN> sourceIterator =
2012           moveSubtreeFrom.getValues().iterator();
2013      final Iterator<DN> targetIterator = moveSubtreeTo.getValues().iterator();
2014      while (sourceIterator.hasNext())
2015      {
2016        transformations.add(new MoveSubtreeTransformation(sourceIterator.next(),
2017             targetIterator.next()));
2018      }
2019    }
2020
2021    if (! transformations.isEmpty())
2022    {
2023      entryTransformations = transformations;
2024    }
2025
2026
2027    // Create the output handler.
2028    final String outputFormatStr =
2029         StaticUtils.toLowerCase(outputFormat.getValue());
2030    if (outputFormatStr.equals("json"))
2031    {
2032      outputHandler = new JSONLDAPSearchOutputHandler(this);
2033    }
2034    else if (outputFormatStr.equals("csv") ||
2035             outputFormatStr.equals("tab-delimited"))
2036    {
2037      // These output formats cannot be used with the --ldapURLFile argument.
2038      if (ldapURLFile.isPresent())
2039      {
2040        throw new ArgumentException(
2041             ERR_LDAPSEARCH_OUTPUT_FORMAT_NOT_SUPPORTED_WITH_URLS.get(
2042                  outputFormat.getValue(), ldapURLFile.getIdentifierString()));
2043      }
2044
2045      // These output formats require the requested attributes to be specified
2046      // via the --requestedAttribute argument rather than as unnamed trailing
2047      // arguments.
2048      final List<String> requestedAttributes = requestedAttribute.getValues();
2049      if ((requestedAttributes == null) || requestedAttributes.isEmpty())
2050      {
2051        throw new ArgumentException(
2052             ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2053                  outputFormat.getValue(),
2054                  requestedAttribute.getIdentifierString()));
2055      }
2056
2057      switch (trailingArgs.size())
2058      {
2059        case 0:
2060          // This is fine.
2061          break;
2062
2063        case 1:
2064          // Make sure that the trailing argument is a filter rather than a
2065          // requested attribute.  It's sufficient to ensure that neither the
2066          // filter nor filterFile argument was provided.
2067          if (filter.isPresent() || filterFile.isPresent())
2068          {
2069            throw new ArgumentException(
2070                 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2071                      outputFormat.getValue(),
2072                      requestedAttribute.getIdentifierString()));
2073          }
2074          break;
2075
2076        default:
2077          throw new ArgumentException(
2078               ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get(
2079                    outputFormat.getValue(),
2080                    requestedAttribute.getIdentifierString()));
2081      }
2082
2083      outputHandler = new ColumnFormatterLDAPSearchOutputHandler(this,
2084           (outputFormatStr.equals("csv")
2085                ? OutputFormat.CSV
2086                : OutputFormat.TAB_DELIMITED_TEXT),
2087           requestedAttributes, WRAP_COLUMN);
2088    }
2089    else
2090    {
2091      outputHandler = new LDIFLDAPSearchOutputHandler(this, WRAP_COLUMN);
2092    }
2093  }
2094
2095
2096
2097  /**
2098   * {@inheritDoc}
2099   */
2100  @Override()
2101  public LDAPConnectionOptions getConnectionOptions()
2102  {
2103    final LDAPConnectionOptions options = new LDAPConnectionOptions();
2104
2105    options.setUseSynchronousMode(true);
2106    options.setFollowReferrals(followReferrals.isPresent());
2107    options.setUnsolicitedNotificationHandler(this);
2108
2109    return options;
2110  }
2111
2112
2113
2114  /**
2115   * {@inheritDoc}
2116   */
2117  @Override()
2118  public ResultCode doToolProcessing()
2119  {
2120    // If we should encrypt the output, then get the encryption passphrase.
2121    if (encryptOutput.isPresent())
2122    {
2123      if (encryptionPassphraseFile.isPresent())
2124      {
2125        try
2126        {
2127          encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile(
2128               encryptionPassphraseFile.getValue());
2129        }
2130        catch (final LDAPException e)
2131        {
2132          Debug.debugException(e);
2133          wrapErr(0, WRAP_COLUMN, e.getMessage());
2134          return e.getResultCode();
2135        }
2136      }
2137      else
2138      {
2139        try
2140        {
2141          encryptionPassphrase = ToolUtils.promptForEncryptionPassphrase(false,
2142               true, getOut(), getErr());
2143        }
2144        catch (final LDAPException e)
2145        {
2146          Debug.debugException(e);
2147          wrapErr(0, WRAP_COLUMN, e.getMessage());
2148          return e.getResultCode();
2149        }
2150      }
2151    }
2152
2153
2154    // If we should use an output file, then set that up now.  Otherwise, write
2155    // the header to standard output.
2156    if (outputFile.isPresent())
2157    {
2158      if (! separateOutputFilePerSearch.isPresent())
2159      {
2160        try
2161        {
2162          OutputStream s = new FileOutputStream(outputFile.getValue());
2163
2164          if (encryptOutput.isPresent())
2165          {
2166            s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s);
2167          }
2168
2169          if (compressOutput.isPresent())
2170          {
2171            s = new GZIPOutputStream(s);
2172          }
2173
2174          if (teeResultsToStandardOut.isPresent())
2175          {
2176            outStream = new PrintStream(new TeeOutputStream(s, getOut()));
2177          }
2178          else
2179          {
2180            outStream = new PrintStream(s);
2181          }
2182          errStream = outStream;
2183        }
2184        catch (final Exception e)
2185        {
2186          Debug.debugException(e);
2187          wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(
2188               outputFile.getValue().getAbsolutePath(),
2189               StaticUtils.getExceptionMessage(e)));
2190          return ResultCode.LOCAL_ERROR;
2191        }
2192
2193        outputHandler.formatHeader();
2194      }
2195    }
2196    else
2197    {
2198      outputHandler.formatHeader();
2199    }
2200
2201
2202    // Examine the arguments to determine the sets of controls to use for each
2203    // type of request.
2204    final List<Control> searchControls = getSearchControls();
2205
2206
2207    // If appropriate, ensure that any search result entries that include
2208    // base64-encoded attribute values will also include comments that attempt
2209    // to provide a human-readable representation of that value.
2210    final boolean originalCommentAboutBase64EncodedValues =
2211         LDIFWriter.commentAboutBase64EncodedValues();
2212    LDIFWriter.setCommentAboutBase64EncodedValues(
2213         ! suppressBase64EncodedValueComments.isPresent());
2214
2215
2216    LDAPConnectionPool pool = null;
2217    try
2218    {
2219      // Create a connection pool that will be used to communicate with the
2220      // directory server.
2221      if (! dryRun.isPresent())
2222      {
2223        try
2224        {
2225          final StartAdministrativeSessionPostConnectProcessor p;
2226          if (useAdministrativeSession.isPresent())
2227          {
2228            p = new StartAdministrativeSessionPostConnectProcessor(
2229                 new StartAdministrativeSessionExtendedRequest(getToolName(),
2230                      true));
2231          }
2232          else
2233          {
2234            p = null;
2235          }
2236
2237          pool = getConnectionPool(1, 1, 0, p, null, true,
2238               new ReportBindResultLDAPConnectionPoolHealthCheck(this, true,
2239                    false));
2240        }
2241        catch (final LDAPException le)
2242        {
2243          // This shouldn't happen since the pool won't throw an exception if an
2244          // attempt to create an initial connection fails.
2245          Debug.debugException(le);
2246          commentToErr(ERR_LDAPSEARCH_CANNOT_CREATE_CONNECTION_POOL.get(
2247               StaticUtils.getExceptionMessage(le)));
2248          return le.getResultCode();
2249        }
2250
2251        if (retryFailedOperations.isPresent())
2252        {
2253          pool.setRetryFailedOperationsDueToInvalidConnections(true);
2254        }
2255      }
2256
2257
2258      // If appropriate, create a rate limiter.
2259      final FixedRateBarrier rateLimiter;
2260      if (ratePerSecond.isPresent())
2261      {
2262        rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue());
2263      }
2264      else
2265      {
2266        rateLimiter = null;
2267      }
2268
2269
2270      // If one or more LDAP URL files are provided, then construct search
2271      // requests from those URLs.
2272      if (ldapURLFile.isPresent())
2273      {
2274        return searchWithLDAPURLs(pool, rateLimiter, searchControls);
2275      }
2276
2277
2278      // Get the set of requested attributes, as a combination of the
2279      // requestedAttribute argument values and any trailing arguments.
2280      final ArrayList<String> attrList = new ArrayList<>(10);
2281      if (requestedAttribute.isPresent())
2282      {
2283        attrList.addAll(requestedAttribute.getValues());
2284      }
2285
2286      final List<String> trailingArgs = parser.getTrailingArguments();
2287      if (! trailingArgs.isEmpty())
2288      {
2289        final Iterator<String> trailingArgIterator = trailingArgs.iterator();
2290        if (! (filter.isPresent() || filterFile.isPresent()))
2291        {
2292          trailingArgIterator.next();
2293        }
2294
2295        while (trailingArgIterator.hasNext())
2296        {
2297          attrList.add(trailingArgIterator.next());
2298        }
2299      }
2300
2301      final String[] attributes = new String[attrList.size()];
2302      attrList.toArray(attributes);
2303
2304
2305      // If either or both the filter or filterFile arguments are provided, then
2306      // use them to get the filters to process.  Otherwise, the first trailing
2307      // argument should be a filter.
2308      ResultCode resultCode = ResultCode.SUCCESS;
2309      if (filter.isPresent() || filterFile.isPresent())
2310      {
2311        if (filter.isPresent())
2312        {
2313          for (final Filter f : filter.getValues())
2314          {
2315            final ResultCode rc = searchWithFilter(pool, f, attributes,
2316                 rateLimiter, searchControls);
2317            if (rc != ResultCode.SUCCESS)
2318            {
2319              if (resultCode == ResultCode.SUCCESS)
2320              {
2321                resultCode = rc;
2322              }
2323
2324              if (! continueOnError.isPresent())
2325              {
2326                return resultCode;
2327              }
2328            }
2329          }
2330        }
2331
2332        if (filterFile.isPresent())
2333        {
2334          final ResultCode rc = searchWithFilterFile(pool, attributes,
2335               rateLimiter, searchControls);
2336          if (rc != ResultCode.SUCCESS)
2337          {
2338            if (resultCode == ResultCode.SUCCESS)
2339            {
2340              resultCode = rc;
2341            }
2342
2343            if (! continueOnError.isPresent())
2344            {
2345              return resultCode;
2346            }
2347          }
2348        }
2349      }
2350      else
2351      {
2352        final Filter f;
2353        try
2354        {
2355          final String filterStr =
2356               parser.getTrailingArguments().iterator().next();
2357          f = Filter.create(filterStr);
2358        }
2359        catch (final LDAPException le)
2360        {
2361          // This should never happen.
2362          Debug.debugException(le);
2363          displayResult(le.toLDAPResult());
2364          return le.getResultCode();
2365        }
2366
2367        resultCode =
2368             searchWithFilter(pool, f, attributes, rateLimiter, searchControls);
2369      }
2370
2371      return resultCode;
2372    }
2373    finally
2374    {
2375      if (pool != null)
2376      {
2377        try
2378        {
2379          pool.close();
2380        }
2381        catch (final Exception e)
2382        {
2383          Debug.debugException(e);
2384        }
2385      }
2386
2387      if (outStream != null)
2388      {
2389        try
2390        {
2391          outStream.close();
2392          outStream = null;
2393        }
2394        catch (final Exception e)
2395        {
2396          Debug.debugException(e);
2397        }
2398      }
2399
2400      if (errStream != null)
2401      {
2402        try
2403        {
2404          errStream.close();
2405          errStream = null;
2406        }
2407        catch (final Exception e)
2408        {
2409          Debug.debugException(e);
2410        }
2411      }
2412
2413      LDIFWriter.setCommentAboutBase64EncodedValues(
2414           originalCommentAboutBase64EncodedValues);
2415    }
2416  }
2417
2418
2419
2420  /**
2421   * Processes a set of searches using LDAP URLs read from one or more files.
2422   *
2423   * @param  pool            The connection pool to use to communicate with the
2424   *                         directory server.
2425   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2426   *                         request rate limiting.
2427   * @param  searchControls  The set of controls to include in search requests.
2428   *
2429   * @return  A result code indicating the result of the processing.
2430   */
2431  private ResultCode searchWithLDAPURLs(final LDAPConnectionPool pool,
2432                                        final FixedRateBarrier rateLimiter,
2433                                        final List<Control> searchControls)
2434  {
2435    ResultCode resultCode = ResultCode.SUCCESS;
2436    for (final File f : ldapURLFile.getValues())
2437    {
2438      BufferedReader reader = null;
2439
2440      try
2441      {
2442        reader = new BufferedReader(new FileReader(f));
2443        while (true)
2444        {
2445          final String line = reader.readLine();
2446          if (line == null)
2447          {
2448            break;
2449          }
2450
2451          if ((line.length() == 0) || line.startsWith("#"))
2452          {
2453            continue;
2454          }
2455
2456          final LDAPURL url;
2457          try
2458          {
2459            url = new LDAPURL(line);
2460          }
2461          catch (final LDAPException le)
2462          {
2463            Debug.debugException(le);
2464
2465            commentToErr(ERR_LDAPSEARCH_MALFORMED_LDAP_URL.get(
2466                 f.getAbsolutePath(), line));
2467            if (resultCode == ResultCode.SUCCESS)
2468            {
2469              resultCode = le.getResultCode();
2470            }
2471
2472            if (continueOnError.isPresent())
2473            {
2474              continue;
2475            }
2476            else
2477            {
2478              return resultCode;
2479            }
2480          }
2481
2482          final SearchRequest searchRequest = new SearchRequest(
2483               new LDAPSearchListener(outputHandler, entryTransformations),
2484               url.getBaseDN().toString(), url.getScope(), derefPolicy,
2485               sizeLimit.getValue(), timeLimitSeconds.getValue(),
2486               typesOnly.isPresent(), url.getFilter(), url.getAttributes());
2487          final ResultCode rc =
2488               doSearch(pool, searchRequest, rateLimiter, searchControls);
2489          if (rc != ResultCode.SUCCESS)
2490          {
2491            if (resultCode == ResultCode.SUCCESS)
2492            {
2493              resultCode = rc;
2494            }
2495
2496            if (! continueOnError.isPresent())
2497            {
2498              return resultCode;
2499            }
2500          }
2501        }
2502      }
2503      catch (final IOException ioe)
2504      {
2505        commentToErr(ERR_LDAPSEARCH_CANNOT_READ_LDAP_URL_FILE.get(
2506             f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe)));
2507        return ResultCode.LOCAL_ERROR;
2508      }
2509      finally
2510      {
2511        if (reader != null)
2512        {
2513          try
2514          {
2515            reader.close();
2516          }
2517          catch (final Exception e)
2518          {
2519            Debug.debugException(e);
2520          }
2521        }
2522      }
2523    }
2524
2525    return resultCode;
2526  }
2527
2528
2529
2530  /**
2531   * Processes a set of searches using filters read from one or more files.
2532   *
2533   * @param  pool            The connection pool to use to communicate with the
2534   *                         directory server.
2535   * @param  attributes      The set of attributes to request that the server
2536   *                         include in matching entries.
2537   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2538   *                         request rate limiting.
2539   * @param  searchControls  The set of controls to include in search requests.
2540   *
2541   * @return  A result code indicating the result of the processing.
2542   */
2543  private ResultCode searchWithFilterFile(final LDAPConnectionPool pool,
2544                                          final String[] attributes,
2545                                          final FixedRateBarrier rateLimiter,
2546                                          final List<Control> searchControls)
2547  {
2548    ResultCode resultCode = ResultCode.SUCCESS;
2549    for (final File f : filterFile.getValues())
2550    {
2551      FilterFileReader reader = null;
2552
2553      try
2554      {
2555        reader = new FilterFileReader(f);
2556        while (true)
2557        {
2558          final Filter searchFilter;
2559          try
2560          {
2561            searchFilter = reader.readFilter();
2562          }
2563          catch (final LDAPException le)
2564          {
2565            Debug.debugException(le);
2566            commentToErr(ERR_LDAPSEARCH_MALFORMED_FILTER.get(
2567                 f.getAbsolutePath(), le.getMessage()));
2568            if (resultCode == ResultCode.SUCCESS)
2569            {
2570              resultCode = le.getResultCode();
2571            }
2572
2573            if (continueOnError.isPresent())
2574            {
2575              continue;
2576            }
2577            else
2578            {
2579              return resultCode;
2580            }
2581          }
2582
2583          if (searchFilter == null)
2584          {
2585            break;
2586          }
2587
2588          final ResultCode rc = searchWithFilter(pool, searchFilter, attributes,
2589               rateLimiter, searchControls);
2590          if (rc != ResultCode.SUCCESS)
2591          {
2592            if (resultCode == ResultCode.SUCCESS)
2593            {
2594              resultCode = rc;
2595            }
2596
2597            if (! continueOnError.isPresent())
2598            {
2599              return resultCode;
2600            }
2601          }
2602        }
2603      }
2604      catch (final IOException ioe)
2605      {
2606        Debug.debugException(ioe);
2607        commentToErr(ERR_LDAPSEARCH_CANNOT_READ_FILTER_FILE.get(
2608             f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe)));
2609        return ResultCode.LOCAL_ERROR;
2610      }
2611      finally
2612      {
2613        if (reader != null)
2614        {
2615          try
2616          {
2617            reader.close();
2618          }
2619          catch (final Exception e)
2620          {
2621            Debug.debugException(e);
2622          }
2623        }
2624      }
2625    }
2626
2627    return resultCode;
2628  }
2629
2630
2631
2632  /**
2633   * Processes a search using the provided filter.
2634   *
2635   * @param  pool            The connection pool to use to communicate with the
2636   *                         directory server.
2637   * @param  filter          The filter to use for the search.
2638   * @param  attributes      The set of attributes to request that the server
2639   *                         include in matching entries.
2640   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2641   *                         request rate limiting.
2642   * @param  searchControls  The set of controls to include in search requests.
2643   *
2644   * @return  A result code indicating the result of the processing.
2645   */
2646  private ResultCode searchWithFilter(final LDAPConnectionPool pool,
2647                                      final Filter filter,
2648                                      final String[] attributes,
2649                                      final FixedRateBarrier rateLimiter,
2650                                      final List<Control> searchControls)
2651  {
2652    final String baseDNString;
2653    if (baseDN.isPresent())
2654    {
2655      baseDNString = baseDN.getStringValue();
2656    }
2657    else
2658    {
2659      baseDNString = "";
2660    }
2661
2662    final SearchRequest searchRequest = new SearchRequest(
2663         new LDAPSearchListener(outputHandler, entryTransformations),
2664         baseDNString, scope.getValue(), derefPolicy, sizeLimit.getValue(),
2665         timeLimitSeconds.getValue(), typesOnly.isPresent(), filter,
2666         attributes);
2667    return doSearch(pool, searchRequest, rateLimiter, searchControls);
2668  }
2669
2670
2671
2672  /**
2673   * Processes a search with the provided information.
2674   *
2675   * @param  pool            The connection pool to use to communicate with the
2676   *                         directory server.
2677   * @param  searchRequest   The search request to process.
2678   * @param  rateLimiter     An optional fixed-rate barrier that can be used for
2679   *                         request rate limiting.
2680   * @param  searchControls  The set of controls to include in search requests.
2681   *
2682   * @return  A result code indicating the result of the processing.
2683   */
2684  private ResultCode doSearch(final LDAPConnectionPool pool,
2685                              final SearchRequest searchRequest,
2686                              final FixedRateBarrier rateLimiter,
2687                              final List<Control> searchControls)
2688  {
2689    if (separateOutputFilePerSearch.isPresent())
2690    {
2691      try
2692      {
2693        final String path = outputFile.getValue().getAbsolutePath() + '.' +
2694             outputFileCounter.getAndIncrement();
2695
2696        OutputStream s = new FileOutputStream(path);
2697
2698        if (encryptOutput.isPresent())
2699        {
2700          s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s);
2701        }
2702
2703        if (compressOutput.isPresent())
2704        {
2705          s = new GZIPOutputStream(s);
2706        }
2707
2708        if (teeResultsToStandardOut.isPresent())
2709        {
2710          outStream = new PrintStream(new TeeOutputStream(s, getOut()));
2711        }
2712        else
2713        {
2714          outStream = new PrintStream(s);
2715        }
2716        errStream = outStream;
2717      }
2718      catch (final Exception e)
2719      {
2720        Debug.debugException(e);
2721        wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(
2722             outputFile.getValue().getAbsolutePath(),
2723             StaticUtils.getExceptionMessage(e)));
2724        return ResultCode.LOCAL_ERROR;
2725      }
2726
2727      outputHandler.formatHeader();
2728    }
2729
2730    try
2731    {
2732      if (rateLimiter != null)
2733      {
2734        rateLimiter.await();
2735      }
2736
2737
2738      ASN1OctetString pagedResultsCookie = null;
2739      boolean multiplePages = false;
2740      long totalEntries = 0;
2741      long totalReferences = 0;
2742
2743      SearchResult searchResult;
2744      try
2745      {
2746        while (true)
2747        {
2748          searchRequest.setControls(searchControls);
2749          if (simplePageSize.isPresent())
2750          {
2751            searchRequest.addControl(new SimplePagedResultsControl(
2752                 simplePageSize.getValue(), pagedResultsCookie));
2753          }
2754
2755          if (dryRun.isPresent())
2756          {
2757            searchResult = new SearchResult(-1, ResultCode.SUCCESS,
2758                 INFO_LDAPSEARCH_DRY_RUN_REQUEST_NOT_SENT.get(
2759                      dryRun.getIdentifierString(),
2760                      String.valueOf(searchRequest)),
2761                 null, null, 0, 0, null);
2762            break;
2763          }
2764          else
2765          {
2766            if (! terse.isPresent())
2767            {
2768              if (verbose.isPresent() || persistentSearch.isPresent() ||
2769                  filterFile.isPresent() || ldapURLFile.isPresent() ||
2770                  (filter.isPresent() && (filter.getNumOccurrences() > 1)))
2771              {
2772                commentToOut(INFO_LDAPSEARCH_SENDING_SEARCH_REQUEST.get(
2773                     String.valueOf(searchRequest)));
2774              }
2775            }
2776            searchResult = pool.search(searchRequest);
2777          }
2778
2779          if (searchResult.getEntryCount() > 0)
2780          {
2781            totalEntries += searchResult.getEntryCount();
2782          }
2783
2784          if (searchResult.getReferenceCount() > 0)
2785          {
2786            totalReferences += searchResult.getReferenceCount();
2787          }
2788
2789          if (simplePageSize.isPresent())
2790          {
2791            final SimplePagedResultsControl pagedResultsControl;
2792            try
2793            {
2794              pagedResultsControl = SimplePagedResultsControl.get(searchResult);
2795              if (pagedResultsControl == null)
2796              {
2797                throw new LDAPSearchException(new SearchResult(
2798                     searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND,
2799                     ERR_LDAPSEARCH_MISSING_PAGED_RESULTS_RESPONSE_CONTROL.
2800                          get(),
2801                     searchResult.getMatchedDN(),
2802                     searchResult.getReferralURLs(),
2803                     searchResult.getSearchEntries(),
2804                     searchResult.getSearchReferences(),
2805                     searchResult.getEntryCount(),
2806                     searchResult.getReferenceCount(),
2807                     searchResult.getResponseControls()));
2808              }
2809
2810              if (pagedResultsControl.moreResultsToReturn())
2811              {
2812                if (verbose.isPresent())
2813                {
2814                  commentToOut(
2815                       INFO_LDAPSEARCH_INTERMEDIATE_PAGED_SEARCH_RESULT.get());
2816                  displayResult(searchResult);
2817                }
2818
2819                multiplePages = true;
2820                pagedResultsCookie = pagedResultsControl.getCookie();
2821              }
2822              else
2823              {
2824                break;
2825              }
2826            }
2827            catch (final LDAPException le)
2828            {
2829              Debug.debugException(le);
2830              throw new LDAPSearchException(new SearchResult(
2831                   searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND,
2832                   ERR_LDAPSEARCH_CANNOT_DECODE_PAGED_RESULTS_RESPONSE_CONTROL.
2833                        get(StaticUtils.getExceptionMessage(le)),
2834                   searchResult.getMatchedDN(), searchResult.getReferralURLs(),
2835                   searchResult.getSearchEntries(),
2836                   searchResult.getSearchReferences(),
2837                   searchResult.getEntryCount(),
2838                   searchResult.getReferenceCount(),
2839                   searchResult.getResponseControls()));
2840            }
2841          }
2842          else
2843          {
2844            break;
2845          }
2846        }
2847      }
2848      catch (final LDAPSearchException lse)
2849      {
2850        Debug.debugException(lse);
2851        searchResult = lse.toLDAPResult();
2852
2853        if (searchResult.getEntryCount() > 0)
2854        {
2855          totalEntries += searchResult.getEntryCount();
2856        }
2857
2858        if (searchResult.getReferenceCount() > 0)
2859        {
2860          totalReferences += searchResult.getReferenceCount();
2861        }
2862      }
2863
2864      if ((searchResult.getResultCode() != ResultCode.SUCCESS) ||
2865          (searchResult.getDiagnosticMessage() != null) ||
2866          (! terse.isPresent()))
2867      {
2868        displayResult(searchResult);
2869      }
2870
2871      if (multiplePages && (! terse.isPresent()))
2872      {
2873        commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_ENTRIES.get(totalEntries));
2874
2875        if (totalReferences > 0)
2876        {
2877          commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_REFERENCES.get(
2878               totalReferences));
2879        }
2880      }
2881
2882      if (countEntries.isPresent())
2883      {
2884        return ResultCode.valueOf((int) Math.min(totalEntries, 255));
2885      }
2886      else
2887      {
2888        return searchResult.getResultCode();
2889      }
2890    }
2891    finally
2892    {
2893      if (separateOutputFilePerSearch.isPresent())
2894      {
2895        try
2896        {
2897          outStream.close();
2898        }
2899        catch (final Exception e)
2900        {
2901          Debug.debugException(e);
2902        }
2903
2904        outStream = null;
2905        errStream = null;
2906      }
2907    }
2908  }
2909
2910
2911
2912  /**
2913   * Retrieves a list of the controls that should be used when processing search
2914   * operations.
2915   *
2916   * @return  A list of the controls that should be used when processing search
2917   *          operations.
2918   */
2919  private List<Control> getSearchControls()
2920  {
2921    final ArrayList<Control> controls = new ArrayList<>(10);
2922
2923    if (searchControl.isPresent())
2924    {
2925      controls.addAll(searchControl.getValues());
2926    }
2927
2928    if (joinRequestControl != null)
2929    {
2930      controls.add(joinRequestControl);
2931    }
2932
2933    if (matchedValuesRequestControl != null)
2934    {
2935      controls.add(matchedValuesRequestControl);
2936    }
2937
2938    if (matchingEntryCountRequestControl != null)
2939    {
2940      controls.add(matchingEntryCountRequestControl);
2941    }
2942
2943    if (overrideSearchLimitsRequestControl != null)
2944    {
2945      controls.add(overrideSearchLimitsRequestControl);
2946    }
2947
2948    if (persistentSearchRequestControl != null)
2949    {
2950      controls.add(persistentSearchRequestControl);
2951    }
2952
2953    if (sortRequestControl != null)
2954    {
2955      controls.add(sortRequestControl);
2956    }
2957
2958    if (vlvRequestControl != null)
2959    {
2960      controls.add(vlvRequestControl);
2961    }
2962
2963    if (accountUsable.isPresent())
2964    {
2965      controls.add(new AccountUsableRequestControl(true));
2966    }
2967
2968    if (includeReplicationConflictEntries.isPresent())
2969    {
2970      controls.add(new ReturnConflictEntriesRequestControl(true));
2971    }
2972
2973    if (includeSoftDeletedEntries.isPresent())
2974    {
2975      final String valueStr =
2976           StaticUtils.toLowerCase(includeSoftDeletedEntries.getValue());
2977      if (valueStr.equals("with-non-deleted-entries"))
2978      {
2979        controls.add(new SoftDeletedEntryAccessRequestControl(true, true,
2980             false));
2981      }
2982      else if (valueStr.equals("without-non-deleted-entries"))
2983      {
2984        controls.add(new SoftDeletedEntryAccessRequestControl(true, false,
2985             false));
2986      }
2987      else
2988      {
2989        controls.add(new SoftDeletedEntryAccessRequestControl(true, false,
2990             true));
2991      }
2992    }
2993
2994    if (includeSubentries.isPresent())
2995    {
2996      controls.add(new SubentriesRequestControl(true));
2997    }
2998
2999    if (manageDsaIT.isPresent())
3000    {
3001      controls.add(new ManageDsaITRequestControl(true));
3002    }
3003
3004    if (realAttributesOnly.isPresent())
3005    {
3006      controls.add(new RealAttributesOnlyRequestControl(true));
3007    }
3008
3009    if (virtualAttributesOnly.isPresent())
3010    {
3011      controls.add(new VirtualAttributesOnlyRequestControl(true));
3012    }
3013
3014    if (excludeBranch.isPresent())
3015    {
3016      final ArrayList<String> dns =
3017           new ArrayList<>(excludeBranch.getValues().size());
3018      for (final DN dn : excludeBranch.getValues())
3019      {
3020        dns.add(dn.toString());
3021      }
3022      controls.add(new ExcludeBranchRequestControl(true, dns));
3023    }
3024
3025    if (assertionFilter.isPresent())
3026    {
3027      controls.add(new AssertionRequestControl(
3028           assertionFilter.getValue(), true));
3029    }
3030
3031    if (getEffectiveRightsAuthzID.isPresent())
3032    {
3033      final String[] attributes;
3034      if (getEffectiveRightsAttribute.isPresent())
3035      {
3036        attributes = new String[getEffectiveRightsAttribute.getValues().size()];
3037        for (int i=0; i < attributes.length; i++)
3038        {
3039          attributes[i] = getEffectiveRightsAttribute.getValues().get(i);
3040        }
3041      }
3042      else
3043      {
3044        attributes = StaticUtils.NO_STRINGS;
3045      }
3046
3047      controls.add(new GetEffectiveRightsRequestControl(true,
3048           getEffectiveRightsAuthzID.getValue(), attributes));
3049    }
3050
3051    if (operationPurpose.isPresent())
3052    {
3053      controls.add(new OperationPurposeRequestControl(true, "ldapsearch",
3054           Version.NUMERIC_VERSION_STRING, "LDAPSearch.getSearchControls",
3055           operationPurpose.getValue()));
3056    }
3057
3058    if (proxyAs.isPresent())
3059    {
3060      controls.add(new ProxiedAuthorizationV2RequestControl(
3061           proxyAs.getValue()));
3062    }
3063
3064    if (proxyV1As.isPresent())
3065    {
3066      controls.add(new ProxiedAuthorizationV1RequestControl(
3067           proxyV1As.getValue()));
3068    }
3069
3070    if (suppressOperationalAttributeUpdates.isPresent())
3071    {
3072      final EnumSet<SuppressType> suppressTypes =
3073           EnumSet.noneOf(SuppressType.class);
3074      for (final String s : suppressOperationalAttributeUpdates.getValues())
3075      {
3076        if (s.equalsIgnoreCase("last-access-time"))
3077        {
3078          suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
3079        }
3080        else if (s.equalsIgnoreCase("last-login-time"))
3081        {
3082          suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
3083        }
3084        else if (s.equalsIgnoreCase("last-login-ip"))
3085        {
3086          suppressTypes.add(SuppressType.LAST_LOGIN_IP);
3087        }
3088      }
3089
3090      controls.add(new SuppressOperationalAttributeUpdateRequestControl(
3091           suppressTypes));
3092    }
3093
3094    if (rejectUnindexedSearch.isPresent())
3095    {
3096      controls.add(new RejectUnindexedSearchRequestControl());
3097    }
3098
3099    if (permitUnindexedSearch.isPresent())
3100    {
3101      controls.add(new PermitUnindexedSearchRequestControl());
3102    }
3103
3104    return controls;
3105  }
3106
3107
3108
3109  /**
3110   * Displays information about the provided result, including special
3111   * processing for a number of supported response controls.
3112   *
3113   * @param  result  The result to examine.
3114   */
3115  private void displayResult(final LDAPResult result)
3116  {
3117    outputHandler.formatResult(result);
3118  }
3119
3120
3121
3122  /**
3123   * Writes the provided message to the output stream.
3124   *
3125   * @param  message  The message to be written.
3126   */
3127  void writeOut(final String message)
3128  {
3129    if (outStream == null)
3130    {
3131      out(message);
3132    }
3133    else
3134    {
3135      outStream.println(message);
3136    }
3137  }
3138
3139
3140
3141  /**
3142   * Writes the provided message to the error stream.
3143   *
3144   * @param  message  The message to be written.
3145   */
3146  private void writeErr(final String message)
3147  {
3148    if (errStream == null)
3149    {
3150      err(message);
3151    }
3152    else
3153    {
3154      errStream.println(message);
3155    }
3156  }
3157
3158
3159
3160  /**
3161   * Writes a line-wrapped, commented version of the provided message to
3162   * standard output.
3163   *
3164   * @param  message  The message to be written.
3165   */
3166  private void commentToOut(final String message)
3167  {
3168    if (terse.isPresent())
3169    {
3170      return;
3171    }
3172
3173    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
3174    {
3175      writeOut("# " + line);
3176    }
3177  }
3178
3179
3180
3181  /**
3182   * Writes a line-wrapped, commented version of the provided message to
3183   * standard error.
3184   *
3185   * @param  message  The message to be written.
3186   */
3187  private void commentToErr(final String message)
3188  {
3189    for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2)))
3190    {
3191      writeErr("# " + line);
3192    }
3193  }
3194
3195
3196
3197  /**
3198   * Sets the output handler that should be used by this tool  This is primarily
3199   * intended for testing purposes.
3200   *
3201   * @param  outputHandler  The output handler that should be used by this tool.
3202   */
3203  void setOutputHandler(final LDAPSearchOutputHandler outputHandler)
3204  {
3205    this.outputHandler = outputHandler;
3206  }
3207
3208
3209
3210  /**
3211   * {@inheritDoc}
3212   */
3213  @Override()
3214  public void handleUnsolicitedNotification(final LDAPConnection connection,
3215                                            final ExtendedResult notification)
3216  {
3217    outputHandler.formatUnsolicitedNotification(connection, notification);
3218  }
3219
3220
3221
3222  /**
3223   * {@inheritDoc}
3224   */
3225  @Override()
3226  public LinkedHashMap<String[],String> getExampleUsages()
3227  {
3228    final LinkedHashMap<String[],String> examples =
3229         new LinkedHashMap<>(StaticUtils.computeMapCapacity(5));
3230
3231    String[] args =
3232    {
3233      "--hostname", "directory.example.com",
3234      "--port", "389",
3235      "--bindDN", "uid=jdoe,ou=People,dc=example,dc=com",
3236      "--bindPassword", "password",
3237      "--baseDN", "ou=People,dc=example,dc=com",
3238      "--searchScope", "sub",
3239      "(uid=jqpublic)",
3240      "givenName",
3241      "sn",
3242      "mail"
3243    };
3244    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_1.get());
3245
3246
3247    args = new String[]
3248    {
3249      "--hostname", "directory.example.com",
3250      "--port", "636",
3251      "--useSSL",
3252      "--saslOption", "mech=PLAIN",
3253      "--saslOption", "authID=u:jdoe",
3254      "--bindPasswordFile", "/path/to/password/file",
3255      "--baseDN", "ou=People,dc=example,dc=com",
3256      "--searchScope", "sub",
3257      "--filterFile", "/path/to/filter/file",
3258      "--outputFile", "/path/to/base/output/file",
3259      "--separateOutputFilePerSearch",
3260      "--requestedAttribute", "*",
3261      "--requestedAttribute", "+"
3262    };
3263    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_2.get());
3264
3265
3266    args = new String[]
3267    {
3268      "--hostname", "directory.example.com",
3269      "--port", "389",
3270      "--useStartTLS",
3271      "--trustStorePath", "/path/to/truststore/file",
3272      "--baseDN", "",
3273      "--searchScope", "base",
3274      "--outputFile", "/path/to/output/file",
3275      "--teeResultsToStandardOut",
3276      "(objectClass=*)",
3277      "*",
3278      "+"
3279    };
3280    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_3.get());
3281
3282
3283    args = new String[]
3284    {
3285      "--hostname", "directory.example.com",
3286      "--port", "389",
3287      "--bindDN", "uid=admin,dc=example,dc=com",
3288      "--baseDN", "dc=example,dc=com",
3289      "--searchScope", "sub",
3290      "--outputFile", "/path/to/output/file",
3291      "--simplePageSize", "100",
3292      "(objectClass=*)",
3293      "*",
3294      "+"
3295    };
3296    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_4.get());
3297
3298
3299    args = new String[]
3300    {
3301      "--hostname", "directory.example.com",
3302      "--port", "389",
3303      "--bindDN", "uid=admin,dc=example,dc=com",
3304      "--baseDN", "dc=example,dc=com",
3305      "--searchScope", "sub",
3306      "(&(givenName=John)(sn=Doe))",
3307      "debugsearchindex"
3308    };
3309    examples.put(args, INFO_LDAPSEARCH_EXAMPLE_5.get());
3310
3311    return examples;
3312  }
3313}