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