/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package com.bc.ceres.core.runtime.internal; import com.bc.ceres.core.runtime.RuntimeConfig; import com.bc.ceres.core.runtime.RuntimeConfigException; import com.bc.ceres.util.TemplateReader; import java.io.*; import java.net.URL; import java.security.CodeSource; import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.*; import java.util.logging.Formatter; public final class DefaultRuntimeConfig implements RuntimeConfig { public static final String CONFIG_KEY_CERES_CONTEXT = "ceres.context"; public static final String CONFIG_KEY_DEBUG = "debug"; public static final String CONFIG_KEY_MAIN_CLASS = "mainClass"; public static final String CONFIG_KEY_CLASSPATH = "classpath"; public static final String CONFIG_KEY_HOME = "home"; public static final String CONFIG_KEY_CONFIG_FILE_NAME = "config"; public static final String CONFIG_KEY_MODULES = "modules"; public static final String CONFIG_KEY_LIB_DIRS = "libDirs"; public static final String CONFIG_KEY_APP = "app"; public static final String CONFIG_KEY_CONSOLE_LOG = "consoleLog"; public static final String CONFIG_KEY_LOG_LEVEL = "logLevel"; public static final String DEFAULT_CERES_CONTEXT = "ceres"; public static final String DEFAULT_MAIN_CLASS_NAME = "com.bc.ceres.core.runtime.RuntimeLauncher"; public static final String DEFAULT_MODULES_DIR_NAME = "modules"; public static final String DEFAULT_CONFIG_DIR_NAME = "config"; public static final String DEFAULT_LIB_DIR_NAME = "lib"; public static final SimpleDateFormat LOG_TIME_STAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); private final Properties properties; private String contextId; private String debugKey; private boolean debug; private String homeDirKey; private String homeDirPath; private String mainClassKey; private String mainClassName; private String classpathKey; private String mainClassPath; private String applicationIdKey; private String applicationId; private String configFileKey; private String configFilePath; private String defaultRelConfigFilePath; private String defaultHomeConfigFilePath; private String modulesDirKey; private String modulesDirPath; private String defaultHomeModulesDirPath; private String libDirsKey; private String[] libDirPaths; private String defaultHomeLibDirPath; private Logger logger; private boolean homeDirAssumed; private String consoleLogKey; private String logLevelKey; private Level logLevel; private boolean consoleLog; public DefaultRuntimeConfig() throws RuntimeConfigException { properties = System.getProperties(); initAll(); } public String getContextId() { return contextId; } public String getContextProperty(String key) { return getContextProperty(key, null); } public String getContextProperty(String key, String defaultValue) { return getProperty(contextId + '.' + key, defaultValue); } public boolean isDebug() { return debug; } public String getMainClassName() { return mainClassName; } @Override public String getMainClassPath() { return mainClassPath; } public String getHomeDirPath() { return homeDirPath; } public String getConfigFilePath() { return configFilePath; } public String[] getLibDirPaths() { return libDirPaths; } public String getModulesDirPath() { return modulesDirPath; } public boolean isUsingModuleRuntime() { return DEFAULT_MAIN_CLASS_NAME.equals(mainClassName); } public String getApplicationId() { return applicationId; } public Logger getLogger() { return logger; } ///////////////////////////////////////////////////////////////////////// // Private ///////////////////////////////////////////////////////////////////////// private void initAll() throws RuntimeConfigException { initContext(); initDebug(); initHomeDirAndConfiguration(); initDebug(); // yes, again initMainClassName(); initClasspathPaths(); initModulesDir(); initLibDirs(); if (isUsingModuleRuntime()) { initAppId(); } initLogLevel(); initConsoleLog(); if (System.getProperty("platform") != null && System.getProperty("platform").equals("CEMS")) { // special case: processing on CEMS requires specific logging due to NFS concurrency issues initCemsLogger(); } else { initDefaultLogger(); } setAutoDetectProperties(); } private void initContext() { // Initialize context identifier. Mandatory. contextId = System.getProperty(CONFIG_KEY_CERES_CONTEXT, DEFAULT_CERES_CONTEXT); // Initialize application specific configuration keys homeDirKey = String.format("%s.%s", contextId, CONFIG_KEY_HOME); debugKey = String.format("%s.%s", contextId, CONFIG_KEY_DEBUG); configFileKey = String.format("%s.%s", contextId, CONFIG_KEY_CONFIG_FILE_NAME); modulesDirKey = String.format("%s.%s", contextId, CONFIG_KEY_MODULES); libDirsKey = String.format("%s.%s", contextId, CONFIG_KEY_LIB_DIRS); mainClassKey = String.format("%s.%s", contextId, CONFIG_KEY_MAIN_CLASS); classpathKey = String.format("%s.%s", contextId, CONFIG_KEY_CLASSPATH); applicationIdKey = String.format("%s.%s", contextId, CONFIG_KEY_APP); logLevelKey = String.format("%s.%s", contextId, CONFIG_KEY_LOG_LEVEL); consoleLogKey = String.format("%s.%s", contextId, CONFIG_KEY_CONSOLE_LOG); // Initialize default file and directory paths char sep = File.separatorChar; defaultRelConfigFilePath = String.format("%s/%s", DEFAULT_CONFIG_DIR_NAME, configFileKey).replace('/', sep); defaultHomeConfigFilePath = String.format("${%s}/%s", homeDirKey, defaultRelConfigFilePath).replace('/', sep); defaultHomeModulesDirPath = String.format("${%s}/%s", homeDirKey, DEFAULT_MODULES_DIR_NAME).replace('/', sep); defaultHomeLibDirPath = String.format("${%s}/%s", homeDirKey, DEFAULT_LIB_DIR_NAME).replace('/', sep); } private void initDebug() { debug = Boolean.valueOf(System.getProperty(debugKey, Boolean.toString(debug))); } private void initHomeDirAndConfiguration() throws RuntimeConfigException { maybeInitHomeDirAndConfigFile(); if (configFilePath != null) { loadConfiguration(); } else { trace("A configuration file is not used."); } initHomeDirIfNotAlreadyDone(); } private void maybeInitHomeDirAndConfigFile() throws RuntimeConfigException { maybeInitHomeDir(); maybeInitConfigFile(); if (homeDirPath == null && configFilePath == null) { // we have no home and no config file assumeHomeDir(); } if (configFilePath == null) { maybeInitDefaultConfigFile(); } } private void maybeInitHomeDir() throws RuntimeConfigException { String homeDirPath = getProperty(homeDirKey); if (!isNullOrEmptyString(homeDirPath)) { // ok set: must be an existing directory File homeDir = new File(homeDirPath); if (!homeDir.isDirectory()) { throw createInvalidPropertyValueException(homeDirKey, homeDirPath); } try { this.homeDirPath = homeDir.getCanonicalPath(); } catch (IOException e) { throw new RuntimeConfigException(e.getMessage(), e); } } } private void maybeInitConfigFile() throws RuntimeConfigException { String configFilePath = getProperty(configFileKey); if (!isNullOrEmptyString(configFilePath)) { File configFile = new File(configFilePath); // ok set: must be an existing file if (!configFile.isFile()) { throw createInvalidPropertyValueException(configFileKey, configFile.getPath()); } try { this.configFilePath = configFile.getCanonicalPath(); } catch (IOException e) { throw new RuntimeConfigException(e.getMessage(), e); } } } private void maybeInitDefaultConfigFile() { File configFile = new File(substitute(defaultHomeConfigFilePath)); if (configFile.isFile()) { configFilePath = configFile.getPath(); } } private void assumeHomeDir() { List<File> possibleHomeDirList = createPossibleHomeDirList(); List<String> homeContentPathList = createHomeContentPathList(); // The existance of the files in homeContentPathList is optional. // We assume that the home directory is the one in which most files of homeContentPathList are present. trace("Auto-detecting home directory..."); File mostLikelyHomeDir = findMostLikelyHomeDir(possibleHomeDirList, homeContentPathList); if (mostLikelyHomeDir != null) { homeDirPath = mostLikelyHomeDir.getPath(); } else { homeDirPath = new File(".").getAbsolutePath(); } homeDirAssumed = true; setProperty(homeDirKey, homeDirPath); } private File findMostLikelyHomeDir(List<File> possibleHomeDirList, List<String> homeContentPathList) { int numFoundMax = 0; File mostLikelyHomeDir = null; for (File possibleHomeDir : possibleHomeDirList) { trace(String.format("Is [%s] my home directory?", possibleHomeDir)); int numFound = 0; for (String homeContentPath : homeContentPathList) { File homeContentFile = new File(possibleHomeDir, homeContentPath); if (homeContentFile.exists()) { trace(String.format(" [%s] contained? Yes.", homeContentPath)); numFound++; } else { trace(String.format(" [%s] contained? No.", homeContentPath)); } } if (numFound == 0) { trace("No."); } else { trace(String.format("Maybe. %d related file(s) found.", numFound)); } if (numFound > numFoundMax) { try { mostLikelyHomeDir = possibleHomeDir.getCanonicalFile(); numFoundMax = numFound; } catch (IOException e) { // ??? } } } return mostLikelyHomeDir; } private static List<File> createPossibleHomeDirList() { List<File> homeDirCheckList = new ArrayList<>(4); // include codeSource dir in check list CodeSource lib = DefaultRuntimeConfig.class.getProtectionDomain().getCodeSource(); if (lib != null) { URL libUrl = lib.getLocation(); if (libUrl.getProtocol().equals("file")) { String libPath = libUrl.getPath(); File libParentDir = new File(libPath).getParentFile(); if (libParentDir != null) { // include one above libParentDir if (libParentDir.getParentFile() != null) { homeDirCheckList.add(libParentDir.getParentFile()); } // include libParentDir homeDirCheckList.add(libParentDir); } } } // include CWD in check list homeDirCheckList.add(new File(".").getAbsoluteFile()); // include one above CWD in check list homeDirCheckList.add(new File("..").getAbsoluteFile()); return homeDirCheckList; } private List<String> createHomeContentPathList() { List<String> homeContentPathList = new ArrayList<>(8); homeContentPathList.add(defaultRelConfigFilePath); homeContentPathList.add("bin"); homeContentPathList.add(DEFAULT_LIB_DIR_NAME); homeContentPathList.add(DEFAULT_MODULES_DIR_NAME); return homeContentPathList; } private void loadConfiguration() throws RuntimeConfigException { trace(String.format("Loading configuration from [%s]", configFilePath)); try { try (InputStream stream = new FileInputStream(configFilePath)) { Properties fileProperties = new Properties(); fileProperties.load(stream); // @todo check tests - code was not backward compatible with Java 5 // so i changed it - but this is not the only place of uncompatibilty // add default properties so that they override file properties //Set<String> propertyNames = fileProperties.stringPropertyNames(); // for (String propertyName : propertyNames) { // String propertyValue = fileProperties.getProperty(propertyName); // if (!isPropertySet(propertyName)) { // setProperty(propertyName, propertyValue); // trace(String.format("Configuration property [%s] added", propertyName)); // } else { // trace(String.format("Configuration property [%s] ignored", propertyName)); // } // } Enumeration<?> enumeration = fileProperties.propertyNames(); while (enumeration.hasMoreElements()) { final Object key = enumeration.nextElement(); if (key instanceof String) { final Object value = fileProperties.get(key); if (value instanceof String) { final String keyString = (String) key; String propertyValue = fileProperties.getProperty(keyString); if (!isPropertySet(keyString)) { setProperty(keyString, propertyValue); trace(String.format("Configuration property [%s] added", keyString)); } else { trace(String.format("Configuration property [%s] ignored", keyString)); } } } } } } catch (IOException e) { throw new RuntimeConfigException(String.format("Failed to load configuration [%s]", configFilePath), e); } } private void initHomeDirIfNotAlreadyDone() throws RuntimeConfigException { if (homeDirPath == null || homeDirAssumed) { maybeInitHomeDir(); } if (homeDirPath == null) { homeDirPath = new File(".").getAbsolutePath(); homeDirAssumed = true; } // remove redundant '.'s and '..'s. try { homeDirPath = new File(homeDirPath).getCanonicalPath(); } catch (IOException e) { throw new RuntimeConfigException("Home directory is invalid.", e); } if (homeDirAssumed) { trace(String.format("Home directory not set. Using assumed default.")); } trace(String.format("Home directory is [%s]", homeDirPath)); } private void initMainClassName() throws RuntimeConfigException { mainClassName = getProperty(mainClassKey, DEFAULT_MAIN_CLASS_NAME); if (isNullOrEmptyString(mainClassName)) { throw createMissingPropertyKeyException(mainClassKey); } } private void initClasspathPaths() throws RuntimeConfigException { mainClassPath = getProperty(classpathKey, null); } private void initModulesDir() throws RuntimeConfigException { this.modulesDirPath = null; String modulesDirPath = getProperty(modulesDirKey); try { if (modulesDirPath != null) { File modulesDir = new File(modulesDirPath); if (!modulesDir.isDirectory()) { throw createInvalidPropertyValueException(modulesDirKey, modulesDirPath); } this.modulesDirPath = modulesDir.getCanonicalPath(); } else { // try default File modulesDir = new File(substitute(defaultHomeModulesDirPath)); if (modulesDir.isDirectory()) { this.modulesDirPath = modulesDir.getCanonicalPath(); } } } catch (IOException e) { String msg = String.format("Could not convert modules dir path [%s] into its canonical form.", modulesDirPath); throw new RuntimeConfigException(msg, e); } } private void initLibDirs() throws RuntimeConfigException { this.libDirPaths = new String[0]; String libDirPathsString = getProperty(libDirsKey); if (libDirPathsString != null) { String[] libDirPaths = splitLibDirPaths(libDirPathsString); for (String libDirPath : libDirPaths) { File libDir = new File(libDirPath); if (!libDir.isDirectory()) { throw createInvalidPropertyValueException(libDirsKey, libDirPathsString); } } this.libDirPaths = libDirPaths; } else { // try default libDirPathsString = substitute(defaultHomeLibDirPath); File libDir = new File(libDirPathsString); if (libDir.isDirectory()) { this.libDirPaths = new String[]{libDirPathsString}; } } } private void initAppId() throws RuntimeConfigException { applicationId = getProperty(applicationIdKey); if (applicationId != null && applicationId.length() == 0) { throw createMissingPropertyKeyException(applicationIdKey); } } private void initLogLevel() { String logLevelStr = getProperty(logLevelKey, Level.OFF.getName()); Level[] validLevels = new Level[]{ Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG, Level.FINE, Level.FINER, Level.FINEST, Level.ALL, Level.OFF, }; logLevel = Level.OFF; for (Level level : validLevels) { if (level.getName().equalsIgnoreCase(logLevelStr)) { logLevel = level; break; } } } private void initConsoleLog() { String consoleLogStr = getProperty(consoleLogKey, "false"); consoleLog = Boolean.parseBoolean(consoleLogStr); } private void initDefaultLogger() { ConsoleHandler consoleHandler = null; Logger rootLogger = LogManager.getLogManager().getLogger(""); Handler[] handlers = rootLogger.getHandlers(); for (Handler handler : handlers) { if (handler instanceof ConsoleHandler) { consoleHandler = (ConsoleHandler) handler; rootLogger.removeHandler(handler); } } logger = Logger.getLogger(contextId); logger.setLevel(logLevel); if (!logLevel.equals(Level.OFF)) { // if any of the log handler has our LogFormatter we have already configured the logger for (Handler handler : logger.getHandlers()) { if (handler.getFormatter() instanceof LogFormatter) { return; } } LogFormatter formatter = new LogFormatter(); if (consoleLog) { if (consoleHandler == null) { consoleHandler = new ConsoleHandler(); } consoleHandler.setFormatter(formatter); consoleHandler.setLevel(logLevel); logger.addHandler(consoleHandler); } String userHomePath = getProperty("user.home", "."); File logDir = new File(userHomePath, '.' + contextId + "/log"); logDir.mkdirs(); String logFilePattern = new File(logDir, contextId + "-%g.log").getPath(); try { FileHandler fileHandler = new FileHandler(logFilePattern); fileHandler.setFormatter(formatter); fileHandler.setLevel(logLevel); logger.addHandler(fileHandler); } catch (IOException e) { System.err.println("Error: Failed to create log file: " + logFilePattern); } } } private void initCemsLogger() { // workaround for logging of CEMS LSB processes, following approach from SST project // This is just one special case! todo: find a more general solution! if (logger == null) { final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final Formatter formatter = new Formatter() { @Override public String format(LogRecord record) { final StringBuilder sb = new StringBuilder(); sb.append(dateFormat.format(new Date(record.getMillis()))); sb.append(" - "); sb.append(record.getLevel().getName()); sb.append(": "); sb.append(record.getMessage()); sb.append("\n"); @SuppressWarnings("ThrowableResultOfMethodCallIgnored") final Throwable thrown = record.getThrown(); if (thrown != null) { sb.append(thrown.toString()); sb.append("\n"); } return sb.toString(); } }; final ConsoleHandler handler = new ConsoleHandler(); handler.setFormatter(formatter); handler.setLevel(Level.ALL); logger = Logger.getLogger("ga.cems"); final Handler[] handlers = logger.getHandlers(); for (Handler h : handlers) { logger.removeHandler(h); } logger.setUseParentHandlers(false); logger.addHandler(handler); } // logger.setLevel(Level.INFO); logger.setLevel(logLevel); } private void setAutoDetectProperties() { setPropertyIfNotSet(this.homeDirKey, getHomeDirPath()); setPropertyIfNotSet(configFileKey, getConfigFilePath()); setPropertyIfNotSet(modulesDirKey, getModulesDirPath()); String libDirPaths = assembleLibDirPaths(getLibDirPaths()); setPropertyIfNotSet(libDirsKey, libDirPaths.length() > 0 ? libDirPaths : null); } private void setPropertyIfNotSet(String key, String value) { if (!isPropertySet(key)) { setProperty(key, value); } } private static boolean isNullOrEmptyString(String value) { return value == null || value.length() == 0; } private static String[] splitLibDirPaths(String libDirPathsString) { List<String> libDirPathList = new ArrayList<>(8); StringTokenizer stringTokenizer = new StringTokenizer(libDirPathsString, File.pathSeparator); while (stringTokenizer.hasMoreElements()) { String libDirPath = (String) stringTokenizer.nextElement(); libDirPathList.add(libDirPath); } return libDirPathList.toArray(new String[libDirPathList.size()]); } private static String assembleLibDirPaths(String[] libDirPaths) { StringBuilder sb = new StringBuilder(64); for (String libDirPath : libDirPaths) { if (sb.length() > 0) { sb.append(File.pathSeparator); } sb.append(libDirPath); } return sb.toString(); } private static RuntimeConfigException createMissingPropertyKeyException(String key) { return new RuntimeConfigException( String.format("Property '%s' has not been set.", key)); } private static RuntimeConfigException createInvalidPropertyValueException(String key, String value) { return new RuntimeConfigException(String.format("Value of property '%s' is invalid: %s", key, value)); } private void trace(String msg) { if (debug) { System.out.println(String.format("[DEBUG] ceres-config: %s", msg)); } } private boolean isPropertySet(String key) { return properties.containsKey(key); } private String getProperty(String key) { return getProperty(key, null); } private String getProperty(String key, String defaultValue) { String property = properties.getProperty(key, defaultValue); if (property != null) { return substitute(property); } return property; } private void setProperty(String key, String value) { if (value != null) { properties.setProperty(key, value); } else { properties.remove(key); } } /** * Substitues all occurences of <code>${<i>configKey</i>}</code> in the given string * with the value of <code><i>configKey</i></code>. * * @param value the string in which to perform the substitution. * @return the resulting string. */ private String substitute(String value) { if (value.indexOf('$') == -1) { return value; } StringReader r = new StringReader(value); try { return new TemplateReader(r, properties).readAll(); } catch (IOException e) { return value; } } private static class LogFormatter extends Formatter { @Override public String format(LogRecord record) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.append('['); pw.append(record.getLevel().toString()); pw.append(']'); pw.append(' '); pw.append(LOG_TIME_STAMP_FORMAT.format(new Date(record.getMillis()))); pw.append(' '); pw.append('-'); pw.append(' '); pw.append(record.getMessage()); Throwable thrown = record.getThrown(); if (thrown != null) { pw.println(); thrown.printStackTrace(pw); } pw.println(); return sw.toString(); } } }