/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.component.tool;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.component.ComponentManager;
import com.opengamma.financial.tool.ToolContext;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.ShutdownUtils;
import com.opengamma.util.StartupUtils;
/**
* Abstract class for command line tools.
* <p>
* The command line tools generally require access to key parts of the infrastructure.
* These are provided via {@link ToolContext} which is setup and closed by this class
* using {@link ComponentManager}. Normally the file is named {@code toolcontext.ini}.
*
* @param <T> the tool context type
*/
public abstract class AbstractTool<T extends ToolContext> {
/**
* Logger.
*/
private static final Logger s_logger = LoggerFactory.getLogger(AbstractTool.class);
/**
* Help command line option.
*/
private static final String HELP_OPTION = "h";
/**
* Configuration command line option.
*/
protected static final String CONFIG_RESOURCE_OPTION = "c";
/**
* Logging command line option.
*/
private static final String LOGBACK_RESOURCE_OPTION = "l";
static {
StartupUtils.init();
}
/**
* The command line.
*/
private volatile CommandLine _commandLine;
/**
* The tool contexts.
*/
private volatile T[] _toolContexts;
/**
* Initializes the tool statically.
*
* @param logbackResource the logback resource location, not null
* @return true if successful
*/
public static final boolean init(final String logbackResource) {
return ToolUtils.initLogback(logbackResource);
}
/**
* Creates an instance.
*/
protected AbstractTool() {
}
//-------------------------------------------------------------------------
/**
* Main entry point to initialize and run the tool from standard command-line
* arguments, terminating the JVM once complete.
* <p>
* This base class defines three options:<br />
* c/config - the config file, mandatory<br />
* l/logback - the logback configuration, default tool-logback.xml<br />
* h/help - prints the help tool<br />
* <p>
* This method is intended for use from a standalone main method.
* It will print exceptions to system err and terminate the JVM.
* This method never returns.
* <p>
* This method calculates the {@code ToolContext} type by reflection of generics.
*
* @param args the command-line arguments, not null
*/
public void invokeAndTerminate(final String[] args) {
invokeAndTerminate(args, null, null);
}
/**
* Main entry point to initialize and run the tool from standard command-line
* arguments, terminating the JVM once complete.
* <p>
* This base class defines three options:<br />
* c/config - the config file, mandatory<br />
* l/logback - the logback configuration, default tool-logback.xml<br />
* h/help - prints the help tool<br />
* <p>
* This method is intended for use from a standalone main method.
* It will print exceptions to system err and terminate the JVM.
* This method never returns.
* <p>
* This method calculates the {@code ToolContext} type by reflection of generics.
*
* @param args the command-line arguments, not null
* @param defaultConfigResource the default configuration resource location, null if mandatory on command line
* @param defaultLogbackResource the default logback resource, null to use tool-logback.xml as the default
*/
public void invokeAndTerminate(final String[] args, final String defaultConfigResource, final String defaultLogbackResource) {
try {
// reflection to find the tool context type via reflection
Class<?> cls = getClass();
ParameterizedType type = null;
while (cls != AbstractTool.class) {
Type loop = cls.getGenericSuperclass();
if (loop instanceof ParameterizedType) {
type = (ParameterizedType) loop;
break;
}
cls = cls.getSuperclass();
}
if (type == null || type.getActualTypeArguments().length != 1 ||
type.getActualTypeArguments()[0] instanceof Class == false ||
ToolContext.class.isAssignableFrom((Class<?>) type.getActualTypeArguments()[0]) == false) {
System.err.println("Subclass must declare tool context type");
ShutdownUtils.exit(-2);
}
@SuppressWarnings("unchecked")
Class<T> toolContextClass = (Class<T>) type.getActualTypeArguments()[0];
// invoke and terminate the tool
boolean success = initAndRun(args, defaultConfigResource, defaultLogbackResource, toolContextClass);
ShutdownUtils.exit(success ? 0 : -1);
} catch (Throwable ex) {
ex.printStackTrace();
ShutdownUtils.exit(-2);
}
}
/**
* Initializes and runs the tool from standard command-line arguments.
* <p>
* This base class defines three options:<br />
* c/config - the config file, mandatory<br />
* l/logback - the logback configuration, default tool-logback.xml<br />
* h/help - prints the help tool<br />
*
* @param args the command-line arguments, not null
* @param toolContextClass the type of tool context to create, should match the generic type argument
* @return true if successful, false otherwise
*/
public boolean initAndRun(final String[] args, final Class<? extends T> toolContextClass) {
return initAndRun(args, null, null, toolContextClass);
}
/**
* Initializes and runs the tool from standard command-line arguments.
* <p>
* This base class defines three options:<br />
* c/config - the config file, mandatory unless default specified<br />
* l/logback - the logback configuration, default tool-logback.xml<br />
* h/help - prints the help tool<br />
*
* @param args the command-line arguments, not null
* @param defaultConfigResource the default configuration resource location, null if mandatory on command line
* @param defaultLogbackResource the default logback resource, null to use tool-logback.xml as the default
* @param toolContextClass the type of tool context to create, should match the generic type argument
* @return true if successful, false otherwise
*/
public boolean initAndRun(final String[] args, final String defaultConfigResource, final String defaultLogbackResource,
final Class<? extends T> toolContextClass) {
ArgumentChecker.notNull(args, "args");
final Options options = createOptions(defaultConfigResource == null);
final CommandLineParser parser = new PosixParser();
CommandLine line;
try {
line = parser.parse(options, args);
} catch (final ParseException e) {
System.err.println(e.getMessage());
usage(options);
return false;
}
_commandLine = line;
if (line.hasOption(HELP_OPTION)) {
usage(options);
return true;
}
String logbackResource = line.getOptionValue(LOGBACK_RESOURCE_OPTION);
logbackResource = StringUtils.defaultIfEmpty(logbackResource, ToolUtils.getDefaultLogbackConfiguration());
String[] configResources = line.getOptionValues(CONFIG_RESOURCE_OPTION);
if (configResources == null || configResources.length == 0) {
configResources = new String[] {defaultConfigResource};
}
return init(logbackResource) && run(configResources, toolContextClass);
}
/**
* Runs the tool.
* <p>
* This starts the tool context and calls {@link #run(ToolContext)}. This will catch exceptions and print a stack trace.
*
* @param configResource the config resource location, not null
* @param toolContextClass the type of tool context to create, should match the generic type argument
* @return true if successful
*/
public final boolean run(final String configResource, final Class<? extends T> toolContextClass) {
return run(new String[] {configResource}, toolContextClass);
}
/**
* Runs the tool.
* <p>
* This starts the tool contexts and calls {@link #run(ToolContexts)}. This will catch exceptions and print a stack trace.
*
* @param configResources the config resource locations for multiple tool contexts, not null
* @param toolContextClass the type of tool context to create, should match the generic type argument
* @return true if successful
*/
@SuppressWarnings("unchecked")
public final boolean run(final String[] configResources, final Class<? extends T> toolContextClass) {
ToolContext[] toolContexts = null;
try {
ArgumentChecker.notEmpty(configResources, "configResources");
s_logger.info("Starting " + getClass().getSimpleName());
toolContexts = new ToolContext[configResources.length];
for (int i = 0; i < configResources.length; i++) {
s_logger.info("Populating tool context " + (i + 1) + " of " + configResources.length + "...");
toolContexts[i] = ToolContextUtils.getToolContext(configResources[i], toolContextClass);
}
s_logger.info("Running " + getClass().getSimpleName());
run((T[]) toolContexts);
s_logger.info("Finished " + getClass().getSimpleName());
return true;
} catch (final Exception ex) {
s_logger.error("Caught exception", ex);
ex.printStackTrace();
return false;
} finally {
if (toolContexts != null) {
for (final ToolContext toolContext : toolContexts) {
if (toolContext != null) {
try {
toolContext.close();
} catch (final Exception e) {
s_logger.error("Caught exception", e);
}
}
}
}
}
}
/**
* Runs the tool, calling {@code doRun}.
* <p>
* This will catch not handle exceptions, but will convert checked exceptions to unchecked.
*
* @param toolContext the tool context, not null
* @throws RuntimeException if an error occurs
*/
@SuppressWarnings("unchecked")
public final void run(final T toolContext) {
run((T[]) new ToolContext[] {toolContext});
}
/**
* Runs the tool, calling {@code doRun}.
* <p>
* This will catch not handle exceptions, but will convert checked exceptions to unchecked.
*
* @param toolContexts the tool contexts, not null or empty
* @throws RuntimeException if an error occurs
*/
public final void run(final T[] toolContexts) {
_toolContexts = toolContexts;
try {
doRun();
} catch (final RuntimeException ex) {
throw ex;
} catch (final Exception ex) {
throw new RuntimeException(ex);
} finally {
_toolContexts = null;
}
}
//-------------------------------------------------------------------------
/**
* Override in subclasses to implement the tool.
*
* @throws Exception if an error occurs
*/
protected abstract void doRun() throws Exception;
//-------------------------------------------------------------------------
/**
* Gets the (first) tool context.
*
* @return the context, not null during {@code doRun}
*/
protected T getToolContext() {
return getToolContext(0);
}
//-------------------------------------------------------------------------
/**
* Gets the i-th tool context.
*
* @param i the index of the tool context to retrieve
* @return the i-th context, not null during {@code doRun}
*/
protected T getToolContext(final int i) {
ArgumentChecker.notNegative(i, "ToolContext index");
if (getToolContexts().length > i) {
return getToolContexts()[i];
} else {
throw new OpenGammaRuntimeException("ToolContext " + i + " does not exist");
}
}
//-------------------------------------------------------------------------
/**
* Gets all tool contexts.
*
* @return the array of contexts, not null or empty during {@code doRun}
*/
protected T[] getToolContexts() {
return _toolContexts;
}
/**
* Gets the parsed command line.
*
* @return the parsed command line, not null after parsing
*/
protected CommandLine getCommandLine() {
return _commandLine;
}
//-------------------------------------------------------------------------
/**
* Creates the command line options.
* <p>
* Subclasses may override this and add their own parameters. The base class defined the options h/help, c/config, l/logback.
*
* @param mandatoryConfigResource whether the config resource is mandatory
* @return the set of command line options, not null
*/
protected Options createOptions(final boolean mandatoryConfigResource) {
final Options options = new Options();
options.addOption(createHelpOption());
options.addOption(createConfigOption(mandatoryConfigResource));
options.addOption(createLogbackOption());
return options;
}
private static Option createHelpOption() {
return new Option(HELP_OPTION, "help", false, "prints this message");
}
private static Option createConfigOption(final boolean mandatoryConfigResource) {
final Option option = new Option(CONFIG_RESOURCE_OPTION, "config", true, "the toolcontext configuration resource");
option.setArgName("resource");
option.setRequired(mandatoryConfigResource);
return option;
}
private static Option createLogbackOption() {
final Option option = new Option(LOGBACK_RESOURCE_OPTION, "logback", true, "the logback configuration resource");
option.setArgName("resource");
option.setRequired(false);
return option;
}
protected Class<?> getEntryPointClass() {
return getClass();
}
protected void usage(final Options options) {
final HelpFormatter formatter = new HelpFormatter();
formatter.setWidth(120);
formatter.printHelp("java " + getEntryPointClass().getName(), options, true);
}
}