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