/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.component;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.ShutdownUtils;
import com.opengamma.util.StartupUtils;
import com.opengamma.util.auth.AuthUtils;
/**
* Main entry point for OpenGamma component-based servers.
* <p>
* This class starts an OpenGamma JVM process using the specified config file.
* A {@link OpenGammaComponentServerMonitor monitor} thread will also be started.
* <p>
* Two types of config file format are recognized - properties and INI.
* A properties file must be in the standard Java format and contain a key "MANAGER.NEXT.FILE"
* which is the resource location of the main INI file.
* The INI file is described in {@link ComponentConfigIniLoader}.
* <p>
* This class is not thread-safe.
* A new instance should be created for each thread.
*/
public class OpenGammaComponentServer {
/**
* The server name property.
* DO NOT deduplicate with the same value in ComponentManager.
* This constant is used to set a system property before ComponentManager is class loaded.
*/
private static final String OPENGAMMA_SERVER_NAME = "og.server.name";
/**
* Help command line option.
*/
private static final String HELP_OPTION = "help";
/**
* Verbose command line option.
*/
private static final String VERBOSE_OPTION = "verbose";
/**
* Quiet command line option.
*/
private static final String QUIET_OPTION = "quiet";
/**
* Load-only command line option.
*/
private static final String LOAD_ONLY_OPTION = "load-only";
/**
* Property-display command line option.
*/
private static final String PROPERTY_DISPLAY_OPTION = "property-display";
/**
* Command line options.
*/
private static final Options OPTIONS = getOptions();
/**
* Message logged when startup begins.
*/
public static final String STARTING_MESSAGE = "======== STARTING OPENGAMMA ========";
/**
* Message logged if startup fails.
*/
public static final String STARTUP_FAILED_MESSAGE = "======== OPENGAMMA STARTUP FAILED ========";
/**
* Prefix of the message logged when startup completes.
*/
public static final String STARTUP_COMPLETE_MESSAGE = "======== OPENGAMMA STARTED in ";
/**
* The logger in use.
*/
private ComponentLogger _logger = ComponentLogger.Console.VERBOSE;
static {
StartupUtils.init();
}
/**
* Main method to start an OpenGamma JVM process.
*
* @param args the arguments
*/
public static void main(String[] args) { // CSIGNORE
if (!new OpenGammaComponentServer().run(args)) {
ShutdownUtils.exit(-1);
}
}
//-------------------------------------------------------------------------
/**
* Runs the server.
* <p>
* This takes the same arguments as the standard main method command line.
*
* @param args the arguments, not null
* @return true if the server is started, false if there was a problem
* @throws RuntimeException if an error occurs
*/
public boolean run(String[] args) {
// parse command line
CommandLine cmdLine;
try {
cmdLine = (new PosixParser()).parse(OPTIONS, args);
} catch (ParseException ex) {
_logger.logError(ex.getMessage());
usage();
return false;
}
// help option
if (cmdLine.hasOption(HELP_OPTION)) {
usage();
return false;
}
// logger option
int verbosity = 2;
if (cmdLine.hasOption(VERBOSE_OPTION)) {
verbosity = 3;
} else if (cmdLine.hasOption(QUIET_OPTION)) {
verbosity = 0;
}
_logger = createLogger(verbosity);
// config file
args = cmdLine.getArgs();
if (args.length == 0) {
_logger.logError("No config file specified");
usage();
return false;
}
String configFile = args[0];
// properties
Map<String, String> properties = new HashMap<>();
if (args.length > 1) {
for (int i = 1; i < args.length; i++) {
String arg = args[i];
int equalsPosition = arg.indexOf('=');
if (equalsPosition < 0) {
throw new ComponentConfigException("Invalid property format, must be key=value (no spaces)");
}
String key = arg.substring(0, equalsPosition).trim();
String value = arg.substring(equalsPosition + 1).trim();
if (key.length() == 0) {
throw new ComponentConfigException("Invalid empty property key");
}
if (properties.containsKey(key)) {
throw new ComponentConfigException("Invalid property, key '" + key + "' specified twice");
}
properties.put(key, value);
}
}
// run
if (cmdLine.hasOption(PROPERTY_DISPLAY_OPTION)) {
return displayProperty(configFile, properties, cmdLine.getOptionValue(PROPERTY_DISPLAY_OPTION));
} else if (cmdLine.hasOption(LOAD_ONLY_OPTION)) {
return loadOnly(configFile, properties);
} else {
return (run(configFile, properties) != null);
}
}
//-------------------------------------------------------------------------
/**
* Runs the server from application code.
* <p>
* This is intended for use by applications that wrap the server startup
* in another class with its own main method. This would typically be
* done to control the set of override properties using code.
* <p>
* A variety of loggers are provided in {@link ComponentLogger} nested classes.
* The {@code ComponentLogger.Console.VERBOSE} logger is used if null is passed in.
*
* @param configFile the configuration file to use, not null
* @param properties the set of override properties to use, not null
* @param logger the logger to use, null uses verbose
* @return the component repository, null if there was an error
* @throws RuntimeException if an error occurs
*/
public ComponentRepository run(String configFile, Map<String, String> properties, ComponentLogger logger) {
ArgumentChecker.notNull(configFile, "configFile");
ArgumentChecker.notNull(properties, "properties");
_logger = (logger != null ? logger : ComponentLogger.Console.VERBOSE);
return run(configFile, properties);
}
/**
* Initializes a {@code ComponentManager} using a configuration file and properties.
* <p>
* This is intended for use by applications that wrap this class.
* It allows the caller to initialize a {@link ComponentManager} in a manner
* identical to that of a standard system startup.
* <p>
* The manager will have had the {@code load(String)} method called.
* It can be queried to see the configuration loaded. It can also be used
* to actually start the system using {@code init()} and {@code start()}.
* <p>
* This method will throw an exception on errors rather than using a logger.
*
* @param configFile the configuration file to use, not null
* @param properties the set of override properties to use, not null
* @return the property value, null if no such property
* @throws RuntimeException if an error occurs
*/
public ComponentManager createManager(String configFile, Map<String, String> properties) {
ArgumentChecker.notNull(configFile, "configFile");
ArgumentChecker.notNull(properties, "properties");
_logger = ComponentLogger.Throws.INSTANCE;
ComponentManager manager = buildManager(configFile, properties);
manager.load(configFile);
return manager;
}
//-------------------------------------------------------------------------
/**
* Loads the config files without starting the server.
*
* @param configFile the config file, not null
* @param properties the properties read from the command line, not null
* @return false always
*/
protected boolean loadOnly(String configFile, Map<String, String> properties) {
_logger.logDebug(" Config locator: " + configFile);
try {
ComponentManager manager = buildManager(configFile, properties);
manager.load(configFile);
} catch (Throwable ex) {
_logger.logError(ex);
return false;
}
return false;
}
//-------------------------------------------------------------------------
/**
* Displays the value of the property from the config files without starting the server.
*
* @param configFile the config file, not null
* @param properties the properties read from the command line, not null
* @param property the property to display, not null
* @return false always
*/
protected boolean displayProperty(String configFile, Map<String, String> properties, String property) {
try {
String value = queryProperty(configFile, properties, property);
if (value == null) {
System.out.println("NO-SUCH-PROPERTY");
} else {
System.out.println(value);
}
} catch (Throwable ex) {
_logger.logError(ex);
return false;
}
return false;
}
/**
* Queries the value of the property from the config files without starting the server.
*
* @param configFile the config file, not null
* @param properties the properties read from the command line, not null
* @param property the property to display, not null
* @return the property value, null if no such property
* @throws RuntimeException if an error occurs
*/
protected String queryProperty(String configFile, Map<String, String> properties, String property) {
ComponentManager manager = buildManager(configFile, properties);
manager.load(configFile);
return manager.getProperties().getValue(property);
}
//-------------------------------------------------------------------------
/**
* Runs the server with config file.
*
* @param configFile the config file, not null
* @param properties the properties read from the command line, not null
* @return true if the server was started, false if there was a problem
*/
protected ComponentRepository run(String configFile, Map<String, String> properties) {
long start = System.nanoTime();
_logger.logInfo(STARTING_MESSAGE);
_logger.logDebug(" Config locator: " + configFile);
ComponentRepository repo;
try {
ComponentManager manager = buildManager(configFile, properties);
serverStarting(manager);
repo = manager.start(configFile);
checkSecurityManager();
} catch (Throwable ex) {
_logger.logError(ex);
_logger.logError(STARTUP_FAILED_MESSAGE);
return null;
}
long end = System.nanoTime();
_logger.logInfo(STARTUP_COMPLETE_MESSAGE + ((end - start) / 1000000) + "ms ========");
return repo;
}
//-------------------------------------------------------------------------
/**
* Builds the component manager.
*
* @param configFile the config file, not null
* @param properties the properties read from the command line, not null
* @return the manager, not null
*/
protected ComponentManager buildManager(String configFile, Map<String, String> properties) {
String serverName = extractServerName(configFile);
System.setProperty(OPENGAMMA_SERVER_NAME, serverName);
ComponentManager manager = createManager(serverName);
manager.getProperties().putAll(properties);
return manager;
}
/**
* Extracts the server name.
* <p>
* This examines the first part of the file name and the last directory,
* merging these with a dash.
*
* @param fileName the name to extract from, not null
* @return the server name, not null
*/
protected String extractServerName(String fileName) {
if (fileName.contains(":")) {
fileName = StringUtils.substringAfter(fileName, ":");
}
fileName = FilenameUtils.removeExtension(fileName);
String first = FilenameUtils.getName(FilenameUtils.getPathNoEndSeparator(fileName));
String second = FilenameUtils.getName(fileName);
if (StringUtils.isEmpty(first) || first.equals(second) || second.startsWith(first + "-")) {
return second;
}
return first + "-" + second;
}
/**
* Called just before the server is started. The default implementation here
* creates a monitor thread that allows the server to be stopped remotely.
*
* @param manager the component manager
*/
protected void serverStarting(final ComponentManager manager) {
OpenGammaComponentServerMonitor.create(manager.getRepository());
}
/**
* Called once the server has started to check the security manager.
*/
protected void checkSecurityManager() {
try {
if (AuthUtils.isDefault()) {
_logger.logWarn("*****************************************************************");
_logger.logWarn(" Warning: Server running with default permissive SecurityManager ");
_logger.logWarn("*****************************************************************");
}
} catch (UnavailableSecurityManagerException ex) {
_logger.logError("***************************************************");
_logger.logError(" Error: Server running without any SecurityManager ");
_logger.logError("***************************************************");
}
}
//-------------------------------------------------------------------------
/**
* Creates the logger.
*
* @param verbosity the verbosity required, 0=errors, 3=debug
* @return the logger, not null
*/
protected ComponentLogger createLogger(int verbosity) {
return new ComponentLogger.Console(verbosity);
}
/**
* Creates the component manager.
*
* @param serverName the server name, not null
* @return the manager, not null
*/
protected ComponentManager createManager(String serverName) {
return new ComponentManager(serverName, _logger);
}
//-------------------------------------------------------------------------
private void usage() {
HelpFormatter helpFormatter = new HelpFormatter();
helpFormatter.setWidth(100);
helpFormatter.printHelp(getClass().getSimpleName() + " [options] configFile", OPTIONS);
}
private static Options getOptions() {
Options options = new Options();
options.addOption(new Option("h", HELP_OPTION, false, "print this help message"));
options.addOptionGroup(new OptionGroup()
.addOption(new Option("l", LOAD_ONLY_OPTION, false, "load the config, but do not start the server"))
.addOption(new Option("p", PROPERTY_DISPLAY_OPTION, true, "displays the calculated value of a property")));
options.addOptionGroup(new OptionGroup()
.addOption(new Option("q", QUIET_OPTION, false, "be quiet during startup"))
.addOption(new Option("v", VERBOSE_OPTION, false, "be verbose during startup")));
return options;
}
}