001/*
002 * Copyright 2016-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2016-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.tools;
037
038
039
040import java.io.BufferedReader;
041import java.io.File;
042import java.io.FileOutputStream;
043import java.io.FileReader;
044import java.io.OutputStream;
045import java.util.ArrayList;
046import java.util.Arrays;
047import java.util.LinkedHashMap;
048import java.util.concurrent.atomic.AtomicBoolean;
049
050import com.unboundid.ldap.sdk.DN;
051import com.unboundid.ldap.sdk.ExtendedResult;
052import com.unboundid.ldap.sdk.Filter;
053import com.unboundid.ldap.sdk.LDAPConnection;
054import com.unboundid.ldap.sdk.LDAPConnectionOptions;
055import com.unboundid.ldap.sdk.LDAPConnectionPool;
056import com.unboundid.ldap.sdk.LDAPException;
057import com.unboundid.ldap.sdk.ResultCode;
058import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler;
059import com.unboundid.ldap.sdk.Version;
060import com.unboundid.ldif.LDIFWriter;
061import com.unboundid.util.Debug;
062import com.unboundid.util.DNFileReader;
063import com.unboundid.util.LDAPCommandLineTool;
064import com.unboundid.util.FilterFileReader;
065import com.unboundid.util.FixedRateBarrier;
066import com.unboundid.util.NotNull;
067import com.unboundid.util.Nullable;
068import com.unboundid.util.RateAdjustor;
069import com.unboundid.util.StaticUtils;
070import com.unboundid.util.ThreadSafety;
071import com.unboundid.util.ThreadSafetyLevel;
072import com.unboundid.util.args.ArgumentException;
073import com.unboundid.util.args.ArgumentParser;
074import com.unboundid.util.args.BooleanArgument;
075import com.unboundid.util.args.BooleanValueArgument;
076import com.unboundid.util.args.DNArgument;
077import com.unboundid.util.args.FileArgument;
078import com.unboundid.util.args.FilterArgument;
079import com.unboundid.util.args.IPAddressArgumentValueValidator;
080import com.unboundid.util.args.IntegerArgument;
081import com.unboundid.util.args.StringArgument;
082import com.unboundid.util.args.TimestampArgument;
083import com.unboundid.util.args.SubCommand;
084
085import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
086
087
088
089/**
090 * This class provides a tool that can be used to perform a variety of account
091 * management functions against user entries in the Ping Identity, UnboundID,
092 * or Nokia/Alcatel-Lucent 8661 Directory Server.  It primarily uses the
093 * password policy state extended operation for its processing.
094 * <BR>
095 * <BLOCKQUOTE>
096 *   <B>NOTE:</B>  This class, and other classes within the
097 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
098 *   supported for use against Ping Identity, UnboundID, and
099 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
100 *   for proprietary functionality or for external specifications that are not
101 *   considered stable or mature enough to be guaranteed to work in an
102 *   interoperable way with other types of LDAP servers.
103 * </BLOCKQUOTE>
104 */
105@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
106public final class ManageAccount
107       extends LDAPCommandLineTool
108       implements UnsolicitedNotificationHandler
109{
110  /**
111   * The column at which to wrap long lines.
112   */
113  private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
114
115
116
117  /**
118   * The primary name of the argument used to indicate that the tool should
119   * append to the reject file rather than overwrite it.
120   */
121  @NotNull private static final String ARG_APPEND_TO_REJECT_FILE =
122       "appendToRejectFile";
123
124
125
126  /**
127   * The primary name of the argument used to specify a base DN to use for
128   * searches.
129   */
130  @NotNull static final String ARG_BASE_DN = "baseDN";
131
132
133
134  /**
135   * The primary name of the argument used to specify the path to a file to a
136   * sample variable rate data file to create.
137   */
138  @NotNull private static final String ARG_GENERATE_SAMPLE_RATE_FILE =
139       "generateSampleRateFile";
140
141
142
143  /**
144   * The primary name of the argument used to specify the path to a file
145   * containing the DNs of the users on which to operate.
146   */
147  @NotNull private static final String ARG_DN_INPUT_FILE = "dnInputFile";
148
149
150
151  /**
152   * The primary name of the argument used to specify the path to a file
153   * containing search filters to use to identify users.
154   */
155  @NotNull private static final String ARG_FILTER_INPUT_FILE =
156       "filterInputFile";
157
158
159
160  /**
161   * The primary name of the argument used to specify the number of threads to
162   * use to process search operations to identify which users to target.
163   */
164  @NotNull static final String ARG_NUM_SEARCH_THREADS = "numSearchThreads";
165
166
167
168  /**
169   * The primary name of the argument used to specify the number of threads to
170   * use to perform manage-account processing.
171   */
172  @NotNull static final String ARG_NUM_THREADS = "numThreads";
173
174
175
176  /**
177   * The primary name of the argument used to specify the target rate of
178   * operations per second.
179   */
180  @NotNull private static final String ARG_RATE_PER_SECOND = "ratePerSecond";
181
182
183
184  /**
185   * The primary name of the argument used to specify the path to a reject file
186   * to create.
187   */
188  @NotNull private static final String ARG_REJECT_FILE = "rejectFile";
189
190
191
192  /**
193   * The primary name of the argument used to specify the simple page size to
194   * use when performing searches.
195   */
196  @NotNull static final String ARG_SIMPLE_PAGE_SIZE = "simplePageSize";
197
198
199
200  /**
201   * The primary name of the argument used to suppress result operation types
202   * without values.
203   */
204  @NotNull static final String ARG_SUPPRESS_EMPTY_RESULT_OPERATIONS =
205       "suppressEmptyResultOperations";
206
207
208
209  /**
210   * The primary name of the argument used to specify the DN of the user on
211   * which to operate.
212   */
213  @NotNull private static final String ARG_TARGET_DN = "targetDN";
214
215
216
217  /**
218   * The primary name of the argument used to specify a search filter to use to
219   * identify users.
220   */
221  @NotNull private static final String ARG_TARGET_FILTER = "targetFilter";
222
223
224
225  /**
226   * The primary name of the argument used to specify the user IDs of target
227   * users.
228   */
229  @NotNull private static final String ARG_TARGET_USER_ID = "targetUserID";
230
231
232
233  /**
234   * The primary name of the argument used to specify the name of the attribute
235   * to identify which user has a given user ID.
236   */
237  @NotNull static final String ARG_USER_ID_ATTRIBUTE = "userIDAttribute";
238
239
240
241  /**
242   * The primary name of the argument used to specify the path to a file
243   * containing the user IDs of the target users.
244   */
245  @NotNull private static final String ARG_USER_ID_INPUT_FILE =
246       "userIDInputFile";
247
248
249
250  /**
251   * The primary name of the argument used to specify the path to a variable
252   * rate data file.
253   */
254  @NotNull private static final String ARG_VARIABLE_RATE_DATA =
255       "variableRateData";
256
257
258
259  /**
260   * The default search base DN.
261   */
262  @NotNull private static final DN DEFAULT_BASE_DN = DN.NULL_DN;
263
264
265
266  /**
267   * The default user ID attribute.
268   */
269  @NotNull private static final String DEFAULT_USER_ID_ATTRIBUTE = "uid";
270
271
272
273  /**
274   * A target user DN to use in examples.
275   */
276  @NotNull private static final String EXAMPLE_TARGET_USER_DN =
277       "uid=jdoe,ou=People,dc=example,dc=com";
278
279
280
281  // The argument parser for this tool.
282  @Nullable private volatile ArgumentParser parser;
283
284  // Indicates whether all DNs have been provided to the manage-account
285  // processor.
286  @NotNull private final AtomicBoolean allDNsProvided;
287
288  // Indicates whether all filters have been provided to the manage-account
289  // search processor.
290  @NotNull private final AtomicBoolean allFiltersProvided;
291
292  // Indicates whether a request has been made to cancel processing.
293  @NotNull private final AtomicBoolean cancelRequested;
294
295  // The rate limiter to use for this tool.
296  @Nullable private volatile FixedRateBarrier rateLimiter;
297
298  // The LDAP connection options to use for connections created by this tool.
299  @NotNull private final LDAPConnectionOptions connectionOptions;
300
301  // The LDIF writer to use to write information about successful and failed
302  // operations.
303  @Nullable private volatile LDIFWriter outputWriter;
304
305  // The LDIF writer to use to write information about failed operations.
306  @Nullable private volatile LDIFWriter rejectWriter;
307
308  // The search processor for this tool.
309  @Nullable private volatile ManageAccountSearchProcessor searchProcessor;
310
311  // The rate adjustor to use to vary the load over time.
312  @Nullable private volatile RateAdjustor rateAdjustor;
313
314
315
316  /**
317   * Invokes the tool with the provided set of arguments.
318   *
319   * @param  args  The command-line arguments provided to this tool.
320   */
321  public static void main(@NotNull final String... args)
322  {
323    final ResultCode resultCode = main(System.out, System.err, args);
324    if (resultCode != ResultCode.SUCCESS)
325    {
326      System.exit(resultCode.intValue());
327    }
328  }
329
330
331
332  /**
333   * Invokes the tool with the provided set of arguments.
334   *
335   * @param  out   The output stream to use for standard out.  It may be
336   *               {@code null} if standard out should be suppressed.
337   * @param  err   The output stream to use for standard error.  It may be
338   *               {@code null} if standard error should be suppressed.
339   * @param  args  The command-line arguments provided to this tool.
340   *
341   * @return  A result code with the status of the tool processing.  Any result
342   *          code other than {@link ResultCode#SUCCESS} should be considered a
343   *          failure.
344   */
345  @NotNull()
346  public static ResultCode main(@Nullable final OutputStream out,
347                                @Nullable final OutputStream err,
348                                @NotNull final String... args)
349  {
350    final ManageAccount tool = new ManageAccount(out, err);
351
352    final boolean origCommentAboutBase64EncodedValues =
353         LDIFWriter.commentAboutBase64EncodedValues();
354    LDIFWriter.setCommentAboutBase64EncodedValues(true);
355    try
356    {
357      return tool.runTool(args);
358    }
359    finally
360    {
361      LDIFWriter.setCommentAboutBase64EncodedValues(
362           origCommentAboutBase64EncodedValues);
363    }
364  }
365
366
367
368  /**
369   * Creates a new instance of this tool with the provided arguments.
370   *
371   * @param  out  The output stream to use for standard out.  It may be
372   *              {@code null} if standard out should be suppressed.
373   * @param  err  The output stream to use for standard error.  It may be
374   *              {@code null} if standard error should be suppressed.
375   */
376  public ManageAccount(@Nullable final OutputStream out,
377                       @Nullable final OutputStream err)
378  {
379    super(out, err);
380
381    connectionOptions = new LDAPConnectionOptions();
382    connectionOptions.setUnsolicitedNotificationHandler(this);
383
384    allDNsProvided = new AtomicBoolean(false);
385    allFiltersProvided = new AtomicBoolean(false);
386    cancelRequested = new AtomicBoolean(false);
387
388    parser = null;
389    rateLimiter = null;
390    rateAdjustor = null;
391    outputWriter = null;
392    rejectWriter = null;
393    searchProcessor = null;
394  }
395
396
397
398  /**
399   * {@inheritDoc}
400   */
401  @Override()
402  @NotNull()
403  public String getToolName()
404  {
405    return "manage-account";
406  }
407
408
409
410  /**
411   * {@inheritDoc}
412   */
413  @Override()
414  @NotNull()
415  public String getToolDescription()
416  {
417    return INFO_MANAGE_ACCT_TOOL_DESC.get();
418  }
419
420
421
422  /**
423   * {@inheritDoc}
424   */
425  @Override()
426  @NotNull()
427  public String getToolVersion()
428  {
429    return Version.NUMERIC_VERSION_STRING;
430  }
431
432
433
434  /**
435   * {@inheritDoc}
436   */
437  @Override()
438  public boolean supportsInteractiveMode()
439  {
440    return true;
441  }
442
443
444
445  /**
446   * {@inheritDoc}
447   */
448  @Override()
449  public boolean defaultsToInteractiveMode()
450  {
451    return true;
452  }
453
454
455
456  /**
457   * {@inheritDoc}
458   */
459  @Override()
460  public boolean supportsPropertiesFile()
461  {
462    return true;
463  }
464
465
466
467  /**
468   * {@inheritDoc}
469   */
470  @Override()
471  protected boolean supportsOutputFile()
472  {
473    return true;
474  }
475
476
477
478  /**
479   * {@inheritDoc}
480   */
481  @Override()
482  protected boolean supportsDebugLogging()
483  {
484    return true;
485  }
486
487
488
489  /**
490   * {@inheritDoc}
491   */
492  @Override()
493  protected boolean supportsAuthentication()
494  {
495    return true;
496  }
497
498
499
500  /**
501   * {@inheritDoc}
502   */
503  @Override()
504  protected boolean defaultToPromptForBindPassword()
505  {
506    return true;
507  }
508
509
510
511  /**
512   * {@inheritDoc}
513   */
514  @Override()
515  protected boolean supportsSASLHelp()
516  {
517    return true;
518  }
519
520
521
522  /**
523   * {@inheritDoc}
524   */
525  @Override()
526  protected boolean includeAlternateLongIdentifiers()
527  {
528    return true;
529  }
530
531
532
533  /**
534   * {@inheritDoc}
535   */
536  @Override()
537  protected boolean supportsSSLDebugging()
538  {
539    return true;
540  }
541
542
543
544  /**
545   * {@inheritDoc}
546   */
547  @Override()
548  protected boolean supportsMultipleServers()
549  {
550    return true;
551  }
552
553
554
555  /**
556   * {@inheritDoc}
557   */
558  @Override()
559  protected boolean logToolInvocationByDefault()
560  {
561    return true;
562  }
563
564
565
566  /**
567   * {@inheritDoc}
568   */
569  @Override()
570  public void addNonLDAPArguments(@NotNull final ArgumentParser parser)
571       throws ArgumentException
572  {
573    // Get a copy of the argument parser for later use.
574    this.parser = parser;
575
576
577    // Get the current time formatted as a generalized time.
578    final String currentGeneralizedTime =
579         StaticUtils.encodeGeneralizedTime(System.currentTimeMillis());
580    final String olderGeneralizedTime =
581         StaticUtils.encodeGeneralizedTime(
582              System.currentTimeMillis() - 12_345L);
583
584
585    // Define the global arguments used to indicate which users to target.
586    final DNArgument targetDN = new DNArgument('b', ARG_TARGET_DN, false, 0,
587         null, INFO_MANAGE_ACCT_ARG_DESC_TARGET_DN.get());
588    targetDN.addLongIdentifier("userDN", true);
589    targetDN.addLongIdentifier("target-dn", true);
590    targetDN.addLongIdentifier("user-dn", true);
591    targetDN.setArgumentGroupName(
592         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
593    parser.addArgument(targetDN);
594
595    final FileArgument dnInputFile = new FileArgument(null, ARG_DN_INPUT_FILE,
596         false, 0, null, INFO_MANAGE_ACCT_ARG_DESC_DN_FILE.get(), true,
597         true, true, false);
598    dnInputFile.addLongIdentifier("targetDNFile", true);
599    dnInputFile.addLongIdentifier("userDNFile", true);
600    dnInputFile.addLongIdentifier("dn-input-file", true);
601    dnInputFile.addLongIdentifier("target-dn-file", true);
602    dnInputFile.addLongIdentifier("user-dn-file", true);
603    dnInputFile.setArgumentGroupName(
604         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
605    parser.addArgument(dnInputFile);
606
607    final FilterArgument targetFilter = new FilterArgument(null,
608         ARG_TARGET_FILTER, false, 0, null,
609         INFO_MANAGE_ACCT_ARG_DESC_TARGET_FILTER.get(ARG_BASE_DN));
610    targetFilter.addLongIdentifier("target-filter", true);
611    targetFilter.setArgumentGroupName(
612         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
613    parser.addArgument(targetFilter);
614
615    final FileArgument filterInputFile = new FileArgument(null,
616         ARG_FILTER_INPUT_FILE, false, 0, null,
617         INFO_MANAGE_ACCT_ARG_DESC_FILTER_INPUT_FILE.get(ARG_BASE_DN),
618         true, true, true, false);
619    filterInputFile.addLongIdentifier("targetFilterFile", true);
620    filterInputFile.addLongIdentifier("filter-input-file", true);
621    filterInputFile.addLongIdentifier("target-filter-file", true);
622    filterInputFile.setArgumentGroupName(
623         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
624    parser.addArgument(filterInputFile);
625
626    final StringArgument targetUserID = new StringArgument(null,
627         ARG_TARGET_USER_ID, false, 0, null,
628         INFO_MANAGE_ACCT_ARG_DESC_TARGET_USER_ID.get(ARG_BASE_DN,
629              ARG_USER_ID_ATTRIBUTE));
630    targetUserID.addLongIdentifier("userID", true);
631    targetUserID.addLongIdentifier("target-user-id", true);
632    targetUserID.addLongIdentifier("user-id", true);
633    targetUserID.setArgumentGroupName(
634         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
635    parser.addArgument(targetUserID);
636
637    final FileArgument userIDInputFile = new FileArgument(null,
638         ARG_USER_ID_INPUT_FILE, false, 0, null,
639         INFO_MANAGE_ACCT_ARG_DESC_USER_ID_INPUT_FILE.get(ARG_BASE_DN,
640              ARG_USER_ID_ATTRIBUTE),
641         true, true, true, false);
642    userIDInputFile.addLongIdentifier("targetUserIDFile", true);
643    userIDInputFile.addLongIdentifier("user-id-input-file", true);
644    userIDInputFile.addLongIdentifier("target-user-id-file", true);
645    userIDInputFile.setArgumentGroupName(
646         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
647    parser.addArgument(userIDInputFile);
648
649    final StringArgument userIDAttribute = new StringArgument(null,
650         ARG_USER_ID_ATTRIBUTE, false, 1, null,
651         INFO_MANAGE_ACCT_ARG_DESC_USER_ID_ATTR.get(
652              ARG_TARGET_USER_ID, ARG_USER_ID_INPUT_FILE,
653              DEFAULT_USER_ID_ATTRIBUTE),
654         DEFAULT_USER_ID_ATTRIBUTE);
655    userIDAttribute.addLongIdentifier("user-id-attribute", true);
656    userIDAttribute.setArgumentGroupName(
657         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
658    parser.addArgument(userIDAttribute);
659
660    final DNArgument baseDN = new DNArgument(null, ARG_BASE_DN, false, 1, null,
661         INFO_MANAGE_ACCT_ARG_DESC_BASE_DN.get(ARG_TARGET_FILTER,
662              ARG_FILTER_INPUT_FILE, ARG_TARGET_USER_ID,
663              ARG_USER_ID_INPUT_FILE),
664         DEFAULT_BASE_DN);
665    baseDN.addLongIdentifier("base-dn", true);
666    baseDN.setArgumentGroupName(
667         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get());
668    parser.addArgument(baseDN);
669
670    final IntegerArgument simplePageSize = new IntegerArgument('z',
671         ARG_SIMPLE_PAGE_SIZE, false, 1, null,
672         INFO_MANAGE_ACCT_ARG_DESC_SIMPLE_PAGE_SIZE.get(getToolName()), 1,
673         Integer.MAX_VALUE);
674    simplePageSize.addLongIdentifier("simple-page-size", true);
675    simplePageSize.setArgumentGroupName(
676         INFO_MANAGE_ACCT_ARG_GROUP_TARGET_USER_ARGS.get(getToolName()));
677    parser.addArgument(simplePageSize);
678
679
680    // Ensure that the user will be required ot provide at least one of the
681    // arguments to specify which users to target.
682    parser.addRequiredArgumentSet(targetDN, dnInputFile, targetFilter,
683         filterInputFile, targetUserID, userIDInputFile);
684
685
686    // Define the global arguments used to control the amount of load the tool
687    // should be permitted to generate.
688    final IntegerArgument numThreads = new IntegerArgument('t', ARG_NUM_THREADS,
689         false, 1, null,
690         INFO_MANAGE_ACCT_ARG_DESC_NUM_THREADS.get(getToolName()), 1,
691         Integer.MAX_VALUE, 1);
692    numThreads.addLongIdentifier("num-threads", true);
693    numThreads.setArgumentGroupName(
694         INFO_MANAGE_ACCT_ARG_GROUP_PERFORMANCE.get());
695    parser.addArgument(numThreads);
696
697    final IntegerArgument numSearchThreads = new IntegerArgument(null,
698         ARG_NUM_SEARCH_THREADS, false, 1, null,
699         INFO_MANAGE_ACCT_ARG_DESC_NUM_SEARCH_THREADS.get(getToolName()), 1,
700         Integer.MAX_VALUE, 1);
701    numSearchThreads.addLongIdentifier("num-search-threads", true);
702    numSearchThreads.setArgumentGroupName(
703         INFO_MANAGE_ACCT_ARG_GROUP_PERFORMANCE.get());
704    parser.addArgument(numSearchThreads);
705
706    final IntegerArgument ratePerSecond = new IntegerArgument('r',
707         ARG_RATE_PER_SECOND, false, 1, null,
708         INFO_MANAGE_ACCT_ARG_DESC_RATE_PER_SECOND.get(
709              ARG_VARIABLE_RATE_DATA),
710         1, Integer.MAX_VALUE);
711    ratePerSecond.addLongIdentifier("rate-per-second", true);
712    ratePerSecond.setArgumentGroupName(
713         INFO_MANAGE_ACCT_ARG_GROUP_PERFORMANCE.get());
714    parser.addArgument(ratePerSecond);
715
716    final FileArgument variableRateData = new FileArgument(null,
717         ARG_VARIABLE_RATE_DATA, false, 1, null,
718         INFO_MANAGE_ACCT_ARG_DESC_VARIABLE_RATE_DATA.get(
719              ARG_RATE_PER_SECOND),
720         true, true, true, false);
721    variableRateData.addLongIdentifier("variable-rate-data", true);
722    variableRateData.setArgumentGroupName(
723         INFO_MANAGE_ACCT_ARG_GROUP_PERFORMANCE.get());
724    parser.addArgument(variableRateData);
725
726    final FileArgument generateSampleRateFile = new FileArgument(null,
727         ARG_GENERATE_SAMPLE_RATE_FILE, false, 1, null,
728         INFO_MANAGE_ACCT_ARG_DESC_GENERATE_SAMPLE_RATE_FILE.get(
729              ARG_VARIABLE_RATE_DATA),
730         false, true, true, false);
731    generateSampleRateFile.addLongIdentifier("generate-sample-rate-file", true);
732    generateSampleRateFile.setArgumentGroupName(
733         INFO_MANAGE_ACCT_ARG_GROUP_PERFORMANCE.get());
734    generateSampleRateFile.setUsageArgument(true);
735    parser.addArgument(generateSampleRateFile);
736
737
738    // Define the global arguments tht pertain to the reject file.
739    final FileArgument rejectFile = new FileArgument('R', ARG_REJECT_FILE,
740         false, 1, null, INFO_MANAGE_ACCT_ARG_DESC_REJECT_FILE.get(),
741         false, true, true, false);
742    rejectFile.addLongIdentifier("reject-file", true);
743    parser.addArgument(rejectFile);
744
745    final BooleanArgument appendToRejectFile = new BooleanArgument(null,
746         ARG_APPEND_TO_REJECT_FILE, 1,
747         INFO_MANAGE_ACCT_ARG_DESC_APPEND_TO_REJECT_FILE.get(
748              rejectFile.getIdentifierString()));
749    appendToRejectFile.addLongIdentifier("append-to-reject-file", true);
750    parser.addArgument(appendToRejectFile);
751
752    parser.addDependentArgumentSet(appendToRejectFile, rejectFile);
753
754
755    // Define the argument used to suppress result operations without values.
756    final BooleanArgument suppressEmptyResultOperations =
757         new BooleanArgument(null, ARG_SUPPRESS_EMPTY_RESULT_OPERATIONS,
758              1,
759              INFO_MANAGE_ACCT_ARG_DESC_SUPPRESS_EMPTY_RESULT_OPERATIONS.get(
760                   getToolName()));
761    parser.addArgument(suppressEmptyResultOperations);
762
763
764    // Define the subcommand used to retrieve all state information for a user.
765    createSubCommand(ManageAccountSubCommandType.GET_ALL,
766         INFO_MANAGE_ACCT_SC_GET_ALL_EXAMPLE.get(EXAMPLE_TARGET_USER_DN));
767
768
769    // Define the subcommand used to retrieve the password policy DN for a user.
770    createSubCommand(ManageAccountSubCommandType.GET_PASSWORD_POLICY_DN,
771         INFO_MANAGE_ACCT_SC_GET_POLICY_DN_EXAMPLE.get(EXAMPLE_TARGET_USER_DN));
772
773
774    // Define the subcommand to determine whether the account is usable.
775    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_IS_USABLE,
776         INFO_MANAGE_ACCT_SC_GET_IS_USABLE_EXAMPLE.get(EXAMPLE_TARGET_USER_DN));
777
778
779    // Define the subcommand to retrieve the set of password policy state
780    // account usability notice messages.
781    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_USABILITY_NOTICES,
782         INFO_MANAGE_ACCT_SC_GET_USABILITY_NOTICES_EXAMPLE.get(
783              EXAMPLE_TARGET_USER_DN));
784
785
786    // Define the subcommand to retrieve the set of password policy state
787    // account usability warning messages.
788    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_USABILITY_WARNINGS,
789         INFO_MANAGE_ACCT_SC_GET_USABILITY_WARNINGS_EXAMPLE.get(
790              EXAMPLE_TARGET_USER_DN));
791
792
793    // Define the subcommand to retrieve the set of password policy state
794    // account usability error messages.
795    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_USABILITY_ERRORS,
796         INFO_MANAGE_ACCT_SC_GET_USABILITY_ERRORS_EXAMPLE.get(
797              EXAMPLE_TARGET_USER_DN));
798
799
800    // Define the subcommand to retrieve the password changed time for a user.
801    createSubCommand(ManageAccountSubCommandType.GET_PASSWORD_CHANGED_TIME,
802         INFO_MANAGE_ACCT_SC_GET_PW_CHANGED_TIME_EXAMPLE.get(
803              EXAMPLE_TARGET_USER_DN));
804
805
806    // Define the subcommand to set the password changed time for a user.
807    final ArgumentParser setPWChangedTimeParser =
808         createSubCommandParser(
809              ManageAccountSubCommandType.SET_PASSWORD_CHANGED_TIME);
810
811    final TimestampArgument setPWChangedTimeValueArg = new TimestampArgument(
812         'O', "passwordChangedTime", false, 1, null,
813         INFO_MANAGE_ACCT_SC_SET_PW_CHANGED_TIME_ARG_VALUE.get());
814    setPWChangedTimeValueArg.addLongIdentifier("operationValue", true);
815    setPWChangedTimeValueArg.addLongIdentifier("password-changed-time", true);
816    setPWChangedTimeValueArg.addLongIdentifier("operation-value", true);
817    setPWChangedTimeParser.addArgument(setPWChangedTimeValueArg);
818
819    createSubCommand(ManageAccountSubCommandType.SET_PASSWORD_CHANGED_TIME,
820         setPWChangedTimeParser,
821         createSubCommandExample(
822              ManageAccountSubCommandType.SET_PASSWORD_CHANGED_TIME,
823              INFO_MANAGE_ACCT_SC_SET_PW_CHANGED_TIME_EXAMPLE.get(
824                   EXAMPLE_TARGET_USER_DN, currentGeneralizedTime),
825              "--passwordChangedTime", currentGeneralizedTime));
826
827
828    // Define the subcommand to clear the password changed time for a user.
829    createSubCommand(ManageAccountSubCommandType.CLEAR_PASSWORD_CHANGED_TIME,
830         INFO_MANAGE_ACCT_SC_CLEAR_PW_CHANGED_TIME_EXAMPLE.get(
831              EXAMPLE_TARGET_USER_DN));
832
833
834    // Define the subcommand to determine whether a user account is disabled.
835    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_IS_DISABLED,
836         INFO_MANAGE_ACCT_SC_GET_IS_DISABLED_EXAMPLE.get(
837              EXAMPLE_TARGET_USER_DN));
838
839
840    // Define the subcommand to specify whether a user's account is disabled.
841    final ArgumentParser setAcctDisabledParser =
842         createSubCommandParser(
843              ManageAccountSubCommandType.SET_ACCOUNT_IS_DISABLED);
844
845    final BooleanValueArgument setAcctDisabledValueArg =
846         new BooleanValueArgument('O', "accountIsDisabled", true, null,
847              INFO_MANAGE_ACCT_SC_SET_IS_DISABLED_ARG_VALUE.get());
848    setAcctDisabledValueArg.addLongIdentifier("operationValue", true);
849    setAcctDisabledValueArg.addLongIdentifier("account-is-disabled", true);
850    setAcctDisabledValueArg.addLongIdentifier("operation-value", true);
851    setAcctDisabledParser.addArgument(setAcctDisabledValueArg);
852
853    createSubCommand(ManageAccountSubCommandType.SET_ACCOUNT_IS_DISABLED,
854         setAcctDisabledParser,
855         createSubCommandExample(
856              ManageAccountSubCommandType.SET_ACCOUNT_IS_DISABLED,
857              INFO_MANAGE_ACCT_SC_SET_IS_DISABLED_EXAMPLE.get(
858                   EXAMPLE_TARGET_USER_DN),
859              "--accountIsDisabled", "true"));
860
861
862    // Define the subcommand to clear the account disabled state.
863    createSubCommand(ManageAccountSubCommandType.CLEAR_ACCOUNT_IS_DISABLED,
864         INFO_MANAGE_ACCT_SC_CLEAR_IS_DISABLED_EXAMPLE.get(
865              EXAMPLE_TARGET_USER_DN));
866
867
868    // Define the subcommand to retrieve the account activation time for a user.
869    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_ACTIVATION_TIME,
870         INFO_MANAGE_ACCT_SC_GET_ACCT_ACT_TIME_EXAMPLE.get(
871              EXAMPLE_TARGET_USER_DN));
872
873
874    // Define the subcommand to set the account activation time for a user.
875    final ArgumentParser setAcctActivationTimeParser =
876         createSubCommandParser(
877              ManageAccountSubCommandType.SET_ACCOUNT_ACTIVATION_TIME);
878
879    final TimestampArgument setAcctActivationTimeValueArg =
880         new TimestampArgument('O', "accountActivationTime", false, 1, null,
881              INFO_MANAGE_ACCT_SC_SET_ACCT_ACT_TIME_ARG_VALUE.get());
882    setAcctActivationTimeValueArg.addLongIdentifier("operationValue", true);
883    setAcctActivationTimeValueArg.addLongIdentifier("account-activation-time",
884         true);
885    setAcctActivationTimeValueArg.addLongIdentifier("operation-value", true);
886    setAcctActivationTimeParser.addArgument(setAcctActivationTimeValueArg);
887
888    createSubCommand(ManageAccountSubCommandType.SET_ACCOUNT_ACTIVATION_TIME,
889         setAcctActivationTimeParser,
890         createSubCommandExample(
891              ManageAccountSubCommandType.SET_ACCOUNT_ACTIVATION_TIME,
892              INFO_MANAGE_ACCT_SC_SET_ACCT_ACT_TIME_EXAMPLE.get(
893                   EXAMPLE_TARGET_USER_DN, currentGeneralizedTime),
894              "--accountActivationTime", currentGeneralizedTime));
895
896
897    // Define the subcommand to clear the account activation time for a user.
898    createSubCommand(ManageAccountSubCommandType.CLEAR_ACCOUNT_ACTIVATION_TIME,
899         INFO_MANAGE_ACCT_SC_CLEAR_ACCT_ACT_TIME_EXAMPLE.get(
900              EXAMPLE_TARGET_USER_DN));
901
902
903    // Define the subcommand to retrieve the length of time until a user's
904    // account is activated.
905    createSubCommand(
906         ManageAccountSubCommandType.GET_SECONDS_UNTIL_ACCOUNT_ACTIVATION,
907         INFO_MANAGE_ACCT_SC_GET_SECONDS_UNTIL_ACCT_ACT_EXAMPLE.get(
908              EXAMPLE_TARGET_USER_DN));
909
910
911    // Define the subcommand to determine whether a user's account is not yet
912    // active.
913    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_IS_NOT_YET_ACTIVE,
914         INFO_MANAGE_ACCT_SC_GET_ACCT_NOT_YET_ACTIVE_EXAMPLE.get(
915              EXAMPLE_TARGET_USER_DN));
916
917
918    // Define the subcommand to retrieve the account expiration time for a user.
919    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_EXPIRATION_TIME,
920         INFO_MANAGE_ACCT_SC_GET_ACCT_EXP_TIME_EXAMPLE.get(
921              EXAMPLE_TARGET_USER_DN));
922
923
924    // Define the subcommand to set the account expiration time for a user.
925    final ArgumentParser setAcctExpirationTimeParser =
926         createSubCommandParser(
927              ManageAccountSubCommandType.SET_ACCOUNT_EXPIRATION_TIME);
928
929    final TimestampArgument setAcctExpirationTimeValueArg =
930         new TimestampArgument('O', "accountExpirationTime", false, 1, null,
931              INFO_MANAGE_ACCT_SC_SET_ACCT_EXP_TIME_ARG_VALUE.get());
932    setAcctExpirationTimeValueArg.addLongIdentifier("operationValue", true);
933    setAcctExpirationTimeValueArg.addLongIdentifier("account-expiration-time",
934         true);
935    setAcctExpirationTimeValueArg.addLongIdentifier("operation-value", true);
936    setAcctExpirationTimeParser.addArgument(setAcctExpirationTimeValueArg);
937
938    createSubCommand(ManageAccountSubCommandType.SET_ACCOUNT_EXPIRATION_TIME,
939         setAcctExpirationTimeParser,
940         createSubCommandExample(
941              ManageAccountSubCommandType.SET_ACCOUNT_EXPIRATION_TIME,
942              INFO_MANAGE_ACCT_SC_SET_ACCT_EXP_TIME_EXAMPLE.get(
943                   EXAMPLE_TARGET_USER_DN, currentGeneralizedTime),
944              "--accountExpirationTime", currentGeneralizedTime));
945
946
947    // Define the subcommand to clear the account expiration time for a user.
948    createSubCommand(ManageAccountSubCommandType.CLEAR_ACCOUNT_EXPIRATION_TIME,
949         INFO_MANAGE_ACCT_SC_CLEAR_ACCT_EXP_TIME_EXAMPLE.get(
950              EXAMPLE_TARGET_USER_DN));
951
952
953    // Define the subcommand to retrieve the length of time until a user's
954    // account is expired.
955    createSubCommand(
956         ManageAccountSubCommandType.GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION,
957         INFO_MANAGE_ACCT_SC_GET_SECONDS_UNTIL_ACCT_EXP_EXAMPLE.get(
958              EXAMPLE_TARGET_USER_DN));
959
960
961    // Define the subcommand to determine whether a user's account is expired.
962    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_IS_EXPIRED,
963         INFO_MANAGE_ACCT_SC_GET_ACCT_IS_EXPIRED_EXAMPLE.get(
964              EXAMPLE_TARGET_USER_DN));
965
966
967    // Define the subcommand to retrieve a user's password expiration warned
968    // time.
969    createSubCommand(
970         ManageAccountSubCommandType.GET_PASSWORD_EXPIRATION_WARNED_TIME,
971         INFO_MANAGE_ACCT_SC_GET_PW_EXP_WARNED_TIME_EXAMPLE.get(
972              EXAMPLE_TARGET_USER_DN));
973
974
975    // Define the subcommand to set a user's password expiration warned time.
976    final ArgumentParser setPWExpWarnedTimeParser =
977         createSubCommandParser(
978              ManageAccountSubCommandType.SET_PASSWORD_EXPIRATION_WARNED_TIME);
979
980    final TimestampArgument setPWExpWarnedTimeValueArg =
981         new TimestampArgument('O', "passwordExpirationWarnedTime", false, 1,
982              null, INFO_MANAGE_ACCT_SC_SET_PW_EXP_WARNED_TIME_ARG_VALUE.get());
983    setPWExpWarnedTimeValueArg.addLongIdentifier("operationValue", true);
984    setPWExpWarnedTimeValueArg.addLongIdentifier(
985         "password-expiration-warned-time", true);
986    setPWExpWarnedTimeValueArg.addLongIdentifier("operation-value", true);
987    setPWExpWarnedTimeParser.addArgument(setPWExpWarnedTimeValueArg);
988
989    createSubCommand(
990         ManageAccountSubCommandType.SET_PASSWORD_EXPIRATION_WARNED_TIME,
991         setPWExpWarnedTimeParser,
992         createSubCommandExample(
993              ManageAccountSubCommandType.SET_PASSWORD_EXPIRATION_WARNED_TIME,
994              INFO_MANAGE_ACCT_SC_SET_PW_EXP_WARNED_TIME_EXAMPLE.get(
995                   EXAMPLE_TARGET_USER_DN, currentGeneralizedTime),
996              "--passwordExpirationWarnedTime", currentGeneralizedTime));
997
998
999    // Define the subcommand to clear a user's password expiration warned time.
1000    createSubCommand(
1001         ManageAccountSubCommandType.CLEAR_PASSWORD_EXPIRATION_WARNED_TIME,
1002         INFO_MANAGE_ACCT_SC_CLEAR_PW_EXP_WARNED_TIME_EXAMPLE.get(
1003              EXAMPLE_TARGET_USER_DN));
1004
1005
1006    // Define the subcommand to get the number of seconds until a user is
1007    // eligible to receive a password expiration warning.
1008    createSubCommand(
1009         ManageAccountSubCommandType.
1010              GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING,
1011         INFO_MANAGE_ACCT_SC_GET_SECONDS_UNTIL_PW_EXP_WARNING_EXAMPLE.get(
1012              EXAMPLE_TARGET_USER_DN));
1013
1014
1015    // Define the subcommand to retrieve a user's password expiration time.
1016    createSubCommand(ManageAccountSubCommandType.GET_PASSWORD_EXPIRATION_TIME,
1017         INFO_MANAGE_ACCT_SC_GET_PW_EXP_TIME_EXAMPLE.get(
1018              EXAMPLE_TARGET_USER_DN));
1019
1020
1021    // Define the subcommand to get the number of seconds until a user's
1022    // password expires.
1023    createSubCommand(
1024         ManageAccountSubCommandType.GET_SECONDS_UNTIL_PASSWORD_EXPIRATION,
1025         INFO_MANAGE_ACCT_SC_GET_SECONDS_UNTIL_PW_EXP_EXAMPLE.get(
1026              EXAMPLE_TARGET_USER_DN));
1027
1028
1029    // Define the subcommand to determine whether a user's password is expired.
1030    createSubCommand(ManageAccountSubCommandType.GET_PASSWORD_IS_EXPIRED,
1031         INFO_MANAGE_ACCT_SC_GET_PW_IS_EXPIRED_EXAMPLE.get(
1032              EXAMPLE_TARGET_USER_DN));
1033
1034
1035    // Define the subcommand to determine whether an account is failure locked.
1036    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_IS_FAILURE_LOCKED,
1037         INFO_MANAGE_ACCT_SC_GET_ACCT_FAILURE_LOCKED_EXAMPLE.get(
1038              EXAMPLE_TARGET_USER_DN));
1039
1040
1041    // Define the subcommand to specify whether an account is failure locked.
1042    final ArgumentParser setIsFailureLockedParser =
1043         createSubCommandParser(
1044              ManageAccountSubCommandType.SET_ACCOUNT_IS_FAILURE_LOCKED);
1045
1046    final BooleanValueArgument setIsFailureLockedValueArg =
1047         new BooleanValueArgument('O', "accountIsFailureLocked", true, null,
1048              INFO_MANAGE_ACCT_SC_SET_ACCT_FAILURE_LOCKED_ARG_VALUE.get());
1049    setIsFailureLockedValueArg.addLongIdentifier("operationValue", true);
1050    setIsFailureLockedValueArg.addLongIdentifier("account-is-failure-locked",
1051         true);
1052    setIsFailureLockedValueArg.addLongIdentifier("operation-value", true);
1053    setIsFailureLockedParser.addArgument(setIsFailureLockedValueArg);
1054
1055    createSubCommand(ManageAccountSubCommandType.SET_ACCOUNT_IS_FAILURE_LOCKED,
1056         setIsFailureLockedParser,
1057         createSubCommandExample(
1058              ManageAccountSubCommandType.SET_ACCOUNT_IS_FAILURE_LOCKED,
1059              INFO_MANAGE_ACCT_SC_SET_ACCT_FAILURE_LOCKED_EXAMPLE.get(
1060                   EXAMPLE_TARGET_USER_DN),
1061              "--accountIsFailureLocked", "true"));
1062
1063
1064    // Define the subcommand to get the time an account was failure locked.
1065    createSubCommand(ManageAccountSubCommandType.GET_FAILURE_LOCKOUT_TIME,
1066         INFO_MANAGE_ACCT_SC_GET_FAILURE_LOCKED_TIME_EXAMPLE.get(
1067              EXAMPLE_TARGET_USER_DN));
1068
1069
1070    // Define the subcommand to get the length of time until a failure-locked
1071    // account will be automatically unlocked.
1072    createSubCommand(
1073         ManageAccountSubCommandType.
1074              GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK,
1075         INFO_MANAGE_ACCT_SC_GET_SECONDS_UNTIL_FAILURE_UNLOCK_EXAMPLE.get(
1076              EXAMPLE_TARGET_USER_DN));
1077
1078
1079    // Define the subcommand to determine the authentication failure times.
1080    createSubCommand(
1081         ManageAccountSubCommandType.GET_AUTHENTICATION_FAILURE_TIMES,
1082         INFO_MANAGE_ACCT_SC_GET_AUTH_FAILURE_TIMES_EXAMPLE.get(
1083              EXAMPLE_TARGET_USER_DN));
1084
1085
1086    // Define the subcommand to add values to the set of authentication failure
1087    // times.
1088    final ArgumentParser addAuthFailureTimeParser =
1089         createSubCommandParser(
1090              ManageAccountSubCommandType.ADD_AUTHENTICATION_FAILURE_TIME);
1091
1092    final TimestampArgument addAuthFailureTimeValueArg =
1093         new TimestampArgument('O', "authenticationFailureTime", false, 0, null,
1094              INFO_MANAGE_ACCT_SC_ADD_AUTH_FAILURE_TIME_ARG_VALUE.get());
1095    addAuthFailureTimeValueArg.addLongIdentifier("operationValue", true);
1096    addAuthFailureTimeValueArg.addLongIdentifier(
1097         "authentication-failure-time", true);
1098    addAuthFailureTimeValueArg.addLongIdentifier("operation-value", true);
1099    addAuthFailureTimeParser.addArgument(addAuthFailureTimeValueArg);
1100
1101    createSubCommand(
1102         ManageAccountSubCommandType.ADD_AUTHENTICATION_FAILURE_TIME,
1103         addAuthFailureTimeParser,
1104         createSubCommandExample(
1105              ManageAccountSubCommandType.ADD_AUTHENTICATION_FAILURE_TIME,
1106              INFO_MANAGE_ACCT_SC_ADD_AUTH_FAILURE_TIME_EXAMPLE.get(
1107                   EXAMPLE_TARGET_USER_DN)));
1108
1109
1110    // Define the subcommand to replace the authentication failure times.
1111    final ArgumentParser setAuthFailureTimesParser =
1112         createSubCommandParser(
1113              ManageAccountSubCommandType.SET_AUTHENTICATION_FAILURE_TIMES);
1114
1115    final TimestampArgument setAuthFailureTimesValueArg =
1116         new TimestampArgument('O', "authenticationFailureTime", false, 0, null,
1117              INFO_MANAGE_ACCT_SC_SET_AUTH_FAILURE_TIMES_ARG_VALUE.get());
1118    setAuthFailureTimesValueArg.addLongIdentifier("operationValue", true);
1119    setAuthFailureTimesValueArg.addLongIdentifier(
1120         "authentication-failure-time", true);
1121    setAuthFailureTimesValueArg.addLongIdentifier("operation-value", true);
1122    setAuthFailureTimesParser.addArgument(setAuthFailureTimesValueArg);
1123
1124    createSubCommand(
1125         ManageAccountSubCommandType.SET_AUTHENTICATION_FAILURE_TIMES,
1126         setAuthFailureTimesParser,
1127         createSubCommandExample(
1128              ManageAccountSubCommandType.SET_AUTHENTICATION_FAILURE_TIMES,
1129              INFO_MANAGE_ACCT_SC_SET_AUTH_FAILURE_TIMES_EXAMPLE.get(
1130                   EXAMPLE_TARGET_USER_DN, olderGeneralizedTime,
1131                   currentGeneralizedTime),
1132              "--authenticationFailureTime", olderGeneralizedTime,
1133              "--authenticationFailureTime", currentGeneralizedTime));
1134
1135
1136    // Define the subcommand to clear the authentication failure times.
1137    createSubCommand(
1138         ManageAccountSubCommandType.CLEAR_AUTHENTICATION_FAILURE_TIMES,
1139         INFO_MANAGE_ACCT_SC_CLEAR_AUTH_FAILURE_TIMES_EXAMPLE.get(
1140              EXAMPLE_TARGET_USER_DN));
1141
1142
1143    // Define the subcommand to get the remaining authentication failure count.
1144    createSubCommand(
1145         ManageAccountSubCommandType.GET_REMAINING_AUTHENTICATION_FAILURE_COUNT,
1146         INFO_MANAGE_ACCT_SC_GET_REMAINING_FAILURE_COUNT_EXAMPLE.get(
1147              EXAMPLE_TARGET_USER_DN));
1148
1149
1150    // Define the subcommand to determine whether the account is idle locked.
1151    createSubCommand(ManageAccountSubCommandType.GET_ACCOUNT_IS_IDLE_LOCKED,
1152         INFO_MANAGE_ACCT_SC_GET_ACCT_IDLE_LOCKED_EXAMPLE.get(
1153              EXAMPLE_TARGET_USER_DN));
1154
1155
1156    // Define the subcommand to get the length of time until the account is
1157    // idle locked.
1158    createSubCommand(ManageAccountSubCommandType.GET_SECONDS_UNTIL_IDLE_LOCKOUT,
1159         INFO_MANAGE_ACCT_SC_GET_SECONDS_UNTIL_IDLE_LOCKOUT_EXAMPLE.get(
1160              EXAMPLE_TARGET_USER_DN));
1161
1162
1163    // Define the subcommand to get the idle lockout time for an account.
1164    createSubCommand(ManageAccountSubCommandType.GET_IDLE_LOCKOUT_TIME,
1165         INFO_MANAGE_ACCT_SC_GET_IDLE_LOCKOUT_TIME_EXAMPLE.get(
1166              EXAMPLE_TARGET_USER_DN));
1167
1168
1169    // Define the subcommand to determine whether a user's password has been
1170    // reset.
1171    createSubCommand(ManageAccountSubCommandType.GET_MUST_CHANGE_PASSWORD,
1172         INFO_MANAGE_ACCT_SC_GET_MUST_CHANGE_PW_EXAMPLE.get(
1173              EXAMPLE_TARGET_USER_DN));
1174
1175
1176    // Define the subcommand to specify whether a user's password has been
1177    // reset.
1178    final ArgumentParser setPWIsResetParser =
1179         createSubCommandParser(
1180              ManageAccountSubCommandType.SET_MUST_CHANGE_PASSWORD);
1181
1182    final BooleanValueArgument setPWIsResetValueArg =
1183         new BooleanValueArgument('O', "mustChangePassword", true, null,
1184              INFO_MANAGE_ACCT_SC_SET_MUST_CHANGE_PW_ARG_VALUE.get());
1185    setPWIsResetValueArg.addLongIdentifier("passwordIsReset", true);
1186    setPWIsResetValueArg.addLongIdentifier("operationValue", true);
1187    setPWIsResetValueArg.addLongIdentifier("must-change-password", true);
1188    setPWIsResetValueArg.addLongIdentifier("password-is-reset", true);
1189    setPWIsResetValueArg.addLongIdentifier("operation-value", true);
1190    setPWIsResetParser.addArgument(setPWIsResetValueArg);
1191
1192    createSubCommand(ManageAccountSubCommandType.SET_MUST_CHANGE_PASSWORD,
1193         setPWIsResetParser,
1194         createSubCommandExample(
1195              ManageAccountSubCommandType.SET_MUST_CHANGE_PASSWORD,
1196              INFO_MANAGE_ACCT_SC_SET_MUST_CHANGE_PW_EXAMPLE.get(
1197                   EXAMPLE_TARGET_USER_DN),
1198              "--mustChangePassword", "true"));
1199
1200
1201    // Define the subcommand to clear the password reset state information.
1202    createSubCommand(ManageAccountSubCommandType.CLEAR_MUST_CHANGE_PASSWORD,
1203         INFO_MANAGE_ACCT_SC_CLEAR_MUST_CHANGE_PW_EXAMPLE.get(
1204              EXAMPLE_TARGET_USER_DN));
1205
1206
1207    // Define the subcommand to determine whether the account is reset locked.
1208    createSubCommand(
1209         ManageAccountSubCommandType.GET_ACCOUNT_IS_PASSWORD_RESET_LOCKED,
1210         INFO_MANAGE_ACCT_SC_GET_ACCT_IS_RESET_LOCKED_EXAMPLE.get(
1211              EXAMPLE_TARGET_USER_DN));
1212
1213
1214    // Define the subcommand to get the length of time until the password is
1215    // reset locked.
1216    createSubCommand(
1217         ManageAccountSubCommandType.GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT,
1218         INFO_MANAGE_ACCT_SC_GET_SECONDS_UNTIL_RESET_LOCKOUT_EXAMPLE.get(
1219              EXAMPLE_TARGET_USER_DN));
1220
1221
1222    // Define the subcommand to get the password reset lockout time.
1223    createSubCommand(
1224         ManageAccountSubCommandType.GET_PASSWORD_RESET_LOCKOUT_TIME,
1225         INFO_MANAGE_ACCT_SC_GET_RESET_LOCKOUT_TIME_EXAMPLE.get(
1226              EXAMPLE_TARGET_USER_DN));
1227
1228
1229    // Define the subcommand to get the last login time.
1230    createSubCommand(ManageAccountSubCommandType.GET_LAST_LOGIN_TIME,
1231         INFO_MANAGE_ACCT_SC_GET_LAST_LOGIN_TIME_EXAMPLE.get(
1232              EXAMPLE_TARGET_USER_DN));
1233
1234
1235    // Define the subcommand to set the last login time.
1236    final ArgumentParser setLastLoginTimeParser =
1237         createSubCommandParser(
1238              ManageAccountSubCommandType.SET_LAST_LOGIN_TIME);
1239
1240    final TimestampArgument setLastLoginTimeValueArg = new TimestampArgument(
1241         'O', "lastLoginTime", false, 1, null,
1242         INFO_MANAGE_ACCT_SC_SET_LAST_LOGIN_TIME_ARG_VALUE.get());
1243    setLastLoginTimeValueArg.addLongIdentifier("operationValue", true);
1244    setLastLoginTimeValueArg.addLongIdentifier("last-login-time", true);
1245    setLastLoginTimeValueArg.addLongIdentifier("operation-value", true);
1246    setLastLoginTimeParser.addArgument(setLastLoginTimeValueArg);
1247
1248    createSubCommand(ManageAccountSubCommandType.SET_LAST_LOGIN_TIME,
1249         setLastLoginTimeParser,
1250         createSubCommandExample(
1251              ManageAccountSubCommandType.SET_LAST_LOGIN_TIME,
1252              INFO_MANAGE_ACCT_SC_SET_LAST_LOGIN_TIME_EXAMPLE.get(
1253                   EXAMPLE_TARGET_USER_DN, currentGeneralizedTime),
1254              "--lastLoginTime", currentGeneralizedTime));
1255
1256
1257    // Define the subcommand to clear the last login time.
1258    createSubCommand(ManageAccountSubCommandType.CLEAR_LAST_LOGIN_TIME,
1259         INFO_MANAGE_ACCT_SC_CLEAR_LAST_LOGIN_TIME_EXAMPLE.get(
1260              EXAMPLE_TARGET_USER_DN));
1261
1262
1263    // Define the subcommand to get the last login IP address.
1264    createSubCommand(ManageAccountSubCommandType.GET_LAST_LOGIN_IP_ADDRESS,
1265         INFO_MANAGE_ACCT_SC_GET_LAST_LOGIN_IP_EXAMPLE.get(
1266              EXAMPLE_TARGET_USER_DN));
1267
1268
1269    // Define the subcommand to set the last login IP address.
1270    final ArgumentParser setLastLoginIPParser =
1271         createSubCommandParser(
1272              ManageAccountSubCommandType.SET_LAST_LOGIN_IP_ADDRESS);
1273
1274    final StringArgument setLastLoginIPValueArg = new StringArgument('O',
1275         "lastLoginIPAddress", true, 1, null,
1276         INFO_MANAGE_ACCT_SC_SET_LAST_LOGIN_IP_ARG_VALUE.get());
1277    setLastLoginIPValueArg.addLongIdentifier("operationValue", true);
1278    setLastLoginIPValueArg.addLongIdentifier("last-login-ip-address", true);
1279    setLastLoginIPValueArg.addLongIdentifier("operation-value", true);
1280    setLastLoginIPValueArg.addValueValidator(
1281         new IPAddressArgumentValueValidator());
1282    setLastLoginIPParser.addArgument(setLastLoginIPValueArg);
1283
1284
1285    createSubCommand(ManageAccountSubCommandType.SET_LAST_LOGIN_IP_ADDRESS,
1286         setLastLoginIPParser,
1287         createSubCommandExample(
1288              ManageAccountSubCommandType.SET_LAST_LOGIN_IP_ADDRESS,
1289              INFO_MANAGE_ACCT_SC_SET_LAST_LOGIN_IP_EXAMPLE.get(
1290                   EXAMPLE_TARGET_USER_DN, "1.2.3.4"),
1291              "--lastLoginIPAddress", "1.2.3.4"));
1292
1293
1294    // Define the subcommand to clear the last login IP address.
1295    createSubCommand(ManageAccountSubCommandType.CLEAR_LAST_LOGIN_IP_ADDRESS,
1296         INFO_MANAGE_ACCT_SC_CLEAR_LAST_LOGIN_IP_EXAMPLE.get(
1297              EXAMPLE_TARGET_USER_DN));
1298
1299
1300    // Define the subcommand to get the grace login use times.
1301    createSubCommand(ManageAccountSubCommandType.GET_GRACE_LOGIN_USE_TIMES,
1302         INFO_MANAGE_ACCT_SC_GET_GRACE_LOGIN_TIMES_EXAMPLE.get(
1303              EXAMPLE_TARGET_USER_DN));
1304
1305
1306    // Define the subcommand to add values to the set of grace login use times.
1307    final ArgumentParser addGraceLoginTimeParser =
1308         createSubCommandParser(
1309              ManageAccountSubCommandType.ADD_GRACE_LOGIN_USE_TIME);
1310
1311    final TimestampArgument addGraceLoginTimeValueArg =
1312         new TimestampArgument('O', "graceLoginUseTime", false, 0, null,
1313              INFO_MANAGE_ACCT_SC_ADD_GRACE_LOGIN_TIME_ARG_VALUE.get());
1314    addGraceLoginTimeValueArg.addLongIdentifier("operationValue", true);
1315    addGraceLoginTimeValueArg.addLongIdentifier("grace-login-use-time", true);
1316    addGraceLoginTimeValueArg.addLongIdentifier("operation-value", true);
1317    addGraceLoginTimeParser.addArgument(addGraceLoginTimeValueArg);
1318
1319    createSubCommand(ManageAccountSubCommandType.ADD_GRACE_LOGIN_USE_TIME,
1320         addGraceLoginTimeParser,
1321         createSubCommandExample(
1322              ManageAccountSubCommandType.ADD_GRACE_LOGIN_USE_TIME,
1323              INFO_MANAGE_ACCT_SC_ADD_GRACE_LOGIN_TIME_EXAMPLE.get(
1324                   EXAMPLE_TARGET_USER_DN)));
1325
1326
1327    // Define the subcommand to replace the set of grace login use times.
1328    final ArgumentParser setGraceLoginTimesParser =
1329         createSubCommandParser(
1330              ManageAccountSubCommandType.SET_GRACE_LOGIN_USE_TIMES);
1331
1332    final TimestampArgument setGraceLoginTimesValueArg =
1333         new TimestampArgument('O', "graceLoginUseTime", false, 0, null,
1334              INFO_MANAGE_ACCT_SC_SET_GRACE_LOGIN_TIMES_ARG_VALUE.get());
1335    setGraceLoginTimesValueArg.addLongIdentifier("operationValue", true);
1336    setGraceLoginTimesValueArg.addLongIdentifier("grace-login-use-time", true);
1337    setGraceLoginTimesValueArg.addLongIdentifier("operation-value", true);
1338    setGraceLoginTimesParser.addArgument(setGraceLoginTimesValueArg);
1339
1340    createSubCommand(ManageAccountSubCommandType.SET_GRACE_LOGIN_USE_TIMES,
1341         setGraceLoginTimesParser,
1342         createSubCommandExample(
1343              ManageAccountSubCommandType.SET_GRACE_LOGIN_USE_TIMES,
1344              INFO_MANAGE_ACCT_SC_SET_GRACE_LOGIN_TIMES_EXAMPLE.get(
1345                   EXAMPLE_TARGET_USER_DN, olderGeneralizedTime,
1346                   currentGeneralizedTime),
1347              "--graceLoginUseTime", olderGeneralizedTime,
1348              "--graceLoginUseTime", currentGeneralizedTime));
1349
1350
1351    // Define the subcommand to clear the grace login use times.
1352    createSubCommand(ManageAccountSubCommandType.CLEAR_GRACE_LOGIN_USE_TIMES,
1353         INFO_MANAGE_ACCT_SC_CLEAR_GRACE_LOGIN_TIMES_EXAMPLE.get(
1354              EXAMPLE_TARGET_USER_DN));
1355
1356
1357    // Define the subcommand to get the remaining grace login count.
1358    createSubCommand(
1359         ManageAccountSubCommandType.GET_REMAINING_GRACE_LOGIN_COUNT,
1360         INFO_MANAGE_ACCT_SC_GET_REMAINING_GRACE_LOGIN_COUNT_EXAMPLE.get(
1361              EXAMPLE_TARGET_USER_DN));
1362
1363
1364    // Define the subcommand to get the password changed by required time value.
1365    createSubCommand(
1366         ManageAccountSubCommandType.GET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
1367         INFO_MANAGE_ACCT_SC_GET_PW_CHANGED_BY_REQ_TIME_EXAMPLE.get(
1368              EXAMPLE_TARGET_USER_DN));
1369
1370
1371    // Define the subcommand to set the password changed by required time value.
1372    final ArgumentParser setPWChangedByReqTimeParser =
1373         createSubCommandParser(ManageAccountSubCommandType.
1374              SET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
1375
1376    final TimestampArgument setPWChangedByReqTimeValueArg =
1377         new TimestampArgument('O', "passwordChangedByRequiredTime", false, 1,
1378              null,
1379              INFO_MANAGE_ACCT_SC_SET_PW_CHANGED_BY_REQ_TIME_ARG_VALUE.get());
1380    setPWChangedByReqTimeValueArg.addLongIdentifier("operationValue", true);
1381    setPWChangedByReqTimeValueArg.addLongIdentifier(
1382         "password-changed-by-required-time", true);
1383    setPWChangedByReqTimeValueArg.addLongIdentifier("operation-value", true);
1384    setPWChangedByReqTimeParser.addArgument(
1385         setPWChangedByReqTimeValueArg);
1386
1387    createSubCommand(
1388         ManageAccountSubCommandType.SET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
1389         setPWChangedByReqTimeParser,
1390         createSubCommandExample(
1391              ManageAccountSubCommandType.SET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
1392              INFO_MANAGE_ACCT_SC_SET_PW_CHANGED_BY_REQ_TIME_EXAMPLE.get(
1393                   EXAMPLE_TARGET_USER_DN)));
1394
1395
1396    // Define the subcommand to clear the password changed by required time
1397    // value.
1398    createSubCommand(
1399         ManageAccountSubCommandType.CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME,
1400         INFO_MANAGE_ACCT_SC_CLEAR_PW_CHANGED_BY_REQ_TIME_EXAMPLE.get(
1401              EXAMPLE_TARGET_USER_DN));
1402
1403
1404    // Define the subcommand to get the length of time until the required change
1405    // time.
1406    createSubCommand(
1407         ManageAccountSubCommandType.
1408              GET_SECONDS_UNTIL_REQUIRED_PASSWORD_CHANGE_TIME,
1409         INFO_MANAGE_ACCT_SC_GET_SECS_UNTIL_REQ_CHANGE_TIME_EXAMPLE.get(
1410              EXAMPLE_TARGET_USER_DN));
1411
1412
1413    // Define the subcommand to get the password history count.
1414    createSubCommand(ManageAccountSubCommandType.GET_PASSWORD_HISTORY_COUNT,
1415         INFO_MANAGE_ACCT_SC_GET_PW_HISTORY_COUNT_EXAMPLE.get(
1416              EXAMPLE_TARGET_USER_DN));
1417
1418
1419    // Define the subcommand to clear a user's password history.
1420    createSubCommand(ManageAccountSubCommandType.CLEAR_PASSWORD_HISTORY,
1421         INFO_MANAGE_ACCT_SC_CLEAR_PW_HISTORY_EXAMPLE.get(
1422              EXAMPLE_TARGET_USER_DN));
1423
1424
1425    // Define the subcommand to determine whether a user has a retired password.
1426    createSubCommand(ManageAccountSubCommandType.GET_HAS_RETIRED_PASSWORD,
1427         INFO_MANAGE_ACCT_SC_GET_HAS_RETIRED_PW_EXAMPLE.get(
1428              EXAMPLE_TARGET_USER_DN));
1429
1430
1431    // Define the subcommand to retrieve the time that a user's former password
1432    // was retired.
1433    createSubCommand(ManageAccountSubCommandType.GET_PASSWORD_RETIRED_TIME,
1434         INFO_MANAGE_ACCT_SC_GET_PW_RETIRED_TIME_EXAMPLE.get(
1435              EXAMPLE_TARGET_USER_DN));
1436
1437
1438    // Define the subcommand to retrieve the retired password expiration time.
1439    createSubCommand(
1440         ManageAccountSubCommandType.GET_RETIRED_PASSWORD_EXPIRATION_TIME,
1441         INFO_MANAGE_ACCT_SC_GET_RETIRED_PW_EXP_TIME_EXAMPLE.get(
1442              EXAMPLE_TARGET_USER_DN));
1443
1444
1445    // Define the subcommand to purge a retired password.
1446    createSubCommand(ManageAccountSubCommandType.CLEAR_RETIRED_PASSWORD,
1447         INFO_MANAGE_ACCT_SC_PURGE_RETIRED_PW_EXAMPLE.get(
1448              EXAMPLE_TARGET_USER_DN));
1449
1450
1451    // Define the subcommand to get the available SASL mechanisms for a user.
1452    createSubCommand(ManageAccountSubCommandType.GET_AVAILABLE_SASL_MECHANISMS,
1453         INFO_MANAGE_ACCT_SC_GET_AVAILABLE_SASL_MECHS_EXAMPLE.get(
1454              EXAMPLE_TARGET_USER_DN));
1455
1456
1457    // Define the subcommand to get the available OTP delivery mechanisms for a
1458    // user.
1459    createSubCommand(
1460         ManageAccountSubCommandType.GET_AVAILABLE_OTP_DELIVERY_MECHANISMS,
1461         INFO_MANAGE_ACCT_SC_GET_AVAILABLE_OTP_MECHS_EXAMPLE.get(
1462              EXAMPLE_TARGET_USER_DN));
1463
1464
1465    // Define the subcommand to determine whether a user has at least one TOTP
1466    // shared secret.
1467    createSubCommand(ManageAccountSubCommandType.GET_HAS_TOTP_SHARED_SECRET,
1468         INFO_MANAGE_ACCT_SC_GET_HAS_TOTP_SHARED_SECRET_EXAMPLE.get(
1469              EXAMPLE_TARGET_USER_DN));
1470
1471
1472    // Define the subcommand to add a value to the set of TOTP shared secrets
1473    // for a user.
1474    final ArgumentParser addTOTPSharedSecretParser =
1475         createSubCommandParser(
1476              ManageAccountSubCommandType.ADD_TOTP_SHARED_SECRET);
1477
1478    final StringArgument addTOTPSharedSecretValueArg =
1479         new StringArgument('O', "totpSharedSecret", true, 0, null,
1480              INFO_MANAGE_ACCT_SC_ADD_YUBIKEY_ID_ARG_VALUE.get());
1481    addTOTPSharedSecretValueArg.addLongIdentifier("operationValue", true);
1482    addTOTPSharedSecretValueArg.addLongIdentifier("totp-shared-secret", true);
1483    addTOTPSharedSecretValueArg.addLongIdentifier("operation-value", true);
1484    addTOTPSharedSecretValueArg.setSensitive(true);
1485    addTOTPSharedSecretParser.addArgument(
1486         addTOTPSharedSecretValueArg);
1487
1488    createSubCommand(ManageAccountSubCommandType.ADD_TOTP_SHARED_SECRET,
1489         addTOTPSharedSecretParser,
1490         createSubCommandExample(
1491              ManageAccountSubCommandType.ADD_TOTP_SHARED_SECRET,
1492              INFO_MANAGE_ACCT_SC_ADD_TOTP_SHARED_SECRET_EXAMPLE.get(
1493                   "abcdefghijklmnop", EXAMPLE_TARGET_USER_DN),
1494              "--totpSharedSecret", "abcdefghijklmnop"));
1495
1496
1497    // Define the subcommand to remove a value from the set of TOTP shared
1498    // secrets for a user.
1499    final ArgumentParser removeTOTPSharedSecretParser =
1500         createSubCommandParser(
1501              ManageAccountSubCommandType.REMOVE_TOTP_SHARED_SECRET);
1502
1503    final StringArgument removeTOTPSharedSecretValueArg =
1504         new StringArgument('O', "totpSharedSecret", true, 0, null,
1505              INFO_MANAGE_ACCT_SC_REMOVE_YUBIKEY_ID_ARG_VALUE.get());
1506    removeTOTPSharedSecretValueArg.addLongIdentifier("operationValue", true);
1507    removeTOTPSharedSecretValueArg.addLongIdentifier("totp-shared-secret",
1508         true);
1509    removeTOTPSharedSecretValueArg.addLongIdentifier("operation-value", true);
1510    removeTOTPSharedSecretValueArg.setSensitive(true);
1511    removeTOTPSharedSecretParser.addArgument(
1512         removeTOTPSharedSecretValueArg);
1513
1514    createSubCommand(ManageAccountSubCommandType.REMOVE_TOTP_SHARED_SECRET,
1515         removeTOTPSharedSecretParser,
1516         createSubCommandExample(
1517              ManageAccountSubCommandType.REMOVE_TOTP_SHARED_SECRET,
1518              INFO_MANAGE_ACCT_SC_REMOVE_TOTP_SHARED_SECRET_EXAMPLE.get(
1519                   "abcdefghijklmnop", EXAMPLE_TARGET_USER_DN),
1520              "--totpSharedSecret", "abcdefghijklmnop"));
1521
1522
1523    // Define the subcommand to replace set of TOTP shared secrets for a user.
1524    final ArgumentParser setTOTPSharedSecretsParser =
1525         createSubCommandParser(
1526              ManageAccountSubCommandType.SET_TOTP_SHARED_SECRETS);
1527
1528    final StringArgument setTOTPSharedSecretsValueArg =
1529         new StringArgument('O', "totpSharedSecret", true, 0, null,
1530              INFO_MANAGE_ACCT_SC_SET_TOTP_SHARED_SECRETS_ARG_VALUE.get());
1531    setTOTPSharedSecretsValueArg.addLongIdentifier("operationValue", true);
1532    setTOTPSharedSecretsValueArg.addLongIdentifier("totp-shared-secret", true);
1533    setTOTPSharedSecretsValueArg.addLongIdentifier("operation-value", true);
1534    setTOTPSharedSecretsValueArg.setSensitive(true);
1535    setTOTPSharedSecretsParser.addArgument(
1536         setTOTPSharedSecretsValueArg);
1537
1538    createSubCommand(ManageAccountSubCommandType.SET_TOTP_SHARED_SECRETS,
1539         setTOTPSharedSecretsParser,
1540         createSubCommandExample(
1541              ManageAccountSubCommandType.SET_TOTP_SHARED_SECRETS,
1542              INFO_MANAGE_ACCT_SC_SET_TOTP_SHARED_SECRETS_EXAMPLE.get(
1543                   EXAMPLE_TARGET_USER_DN, "abcdefghijklmnop"),
1544              "--totpSharedSecret", "abcdefghijklmnop"));
1545
1546
1547    // Define the subcommand to clear the set of TOTP shared secrets for a user.
1548    createSubCommand(
1549         ManageAccountSubCommandType.CLEAR_TOTP_SHARED_SECRETS,
1550         INFO_MANAGE_ACCT_SC_CLEAR_TOTP_SHARED_SECRETS_EXAMPLE.get(
1551              EXAMPLE_TARGET_USER_DN));
1552
1553
1554    // Define the subcommand to determine whether a user has at least one
1555    // registered YubiKey OTP device public ID.
1556    createSubCommand(
1557         ManageAccountSubCommandType.GET_HAS_REGISTERED_YUBIKEY_PUBLIC_ID,
1558         INFO_MANAGE_ACCT_SC_GET_HAS_YUBIKEY_ID_EXAMPLE.get(
1559              EXAMPLE_TARGET_USER_DN));
1560
1561
1562    // Define the subcommand to get the set of registered YubiKey OTP device
1563    // public IDs for a user.
1564    createSubCommand(
1565         ManageAccountSubCommandType.GET_REGISTERED_YUBIKEY_PUBLIC_IDS,
1566         INFO_MANAGE_ACCT_SC_GET_YUBIKEY_IDS_EXAMPLE.get(
1567              EXAMPLE_TARGET_USER_DN));
1568
1569
1570    // Define the subcommand to add a value to the set of registered YubiKey OTP
1571    // device public IDs for a user.
1572    final ArgumentParser addRegisteredYubiKeyPublicIDParser =
1573         createSubCommandParser(
1574              ManageAccountSubCommandType.ADD_REGISTERED_YUBIKEY_PUBLIC_ID);
1575
1576    final StringArgument addRegisteredYubiKeyPublicIDValueArg =
1577         new StringArgument('O', "publicID", true, 0, null,
1578              INFO_MANAGE_ACCT_SC_ADD_YUBIKEY_ID_ARG_VALUE.get());
1579    addRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("operationValue",
1580         true);
1581    addRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("public-id", true);
1582    addRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("operation-value",
1583         true);
1584    addRegisteredYubiKeyPublicIDParser.addArgument(
1585         addRegisteredYubiKeyPublicIDValueArg);
1586
1587    createSubCommand(
1588         ManageAccountSubCommandType.ADD_REGISTERED_YUBIKEY_PUBLIC_ID,
1589         addRegisteredYubiKeyPublicIDParser,
1590         createSubCommandExample(
1591              ManageAccountSubCommandType.ADD_REGISTERED_YUBIKEY_PUBLIC_ID,
1592              INFO_MANAGE_ACCT_SC_ADD_YUBIKEY_ID_EXAMPLE.get(
1593                   "abcdefghijkl", EXAMPLE_TARGET_USER_DN),
1594              "--publicID", "abcdefghijkl"));
1595
1596
1597    // Define the subcommand to remove a value from the set of registered
1598    // YubiKey OTP device public IDs for a user.
1599    final ArgumentParser removeRegisteredYubiKeyPublicIDParser =
1600         createSubCommandParser(
1601              ManageAccountSubCommandType.REMOVE_REGISTERED_YUBIKEY_PUBLIC_ID);
1602
1603    final StringArgument removeRegisteredYubiKeyPublicIDValueArg =
1604         new StringArgument('O', "publicID", true, 0, null,
1605              INFO_MANAGE_ACCT_SC_REMOVE_YUBIKEY_ID_ARG_VALUE.get());
1606    removeRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("operationValue",
1607         true);
1608    removeRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("public-id",
1609         true);
1610    removeRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("operation-value",
1611         true);
1612    removeRegisteredYubiKeyPublicIDParser.addArgument(
1613         removeRegisteredYubiKeyPublicIDValueArg);
1614
1615    createSubCommand(
1616         ManageAccountSubCommandType.REMOVE_REGISTERED_YUBIKEY_PUBLIC_ID,
1617         removeRegisteredYubiKeyPublicIDParser,
1618         createSubCommandExample(
1619              ManageAccountSubCommandType.REMOVE_REGISTERED_YUBIKEY_PUBLIC_ID,
1620              INFO_MANAGE_ACCT_SC_REMOVE_YUBIKEY_ID_EXAMPLE.get(
1621                   "abcdefghijkl", EXAMPLE_TARGET_USER_DN),
1622              "--publicID", "abcdefghijkl"));
1623
1624
1625    // Define the subcommand to replace set of registered YubiKey OTP device
1626    // public IDs for a user.
1627    final ArgumentParser setRegisteredYubiKeyPublicIDParser =
1628         createSubCommandParser(
1629              ManageAccountSubCommandType.SET_REGISTERED_YUBIKEY_PUBLIC_IDS);
1630
1631    final StringArgument setRegisteredYubiKeyPublicIDValueArg =
1632         new StringArgument('O', "publicID", true, 0, null,
1633              INFO_MANAGE_ACCT_SC_SET_YUBIKEY_IDS_ARG_VALUE.get());
1634    setRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("operationValue",
1635         true);
1636    setRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("public-id", true);
1637    setRegisteredYubiKeyPublicIDValueArg.addLongIdentifier("operation-value",
1638         true);
1639    setRegisteredYubiKeyPublicIDParser.addArgument(
1640         setRegisteredYubiKeyPublicIDValueArg);
1641
1642    createSubCommand(
1643         ManageAccountSubCommandType.SET_REGISTERED_YUBIKEY_PUBLIC_IDS,
1644         setRegisteredYubiKeyPublicIDParser,
1645         createSubCommandExample(
1646              ManageAccountSubCommandType.SET_REGISTERED_YUBIKEY_PUBLIC_IDS,
1647              INFO_MANAGE_ACCT_SC_SET_YUBIKEY_IDS_EXAMPLE.get(
1648                   EXAMPLE_TARGET_USER_DN, "abcdefghijkl"),
1649              "--publicID", "abcdefghijkl"));
1650
1651
1652    // Define the subcommand to clear the set of registered YubiKey OTP device
1653    // public IDs for a user.
1654    createSubCommand(
1655         ManageAccountSubCommandType.CLEAR_REGISTERED_YUBIKEY_PUBLIC_IDS,
1656         INFO_MANAGE_ACCT_SC_CLEAR_YUBIKEY_IDS_EXAMPLE.get(
1657              EXAMPLE_TARGET_USER_DN));
1658
1659
1660    // Define the subcommand to determine whether a user has at least one static
1661    // password.
1662    createSubCommand(ManageAccountSubCommandType.GET_HAS_STATIC_PASSWORD,
1663         INFO_MANAGE_ACCT_SC_GET_HAS_STATIC_PW_EXAMPLE.get(
1664              EXAMPLE_TARGET_USER_DN));
1665
1666
1667    // Define the subcommand to retrieve the last bind password validation time
1668    // for a user.
1669    createSubCommand(
1670         ManageAccountSubCommandType.GET_LAST_BIND_PASSWORD_VALIDATION_TIME,
1671         INFO_MANAGE_ACCT_SC_GET_LAST_BIND_PW_VALIDATION_TIME_EXAMPLE.get(
1672              EXAMPLE_TARGET_USER_DN));
1673
1674
1675    // Define the subcommand to retrieve the length of time since the last bind
1676    // password validation for a user.
1677    createSubCommand(
1678         ManageAccountSubCommandType.
1679              GET_SECONDS_SINCE_LAST_BIND_PASSWORD_VALIDATION,
1680         INFO_MANAGE_ACCT_SC_GET_SECS_SINCE_LAST_BIND_PW_VALIDATION_EXAMPLE.get(
1681              EXAMPLE_TARGET_USER_DN));
1682
1683
1684    // Define the subcommand to set the last bind password validation time for a
1685    // user.
1686    final ArgumentParser setLastBindPasswordValidationTimeParser =
1687         createSubCommandParser(ManageAccountSubCommandType.
1688              SET_LAST_BIND_PASSWORD_VALIDATION_TIME);
1689
1690    final TimestampArgument setLastBindPasswordValidationTimeValueArg =
1691         new TimestampArgument('O', "validationTime", false, 1, null,
1692              INFO_MANAGE_ACCT_SC_SET_LAST_BIND_PW_VALIDATION_TIME_ARG_VALUE.
1693                   get());
1694    setLastBindPasswordValidationTimeValueArg.addLongIdentifier(
1695         "operationValue", true);
1696    setLastBindPasswordValidationTimeValueArg.addLongIdentifier(
1697         "validation-time", true);
1698    setLastBindPasswordValidationTimeValueArg.addLongIdentifier(
1699         "operation-value", true);
1700    setLastBindPasswordValidationTimeParser.addArgument(
1701         setLastBindPasswordValidationTimeValueArg);
1702
1703    createSubCommand(
1704         ManageAccountSubCommandType.SET_LAST_BIND_PASSWORD_VALIDATION_TIME,
1705         setLastBindPasswordValidationTimeParser,
1706         createSubCommandExample(
1707              ManageAccountSubCommandType.
1708                   SET_LAST_BIND_PASSWORD_VALIDATION_TIME,
1709              INFO_MANAGE_ACCT_SC_SET_LAST_BIND_PW_VALIDATION_TIME_EXAMPLE.get(
1710                   EXAMPLE_TARGET_USER_DN, currentGeneralizedTime),
1711              "--validationTime", currentGeneralizedTime));
1712
1713
1714    // Define the subcommand to clear the last bind password validation time for
1715    // a user.
1716    createSubCommand(
1717         ManageAccountSubCommandType.CLEAR_LAST_BIND_PASSWORD_VALIDATION_TIME,
1718         INFO_MANAGE_ACCT_SC_CLEAR_LAST_BIND_PW_VALIDATION_TIME_EXAMPLE.get(
1719              EXAMPLE_TARGET_USER_DN));
1720
1721
1722    // Define the subcommand to determine whether an account is locked because
1723    // it contains a password that does not satisfy all of the configured
1724    // password validators.
1725    createSubCommand(
1726         ManageAccountSubCommandType.GET_ACCOUNT_IS_VALIDATION_LOCKED,
1727         INFO_MANAGE_ACCT_SC_GET_ACCT_VALIDATION_LOCKED_EXAMPLE.get(
1728              EXAMPLE_TARGET_USER_DN));
1729
1730
1731    // Define the subcommand to specify whether an account is locked because it
1732    // contains a password that does not satisfy all of the configured password
1733    // validators.
1734    final ArgumentParser setIsValidationLockedParser =
1735         createSubCommandParser(
1736              ManageAccountSubCommandType.SET_ACCOUNT_IS_VALIDATION_LOCKED);
1737
1738    final BooleanValueArgument setIsValidationLockedValueArg =
1739         new BooleanValueArgument('O', "accountIsValidationLocked", true, null,
1740              INFO_MANAGE_ACCT_SC_SET_ACCT_VALIDATION_LOCKED_ARG_VALUE.get());
1741    setIsValidationLockedValueArg.addLongIdentifier("operationValue", true);
1742    setIsValidationLockedValueArg.addLongIdentifier(
1743         "account-is-validation-locked", true);
1744    setIsValidationLockedValueArg.addLongIdentifier("operation-value", true);
1745    setIsValidationLockedParser.addArgument(setIsValidationLockedValueArg);
1746
1747    createSubCommand(
1748         ManageAccountSubCommandType.SET_ACCOUNT_IS_VALIDATION_LOCKED,
1749         setIsValidationLockedParser,
1750         createSubCommandExample(
1751              ManageAccountSubCommandType.SET_ACCOUNT_IS_VALIDATION_LOCKED,
1752              INFO_MANAGE_ACCT_SC_SET_ACCT_VALIDATION_LOCKED_EXAMPLE.get(
1753                   EXAMPLE_TARGET_USER_DN),
1754              "--accountIsValidationLocked", "true"));
1755
1756
1757    // Define the subcommand to retrieve a user's recent login history.
1758    createSubCommand(
1759         ManageAccountSubCommandType.GET_RECENT_LOGIN_HISTORY,
1760         INFO_MANAGE_ACCT_SC_GET_RECENT_LOGIN_HISTORY_EXAMPLE.get(
1761              EXAMPLE_TARGET_USER_DN));
1762
1763
1764    // Define the subcommand to retrieve a user's recent login history.
1765    createSubCommand(
1766         ManageAccountSubCommandType.CLEAR_RECENT_LOGIN_HISTORY,
1767         INFO_MANAGE_ACCT_SC_CLEAR_RECENT_LOGIN_HISTORY_EXAMPLE.get(
1768              EXAMPLE_TARGET_USER_DN));
1769  }
1770
1771
1772
1773  /**
1774   * Creates an argument parser for the provided subcommand type.  It will not
1775   * have any arguments associated with it.
1776   *
1777   * @param  type  The subcommand type for which to create the argument parser.
1778   *
1779   * @return  The created argument parser.
1780   *
1781   * @throws  ArgumentException  If a problem is encountered while creating the
1782   *                             argument parser.
1783   */
1784  @NotNull()
1785  private static ArgumentParser createSubCommandParser(
1786               @NotNull final ManageAccountSubCommandType type)
1787          throws ArgumentException
1788  {
1789    return new ArgumentParser(type.getPrimaryName(), type.getDescription());
1790  }
1791
1792
1793
1794  /**
1795   * Generates an example usage map for a specified subcommand.
1796   *
1797   * @param  t            The subcommand type.
1798   * @param  description  The description to use for the example.
1799   * @param  args         The set of arguments to include in the example,
1800   *                      excluding the subcommand name and the arguments used
1801   *                      to connect and authenticate to the server.  This may
1802   *                      be empty if no additional arguments are needed.
1803   *
1804   * @return The generated example usage map.
1805   */
1806  @NotNull()
1807  private static LinkedHashMap<String[],String> createSubCommandExample(
1808               @NotNull final ManageAccountSubCommandType t,
1809               @NotNull final String description,
1810               @NotNull final String... args)
1811  {
1812    final LinkedHashMap<String[], String> examples =
1813         new LinkedHashMap<>(StaticUtils.computeMapCapacity(1));
1814    createSubCommandExample(examples, t, description, args);
1815    return examples;
1816  }
1817
1818
1819
1820  /**
1821   * Adds an example for a specified subcommand to the given map.
1822   *
1823   * @param  examples     The map to which the example should be added.
1824   * @param  t            The subcommand type.
1825   * @param  description  The description to use for the example.
1826   * @param  args         The set of arguments to include in the example,
1827   *                      excluding the subcommand name and the arguments used
1828   *                      to connect and authenticate to the server.  This may
1829   *                      be empty if no additional arguments are needed.
1830   */
1831  private static void createSubCommandExample(
1832       @NotNull final LinkedHashMap<String[], String> examples,
1833       @NotNull final ManageAccountSubCommandType t, final
1834       @NotNull String description,
1835       @NotNull final String... args)
1836  {
1837    final ArrayList<String> argList = new ArrayList<>(10 + args.length);
1838    argList.add(t.getPrimaryName());
1839    argList.add("--hostname");
1840    argList.add("server.example.com");
1841    argList.add("--port");
1842    argList.add("389");
1843    argList.add("--bindDN");
1844    argList.add("uid=admin,dc=example,dc=com");
1845    argList.add("--promptForBindPassword");
1846    argList.add("--targetDN");
1847    argList.add("uid=jdoe,ou=People,dc=example,dc=com");
1848
1849    if (args.length > 0)
1850    {
1851      argList.addAll(Arrays.asList(args));
1852    }
1853
1854    final String[] argArray = new String[argList.size()];
1855    argList.toArray(argArray);
1856
1857    examples.put(argArray, description);
1858  }
1859
1860
1861
1862  /**
1863   * Creates a subcommand with the provided information.
1864   *
1865   * @param  subcommandType       The subcommand type.
1866   * @param  exampleDescription   The description to use for the
1867   *                              automatically-generated example.
1868   *
1869   * @throws  ArgumentException  If a problem is encountered while creating the
1870   *                             subcommand.
1871   */
1872  private void createSubCommand(
1873                    @NotNull final ManageAccountSubCommandType subcommandType,
1874                    @NotNull final String exampleDescription)
1875          throws ArgumentException
1876  {
1877    final ArgumentParser subcommandParser =
1878         createSubCommandParser(subcommandType);
1879
1880    final LinkedHashMap<String[],String> examples =
1881         createSubCommandExample(subcommandType, exampleDescription);
1882
1883    createSubCommand(subcommandType, subcommandParser, examples);
1884  }
1885
1886
1887
1888  /**
1889   * Creates a subcommand with the provided information.
1890   *
1891   * @param  subcommandType    The subcommand type.
1892   * @param  subcommandParser  The argument parser for the subcommand-specific
1893   *                           arguments.
1894   * @param  examples          The example usages for the subcommand.
1895   *
1896   * @throws  ArgumentException  If a problem is encountered while creating the
1897   *                             subcommand.
1898   */
1899  private void createSubCommand(
1900                    @NotNull final ManageAccountSubCommandType subcommandType,
1901                    @NotNull final ArgumentParser subcommandParser,
1902                    @NotNull final LinkedHashMap<String[],String> examples)
1903          throws ArgumentException
1904  {
1905    final SubCommand subCommand = new SubCommand(
1906         subcommandType.getPrimaryName(), subcommandType.getDescription(),
1907         subcommandParser, examples);
1908
1909    for (final String alternateName : subcommandType.getAlternateNames())
1910    {
1911      subCommand.addName(alternateName, true);
1912    }
1913
1914    parser.addSubCommand(subCommand);
1915  }
1916
1917
1918
1919  /**
1920   * {@inheritDoc}
1921   */
1922  @Override()
1923  @NotNull()
1924  public LDAPConnectionOptions getConnectionOptions()
1925  {
1926    return connectionOptions;
1927  }
1928
1929
1930
1931  /**
1932   * {@inheritDoc}
1933   */
1934  @Override()
1935  @NotNull()
1936  public ResultCode doToolProcessing()
1937  {
1938    // If we should just generate a sample rate data file, then do that now.
1939    final FileArgument generateSampleRateFile =
1940         parser.getFileArgument(ARG_GENERATE_SAMPLE_RATE_FILE);
1941    if (generateSampleRateFile.isPresent())
1942    {
1943      try
1944      {
1945        RateAdjustor.writeSampleVariableRateFile(
1946             generateSampleRateFile.getValue());
1947        return ResultCode.SUCCESS;
1948      }
1949      catch (final Exception e)
1950      {
1951        Debug.debugException(e);
1952        wrapErr(0, WRAP_COLUMN,
1953             ERR_MANAGE_ACCT_CANNOT_GENERATE_SAMPLE_RATE_FILE.get(
1954                  generateSampleRateFile.getValue().getAbsolutePath(),
1955                  StaticUtils.getExceptionMessage(e)));
1956        return ResultCode.LOCAL_ERROR;
1957      }
1958    }
1959
1960
1961    // If we need to create a fixed-rate barrier and/or use a variable rate
1962    // definition, then set that up.
1963    final IntegerArgument ratePerSecond =
1964         parser.getIntegerArgument(ARG_RATE_PER_SECOND);
1965    final FileArgument variableRateData =
1966         parser.getFileArgument(ARG_VARIABLE_RATE_DATA);
1967    if (ratePerSecond.isPresent() || variableRateData.isPresent())
1968    {
1969      if (ratePerSecond.isPresent())
1970      {
1971        rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue());
1972      }
1973      else
1974      {
1975        rateLimiter = new FixedRateBarrier(1000L, Integer.MAX_VALUE);
1976      }
1977
1978      if (variableRateData.isPresent())
1979      {
1980        try
1981        {
1982          rateAdjustor = RateAdjustor.newInstance(rateLimiter,
1983               ratePerSecond.getValue(), variableRateData.getValue());
1984        }
1985        catch (final Exception e)
1986        {
1987          Debug.debugException(e);
1988          wrapErr(0, WRAP_COLUMN,
1989               ERR_MANAGE_ACCT_CANNOT_CREATE_RATE_ADJUSTOR.get(
1990                    variableRateData.getValue().getAbsolutePath(),
1991                    StaticUtils.getExceptionMessage(e)));
1992          return ResultCode.PARAM_ERROR;
1993        }
1994      }
1995    }
1996
1997
1998    // Create the connection pool to use for all processing.
1999    final LDAPConnectionPool pool;
2000    final int numSearchThreads =
2001         parser.getIntegerArgument(ARG_NUM_SEARCH_THREADS).getValue();
2002    try
2003    {
2004      final int numOperationThreads =
2005           parser.getIntegerArgument(ARG_NUM_THREADS).getValue();
2006      pool = getConnectionPool(numOperationThreads,
2007           (numOperationThreads + numSearchThreads));
2008
2009      // Explicitly disable automatic retry, since it probably won't work
2010      // reliably for extended operations anyway.  We'll handle retry manually.
2011      pool.setRetryFailedOperationsDueToInvalidConnections(false);
2012
2013      // Set a maximum connection age of 30 minutes.
2014      pool.setMaxConnectionAgeMillis(1_800_000L);
2015    }
2016    catch (final LDAPException le)
2017    {
2018      Debug.debugException(le);
2019
2020      wrapErr(0, WRAP_COLUMN,
2021           ERR_MANAGE_ACCT_CANNOT_CREATE_CONNECTION_POOL.get(getToolName(),
2022                le.getMessage()));
2023      return le.getResultCode();
2024    }
2025
2026
2027    try
2028    {
2029      // Create the output writer.  This should always succeed.
2030      outputWriter = new LDIFWriter(getOut());
2031
2032
2033
2034      // Create the reject writer if appropriate.
2035      final FileArgument rejectFile = parser.getFileArgument(ARG_REJECT_FILE);
2036      if (rejectFile.isPresent())
2037      {
2038        final BooleanArgument appendToRejectFile =
2039             parser.getBooleanArgument(ARG_APPEND_TO_REJECT_FILE);
2040
2041        try
2042        {
2043          rejectWriter = new LDIFWriter(new FileOutputStream(
2044               rejectFile.getValue(), appendToRejectFile.isPresent()));
2045        }
2046        catch (final Exception e)
2047        {
2048          Debug.debugException(e);
2049          wrapErr(0, WRAP_COLUMN,
2050               ERR_MANAGE_ACCT_CANNOT_CREATE_REJECT_WRITER.get(
2051                    rejectFile.getValue().getAbsolutePath(),
2052                    StaticUtils.getExceptionMessage(e)));
2053          return ResultCode.LOCAL_ERROR;
2054        }
2055      }
2056
2057
2058      // Create the processor that will be used to actually perform the
2059      // manage-account operation processing for each entry.
2060      final ManageAccountProcessor processor;
2061      try
2062      {
2063        processor = new ManageAccountProcessor(this, pool, rateLimiter,
2064             outputWriter, rejectWriter);
2065      }
2066      catch (final LDAPException le)
2067      {
2068        Debug.debugException(le);
2069        wrapErr(0, WRAP_COLUMN,
2070             ERR_MANAGE_ACCT_CANNOT_CREATE_PROCESSOR.get(
2071                  StaticUtils.getExceptionMessage(le)));
2072        return le.getResultCode();
2073      }
2074
2075
2076      // If we should use a rate adjustor, then start it now.
2077      if (rateAdjustor != null)
2078      {
2079        rateAdjustor.start();
2080      }
2081
2082
2083      // If any targetDN values were provided, then process them now.
2084      final DNArgument targetDN = parser.getDNArgument(ARG_TARGET_DN);
2085      if (targetDN.isPresent())
2086      {
2087        for (final DN dn : targetDN.getValues())
2088        {
2089          if (cancelRequested())
2090          {
2091            return ResultCode.USER_CANCELED;
2092          }
2093
2094          processor.process(dn.toString());
2095        }
2096      }
2097
2098
2099      // If any DN input files were specified, then process them now.
2100      final FileArgument dnInputFile =
2101           parser.getFileArgument(ARG_DN_INPUT_FILE);
2102      if (dnInputFile.isPresent())
2103      {
2104        for (final File f : dnInputFile.getValues())
2105        {
2106          DNFileReader reader = null;
2107          try
2108          {
2109            reader = new DNFileReader(f);
2110            while (true)
2111            {
2112              if (cancelRequested())
2113              {
2114                return ResultCode.USER_CANCELED;
2115              }
2116
2117              final DN dn;
2118              try
2119              {
2120                dn = reader.readDN();
2121              }
2122              catch (final LDAPException le)
2123              {
2124                Debug.debugException(le);
2125                processor.handleMessage(le.getMessage(), true);
2126                continue;
2127              }
2128
2129              if (dn == null)
2130              {
2131                break;
2132              }
2133
2134              processor.process(dn.toString());
2135            }
2136          }
2137          catch (final Exception e)
2138          {
2139            Debug.debugException(e);
2140            processor.handleMessage(
2141                 ERR_MANAGE_ACCT_ERROR_READING_DN_FILE.get(
2142                      f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
2143                 true);
2144          }
2145          finally
2146          {
2147            if (reader != null)
2148            {
2149              try
2150              {
2151                reader.close();
2152              }
2153              catch (final Exception e2)
2154              {
2155                Debug.debugException(e2);
2156              }
2157            }
2158          }
2159        }
2160      }
2161
2162
2163      // If any target filters were specified, then process them now.
2164      final FilterArgument targetFilter =
2165           parser.getFilterArgument(ARG_TARGET_FILTER);
2166      if (targetFilter.isPresent())
2167      {
2168        searchProcessor =
2169             new ManageAccountSearchProcessor(this, processor, pool);
2170        for (final Filter f : targetFilter.getValues())
2171        {
2172          searchProcessor.processFilter(f);
2173        }
2174      }
2175
2176
2177      // If any filter input files were specified, then process them now.
2178      final FileArgument filterInputFile =
2179           parser.getFileArgument(ARG_FILTER_INPUT_FILE);
2180      if (filterInputFile.isPresent())
2181      {
2182        if (searchProcessor == null)
2183        {
2184          searchProcessor =
2185               new ManageAccountSearchProcessor(this, processor, pool);
2186        }
2187
2188        for (final File f : filterInputFile.getValues())
2189        {
2190          FilterFileReader reader = null;
2191          try
2192          {
2193            reader = new FilterFileReader(f);
2194            while (true)
2195            {
2196              if (cancelRequested())
2197              {
2198                return ResultCode.USER_CANCELED;
2199              }
2200
2201              final Filter filter;
2202              try
2203              {
2204                filter = reader.readFilter();
2205              }
2206              catch (final LDAPException le)
2207              {
2208                Debug.debugException(le);
2209                processor.handleMessage(le.getMessage(), true);
2210                continue;
2211              }
2212
2213              if (filter == null)
2214              {
2215                break;
2216              }
2217
2218              searchProcessor.processFilter(filter);
2219            }
2220          }
2221          catch (final Exception e)
2222          {
2223            Debug.debugException(e);
2224            processor.handleMessage(
2225                 ERR_MANAGE_ACCT_ERROR_READING_FILTER_FILE.get(
2226                      f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
2227                 true);
2228          }
2229          finally
2230          {
2231            if (reader != null)
2232            {
2233              try
2234              {
2235                reader.close();
2236              }
2237              catch (final Exception e2)
2238              {
2239                Debug.debugException(e2);
2240              }
2241            }
2242          }
2243        }
2244      }
2245
2246
2247      // If any target user IDs were specified, then process them now.
2248      final StringArgument targetUserID =
2249           parser.getStringArgument(ARG_TARGET_USER_ID);
2250      if (targetUserID.isPresent())
2251      {
2252        if (searchProcessor == null)
2253        {
2254          searchProcessor =
2255               new ManageAccountSearchProcessor(this, processor, pool);
2256        }
2257
2258        for (final String userID : targetUserID.getValues())
2259        {
2260          searchProcessor.processUserID(userID);
2261        }
2262      }
2263
2264
2265      // If any user ID input files were specified, then process them now.
2266      final FileArgument userIDInputFile =
2267           parser.getFileArgument(ARG_USER_ID_INPUT_FILE);
2268      if (userIDInputFile.isPresent())
2269      {
2270        if (searchProcessor == null)
2271        {
2272          searchProcessor =
2273               new ManageAccountSearchProcessor(this, processor, pool);
2274        }
2275
2276        for (final File f : userIDInputFile.getValues())
2277        {
2278          BufferedReader reader = null;
2279          try
2280          {
2281            reader = new BufferedReader(new FileReader(f));
2282            while (true)
2283            {
2284              if (cancelRequested())
2285              {
2286                return ResultCode.USER_CANCELED;
2287              }
2288
2289              final String line = reader.readLine();
2290              if (line == null)
2291              {
2292                break;
2293              }
2294
2295              if ((line.length() == 0) || line.startsWith("#"))
2296              {
2297                continue;
2298              }
2299
2300              searchProcessor.processUserID(line.trim());
2301            }
2302          }
2303          catch (final Exception e)
2304          {
2305            Debug.debugException(e);
2306            processor.handleMessage(
2307                 ERR_MANAGE_ACCT_ERROR_READING_USER_ID_FILE.get(
2308                      f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)),
2309                 true);
2310          }
2311          finally
2312          {
2313            if (reader != null)
2314            {
2315              try
2316              {
2317                reader.close();
2318              }
2319              catch (final Exception e2)
2320              {
2321                Debug.debugException(e2);
2322              }
2323            }
2324          }
2325        }
2326      }
2327
2328
2329      allFiltersProvided.set(true);
2330      if (searchProcessor != null)
2331      {
2332        searchProcessor.waitForCompletion();
2333      }
2334
2335      allDNsProvided.set(true);
2336      processor.waitForCompletion();
2337    }
2338    finally
2339    {
2340      pool.close();
2341
2342      if (rejectWriter != null)
2343      {
2344        try
2345        {
2346          rejectWriter.close();
2347        }
2348        catch (final Exception e)
2349        {
2350          Debug.debugException(e);
2351        }
2352      }
2353    }
2354
2355
2356    // If we've gotten here, then we can consider the command successful, even
2357    // if some of the operations failed.
2358    return ResultCode.SUCCESS;
2359  }
2360
2361
2362
2363  /**
2364   * Retrieves the argument parser for this tool.
2365   *
2366   * @return  The argument parser for this tool.
2367   */
2368  @Nullable()
2369  ArgumentParser getArgumentParser()
2370  {
2371    return parser;
2372  }
2373
2374
2375
2376  /**
2377   * Indicates whether the tool should cancel its processing.
2378   *
2379   * @return  {@code true} if the tool should cancel its processing, or
2380   *          {@code false} if not.
2381   */
2382  boolean cancelRequested()
2383  {
2384    return cancelRequested.get();
2385  }
2386
2387
2388
2389  /**
2390   * Indicates whether the manage-account processor has been provided with all
2391   * of the DNs of all of the entries to process.
2392   *
2393   * @return  {@code true} if the manage-account processor has been provided
2394   *          with all of the DNs of all of the entries to process, or
2395   *          {@code false} if not.
2396   */
2397  boolean allDNsProvided()
2398  {
2399    return allDNsProvided.get();
2400  }
2401
2402
2403
2404  /**
2405   * Indicates whether the manage-account search processor has been provided
2406   * with all of the filters to use to identify entries to process.
2407   *
2408   * @return  {@code true} if the manage-account search processor has been
2409   *          provided with all of the filters to use to identify entries to
2410   *          process, or {@code false} if not.
2411   */
2412  boolean allFiltersProvided()
2413  {
2414    return allFiltersProvided.get();
2415  }
2416
2417
2418
2419  /**
2420   * {@inheritDoc}
2421   */
2422  @Override()
2423  protected boolean registerShutdownHook()
2424  {
2425    return true;
2426  }
2427
2428
2429
2430  /**
2431   * {@inheritDoc}
2432   */
2433  @Override()
2434  protected void doShutdownHookProcessing(
2435                      @Nullable final ResultCode resultCode)
2436  {
2437    cancelRequested.set(true);
2438
2439    if (rateLimiter != null)
2440    {
2441      rateLimiter.shutdownRequested();
2442    }
2443
2444    if (searchProcessor != null)
2445    {
2446      searchProcessor.cancelSearches();
2447    }
2448  }
2449
2450
2451
2452  /**
2453   * Performs any processing that may be necessary in response to the provided
2454   * unsolicited notification that has been received from the server.
2455   *
2456   * @param connection   The connection on which the unsolicited notification
2457   *                     was received.
2458   * @param notification The unsolicited notification that has been received
2459   *                     from the server.
2460   */
2461  @Override()
2462  public void handleUnsolicitedNotification(
2463                   @NotNull final LDAPConnection connection,
2464                   @NotNull final ExtendedResult notification)
2465  {
2466    final String message = INFO_MANAGE_ACCT_UNSOLICITED_NOTIFICATION.get(
2467         String.valueOf(connection), String.valueOf(notification));
2468    if (outputWriter == null)
2469    {
2470      err();
2471      err("* " + message);
2472      err();
2473    }
2474    else
2475    {
2476      try
2477      {
2478        outputWriter.writeComment(message, true, true);
2479        outputWriter.flush();
2480      }
2481      catch (final Exception e)
2482      {
2483        // We can't really do anything about this.
2484        Debug.debugException(e);
2485      }
2486    }
2487  }
2488
2489
2490
2491  /**
2492   * {@inheritDoc}
2493   */
2494  @Override()
2495  @NotNull()
2496  public LinkedHashMap<String[],String> getExampleUsages()
2497  {
2498    final LinkedHashMap<String[],String> examples =
2499         new LinkedHashMap<>(StaticUtils.computeMapCapacity(4));
2500
2501    createSubCommandExample(examples,
2502         ManageAccountSubCommandType.GET_ALL,
2503         INFO_MANAGE_ACCT_SC_GET_ALL_EXAMPLE.get(EXAMPLE_TARGET_USER_DN));
2504
2505    createSubCommandExample(examples,
2506         ManageAccountSubCommandType.GET_ACCOUNT_USABILITY_ERRORS,
2507         INFO_MANAGE_ACCT_SC_GET_USABILITY_ERRORS_EXAMPLE.get(
2508              EXAMPLE_TARGET_USER_DN));
2509
2510    createSubCommandExample(examples,
2511         ManageAccountSubCommandType.SET_ACCOUNT_IS_DISABLED,
2512         INFO_MANAGE_ACCT_SC_SET_IS_DISABLED_EXAMPLE.get(
2513              EXAMPLE_TARGET_USER_DN),
2514         "--accountIsDisabled", "true");
2515
2516    createSubCommandExample(examples,
2517         ManageAccountSubCommandType.CLEAR_AUTHENTICATION_FAILURE_TIMES,
2518         INFO_MANAGE_ACCT_SC_CLEAR_AUTH_FAILURE_TIMES_EXAMPLE.get(
2519              EXAMPLE_TARGET_USER_DN));
2520
2521    return examples;
2522  }
2523}