package uk.ac.ebi.fg.myequivalents.cmdline;
import static java.lang.System.err;
import java.util.ServiceLoader;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
/**
* Represents one of the sub-commands available in {@link Main}. E.g., 'service store' is managed by
* {@link ServiceStoreLineCommand}. As expected, this is based on the command pattern.
*
* TODO: it's becoming messy, we have to re-arrange 1) the way {@link #getOptions()} works, common options should be
* constant variable and each line command should explicitly choose what to return 2) {@link #printUsage()}, we should present
* a very general message, which includes a list of available commands and then show specific details when the user
* issues: {@code 'myeq.sh <command> --help'}
*
*
* <dl><dt>date</dt><dd>Jul 18, 2012</dd></dl>
* @author Marco Brandizi
*
*/
public abstract class LineCommand
{
protected String email, apiPassword, userPassword, outputFormat;
protected final String commandString;
/**
* The result of {@link #parse(String...)}, use this to know what remains in the command line arguments after
* having parsed the options and to gather info about the specified options. This is defined in order to ease the
* overriding of {@link #run(String...)}.
*/
protected CommandLine cmdLine;
/**
* The exit code that will be returned to the operating system after the command line invocation. This is defined
* to allow {@link #run(String...)} to throw exceptions, after having set the corresponding exit code.
*/
protected int exitCode = 0;
/**
* @param commandString which command is managed by a particular sub-class of this class.
*/
protected LineCommand ( String commandString )
{
super ();
this.commandString = commandString;
}
/**
* Run the command. args are all the command parameters coming from the line command
* (i.e., the same in {@link Main#main}. It is expected to set the {@link #exitCode} you want to return to the
* operating system, after the line command execution.
*
* The default method does nothing but: invokes {@link #parse(String[])} and, if this returns null or
* the --help option is specified, invokes printUsage(), sets {@link #exitCode} to 1 and returns. Otherwise returns
* with the exit code left at 0; This also sets {@link #cmdLine}, so your extension likely will work this way:
*
* <pre>
* super.run ( args );
* if ( this.exitCode != 0 ) return;
* <your command execution>, which will use this.cmdLine and set {@link #exitCode} != 0 in case of error
* </pre>
*
* TODO: review the exit codes.
*/
public void run ( String... args )
{
if ( parse ( args ) == null || cmdLine.hasOption ( "h" ) ) {
printUsage ();
exitCode = 1;
}
}
/**
* <p>Uses {@link GnuParser} to parse the command line options, doing command initialisation and possibly setup
* {@link #exitCode}. Returns false if there is some parse exception or the --help option.</p>
*
* <p>The default implementation parses {@link #getOptions()} and returns the corresponding {@link CommandLine}
* generated this way. It prints an error message and returns null in case of {@link ParseException}. So you should
* be just fine with this implementation.</p>
*
*/
protected CommandLine parse ( String... args )
{
CommandLineParser clparser = new GnuParser ();
try
{
cmdLine = clparser.parse ( getOptions (), args );
email = StringUtils.trimToNull ( cmdLine.getOptionValue ( 'u' ) );
apiPassword = StringUtils.trimToNull ( cmdLine.getOptionValue ( 's' ) );
userPassword = StringUtils.trimToNull ( cmdLine.getOptionValue ( 'w' ) );
outputFormat = cmdLine.getOptionValue ( "format", "xml" );
return cmdLine;
}
catch ( ParseException e ) {
// Syntax error, report what the parser says and then leave run() to do printUsage()
err.println ( "\n\n " + e.getMessage () + "\n" );
return null;
}
}
/**
* Gets the command line options. This default implementations should be fine for all the commands (and it's quicker
* to implement it here, see its internals).
*
*/
@SuppressWarnings ( { "static-access" } )
protected Options getOptions()
{
Options opts = new Options ();
opts.addOption ( OptionBuilder
.withDescription ( "Prints this help message" )
.withLongOpt ( "help" )
.create ( "h" )
);
opts.addOption ( OptionBuilder
.withDescription ( "User email to be used to login the myEquivalents repository" )
.withLongOpt ( "user" )
.hasArg ( true ).withArgName ( "email" )
.create ( "u" )
);
opts.addOption ( OptionBuilder
.withDescription ( "API secret (i.e., password), used for common commands (see documentation)" )
.withLongOpt ( "secret" )
.hasArg ( true ).withArgName ( "value" )
.create ( "s" )
);
opts.addOption ( OptionBuilder
.withDescription ( "User password, needed for administrative/access-control/user-change operations (see documentation)" )
.withLongOpt ( "password" )
.hasArg ( true ).withArgName ( "value" )
.create ( "w" )
);
if ( commandString.equals ( "user store" ) )
{
opts.addOption ( OptionBuilder
.withDescription (
"When issuing 'user store', creates a first user (typically an admin) in an empty database, bypassing "
+ "authentication and permission checking (requires the database backend, see documentation)"
)
.withLongOpt ( "first-user" )
.create ( "y" )
);
}
if ( commandString.endsWith ( " get" ) || commandString.endsWith ( " find" ) )
{
opts.addOption ( OptionBuilder
.hasArg ( true )
.withDescription (
"The result output format. ** ONLY 'xml' IS SUPPORTED IN THIS VERSION **"
)
.withLongOpt ( "format" )
.withArgName ( "out-format" )
.create ( "f" )
);
if ( "mapping get".equals ( commandString ) )
opts.addOption ( OptionBuilder
.withDescription (
"Returns a raw result, i.e., with just the mappings and no details about services/service-collections/repositories"
)
.withLongOpt ( "raw" )
.create ( "r" )
);
} // if ' get'
return opts;
}
/**
* Which command is managed by a particular sub-class of this class, eg 'service get'. This is used to parse
* a command line
*/
public String getCommandString ()
{
return commandString;
}
/**
* This gets the specific {@link LineCommand} that is associated to args[0] and args[1], e.g.,
* returns {@link ServiceStoreLineCommand} for { "service", "store" }. This uses {@link #getCommand(Class)}.
*/
static LineCommand getCommand ( String... args )
{
if ( args.length >= 2 )
{
String cmdStr = ( args [ 0 ].trim () + ' ' + args [ 1 ].trim () ).toLowerCase ();
if ( "set".matches ( args [ 1 ].trim () ) )
// commands like 'user set role' or 'entity set visibility', they'll be further checked below
cmdStr += ' ' + args [ 2 ].trim ().toLowerCase ();
for ( LineCommand command: ServiceLoader.load ( LineCommand.class ) )
if ( cmdStr.equals ( command.getCommandString () ) ) {
return command;
}
err.println ( "\n Wrong command '" + cmdStr + "'\n\n" );
return new HelpLineCommand ();
}
else if ( args.length == 1 && !"--help".equalsIgnoreCase ( args [ 0 ].trim () ) )
err.println ( "\n Wrong command '" + args [ 0 ] + "'\n\n" );
return new HelpLineCommand ();
}
/**
* This prints a usage message for the command.
*/
protected abstract void printUsage ();
/**
* Setup during the command argument parsing and execution with a suitable exit code value. So, you should exit with this
* after execution or exceptions.
*
*/
int getExitCode () {
return exitCode;
}
}