/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jumpmind.symmetric; import static org.apache.commons.lang.StringUtils.isBlank; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.security.Provider; import java.security.Security; import java.sql.Connection; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.commons.cli.CommandLine; 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.dbcp.BasicDataSource; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.log4j.Appender; import org.apache.log4j.FileAppender; import org.apache.log4j.Layout; import org.apache.log4j.PatternLayout; import org.apache.log4j.xml.DOMConfigurator; import org.jumpmind.db.platform.IDatabasePlatform; import org.jumpmind.properties.TypedProperties; import org.jumpmind.security.SecurityConstants; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.common.SystemConstants; import org.jumpmind.symmetric.transport.TransportManagerFactory; import org.jumpmind.util.AppUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; public abstract class AbstractCommandLauncher { protected static final Logger log; public static final String DEFAULT_SERVER_PROPERTIES; protected static final String HELP = "help"; protected static final String OPTION_PROPERTIES_FILE = "properties"; protected static final String OPTION_ENGINE = "engine"; protected static final String OPTION_VERBOSE_CONSOLE = "verbose"; protected static final String OPTION_DEBUG = "debug"; protected static final String OPTION_NO_LOG_CONSOLE = "no-log-console"; protected static final String OPTION_NO_LOG_FILE = "no-log-file"; protected static final String OPTION_KEYSTORE_PASSWORD = "storepass"; protected static final String OPTION_KEYSTORE_TYPE = "storetype"; protected static final String OPTION_JCE_PROVIDER = "providerClass"; protected static final String COMMON_MESSAGE_KEY_PREFIX = "Common.Option."; protected String app; protected String argSyntax; protected String messageKeyPrefix; protected File propertiesFile; protected ISymmetricEngine engine; protected IDatabasePlatform platform; private static boolean serverPropertiesInitialized = false; static { String symHome = System.getenv("SYM_HOME"); if (symHome == null) { symHome = "."; } System.setProperty("log4j.sym.home", symHome); if (isBlank(System.getProperty("h2.baseDir"))) { System.setProperty("h2.baseDir", symHome + "/tmp/h2"); } DEFAULT_SERVER_PROPERTIES = System.getProperty(SystemConstants.SYSPROP_SERVER_PROPERTIES_PATH, symHome + "/conf/symmetric-server.properties"); log = LoggerFactory.getLogger(AbstractCommandLauncher.class); initFromServerProperties(); } public AbstractCommandLauncher(String app, String argSyntax, String messageKeyPrefix) { this.app = app; this.argSyntax = argSyntax; this.messageKeyPrefix = messageKeyPrefix; TransportManagerFactory.initHttps("all", true); } protected static void initFromServerProperties() { if (!serverPropertiesInitialized) { File serverPropertiesFile = new File(DEFAULT_SERVER_PROPERTIES); TypedProperties serverProperties = new TypedProperties(); if (serverPropertiesFile.exists() && serverPropertiesFile.isFile()) { FileInputStream fis = null; try { fis = new FileInputStream(serverPropertiesFile); serverProperties.load(fis); /* System properties always override */ serverProperties.merge(System.getProperties()); /* * Put server properties back into System properties so they * are available to the parameter service */ System.getProperties().putAll(serverProperties); } catch (IOException ex) { log.error("Failed to load " + DEFAULT_SERVER_PROPERTIES, ex); } finally { IOUtils.closeQuietly(fis); } } else if (!serverPropertiesFile.exists()) { log.warn("Failed to load " + DEFAULT_SERVER_PROPERTIES + ". File does not exist."); } else if (!serverPropertiesFile.isFile()) { log.warn("Failed to load " + DEFAULT_SERVER_PROPERTIES + ". Object is not a file."); } serverPropertiesInitialized = true; } } abstract protected boolean printHelpIfNoOptionsAreProvided(); abstract protected boolean requiresPropertiesFile(); public void execute(String args[]) { PosixParser parser = new PosixParser(); Options options = new Options(); buildOptions(options); try { CommandLine line = parser.parse(options, args); if (line.hasOption(HELP) || (line.getArgList().contains(HELP)) || ((args == null || args.length == 0) && line.getOptions().length == 0 && printHelpIfNoOptionsAreProvided())) { printHelp(line, options); System.exit(2); } configureLogging(line); configurePropertiesFile(line); if (line.getOptions() != null) { for (Option option : line.getOptions()) { log.info("Option: name={}, value={}", new Object[] { option.getLongOpt() != null ? option.getLongOpt() : option.getOpt(), ArrayUtils.toString(option.getValues()) }); } } executeWithOptions(line); } catch (ParseException e) { System.err.println(e.getMessage()); printUsage(options); System.exit(4); } catch (Exception e) { System.err .println("-------------------------------------------------------------------------------"); System.err.println("An exception occurred. Please see the following for details:"); System.err .println("-------------------------------------------------------------------------------"); ExceptionUtils.printRootCauseStackTrace(e, System.err); System.err .println("-------------------------------------------------------------------------------"); System.exit(1); } } protected void printHelp(CommandLine cmd, Options options) { new HelpFormatter().printHelp(app + " " + argSyntax, options); } protected void printUsage(Options options) { PrintWriter writer = new PrintWriter(System.out); new HelpFormatter().printUsage(writer, 80, app, options); writer.write("For more options, use " + app + " --" + HELP + "\n"); writer.flush(); } protected void configureLogging(CommandLine line) throws MalformedURLException { /* Optionally remove existing handlers attached to j.u.l root logger */ SLF4JBridgeHandler.removeHandlersForRootLogger(); /* * Add SLF4JBridgeHandler to j.u.l's root logger, should be done once * during the initialization phase of your application */ SLF4JBridgeHandler.install(); URL log4jUrl = new URL(System.getProperty("log4j.configuration", "file:" + AppUtils.getSymHome() + "/conf/log4j-blank.xml")); File log4jFile = new File(new File(log4jUrl.getFile()).getParent(), "log4j.xml"); if (line.hasOption(OPTION_DEBUG)) { log4jFile = new File(log4jFile.getParent(), "log4j-debug.xml"); } if (log4jFile.exists()) { DOMConfigurator.configure(log4jFile.getAbsolutePath()); } if (line.hasOption(OPTION_VERBOSE_CONSOLE)) { Appender consoleAppender = org.apache.log4j.Logger.getRootLogger().getAppender( "CONSOLE"); if (consoleAppender != null) { Layout layout = consoleAppender.getLayout(); if (layout instanceof PatternLayout) { ((PatternLayout) layout).setConversionPattern("%d %-5p [%c{2}] [%t] %m%n"); } } } if (line.hasOption(OPTION_NO_LOG_CONSOLE)) { org.apache.log4j.Logger.getRootLogger().removeAppender("CONSOLE"); } if (line.hasOption(OPTION_NO_LOG_FILE)) { org.apache.log4j.Logger.getRootLogger().removeAppender("ROLLING"); } else { Appender appender = org.apache.log4j.Logger.getRootLogger().getAppender("ROLLING"); if (appender instanceof FileAppender) { FileAppender fileAppender = (FileAppender) appender; if (line.hasOption(OPTION_PROPERTIES_FILE)) { File file = new File(line.getOptionValue(OPTION_PROPERTIES_FILE)); String name = file.getName(); int index = name.lastIndexOf("."); if (index > 0) { name = name.substring(0, index); } fileAppender.setFile(fileAppender.getFile().replace("symmetric.log", name + ".log")); fileAppender.activateOptions(); } System.err.println(String.format("Log output will be written to %s", fileAppender.getFile())); } } } protected void configurePropertiesFile(CommandLine line) throws ParseException { if (line.hasOption(OPTION_PROPERTIES_FILE)) { String propertiesFilename = line.getOptionValue(OPTION_PROPERTIES_FILE); propertiesFile = new File(propertiesFilename); if (!propertiesFile.exists()) { throw new SymmetricException("Could not find the properties file specified: %s", line.getOptionValue(OPTION_PROPERTIES_FILE)); } } else if (line.hasOption(OPTION_ENGINE)) { propertiesFile = findPropertiesFileForEngineWithName(line.getOptionValue(OPTION_ENGINE)); if (propertiesFile == null || (propertiesFile != null && !propertiesFile.exists())) { throw new SymmetricException( "Could not find the properties file for the engine specified: %s", line.getOptionValue(OPTION_ENGINE)); } } else { propertiesFile = findSingleEnginesPropertiesFile(); if (propertiesFile == null && requiresPropertiesFile()) { throw new ParseException(String.format("You must specify either --%s or --%s", OPTION_ENGINE, OPTION_PROPERTIES_FILE)); } } } public static String getEnginesDir() { String enginesDir = System.getProperty(SystemConstants.SYSPROP_ENGINES_DIR, AppUtils.getSymHome() + "/engines"); new File(enginesDir).mkdirs(); return enginesDir; } public File findPropertiesFileForEngineWithName(String engineName) { File[] files = findEnginePropertiesFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; Properties properties = new Properties(); FileInputStream is = null; try { is = new FileInputStream(file); properties.load(is); if (engineName.equals(properties.getProperty(ParameterConstants.ENGINE_NAME))) { return file; } } catch (IOException ex) { } finally { IOUtils.closeQuietly(is); } } return null; } public File[] findEnginePropertiesFiles() { List<File> propFiles = new ArrayList<File>(); File enginesDir = new File(getEnginesDir()); File[] files = enginesDir.listFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.getName().endsWith(".properties")) { propFiles.add(file); } } return propFiles.toArray(new File[propFiles.size()]); } public File findSingleEnginesPropertiesFile() { File[] files = findEnginePropertiesFiles(); if (files.length == 1) { return files[0]; } else { return null; } } protected void configureCrypto(CommandLine line) throws Exception { if (line.hasOption(OPTION_KEYSTORE_PASSWORD)) { System.setProperty(SecurityConstants.SYSPROP_KEYSTORE_PASSWORD, line.getOptionValue(OPTION_KEYSTORE_PASSWORD)); } if (line.hasOption(OPTION_KEYSTORE_TYPE)) { System.setProperty(SystemConstants.SYSPROP_KEYSTORE_TYPE, line.getOptionValue(OPTION_KEYSTORE_TYPE)); } if (line.hasOption(OPTION_JCE_PROVIDER)) { Provider provider = (Provider) Class.forName(line.getOptionValue(OPTION_JCE_PROVIDER)) .newInstance(); Security.addProvider(provider); } } protected ISymmetricEngine getSymmetricEngine() { return getSymmetricEngine(true); } protected ISymmetricEngine getSymmetricEngine(boolean testConnection) { if (engine == null) { if (testConnection) { testConnection(); } engine = new ClientSymmetricEngine(propertiesFile); platform = engine.getSymmetricDialect().getPlatform(); } return engine; } protected void testConnection() { try { BasicDataSource ds = ClientSymmetricEngine .createBasicDataSource(propertiesFile); Connection conn = ds.getConnection(); conn.close(); ds.close(); } catch (Exception e) { throw new RuntimeException(e); } } protected IDatabasePlatform getDatabasePlatform(boolean testConnection) { if (platform == null) { if (testConnection) { testConnection(); } platform = ClientSymmetricEngine.createDatabasePlatform(null, new TypedProperties( propertiesFile), null, false); } return platform; } protected TypedProperties getTypedProperties() { return new TypedProperties(propertiesFile); } protected void buildOptions(Options options) { addCommonOption(options, "h", HELP, false); addCommonOption(options, "p", OPTION_PROPERTIES_FILE, true); addCommonOption(options, "e", OPTION_ENGINE, true); addCommonOption(options, "v", OPTION_VERBOSE_CONSOLE, false); addCommonOption(options, null, OPTION_DEBUG, false); addCommonOption(options, null, OPTION_NO_LOG_CONSOLE, false); addCommonOption(options, null, OPTION_NO_LOG_FILE, false); } protected void buildCryptoOptions(Options options) { addCommonOption(options, OPTION_KEYSTORE_PASSWORD, true); addCommonOption(options, OPTION_KEYSTORE_TYPE, true); addCommonOption(options, OPTION_JCE_PROVIDER, true); } protected void addOption(Options options, String opt, String longOpt, boolean hasArg) { options.addOption(opt, longOpt, hasArg, Message.get(messageKeyPrefix + longOpt)); } protected void addOption(Options options, String opt, boolean hasArg) { options.addOption(opt, null, hasArg, Message.get(messageKeyPrefix + opt)); } protected void addCommonOption(Options options, String opt, String longOpt, boolean hasArg) { options.addOption(opt, longOpt, hasArg, Message.get(COMMON_MESSAGE_KEY_PREFIX + longOpt)); } protected void addCommonOption(Options options, String opt, boolean hasArg) { options.addOption(opt, null, hasArg, Message.get(COMMON_MESSAGE_KEY_PREFIX + opt)); } protected abstract boolean executeWithOptions(CommandLine line) throws Exception; }