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