Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2008-2021 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2008-2021 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2008-2021 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.args.Argument;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentHelper;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.SubCommand;
import com.unboundid.ldap.sdk.unboundidds.tools.ToolInvocationLogger;
import com.unboundid.ldap.sdk.unboundidds.tools.ToolInvocationLogDetails;
import com.unboundid.ldap.sdk.unboundidds.tools.ToolInvocationLogShutdownHook;
import static com.unboundid.util.UtilityMessages.*;
/**
* This class provides a framework for developing command-line tools that use
* the argument parser provided as part of the UnboundID LDAP SDK for Java.
* This tool adds a "-H" or "--help" option, which can be used to display usage
* information for the program, and may also add a "-V" or "--version" option,
* which can display the tool version.
*
* Subclasses should include their own {@code main} method that creates an
* instance of a {@code CommandLineTool} and should invoke the
* {@link CommandLineTool#runTool} method with the provided arguments. For
* example:
*
* public class ExampleCommandLineTool
* extends CommandLineTool
* {
* public static void main(String[] args)
* {
* ExampleCommandLineTool tool = new ExampleCommandLineTool();
* ResultCode resultCode = tool.runTool(args);
* if (resultCode != ResultCode.SUCCESS)
* {
* System.exit(resultCode.intValue());
* }
* }
*
* public ExampleCommandLineTool()
* {
* super(System.out, System.err);
* }
*
* // The rest of the tool implementation goes here.
* ...
* }
*
.
*
* Note that in general, methods in this class are not threadsafe. However, the
* {@link #out(Object...)} and {@link #err(Object...)} methods may be invoked
* concurrently by any number of threads.
*/
@Extensible()
@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
public abstract class CommandLineTool
{
// The argument used to indicate that the tool should append to the output
// file rather than overwrite it.
@Nullable private BooleanArgument appendToOutputFileArgument = null;
// The argument used to request tool help.
@Nullable private BooleanArgument helpArgument = null;
// The argument used to request help about SASL authentication.
@Nullable private BooleanArgument helpSASLArgument = null;
// The argument used to request help information about all of the subcommands.
@Nullable private BooleanArgument helpSubcommandsArgument = null;
// The argument used to request interactive mode.
@Nullable private BooleanArgument interactiveArgument = null;
// The argument used to indicate that output should be written to standard out
// as well as the specified output file.
@Nullable private BooleanArgument teeOutputArgument = null;
// The argument used to request the tool version.
@Nullable private BooleanArgument versionArgument = null;
// The argument used to specify the output file for standard output and
// standard error.
@Nullable private FileArgument outputFileArgument = null;
// A list of arguments that can be used to enable SSL/TLS debugging.
@NotNull private final List enableSSLDebuggingArguments;
// The password file reader for this tool.
@NotNull private final PasswordFileReader passwordFileReader;
// The print stream that was originally used for standard output. It may not
// be the current standard output stream if an output file has been
// configured.
@NotNull private final PrintStream originalOut;
// The print stream that was originally used for standard error. It may not
// be the current standard error stream if an output file has been configured.
@NotNull private final PrintStream originalErr;
// The print stream to use for messages written to standard output.
@NotNull private volatile PrintStream out;
// The print stream to use for messages written to standard error.
@NotNull private volatile PrintStream err;
/**
* Creates a new instance of this command-line tool with the provided
* information.
*
* @param outStream The output stream to use for standard output. It may be
* {@code System.out} for the JVM's default standard output
* stream, {@code null} if no output should be generated,
* or a custom output stream if the output should be sent
* to an alternate location.
* @param errStream The output stream to use for standard error. It may be
* {@code System.err} for the JVM's default standard error
* stream, {@code null} if no output should be generated,
* or a custom output stream if the output should be sent
* to an alternate location.
*/
public CommandLineTool(@Nullable final OutputStream outStream,
@Nullable final OutputStream errStream)
{
if (CryptoHelper.usingFIPSMode())
{
Debug.debug(Level.INFO, DebugType.OTHER,
"Running in FIPS 140-2-compliant mode.");
}
if (outStream == null)
{
out = NullOutputStream.getPrintStream();
}
else
{
out = new PrintStream(outStream);
}
if (errStream == null)
{
err = NullOutputStream.getPrintStream();
}
else
{
err = new PrintStream(errStream);
}
originalOut = out;
originalErr = err;
passwordFileReader = new PasswordFileReader(out, err);
enableSSLDebuggingArguments = new ArrayList<>(1);
}
/**
* Performs all processing for this command-line tool. This includes:
*
*
Creating the argument parser and populating it using the
* {@link #addToolArguments} method.
*
Parsing the provided set of command line arguments, including any
* additional validation using the {@link #doExtendedArgumentValidation}
* method.
*
Invoking the {@link #doToolProcessing} method to do the appropriate
* work for this tool.
*
*
* @param args The command-line arguments provided to this program.
*
* @return The result of processing this tool. It should be
* {@link ResultCode#SUCCESS} if the tool completed its work
* successfully, or some other result if a problem occurred.
*/
@NotNull()
public final ResultCode runTool(@Nullable final String... args)
{
final ArgumentParser parser;
try
{
parser = createArgumentParser();
boolean exceptionFromParsingWithNoArgumentsExplicitlyProvided = false;
if (supportsInteractiveMode() && defaultsToInteractiveMode() &&
((args == null) || (args.length == 0)))
{
// We'll go ahead and perform argument parsing even though no arguments
// were provided because there might be a properties file that should
// prevent running in interactive mode. But we'll ignore any exception
// thrown during argument parsing because the tool might require
// arguments when run non-interactively.
try
{
parser.parse(StaticUtils.NO_STRINGS);
}
catch (final Exception e)
{
Debug.debugException(e);
exceptionFromParsingWithNoArgumentsExplicitlyProvided = true;
}
}
else if (args == null)
{
parser.parse(StaticUtils.NO_STRINGS);
}
else
{
parser.parse(args);
}
final File generatedPropertiesFile = parser.getGeneratedPropertiesFile();
if (supportsPropertiesFile() && (generatedPropertiesFile != null))
{
wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS - 1,
INFO_CL_TOOL_WROTE_PROPERTIES_FILE.get(
generatedPropertiesFile.getAbsolutePath()));
return ResultCode.SUCCESS;
}
if (helpArgument.isPresent())
{
out(parser.getUsageString(StaticUtils.TERMINAL_WIDTH_COLUMNS - 1));
displayExampleUsages(parser);
return ResultCode.SUCCESS;
}
if ((helpSASLArgument != null) && helpSASLArgument.isPresent())
{
String mechanism = null;
final Argument saslOptionArgument =
parser.getNamedArgument("saslOption");
if ((saslOptionArgument != null) && saslOptionArgument.isPresent())
{
for (final String value :
saslOptionArgument.getValueStringRepresentations(false))
{
final String lowerValue = StaticUtils.toLowerCase(value);
if (lowerValue.startsWith("mech="))
{
final String mech = value.substring(5).trim();
if (! mech.isEmpty())
{
mechanism = mech;
break;
}
}
}
}
out(SASLUtils.getUsageString(mechanism,
StaticUtils.TERMINAL_WIDTH_COLUMNS - 1));
return ResultCode.SUCCESS;
}
if ((helpSubcommandsArgument != null) &&
helpSubcommandsArgument.isPresent())
{
final TreeMap subCommands =
getSortedSubCommands(parser);
for (final SubCommand sc : subCommands.values())
{
final StringBuilder nameBuffer = new StringBuilder();
final Iterator nameIterator = sc.getNames(false).iterator();
while (nameIterator.hasNext())
{
nameBuffer.append(nameIterator.next());
if (nameIterator.hasNext())
{
nameBuffer.append(", ");
}
}
out(nameBuffer.toString());
for (final String descriptionLine :
StaticUtils.wrapLine(sc.getDescription(),
(StaticUtils.TERMINAL_WIDTH_COLUMNS - 3)))
{
out(" " + descriptionLine);
}
out();
}
wrapOut(0, (StaticUtils.TERMINAL_WIDTH_COLUMNS - 1),
INFO_CL_TOOL_USE_SUBCOMMAND_HELP.get(getToolName()));
return ResultCode.SUCCESS;
}
if ((versionArgument != null) && versionArgument.isPresent())
{
out(getToolVersion());
return ResultCode.SUCCESS;
}
// If we should enable SSL/TLS debugging, then do that now. Do it before
// any kind of user-defined validation is performed. Java is really
// touchy about when this is done, and we need to do it before any
// connection attempt is made.
for (final BooleanArgument a : enableSSLDebuggingArguments)
{
if (a.isPresent())
{
StaticUtils.setSystemProperty("javax.net.debug", "all");
}
}
boolean extendedValidationDone = false;
if (interactiveArgument != null)
{
if (interactiveArgument.isPresent() ||
(defaultsToInteractiveMode() &&
((args == null) || (args.length == 0)) &&
(parser.getArgumentsSetFromPropertiesFile().isEmpty() ||
exceptionFromParsingWithNoArgumentsExplicitlyProvided)))
{
try
{
final List interactiveArgs =
requestToolArgumentsInteractively(parser);
if (interactiveArgs == null)
{
final CommandLineToolInteractiveModeProcessor processor =
new CommandLineToolInteractiveModeProcessor(this, parser);
processor.doInteractiveModeProcessing();
extendedValidationDone = true;
}
else
{
ArgumentHelper.reset(parser);
parser.parse(StaticUtils.toArray(interactiveArgs, String.class));
}
}
catch (final LDAPException le)
{
Debug.debugException(le);
final String message = le.getMessage();
if ((message != null) && (! message.isEmpty()))
{
err(message);
}
return le.getResultCode();
}
}
}
if (! extendedValidationDone)
{
doExtendedArgumentValidation();
}
}
catch (final ArgumentException ae)
{
Debug.debugException(ae);
err(ae.getMessage());
return ResultCode.PARAM_ERROR;
}
PrintStream outputFileStream = null;
if ((outputFileArgument != null) && outputFileArgument.isPresent())
{
final File outputFile = outputFileArgument.getValue();
final boolean append = ((appendToOutputFileArgument != null) &&
appendToOutputFileArgument.isPresent());
try
{
final FileOutputStream fos = new FileOutputStream(outputFile, append);
outputFileStream = new PrintStream(fos, true, "UTF-8");
}
catch (final Exception e)
{
Debug.debugException(e);
err(ERR_CL_TOOL_ERROR_CREATING_OUTPUT_FILE.get(
outputFile.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
return ResultCode.LOCAL_ERROR;
}
if ((teeOutputArgument != null) && teeOutputArgument.isPresent())
{
out = new PrintStream(new TeeOutputStream(out, outputFileStream));
err = new PrintStream(new TeeOutputStream(err, outputFileStream));
}
else
{
out = outputFileStream;
err = outputFileStream;
}
}
try
{
// If any values were selected using a properties file, then display
// information about them.
final List argsSetFromPropertiesFiles =
parser.getArgumentsSetFromPropertiesFile();
if ((! argsSetFromPropertiesFiles.isEmpty()) &&
(! parser.suppressPropertiesFileComment()))
{
for (final String line :
StaticUtils.wrapLine(
INFO_CL_TOOL_ARGS_FROM_PROPERTIES_FILE.get(
parser.getPropertiesFileUsed().getPath()),
(StaticUtils.TERMINAL_WIDTH_COLUMNS - 3)))
{
out("# ", line);
}
final StringBuilder buffer = new StringBuilder();
for (final String s : argsSetFromPropertiesFiles)
{
if (s.startsWith("-"))
{
if (buffer.length() > 0)
{
out(buffer);
buffer.setLength(0);
}
buffer.append("# ");
buffer.append(s);
}
else
{
if (buffer.length() == 0)
{
// This should never happen.
buffer.append("# ");
}
else
{
buffer.append(' ');
}
buffer.append(StaticUtils.cleanExampleCommandLineArgument(s));
}
}
if (buffer.length() > 0)
{
out(buffer);
}
out();
}
CommandLineToolShutdownHook shutdownHook = null;
final AtomicReference exitCode = new AtomicReference<>();
if (registerShutdownHook())
{
shutdownHook = new CommandLineToolShutdownHook(this, exitCode);
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
final ToolInvocationLogDetails logDetails =
ToolInvocationLogger.getLogMessageDetails(
getToolName(), logToolInvocationByDefault(), getErr());
ToolInvocationLogShutdownHook logShutdownHook = null;
if (logDetails.logInvocation())
{
final HashSet argumentsSetFromPropertiesFile =
new HashSet<>(StaticUtils.computeMapCapacity(10));
final ArrayList> propertiesFileArgList =
new ArrayList<>(10);
getToolInvocationPropertiesFileArguments(parser,
argumentsSetFromPropertiesFile, propertiesFileArgList);
final ArrayList> providedArgList =
new ArrayList<>(10);
getToolInvocationProvidedArguments(parser,
argumentsSetFromPropertiesFile, providedArgList);
logShutdownHook = new ToolInvocationLogShutdownHook(logDetails);
Runtime.getRuntime().addShutdownHook(logShutdownHook);
final String propertiesFilePath;
if (propertiesFileArgList.isEmpty())
{
propertiesFilePath = "";
}
else
{
final File propertiesFile = parser.getPropertiesFileUsed();
if (propertiesFile == null)
{
propertiesFilePath = "";
}
else
{
propertiesFilePath = propertiesFile.getAbsolutePath();
}
}
ToolInvocationLogger.logLaunchMessage(logDetails, providedArgList,
propertiesFileArgList, propertiesFilePath);
}
try
{
exitCode.set(doToolProcessing());
}
catch (final Exception e)
{
Debug.debugException(e);
err(StaticUtils.getExceptionMessage(e));
exitCode.set(ResultCode.LOCAL_ERROR);
}
finally
{
if (logShutdownHook != null)
{
Runtime.getRuntime().removeShutdownHook(logShutdownHook);
String completionMessage = getToolCompletionMessage();
if (completionMessage == null)
{
completionMessage = exitCode.get().getName();
}
ToolInvocationLogger.logCompletionMessage(
logDetails, exitCode.get().intValue(), completionMessage);
}
if (shutdownHook != null)
{
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
}
return exitCode.get();
}
finally
{
if (outputFileStream != null)
{
outputFileStream.close();
}
}
}
/**
* Updates the provided argument list with object pairs that comprise the
* set of arguments actually provided to this tool on the command line.
*
* @param parser The argument parser for this tool.
* It must not be {@code null}.
* @param argumentsSetFromPropertiesFile A set that includes all arguments
* set from the properties file.
* @param argList The list to which the argument
* information should be added. It
* must not be {@code null}. The
* first element of each object pair
* that is added must be
* non-{@code null}. The second
* element in any given pair may be
* {@code null} if the first element
* represents the name of an argument
* that doesn't take any values, the
* name of the selected subcommand, or
* an unnamed trailing argument.
*/
private static void getToolInvocationProvidedArguments(
@NotNull final ArgumentParser parser,
@NotNull final Set argumentsSetFromPropertiesFile,
@NotNull final List> argList)
{
final String noValue = null;
final SubCommand subCommand = parser.getSelectedSubCommand();
if (subCommand != null)
{
argList.add(new ObjectPair<>(subCommand.getPrimaryName(), noValue));
}
for (final Argument arg : parser.getNamedArguments())
{
// Exclude arguments that weren't provided.
if (! arg.isPresent())
{
continue;
}
// Exclude arguments that were set from the properties file.
if (argumentsSetFromPropertiesFile.contains(arg))
{
continue;
}
if (arg.takesValue())
{
for (final String value : arg.getValueStringRepresentations(false))
{
if (arg.isSensitive())
{
argList.add(new ObjectPair<>(arg.getIdentifierString(),
"*****REDACTED*****"));
}
else
{
argList.add(new ObjectPair<>(arg.getIdentifierString(), value));
}
}
}
else
{
argList.add(new ObjectPair<>(arg.getIdentifierString(), noValue));
}
}
if (subCommand != null)
{
getToolInvocationProvidedArguments(subCommand.getArgumentParser(),
argumentsSetFromPropertiesFile, argList);
}
for (final String trailingArgument : parser.getTrailingArguments())
{
argList.add(new ObjectPair<>(trailingArgument, noValue));
}
}
/**
* Updates the provided argument list with object pairs that comprise the
* set of tool arguments set from a properties file.
*
* @param parser The argument parser for this tool.
* It must not be {@code null}.
* @param argumentsSetFromPropertiesFile A set that should be updated with
* each argument set from the
* properties file.
* @param argList The list to which the argument
* information should be added. It
* must not be {@code null}. The
* first element of each object pair
* that is added must be
* non-{@code null}. The second
* element in any given pair may be
* {@code null} if the first element
* represents the name of an argument
* that doesn't take any values, the
* name of the selected subcommand, or
* an unnamed trailing argument.
*/
private static void getToolInvocationPropertiesFileArguments(
@NotNull final ArgumentParser parser,
@NotNull final Set argumentsSetFromPropertiesFile,
@NotNull final List> argList)
{
final ArgumentParser subCommandParser;
final SubCommand subCommand = parser.getSelectedSubCommand();
if (subCommand == null)
{
subCommandParser = null;
}
else
{
subCommandParser = subCommand.getArgumentParser();
}
final String noValue = null;
final Iterator iterator =
parser.getArgumentsSetFromPropertiesFile().iterator();
while (iterator.hasNext())
{
final String arg = iterator.next();
if (arg.startsWith("-"))
{
Argument a;
if (arg.startsWith("--"))
{
final String longIdentifier = arg.substring(2);
a = parser.getNamedArgument(longIdentifier);
if ((a == null) && (subCommandParser != null))
{
a = subCommandParser.getNamedArgument(longIdentifier);
}
}
else
{
final char shortIdentifier = arg.charAt(1);
a = parser.getNamedArgument(shortIdentifier);
if ((a == null) && (subCommandParser != null))
{
a = subCommandParser.getNamedArgument(shortIdentifier);
}
}
if (a != null)
{
argumentsSetFromPropertiesFile.add(a);
if (a.takesValue())
{
final String value = iterator.next();
if (a.isSensitive())
{
argList.add(new ObjectPair<>(a.getIdentifierString(), noValue));
}
else
{
argList.add(new ObjectPair<>(a.getIdentifierString(), value));
}
}
else
{
argList.add(new ObjectPair<>(a.getIdentifierString(), noValue));
}
}
}
else
{
argList.add(new ObjectPair<>(arg, noValue));
}
}
}
/**
* Retrieves a sorted map of subcommands for the provided argument parser,
* alphabetized by primary name.
*
* @param parser The argument parser for which to get the sorted
* subcommands.
*
* @return The sorted map of subcommands.
*/
@NotNull()
private static TreeMap getSortedSubCommands(
@NotNull final ArgumentParser parser)
{
final TreeMap m = new TreeMap<>();
for (final SubCommand sc : parser.getSubCommands())
{
m.put(sc.getPrimaryName(), sc);
}
return m;
}
/**
* Writes example usage information for this tool to the standard output
* stream.
*
* @param parser The argument parser used to process the provided set of
* command-line arguments.
*/
private void displayExampleUsages(@NotNull final ArgumentParser parser)
{
final LinkedHashMap examples;
if ((parser != null) && (parser.getSelectedSubCommand() != null))
{
examples = parser.getSelectedSubCommand().getExampleUsages();
}
else
{
examples = getExampleUsages();
}
if ((examples == null) || examples.isEmpty())
{
return;
}
out(INFO_CL_TOOL_LABEL_EXAMPLES);
final int wrapWidth = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
for (final Map.Entry e : examples.entrySet())
{
out();
wrapOut(2, wrapWidth, e.getValue());
out();
final StringBuilder buffer = new StringBuilder();
buffer.append(" ");
buffer.append(getToolName());
final String[] args = e.getKey();
for (int i=0; i < args.length; i++)
{
buffer.append(' ');
// If the argument has a value, then make sure to keep it on the same
// line as the argument name. This may introduce false positives due to
// unnamed trailing arguments, but the worst that will happen that case
// is that the output may be wrapped earlier than necessary one time.
String arg = args[i];
if (arg.startsWith("-"))
{
if ((i < (args.length - 1)) && (! args[i+1].startsWith("-")))
{
final ExampleCommandLineArgument cleanArg =
ExampleCommandLineArgument.getCleanArgument(args[i+1]);
arg += ' ' + cleanArg.getLocalForm();
i++;
}
}
else
{
final ExampleCommandLineArgument cleanArg =
ExampleCommandLineArgument.getCleanArgument(arg);
arg = cleanArg.getLocalForm();
}
if ((buffer.length() + arg.length() + 2) < wrapWidth)
{
buffer.append(arg);
}
else
{
buffer.append(StaticUtils.getCommandLineContinuationString());
out(buffer.toString());
buffer.setLength(0);
buffer.append(" ");
buffer.append(arg);
}
}
out(buffer.toString());
}
}
/**
* Retrieves the name of this tool. It should be the name of the command used
* to invoke this tool.
*
* @return The name for this tool.
*/
@NotNull()
public abstract String getToolName();
/**
* Retrieves a human-readable description for this tool. If the description
* should include multiple paragraphs, then this method should return the text
* for the first paragraph, and the
* {@link #getAdditionalDescriptionParagraphs()} method should be used to
* return the text for the subsequent paragraphs.
*
* @return A human-readable description for this tool.
*/
@Nullable()
public abstract String getToolDescription();
/**
* Retrieves additional paragraphs that should be included in the description
* for this tool. If the tool description should include multiple paragraphs,
* then the {@link #getToolDescription()} method should return the text of the
* first paragraph, and each item in the list returned by this method should
* be the text for each subsequent paragraph. If the tool description should
* only have a single paragraph, then this method may return {@code null} or
* an empty list.
*
* @return Additional paragraphs that should be included in the description
* for this tool, or {@code null} or an empty list if only a single
* description paragraph (whose text is returned by the
* {@code getToolDescription} method) is needed.
*/
@Nullable()
public List getAdditionalDescriptionParagraphs()
{
return Collections.emptyList();
}
/**
* Retrieves a version string for this tool, if available.
*
* @return A version string for this tool, or {@code null} if none is
* available.
*/
@Nullable()
public String getToolVersion()
{
return null;
}
/**
* Retrieves the minimum number of unnamed trailing arguments that must be
* provided for this tool. If a tool requires the use of trailing arguments,
* then it must override this method and the {@link #getMaxTrailingArguments}
* arguments to return nonzero values, and it must also override the
* {@link #getTrailingArgumentsPlaceholder} method to return a
* non-{@code null} value.
*
* @return The minimum number of unnamed trailing arguments that may be
* provided for this tool. A value of zero indicates that the tool
* may be invoked without any trailing arguments.
*/
public int getMinTrailingArguments()
{
return 0;
}
/**
* Retrieves the maximum number of unnamed trailing arguments that may be
* provided for this tool. If a tool supports trailing arguments, then it
* must override this method to return a nonzero value, and must also override
* the {@link CommandLineTool#getTrailingArgumentsPlaceholder} method to
* return a non-{@code null} value.
*
* @return The maximum number of unnamed trailing arguments that may be
* provided for this tool. A value of zero indicates that trailing
* arguments are not allowed. A negative value indicates that there
* should be no limit on the number of trailing arguments.
*/
public int getMaxTrailingArguments()
{
return 0;
}
/**
* Retrieves a placeholder string that should be used for trailing arguments
* in the usage information for this tool.
*
* @return A placeholder string that should be used for trailing arguments in
* the usage information for this tool, or {@code null} if trailing
* arguments are not supported.
*/
@Nullable()
public String getTrailingArgumentsPlaceholder()
{
return null;
}
/**
* Indicates whether this tool should provide support for an interactive mode,
* in which the tool offers a mode in which the arguments can be provided in
* a text-driven menu rather than requiring them to be given on the command
* line. If interactive mode is supported, it may be invoked using the
* "--interactive" argument. Alternately, if interactive mode is supported
* and {@link #defaultsToInteractiveMode()} returns {@code true}, then
* interactive mode may be invoked by simply launching the tool without any
* arguments.
*
* @return {@code true} if this tool supports interactive mode, or
* {@code false} if not.
*/
public boolean supportsInteractiveMode()
{
return false;
}
/**
* Indicates whether this tool defaults to launching in interactive mode if
* the tool is invoked without any command-line arguments. This will only be
* used if {@link #supportsInteractiveMode()} returns {@code true}.
*
* @return {@code true} if this tool defaults to using interactive mode if
* launched without any command-line arguments, or {@code false} if
* not.
*/
public boolean defaultsToInteractiveMode()
{
return false;
}
/**
* Interactively prompts the user for information needed to invoke this tool
* and returns an appropriate list of arguments that should be used to run it.
*
* This method will only be invoked if {@link #supportsInteractiveMode()}
* returns {@code true}, and if one of the following conditions is satisfied:
*
*
The {@code --interactive} argument is explicitly provided on the
* command line.
*
The tool was invoked without any command-line arguments and
* {@link #defaultsToInteractiveMode()} returns {@code true}.
*
* If this method is invoked and returns {@code null}, then the LDAP SDK's
* default interactive mode processing will be performed. Otherwise, the tool
* will be invoked with only the arguments in the list that is returned.
*
* @param parser The argument parser that has been used to parse any
* command-line arguments that were provided before the
* interactive mode processing was invoked. If this method
* returns a non-{@code null} value, then this parser will be
* reset before parsing the new set of arguments.
*
* @return Retrieves a list of command-line arguments that may be used to
* invoke this tool, or {@code null} if the LDAP SDK's default
* interactive mode processing should be performed.
*
* @throws LDAPException If a problem is encountered while interactively
* obtaining the arguments that should be used to
* run the tool.
*/
@Nullable()
protected List requestToolArgumentsInteractively(
@NotNull final ArgumentParser parser)
throws LDAPException
{
// Fall back to using the LDAP SDK's default interactive mode processor.
return null;
}
/**
* Indicates whether this tool supports the use of a properties file for
* specifying default values for arguments that aren't specified on the
* command line.
*
* @return {@code true} if this tool supports the use of a properties file
* for specifying default values for arguments that aren't specified
* on the command line, or {@code false} if not.
*/
public boolean supportsPropertiesFile()
{
return false;
}
/**
* Indicates whether this tool should provide arguments for redirecting output
* to a file. If this method returns {@code true}, then the tool will offer
* an "--outputFile" argument that will specify the path to a file to which
* all standard output and standard error content will be written, and it will
* also offer a "--teeToStandardOut" argument that can only be used if the
* "--outputFile" argument is present and will cause all output to be written
* to both the specified output file and to standard output.
*
* @return {@code true} if this tool should provide arguments for redirecting
* output to a file, or {@code false} if not.
*/
protected boolean supportsOutputFile()
{
return false;
}
/**
* Indicates whether to log messages about the launch and completion of this
* tool into the invocation log of Ping Identity server products that may
* include it. This method is not needed for tools that are not expected to
* be part of the Ping Identity server products suite. Further, this value
* may be overridden by settings in the server's
* tool-invocation-logging.properties file.
*
* This method should generally return {@code true} for tools that may alter
* the server configuration, data, or other state information, and
* {@code false} for tools that do not make any changes.
*
* @return {@code true} if Ping Identity server products should include
* messages about the launch and completion of this tool in tool
* invocation log files by default, or {@code false} if not.
*/
protected boolean logToolInvocationByDefault()
{
return false;
}
/**
* Retrieves an optional message that may provide additional information about
* the way that the tool completed its processing. For example if the tool
* exited with an error message, it may be useful for this method to return
* that error message.
*
* The message returned by this method is intended for informational purposes
* and is not meant to be parsed or programmatically interpreted.
*
* @return An optional message that may provide additional information about
* the completion state for this tool, or {@code null} if no
* completion message is available.
*/
@Nullable()
protected String getToolCompletionMessage()
{
return null;
}
/**
* Creates a parser that can be used to to parse arguments accepted by
* this tool.
*
* @return ArgumentParser that can be used to parse arguments for this
* tool.
*
* @throws ArgumentException If there was a problem initializing the
* parser for this tool.
*/
@NotNull()
public final ArgumentParser createArgumentParser()
throws ArgumentException
{
final ArgumentParser parser = new ArgumentParser(getToolName(),
getToolDescription(), getAdditionalDescriptionParagraphs(),
getMinTrailingArguments(), getMaxTrailingArguments(),
getTrailingArgumentsPlaceholder());
parser.setCommandLineTool(this);
addToolArguments(parser);
if (supportsInteractiveMode())
{
interactiveArgument = new BooleanArgument(null, "interactive",
INFO_CL_TOOL_DESCRIPTION_INTERACTIVE.get());
interactiveArgument.setUsageArgument(true);
parser.addArgument(interactiveArgument);
}
if (supportsOutputFile())
{
outputFileArgument = new FileArgument(null, "outputFile", false, 1, null,
INFO_CL_TOOL_DESCRIPTION_OUTPUT_FILE.get(), false, true, true,
false);
outputFileArgument.addLongIdentifier("output-file", true);
outputFileArgument.setUsageArgument(true);
parser.addArgument(outputFileArgument);
appendToOutputFileArgument = new BooleanArgument(null,
"appendToOutputFile", 1,
INFO_CL_TOOL_DESCRIPTION_APPEND_TO_OUTPUT_FILE.get(
outputFileArgument.getIdentifierString()));
appendToOutputFileArgument.addLongIdentifier("append-to-output-file",
true);
appendToOutputFileArgument.setUsageArgument(true);
parser.addArgument(appendToOutputFileArgument);
teeOutputArgument = new BooleanArgument(null, "teeOutput", 1,
INFO_CL_TOOL_DESCRIPTION_TEE_OUTPUT.get(
outputFileArgument.getIdentifierString()));
teeOutputArgument.addLongIdentifier("tee-output", true);
teeOutputArgument.setUsageArgument(true);
parser.addArgument(teeOutputArgument);
parser.addDependentArgumentSet(appendToOutputFileArgument,
outputFileArgument);
parser.addDependentArgumentSet(teeOutputArgument,
outputFileArgument);
}
helpArgument = new BooleanArgument('H', "help",
INFO_CL_TOOL_DESCRIPTION_HELP.get());
helpArgument.addShortIdentifier('?', true);
helpArgument.setUsageArgument(true);
parser.addArgument(helpArgument);
if (! parser.getSubCommands().isEmpty())
{
helpSubcommandsArgument = new BooleanArgument(null, "helpSubcommands", 1,
INFO_CL_TOOL_DESCRIPTION_HELP_SUBCOMMANDS.get());
helpSubcommandsArgument.addLongIdentifier("helpSubcommand", true);
helpSubcommandsArgument.addLongIdentifier("help-subcommands", true);
helpSubcommandsArgument.addLongIdentifier("help-subcommand", true);
helpSubcommandsArgument.setUsageArgument(true);
parser.addArgument(helpSubcommandsArgument);
}
final String version = getToolVersion();
if ((version != null) && (! version.isEmpty()) &&
(parser.getNamedArgument("version") == null))
{
final Character shortIdentifier;
if (parser.getNamedArgument('V') == null)
{
shortIdentifier = 'V';
}
else
{
shortIdentifier = null;
}
versionArgument = new BooleanArgument(shortIdentifier, "version",
INFO_CL_TOOL_DESCRIPTION_VERSION.get());
versionArgument.setUsageArgument(true);
parser.addArgument(versionArgument);
}
if (supportsPropertiesFile())
{
parser.enablePropertiesFileSupport();
}
return parser;
}
/**
* Specifies the argument that is used to retrieve usage information about
* SASL authentication.
*
* @param helpSASLArgument The argument that is used to retrieve usage
* information about SASL authentication.
*/
void setHelpSASLArgument(@NotNull final BooleanArgument helpSASLArgument)
{
this.helpSASLArgument = helpSASLArgument;
}
/**
* Adds the provided argument to the set of arguments that may be used to
* enable JVM SSL/TLS debugging.
*
* @param enableSSLDebuggingArgument The argument to add to the set of
* arguments that may be used to enable
* JVM SSL/TLS debugging.
*/
protected void addEnableSSLDebuggingArgument(
@NotNull final BooleanArgument enableSSLDebuggingArgument)
{
enableSSLDebuggingArguments.add(enableSSLDebuggingArgument);
}
/**
* Retrieves a set containing the long identifiers used for usage arguments
* injected by this class.
*
* @param tool The tool to use to help make the determination.
*
* @return A set containing the long identifiers used for usage arguments
* injected by this class.
*/
@NotNull()
static Set getUsageArgumentIdentifiers(
@NotNull final CommandLineTool tool)
{
final LinkedHashSet ids =
new LinkedHashSet<>(StaticUtils.computeMapCapacity(9));
ids.add("help");
ids.add("version");
ids.add("helpSubcommands");
if (tool.supportsInteractiveMode())
{
ids.add("interactive");
}
if (tool.supportsPropertiesFile())
{
ids.add("propertiesFilePath");
ids.add("generatePropertiesFile");
ids.add("noPropertiesFile");
ids.add("suppressPropertiesFileComment");
}
if (tool.supportsOutputFile())
{
ids.add("outputFile");
ids.add("appendToOutputFile");
ids.add("teeOutput");
}
return Collections.unmodifiableSet(ids);
}
/**
* Adds the command-line arguments supported for use with this tool to the
* provided argument parser. The tool may need to retain references to the
* arguments (and/or the argument parser, if trailing arguments are allowed)
* to it in order to obtain their values for use in later processing.
*
* @param parser The argument parser to which the arguments are to be added.
*
* @throws ArgumentException If a problem occurs while adding any of the
* tool-specific arguments to the provided
* argument parser.
*/
public abstract void addToolArguments(@NotNull ArgumentParser parser)
throws ArgumentException;
/**
* Performs any necessary processing that should be done to ensure that the
* provided set of command-line arguments were valid. This method will be
* called after the basic argument parsing has been performed and immediately
* before the {@link CommandLineTool#doToolProcessing} method is invoked.
* Note that if the tool supports interactive mode, then this method may be
* invoked multiple times to allow the user to interactively fix validation
* errors.
*
* @throws ArgumentException If there was a problem with the command-line
* arguments provided to this program.
*/
public void doExtendedArgumentValidation()
throws ArgumentException
{
// No processing will be performed by default.
}
/**
* Performs the core set of processing for this tool.
*
* @return A result code that indicates whether the processing completed
* successfully.
*/
@NotNull()
public abstract ResultCode doToolProcessing();
/**
* Indicates whether this tool should register a shutdown hook with the JVM.
* Shutdown hooks allow for a best-effort attempt to perform a specified set
* of processing when the JVM is shutting down under various conditions,
* including:
*
*
When all non-daemon threads have stopped running (i.e., the tool has
* completed processing).
*
When {@code System.exit()} or {@code Runtime.exit()} is called.
*
When the JVM receives an external kill signal (e.g., via the use of
* the kill tool or interrupting the JVM with Ctrl+C).
*
* Shutdown hooks may not be invoked if the process is forcefully killed
* (e.g., using "kill -9", or the {@code System.halt()} or
* {@code Runtime.halt()} methods).
*
* If this method is overridden to return {@code true}, then the
* {@link #doShutdownHookProcessing(ResultCode)} method should also be
* overridden to contain the logic that will be invoked when the JVM is
* shutting down in a manner that calls shutdown hooks.
*
* @return {@code true} if this tool should register a shutdown hook, or
* {@code false} if not.
*/
protected boolean registerShutdownHook()
{
return false;
}
/**
* Performs any processing that may be needed when the JVM is shutting down,
* whether because tool processing has completed or because it has been
* interrupted (e.g., by a kill or break signal).
*
* Note that because shutdown hooks run at a delicate time in the life of the
* JVM, they should complete quickly and minimize access to external
* resources. See the documentation for the
* {@code java.lang.Runtime.addShutdownHook} method for recommendations and
* restrictions about writing shutdown hooks.
*
* @param resultCode The result code returned by the tool. It may be
* {@code null} if the tool was interrupted before it
* completed processing.
*/
protected void doShutdownHookProcessing(@Nullable final ResultCode resultCode)
{
throw new LDAPSDKUsageException(
ERR_COMMAND_LINE_TOOL_SHUTDOWN_HOOK_NOT_IMPLEMENTED.get(
getToolName()));
}
/**
* Retrieves a set of information that may be used to generate example usage
* information. Each element in the returned map should consist of a map
* between an example set of arguments and a string that describes the
* behavior of the tool when invoked with that set of arguments.
*
* @return A set of information that may be used to generate example usage
* information. It may be {@code null} or empty if no example usage
* information is available.
*/
@ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
@Nullable()
public LinkedHashMap getExampleUsages()
{
return null;
}
/**
* Retrieves the password file reader for this tool, which may be used to
* read passwords from (optionally compressed and encrypted) files.
*
* @return The password file reader for this tool.
*/
@NotNull()
public final PasswordFileReader getPasswordFileReader()
{
return passwordFileReader;
}
/**
* Retrieves the print stream that will be used for standard output.
*
* @return The print stream that will be used for standard output.
*/
@NotNull()
public final PrintStream getOut()
{
return out;
}
/**
* Retrieves the print stream that may be used to write to the original
* standard output. This may be different from the current standard output
* stream if an output file has been configured.
*
* @return The print stream that may be used to write to the original
* standard output.
*/
@NotNull()
public final PrintStream getOriginalOut()
{
return originalOut;
}
/**
* Writes the provided message to the standard output stream for this tool.
*
* This method is completely threadsafe and my be invoked concurrently by any
* number of threads.
*
* @param msg The message components that will be written to the standard
* output stream. They will be concatenated together on the same
* line, and that line will be followed by an end-of-line
* sequence.
*/
@ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
public final synchronized void out(@NotNull final Object... msg)
{
write(out, 0, 0, msg);
}
/**
* Writes the provided message to the standard output stream for this tool,
* optionally wrapping and/or indenting the text in the process.
*
* This method is completely threadsafe and my be invoked concurrently by any
* number of threads.
*
* @param indent The number of spaces each line should be indented. A
* value less than or equal to zero indicates that no
* indent should be used.
* @param wrapColumn The column at which to wrap long lines. A value less
* than or equal to two indicates that no wrapping should
* be performed. If both an indent and a wrap column are
* to be used, then the wrap column must be greater than
* the indent.
* @param msg The message components that will be written to the
* standard output stream. They will be concatenated
* together on the same line, and that line will be
* followed by an end-of-line sequence.
*/
@ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
public final synchronized void wrapOut(final int indent, final int wrapColumn,
@NotNull final Object... msg)
{
write(out, indent, wrapColumn, msg);
}
/**
* Writes the provided message to the standard output stream for this tool,
* optionally wrapping and/or indenting the text in the process.
*
* This method is completely threadsafe and my be invoked concurrently by any
* number of threads.
*
* @param firstLineIndent The number of spaces the first line should be
* indented. A value less than or equal to zero
* indicates that no indent should be used.
* @param subsequentLineIndent The number of spaces each line except the
* first should be indented. A value less than
* or equal to zero indicates that no indent
* should be used.
* @param wrapColumn The column at which to wrap long lines. A
* value less than or equal to two indicates
* that no wrapping should be performed. If
* both an indent and a wrap column are to be
* used, then the wrap column must be greater
* than the indent.
* @param endWithNewline Indicates whether a newline sequence should
* follow the last line that is printed.
* @param msg The message components that will be written
* to the standard output stream. They will be
* concatenated together on the same line, and
* that line will be followed by an end-of-line
* sequence.
*/
final synchronized void wrapStandardOut(final int firstLineIndent,
final int subsequentLineIndent,
final int wrapColumn,
final boolean endWithNewline,
@NotNull final Object... msg)
{
write(out, firstLineIndent, subsequentLineIndent, wrapColumn,
endWithNewline, msg);
}
/**
* Retrieves the print stream that will be used for standard error.
*
* @return The print stream that will be used for standard error.
*/
@NotNull()
public final PrintStream getErr()
{
return err;
}
/**
* Retrieves the print stream that may be used to write to the original
* standard error. This may be different from the current standard error
* stream if an output file has been configured.
*
* @return The print stream that may be used to write to the original
* standard error.
*/
@NotNull()
public final PrintStream getOriginalErr()
{
return originalErr;
}
/**
* Writes the provided message to the standard error stream for this tool.
*
* This method is completely threadsafe and my be invoked concurrently by any
* number of threads.
*
* @param msg The message components that will be written to the standard
* error stream. They will be concatenated together on the same
* line, and that line will be followed by an end-of-line
* sequence.
*/
@ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
public final synchronized void err(@NotNull final Object... msg)
{
write(err, 0, 0, msg);
}
/**
* Writes the provided message to the standard error stream for this tool,
* optionally wrapping and/or indenting the text in the process.
*
* This method is completely threadsafe and my be invoked concurrently by any
* number of threads.
*
* @param indent The number of spaces each line should be indented. A
* value less than or equal to zero indicates that no
* indent should be used.
* @param wrapColumn The column at which to wrap long lines. A value less
* than or equal to two indicates that no wrapping should
* be performed. If both an indent and a wrap column are
* to be used, then the wrap column must be greater than
* the indent.
* @param msg The message components that will be written to the
* standard output stream. They will be concatenated
* together on the same line, and that line will be
* followed by an end-of-line sequence.
*/
@ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
public final synchronized void wrapErr(final int indent, final int wrapColumn,
@NotNull final Object... msg)
{
write(err, indent, wrapColumn, msg);
}
/**
* Writes the provided message to the given print stream, optionally wrapping
* and/or indenting the text in the process.
*
* @param stream The stream to which the message should be written.
* @param indent The number of spaces each line should be indented. A
* value less than or equal to zero indicates that no
* indent should be used.
* @param wrapColumn The column at which to wrap long lines. A value less
* than or equal to two indicates that no wrapping should
* be performed. If both an indent and a wrap column are
* to be used, then the wrap column must be greater than
* the indent.
* @param msg The message components that will be written to the
* standard output stream. They will be concatenated
* together on the same line, and that line will be
* followed by an end-of-line sequence.
*/
private static void write(@NotNull final PrintStream stream,
final int indent,
final int wrapColumn,
@NotNull final Object... msg)
{
write(stream, indent, indent, wrapColumn, true, msg);
}
/**
* Writes the provided message to the given print stream, optionally wrapping
* and/or indenting the text in the process.
*
* @param stream The stream to which the message should be
* written.
* @param firstLineIndent The number of spaces the first line should be
* indented. A value less than or equal to zero
* indicates that no indent should be used.
* @param subsequentLineIndent The number of spaces all lines after the
* first should be indented. A value less than
* or equal to zero indicates that no indent
* should be used.
* @param wrapColumn The column at which to wrap long lines. A
* value less than or equal to two indicates
* that no wrapping should be performed. If
* both an indent and a wrap column are to be
* used, then the wrap column must be greater
* than the indent.
* @param endWithNewline Indicates whether a newline sequence should
* follow the last line that is printed.
* @param msg The message components that will be written
* to the standard output stream. They will be
* concatenated together on the same line, and
* that line will be followed by an end-of-line
* sequence.
*/
private static void write(@NotNull final PrintStream stream,
final int firstLineIndent,
final int subsequentLineIndent,
final int wrapColumn,
final boolean endWithNewline,
@NotNull final Object... msg)
{
final StringBuilder buffer = new StringBuilder();
for (final Object o : msg)
{
buffer.append(o);
}
if (wrapColumn > 2)
{
boolean firstLine = true;
for (final String line :
StaticUtils.wrapLine(buffer.toString(),
(wrapColumn - firstLineIndent),
(wrapColumn - subsequentLineIndent)))
{
final int indent;
if (firstLine)
{
indent = firstLineIndent;
firstLine = false;
}
else
{
stream.println();
indent = subsequentLineIndent;
}
if (indent > 0)
{
for (int i=0; i < indent; i++)
{
stream.print(' ');
}
}
stream.print(line);
}
}
else
{
if (firstLineIndent > 0)
{
for (int i=0; i < firstLineIndent; i++)
{
stream.print(' ');
}
}
stream.print(buffer.toString());
}
if (endWithNewline)
{
stream.println();
}
stream.flush();
}
}