/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan; import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger; import static fr.ens.biologie.genomique.eoulsan.Globals.MINIMAL_JAVA_VERSION_REQUIRED; import static fr.ens.biologie.genomique.eoulsan.util.SystemUtils.getJavaVersion; import static java.util.Collections.unmodifiableList; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.logging.Handler; import java.util.logging.Level; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import com.google.common.base.Joiner; import fr.ens.biologie.genomique.eoulsan.actions.Action; import fr.ens.biologie.genomique.eoulsan.actions.ActionService; import fr.ens.biologie.genomique.eoulsan.util.SystemUtils; /** * This class is the main class. Check the environment, if Hadoop library is in * the classpath launch Hadoop main class else run local main class. * @since 1.0 * @author Laurent Jourdren */ public abstract class Main { public static final String EOULSAN_CLASSPATH_JVM_ARG = "eoulsan.classpath"; public static final String EOULSAN_SCRIPT = "eoulsan.launch.script.path"; public static final String EOULSAN_MEMORY = "eoulsan.memory"; private static Main main; private final String launchModeName; private final List<String> args; private Action action; private List<String> actionArgs; private String logLevel; private String logFile; private String conf; private List<String> commandLineSettings; private final BufferedHandler handler = new BufferedHandler(); // // Getters // /** * Get the instance of the Main class. * @return a Main object */ public static Main getInstance() { return main; } /** * Get java executable path. * @return the path to the java executable */ public String getJavaExecutablePath() { return System.getProperty("java.home") + "/bin/java"; } /** * Get JVM arguments. * @return the JVM arguments as an array */ public List<String> getJVMArgs() { return ManagementFactory.getRuntimeMXBean().getInputArguments(); } /** * Get Eoulsan classpath. The result of the method is based on the content of * the -Deoulsan.hadoop.libs JVM argument. * @return the JVM class as a String */ public String getClassPath() { return System.getProperty(EOULSAN_CLASSPATH_JVM_ARG); } /** * Get Eoulsan script path. * @return the Eoulsan script path */ public String getEoulsanScriptPath() { return System.getProperty(EOULSAN_SCRIPT); } /** * Get Eoulsan memory requirement. * @return the Eoulsan memory requirement */ public int getEoulsanMemory() { String value = System.getProperty(EOULSAN_MEMORY); if (value == null) { return -1; } try { return Integer.parseInt(value.trim()); } catch (NumberFormatException e) { return -1; } } /** * Get Eoulsan directory. * @return the Eoulsan directory */ public File getEoulsanDirectory() { final File scriptFile = new File(getEoulsanScriptPath()); return scriptFile.getParentFile(); } /** * Get command line arguments. * @return Returns the arguments */ public List<String> getArgs() { return unmodifiableList(this.args); } /** * Get the action. * @return Returns the action */ public Action getAction() { return this.action; } /** * Get the action arguments. * @return Returns the actionArgs */ public List<String> getActionArgs() { return unmodifiableList(this.actionArgs); } /** * Get the log level arguments. * @return Returns the logLevel */ public String getLogLevelArgument() { return this.logLevel; } /** * Get the log file argument. * @return Returns the log */ public String getLogFileArgument() { return this.logFile; } /** * Get the configuration file argument. * @return Returns the configuration file */ public String getConfigurationFileArgument() { return this.conf; } /** * Get the command line settings arguments. * @return a list with the settings defined in the command line */ public List<String> getCommandLineSettings() { if (this.commandLineSettings == null) { return Collections.emptyList(); } return unmodifiableList(this.commandLineSettings); } /** * Get the path to the launch script. * @return the path to the launch script or null if no launch script has been * used */ public String getLaunchScriptPath() { return System.getProperty(Globals.LAUNCH_SCRIPT_PATH); } /** * Get the launch mode of the application. * @return the launch mode of the application */ public String getLaunchMode() { return this.launchModeName; } // // Parsing methods // /** * Show command line help. * @param options Options of the software */ protected void help(final Options options) { // Show help message final HelpFormatter formatter = new HelpFormatter(); formatter.printHelp(getHelpEoulsanCommand() + " [options] action arguments", options); System.out.println("Available actions:"); for (Action action : ActionService.getInstance().getActions()) { if (!action.isHadoopJarMode() && !action.isHidden()) { System.out.println(" - " + action.getName() + "\t" + action.getDescription() + (!action.isCurrentArchCompatible() ? " (not available for your platform)." : "")); } } Common.exit(0); } /** * Create options for command line * @return an Options object */ @SuppressWarnings("static-access") protected Options makeOptions() { // create Options object final Options options = new Options(); options.addOption("version", false, "show version of the software"); options.addOption("about", false, "display information about this software"); options.addOption("h", "help", false, "display this help"); options.addOption("license", false, "display information about the license of this software"); options.addOption(OptionBuilder.withArgName("file").hasArg() .withDescription("configuration file to use").create("conf")); options.addOption(OptionBuilder.withArgName("property=value").hasArg() .withDescription("set a configuration setting. This " + "option can be used several times") .create('s')); options.addOption(OptionBuilder.withArgName("file").hasArg() .withDescription("external log file").create("log")); options.addOption(OptionBuilder.withArgName("level").hasArg() .withDescription("log level").create("loglevel")); return options; } /** * Parse the options of the command line * @return the number of options argument in the command line */ private int parseCommandLine() { final Options options = makeOptions(); final CommandLineParser parser = new GnuParser(); final String[] argsArray = this.args.toArray(new String[this.args.size()]); int argsOptions = 0; try { // parse the command line arguments final CommandLine line = parser.parse(options, argsArray, true); // Help option if (line.hasOption("help")) { help(options); } // About option if (line.hasOption("about")) { Common.showMessageAndExit(Globals.ABOUT_TXT); } // Version option if (line.hasOption("version")) { Common.showMessageAndExit(Globals.WELCOME_MSG); } // Licence option if (line.hasOption("license")) { Common.showMessageAndExit(Globals.LICENSE_TXT); } // Set Log file if (line.hasOption("log")) { argsOptions += 2; this.logFile = line.getOptionValue("log"); } // Set log level if (line.hasOption("loglevel")) { argsOptions += 2; this.logLevel = line.getOptionValue("loglevel"); } // Set the configuration file if (line.hasOption("conf")) { argsOptions += 2; this.conf = line.getOptionValue("conf"); } // Set the configuration settings if (line.hasOption('s')) { this.commandLineSettings = Arrays.asList(line.getOptionValues('s')); argsOptions += 2 * this.commandLineSettings.size(); } // eoulsan.sh options if (line.hasOption('j')) { argsOptions += 2; } if (line.hasOption('m')) { argsOptions += 2; } if (line.hasOption('J')) { argsOptions += 2; } if (line.hasOption('p')) { argsOptions += 2; } if (line.hasOption('w')) { argsOptions += 2; } } catch (ParseException e) { Common.errorExit(e, "Error while parsing command line arguments: " + e.getMessage()); } // No arguments found if (this.args == null || this.args.size() == argsOptions) { Common.showErrorMessageAndExit("This program needs one argument." + " Use the -h option to get more information.\n"); } return argsOptions; } // // Other methods // /** * Get in a string with all arch * @return a string with */ private static String availableArchsToString() { final StringBuilder sb = new StringBuilder(); boolean first = true; for (String osArch : Globals.AVAILABLE_BINARY_ARCH) { if (first) { first = false; } else { sb.append(", "); } sb.append(osArch.replace('\t', '/')); } return sb.toString(); } private void startupLog() { // Welcome message getLogger().info("Welcome to " + Globals.WELCOME_MSG); getLogger().info("Start in " + this.launchModeName + " mode"); // Show versions getLogger() .info(Globals.APP_NAME + " version: " + Globals.APP_VERSION_STRING); getLogger() .info(Globals.APP_NAME + " revision: " + Globals.APP_BUILD_COMMIT); getLogger() .info(Globals.APP_NAME + " build date: " + Globals.APP_BUILD_DATE); // Startup script getLogger().info(Globals.APP_NAME + " Startup script: " + (getLaunchScriptPath() == null ? "(no startup script)" : getLaunchScriptPath())); // Command line arguments final List<String> args = new ArrayList<>(); for (String a : getArgs()) { if (a.indexOf(' ') != -1) { args.add("\"" + a + "\""); } else { args.add(a); } } getLogger().info(Globals.APP_NAME + " Command line arguments: " + Joiner.on(' ').join(args)); // Log file getLogger() .info("Log file: " + (this.logFile == null ? "(none)" : this.logFile)); // Log level getLogger().info("Log level: " + getLogger().getLevel()); } /** * Log system information. */ protected void sysInfoLog() { // Host getLogger().info("Host: " + SystemUtils.getHostName()); // Operating system getLogger().info("Operating system name: " + System.getProperty("os.name")); getLogger() .info("Operating system version: " + System.getProperty("os.version")); getLogger().info("Operating system arch: " + System.getProperty("os.arch")); // User information getLogger().info("User name: " + System.getProperty("user.name")); getLogger().info("User home: " + System.getProperty("user.home")); getLogger() .info("User current directory: " + System.getProperty("user.dir")); // Java version getLogger().info("Java vendor: " + System.getProperty("java.vendor")); getLogger().info("Java vm name: " + System.getProperty("java.vm.name")); getLogger().info("Java version: " + System.getProperty("java.version")); } /** * Load the configuration file if exists. * @return a new Settings object * @throws IOException if an error occurs while reading the configuration file * @throws EoulsanException if an error occurs while reading the configuration * file */ private Settings loadConfigurationFile() throws IOException, EoulsanException { // Load the setting file if has been defined in command line if (this.conf != null) { return new Settings(new File(this.conf)); } // Define the default configuration file final File defaultConfFile = new File(Settings.getConfigurationFilePath()); // Test if default configuration file exists if (defaultConfFile.exists()) { this.conf = defaultConfFile.getAbsolutePath(); return new Settings(defaultConfFile); } getLogger().config("No configuration file found."); return new Settings(false); } /** * Set the command line settings entry. * @param settings the settings object */ private void setManualSettings(final Settings settings) { for (String s : getCommandLineSettings()) { final int index = s.indexOf('='); if (index == -1) { settings.setSetting(s, ""); } else { settings.setSetting(s.substring(0, index), s.substring(index + 1)); } } } /** * Initialize the application logger. */ private void initApplicationLogger() { // Disable parent Handler getLogger().setUseParentHandlers(false); // Set log level to all before setting the real log level with // BufferedHandler getLogger().setLevel(Level.ALL); // Add Buffered handler as unique Handler getLogger().addHandler(this.handler); // Set the formatter this.handler.setFormatter(Globals.LOG_FORMATTER); // Set the log level if (this.logLevel != null) { try { this.handler.setLevel(Level.parse(this.logLevel.toUpperCase())); } catch (IllegalArgumentException e) { Common.showErrorMessageAndExit("Unknown log level (" + this.logLevel + "). Accepted values are [SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST]."); } } else { this.handler.setLevel(Globals.LOG_LEVEL); } // Set the log file in arguments if (this.logFile != null) { try { this.handler.addHandler( getLogHandler(new File(this.logFile).getAbsoluteFile().toURI())); } catch (IOException e) { Common.errorExit(e, "Error while creating log file: " + e.getMessage()); } } } /** * Create a new log file and flush log. * @param logFilename log file name * @throws EoulsanException if an error occurs while creating log file */ public void createLogFileAndFlushLog(final URI logFilename) throws EoulsanException { try { Handler h = getLogHandler(logFilename); this.handler.addHandler(h); } catch (IOException e) { throw new EoulsanException(e); } flushLog(); } /** * Create the additional log file for dependencies that use their own logging * system. * @param logFilename the log file name */ public void createOtherLog(final URI logFilename) { OtherLogConfigurator.configureLog4J(null, new File(logFilename).getAbsolutePath()); } /** * Create the log file for Eoulsan and additional log file for dependencies * that use their own logging system. * @param EoulsanlogFilename Eoulsan log file name * @param otherlogFilename other log file name * @throws EoulsanException if an error occurs while creating log file */ public void createLogFiles(final URI EoulsanlogFilename, final URI otherlogFilename) throws EoulsanException { createLogFileAndFlushLog(EoulsanlogFilename); createOtherLog(otherlogFilename); } /** * Flush log. */ public void flushLog() { this.handler.flush(); } /** * Parse the action name and arguments from command line. * @param optionsCount number of options in the command line */ private void parseAction(final int optionsCount) { // Set action name and arguments final String actionName = this.args.get(optionsCount).trim().toLowerCase(); this.actionArgs = this.args.subList(optionsCount + 1, this.args.size()); // Test if is in hadoop mode final boolean hadoopMode = EoulsanRuntime.getRuntime().getMode().isHadoopMode(); // Search action this.action = ActionService.getInstance().newService(actionName); // Action not found ? if (this.action == null || hadoopMode != this.action.isHadoopJarMode()) { Common.showErrorMessageAndExit("Unknown action: " + actionName + ".\n" + "type: " + Globals.APP_NAME_LOWER_CASE + " -help for more help.\n"); } } // // Abstract methods // /** * Initialize the Eoulsan runtime. */ protected abstract void initializeRuntime(Settings settings) throws EoulsanException; /** * Get the command used to launch Eoulsan. * @return a String with the command used to launch Eoulsan */ protected abstract String getHelpEoulsanCommand(); /** * Get the Handler to create the log file. * @param logFile the path to the log file * @return a new Handler object * @throws IOException if an exception occurs while creating the handler */ protected abstract Handler getLogHandler(final URI logFile) throws IOException; // // Constructor // /** * Constructor. * @param args command line argument. */ Main(final String modeName, final String[] args) { this.launchModeName = modeName; this.args = Arrays.asList(args); // Parse the command line final int optionsCount = parseCommandLine(); // Initialize the logger initApplicationLogger(); // Log some information about the application startupLog(); try { // Load configuration file (if needed) final Settings settings = loadConfigurationFile(); // Set manual settings setManualSettings(settings); // Log settings settings.logSettings(); // Initialize the runtime initializeRuntime(settings); // Log system information sysInfoLog(); } catch (IOException e) { Common.errorExit(e, "Error while reading configuration file."); } catch (EoulsanException e) { Common.errorExit(e, e.getMessage()); } // Parse action name and action arguments from command line parseAction(optionsCount); } // // Main method // /** * Main method of the program. * @param args command line arguments */ public static void main(final String[] args) { if (main != null) { throw new IllegalAccessError("Main method cannot be run twice."); } // Set the default local for all the application Globals.setDefaultLocale(); // Check Java version if (getJavaVersion() < MINIMAL_JAVA_VERSION_REQUIRED) { Common.showErrorMessageAndExit(Globals.WELCOME_MSG + "\nError: " + Globals.APP_NAME + " requires Java " + MINIMAL_JAVA_VERSION_REQUIRED + "."); } // Select the application execution mode final String eoulsanMode = System.getProperty(Globals.LAUNCH_MODE_PROPERTY); if (eoulsanMode != null && eoulsanMode.equals("local")) { main = new MainCLI(args); } else { main = new MainHadoop(args); } // Get the action to execute final Action action = main.getAction(); // Get the Eoulsan settings final Settings settings = EoulsanRuntime.getSettings(); // Test if action can be executed with current platform if (!settings.isBypassPlatformChecking() && !action.isCurrentArchCompatible()) { Common.showErrorMessageAndExit(Globals.WELCOME_MSG + "\nError: The " + action.getName() + " of " + Globals.APP_NAME + " is not available for your platform. Required platforms: " + availableArchsToString() + "."); } try { getLogger().info("Start " + action.getName() + " action"); // Run action action.action(main.getActionArgs()); getLogger().info("End of " + action.getName() + " action"); } catch (Throwable e) { Common.errorExit(e, e.getMessage()); } // Flush logs main.flushLog(); } }