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