// $HeadURL: // http://seanderickson1@forge.abcd.harvard.edu/svn/screensaver/trunk/core/src/main/java/edu/harvard/med/screensaver/io/CommandLineApplication.java // $ // $Id$ // // Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College. // // Screensaver is an open-source project developed by the ICCB-L and NSRB labs // at Harvard Medical School. This software is distributed under the terms of // the GNU General Public License. package edu.harvard.med.screensaver.io; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.log4j.Logger; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import edu.harvard.med.screensaver.DatabaseConnectionSettings; import edu.harvard.med.screensaver.db.CommandLineArgumentsDatabaseConnectionSettingsResolver; import edu.harvard.med.screensaver.db.GenericEntityDAO; import edu.harvard.med.screensaver.model.users.AdministratorUser; /** * Convenience class for instantiating a Screensaver-based command-line * application. The main purpose of this class is to house the Spring framework * bootstrapping code, so that developers can forget the details of how to do * this (and don't have to cut and paste code between various main() methods). * Also provides help with: * <ul> * <li>command-line argument parsing (including special-case handling of database connection options) * <li>obtaining Spring-managed beans. * <ul> * . * <p> * Normally, a screensaver distribution will use the database connection settings specified in * "classpath:screensaver.properties". However, if {@link #processOptions} is called with * acceptDatabaseOptions=true, the command line options 'dbhost', 'dbport', 'dbname', 'dbuser', and 'dbpassword' will be * used to configure the database connection settings. * * @author <a mailto="andrew_tolopko@hms.harvard.edu">Andrew Tolopko</a> * @author <a mailto="john_sullivan@hms.harvard.edu">John Sullivan</a> */ public class CommandLineApplication { private static final Logger log = Logger.getLogger(CommandLineApplication.class); public static final String DEFAULT_SPRING_CONFIG = "spring-context-cmdline.xml"; public static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd"; // instance data private String _springConfigurationResource = DEFAULT_SPRING_CONFIG; private ApplicationContext _appCtx; private Options _options; private CommandLine _cmdLine; private String[] _cmdLineArgs; private Map<String,Object> _option2DefaultValue = new HashMap<String,Object>(); private Option _lastAccessOption; // public methods @SuppressWarnings("static-access") public CommandLineApplication(String[] cmdLineArgs) { _cmdLineArgs = cmdLineArgs; _options = new Options(); _options.addOption(OptionBuilder. withLongOpt("help"). withDescription("print this help"). create("h")); } public Object getSpringBean(String springBeanName) { return getSpringApplicationContext().getBean(springBeanName); } @SuppressWarnings("unchecked") public <T> T getSpringBean(String springBeanName, Class<T> ofType) { return (T) getSpringApplicationContext().getBean(springBeanName); } public ApplicationContext getSpringApplicationContext() { if (_appCtx == null) { _appCtx = new ClassPathXmlApplicationContext(getSpringConfigurationResource()); } return _appCtx; } /** * Override this method to specify a different spring configuration resource * file. * * @return the name of the spring configuration resource file (resource name * relative to the classpath root). */ protected String getSpringConfigurationResource() { return _springConfigurationResource; } public void addCommandLineOption(Option option) { _options.addOption(option); } public void addCommandLineOption(Option option, Object defaultValue) { _options.addOption(option); if (option.hasArgs()) { if (defaultValue instanceof List) { throw new IllegalArgumentException("when option takes multiple args, defaultValue must be a List"); } } _option2DefaultValue.put(option.getOpt(), defaultValue); } public String getCommandLineOptionValue(String optionName) { verifyOptionsProcessed(); _lastAccessOption = _options.getOption(optionName); if (!_cmdLine.hasOption(optionName) && _option2DefaultValue.containsKey(optionName)) { return _option2DefaultValue.get(optionName).toString(); } Object optionValue = _cmdLine.getOptionValue(optionName); return optionValue == null ? "" : optionValue.toString(); } public List<String> getCommandLineOptionValues(String optionName) { verifyOptionsProcessed(); _lastAccessOption = _options.getOption(optionName); List<String> optionValues = new ArrayList<String>(); if (!_cmdLine.hasOption(optionName) && _option2DefaultValue.containsKey(optionName)) { for (Object defaultValue : (List<?>) _option2DefaultValue.get(optionName)) { optionValues.add(defaultValue.toString()); } } else { String[] optionValuesArray = _cmdLine.getOptionValues(optionName); if (optionValuesArray != null) { optionValues.addAll(Arrays.asList(optionValuesArray)); } } return optionValues; } public <T> T getCommandLineOptionValue(String optionName, Class<T> ofType) { verifyOptionsProcessed(); _lastAccessOption = _options.getOption(optionName); if (!_cmdLine.hasOption(optionName) && _option2DefaultValue.containsKey(optionName)) { return (T) _option2DefaultValue.get(optionName); } if (_cmdLine.hasOption(optionName)) { Object value = getCommandLineOptionValue(optionName); try { Constructor cstr = ofType.getConstructor(String.class); return (T) cstr.newInstance(value); } catch (Exception e) { showHelpAndExit("could not parse option " + optionName + " with arg " + value + " as type " + ofType.getSimpleName() + ": " + e); } } return null; } public <T extends Enum<T>> T getCommandLineOptionEnumValue(String optionName, Class<T> ofEnum) { verifyOptionsProcessed(); _lastAccessOption = _options.getOption(optionName); if (!_cmdLine.hasOption(optionName) && _option2DefaultValue.containsKey(optionName)) { return (T) _option2DefaultValue.get(optionName); } if (_cmdLine.hasOption(optionName)) { Object value = getCommandLineOptionValue(optionName); try { Enum<T> valueOf = Enum.valueOf(ofEnum, value.toString().toUpperCase()); return (T) valueOf; } catch (Exception e) { showHelpAndExit("could not parse option " + optionName + " with arg " + value + " as enum " + ofEnum.getClass().getSimpleName()); } } return null; } public DateTime getCommandLineOptionValue(String optionName, DateTimeFormatter formatter) throws ParseException { verifyOptionsProcessed(); _lastAccessOption = _options.getOption(optionName); if (!_cmdLine.hasOption(optionName) && _option2DefaultValue.containsKey(optionName)) { return (DateTime) _option2DefaultValue.get(optionName); } if (_cmdLine.hasOption(optionName)) { try { String value = getCommandLineOptionValue(optionName); return formatter.parseDateTime(value); } catch (Exception e) { throw new ParseException("could not parse date option " + optionName); } } return null; } public boolean isCommandLineFlagSet(String optionName) { verifyOptionsProcessed(); return _cmdLine.hasOption(optionName); } public void showHelpAndExit(String errorMessage) { if (errorMessage != null) { System.out.println(errorMessage); } new HelpFormatter().printHelp("command", _options, true); System.exit(errorMessage != null ? 1 : 0); } /** * Parses command line arguments. On error, shows help and exits JVM process with status 1. * * @param acceptDatabaseOptions * @param acceptAdminUserOptions */ @SuppressWarnings("unchecked") public void processOptions(boolean acceptDatabaseOptions, boolean acceptAdminUserOptions) { if (acceptDatabaseOptions) { addDatabaseOptions(); } if (acceptAdminUserOptions) { addAdministratorUserOptions(); } try { _cmdLine = new GnuParser().parse(_options, _cmdLineArgs); if (acceptDatabaseOptions) { if (isCommandLineFlagSet("D")) { DatabaseConnectionSettings settings = // note: we want non-specified options to be recorded as nulls, not as empty strings, as these have // different meanings (null meaning that the option has not been provided by the user at all) new DatabaseConnectionSettings(isCommandLineFlagSet("H") ? getCommandLineOptionValue("H") : null, getCommandLineOptionValue("T", Integer.class), getCommandLineOptionValue("D"), isCommandLineFlagSet("U") ? getCommandLineOptionValue("U") : null, isCommandLineFlagSet("P") ? getCommandLineOptionValue("P") : null); System.getProperties().put(CommandLineArgumentsDatabaseConnectionSettingsResolver.CMD_LINE_ARGS_DATABASE_CONNECTION_SETTINGS, settings); } } log.info(toString()); } catch (ParseException e) { showHelpAndExit(e.getMessage()); } if (_cmdLine.hasOption("help")) { showHelpAndExit(null); } } public String toString() { StringBuilder s = new StringBuilder(); for (Option option : (Collection<Option>) _options.getOptions()) { if (_cmdLine.hasOption(option.getOpt())) { if (s.length() > 0) { s.append(", "); } s.append(option.getLongOpt()); if (option.hasArg()) { s.append("=").append(_cmdLine.getOptionValue(option.getOpt())); } } } return "command line options: " + s.toString(); } // private methods private void verifyOptionsProcessed() { if (_cmdLine == null) { throw new IllegalStateException("processOptions() not yet called or error occurred parsing command line options"); } } /** * Adds "dbhost", "dbport", "dbuser", "dbpassword", and "dbname" options to * the command line parser, enabling this CommandLineApplication to access a * database. */ @SuppressWarnings("static-access") private void addDatabaseOptions() { addCommandLineOption(OptionBuilder. hasArg(). withArgName("host name"). withLongOpt("dbhost"). withDescription("database host"). create("H")); addCommandLineOption(OptionBuilder. hasArg(). withArgName("port"). withLongOpt("dbport"). withDescription("database port"). create("T")); addCommandLineOption(OptionBuilder. hasArg(). withArgName("user name"). withLongOpt("dbuser"). withDescription("database user name"). create("U")); addCommandLineOption(OptionBuilder. hasArg(). withArgName("password"). withLongOpt("dbpassword"). withDescription("database user's password"). create("P")); addCommandLineOption(OptionBuilder. hasArg(). withArgName("database"). withLongOpt("dbname"). withDescription("database name"). create("D")); } @SuppressWarnings("static-access") private void addAdministratorUserOptions() { addCommandLineOption(OptionBuilder .hasArg() .withArgName("user ID #") .withDescription("the User ID of the administrative user performing the load") .withLongOpt("admin-id") .create("A")); addCommandLineOption(OptionBuilder .hasArg() .withArgName("login ID") .withDescription("the Login ID of the administrative user performing the load") .withLongOpt("admin-login-id") .create("AL")); addCommandLineOption(OptionBuilder .hasArg() .withArgName("eCommons ID") .withDescription("the eCommons ID of the administrative user performing the load") .withLongOpt("admin-ecommons-id") .create("AE")); } private AdministratorUser _admin; /** * Find the admin user from the command line flag. If not set, then throw an IllegalArgumentException * @return AdministratorUser running the program * @throws IllegalArgumentException */ public AdministratorUser findAdministratorUser() { if (_admin == null) { String criterionMessage; GenericEntityDAO dao = (GenericEntityDAO) getSpringBean("genericEntityDao"); if (isCommandLineFlagSet("-A")) { Integer userId = getCommandLineOptionValue("-A", Integer.class); _admin = dao.findEntityById(AdministratorUser.class, userId); criterionMessage = "User ID=" + userId; } else if (isCommandLineFlagSet("-AL")) { String loginId = getCommandLineOptionValue("-AL"); _admin = dao.findEntityByProperty(AdministratorUser.class, "loginId", loginId); criterionMessage = "Login ID=" + loginId; } else if (isCommandLineFlagSet("-AE")) { String ecommonsId = getCommandLineOptionValue("-AE"); _admin = dao.findEntityByProperty(AdministratorUser.class, "ECommonsId", ecommonsId); criterionMessage = "eCommons ID=" + ecommonsId; } else { throw new IllegalArgumentException("option --admin-id, --admin-login-id, or --user-ecommons-id (admin) must be speciifed"); } if (_admin == null) { throw new IllegalArgumentException("no administrator user found with " + criterionMessage); } } return _admin; } public void setSpringConfigurationResource(String springConfigurationResource) { if (_appCtx != null) { throw new IllegalStateException("spring application context has already been instantiated; it is too late to set spring configuration resource"); } _springConfigurationResource = springConfigurationResource; } public Option getLastAccessOption() { return _lastAccessOption; } }